Skip to content

Commit 94dfdd3

Browse files
committed
feat: poc of cli (step 3)
1 parent 6bf8840 commit 94dfdd3

File tree

5 files changed

+210
-6
lines changed

5 files changed

+210
-6
lines changed

cli/src/main/java/com/diffplug/spotless/cli/SpotlessCLI.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515
*/
1616
package com.diffplug.spotless.cli;
1717

18+
import java.util.List;
19+
1820
import com.diffplug.spotless.cli.execution.SpotlessExecutionStrategy;
1921
import com.diffplug.spotless.cli.subcommands.SpotlessApply;
2022
import com.diffplug.spotless.cli.subcommands.SpotlessCheck;
23+
import com.diffplug.spotless.cli.version.SpotlessCLIVersionProvider;
2124

2225
import picocli.CommandLine;
2326
import picocli.CommandLine.Command;
2427

25-
@Command(name = "spotless cli", mixinStandardHelpOptions = true, version = "spotless ${version}", // https://picocli.info/#_dynamic_version_information
26-
description = "Runs spotless", subcommands = {SpotlessCheck.class, SpotlessApply.class})
28+
@Command(name = "spotless", mixinStandardHelpOptions = true, versionProvider = SpotlessCLIVersionProvider.class, description = "Runs spotless", subcommands = {SpotlessCheck.class, SpotlessApply.class})
2729
public class SpotlessCLI implements SpotlessCommand {
2830

2931
@CommandLine.Option(names = {"-V", "--version"}, versionHelp = true, description = "Print version information and exit")
@@ -32,9 +34,13 @@ public class SpotlessCLI implements SpotlessCommand {
3234
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help message")
3335
boolean usageHelpRequested;
3436

37+
@CommandLine.Option(names = {"--target", "-t"}, required = true, arity = "1..*", description = "The target files to format", scope = CommandLine.ScopeType.INHERIT)
38+
public List<String> targets;
39+
3540
public static void main(String... args) {
3641
// args = new String[]{"--version"};
37-
args = new String[]{"apply", "license-header", "--header-file", "CHANGES.md", "--delimiter-for", "java", "license-header", "--header", "abc"};
42+
// args = new String[]{"apply", "license-header", "--header-file", "CHANGES.md", "--delimiter-for", "java", "license-header", "--header", "abc"};
43+
args = new String[]{"apply", "--target", "src/poc/java/**/*.java", "license-header", "--header", "abc", "--delimiter-for", "java", "license-header", "--header-file", "TestHeader.txt"};
3844
int exitCode = new CommandLine(new SpotlessCLI())
3945
.setExecutionStrategy(new SpotlessExecutionStrategy())
4046
.setCaseInsensitiveEnumValuesAllowed(true)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli.core;
17+
18+
import static java.util.function.Predicate.not;
19+
20+
import java.io.File;
21+
import java.nio.file.FileSystems;
22+
import java.nio.file.FileVisitOption;
23+
import java.nio.file.FileVisitResult;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.PathMatcher;
27+
import java.nio.file.SimpleFileVisitor;
28+
import java.nio.file.attribute.BasicFileAttributes;
29+
import java.util.ArrayList;
30+
import java.util.EnumSet;
31+
import java.util.List;
32+
import java.util.stream.Collectors;
33+
import java.util.stream.Stream;
34+
35+
import com.diffplug.spotless.ThrowingEx;
36+
37+
public class TargetResolver {
38+
39+
private final List<String> targets;
40+
41+
public TargetResolver(List<String> targets) {
42+
this.targets = targets;
43+
}
44+
45+
public Stream<Path> resolveTargets() {
46+
return targets.stream()
47+
.flatMap(this::resolveTarget);
48+
}
49+
50+
private Stream<Path> resolveTarget(String target) {
51+
52+
final boolean isGlob = target.contains("*") || target.contains("?");
53+
54+
if (isGlob) {
55+
return resolveGlob(target);
56+
}
57+
return resolveDir(Path.of(target));
58+
}
59+
60+
private Stream<Path> resolveDir(Path startDir) {
61+
List<Path> collected = new ArrayList<>();
62+
ThrowingEx.run(() -> Files.walkFileTree(startDir,
63+
EnumSet.of(FileVisitOption.FOLLOW_LINKS),
64+
Integer.MAX_VALUE,
65+
new SimpleFileVisitor<>() {
66+
@Override
67+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
68+
collected.add(file);
69+
return FileVisitResult.CONTINUE;
70+
}
71+
}));
72+
return collected.stream();
73+
}
74+
75+
private Stream<Path> resolveGlob(String glob) {
76+
Path startDir;
77+
String globPart;
78+
// if the glob is absolute, we need to split the glob into its parts and use all parts except glob chars '*', '**', and '?'
79+
String[] parts = glob.split("\\Q" + File.separator + "\\E");
80+
List<String> startDirParts = Stream.of(parts)
81+
.takeWhile(not(TargetResolver::isGlobPathPart))
82+
.collect(Collectors.toList());
83+
84+
startDir = Path.of(glob.startsWith(File.separator) ? File.separator : "", startDirParts.toArray(String[]::new));
85+
globPart = Stream.of(parts)
86+
.skip(startDirParts.size())
87+
.collect(Collectors.joining(File.separator));
88+
89+
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + globPart);
90+
List<Path> collected = new ArrayList<>();
91+
ThrowingEx.run(() -> Files.walkFileTree(startDir,
92+
EnumSet.of(FileVisitOption.FOLLOW_LINKS),
93+
Integer.MAX_VALUE,
94+
new SimpleFileVisitor<>() {
95+
@Override
96+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
97+
if (matcher.matches(file)) {
98+
collected.add(file);
99+
}
100+
return FileVisitResult.CONTINUE;
101+
}
102+
}));
103+
return collected.stream()
104+
.map(Path::toAbsolutePath);
105+
}
106+
107+
private static boolean isGlobPathPart(String part) {
108+
return part.contains("*") || part.contains("?") || part.matches(".*\\[.*].*") || part.matches(".*\\{.*}.*");
109+
}
110+
}

cli/src/main/java/com/diffplug/spotless/cli/subcommands/SpotlessActionSubCommand.java

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,23 @@
1515
*/
1616
package com.diffplug.spotless.cli.subcommands;
1717

18+
import java.nio.charset.Charset;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
import java.nio.file.Paths;
1822
import java.util.List;
23+
import java.util.stream.Collectors;
1924

2025
import javax.annotation.Nonnull;
26+
import javax.xml.transform.Result;
2127

28+
import com.diffplug.spotless.FormatExceptionPolicyStrict;
29+
import com.diffplug.spotless.Formatter;
2230
import com.diffplug.spotless.FormatterStep;
31+
import com.diffplug.spotless.LineEnding;
32+
import com.diffplug.spotless.ThrowingEx;
33+
import com.diffplug.spotless.cli.SpotlessCLI;
34+
import com.diffplug.spotless.cli.core.TargetResolver;
2335
import com.diffplug.spotless.cli.subcommands.steps.generic.LicenseHeader;
2436
import com.diffplug.spotless.cli.subcommands.steps.generic.RemoveMeLaterSubCommand;
2537

@@ -35,10 +47,59 @@
3547
})
3648
public abstract class SpotlessActionSubCommand implements SpotlessActionCommand {
3749

50+
@CommandLine.ParentCommand
51+
SpotlessCLI parent;
52+
3853
@Override
3954
public Integer executeSpotlessAction(@Nonnull List<FormatterStep> formatterSteps) {
40-
System.out.println("Hello " + getClass().getSimpleName() + ", abc!");
41-
formatterSteps.forEach(step -> System.out.println("Step: " + step));
42-
return 0;
55+
TargetResolver targetResolver = new TargetResolver(parent.targets);
56+
57+
try (Formatter formatter = Formatter.builder()
58+
.lineEndingsPolicy(LineEnding.UNIX.createPolicy())
59+
.encoding(Charset.defaultCharset()) // TODO charset!
60+
.rootDir(Paths.get(".")) // TODO root dir?
61+
.steps(formatterSteps)
62+
.exceptionPolicy(new FormatExceptionPolicyStrict())
63+
.build()) {
64+
65+
boolean success = targetResolver.resolveTargets()
66+
.parallel() // needed?
67+
.map(target -> this.executeFormatter(formatter, target))
68+
.filter(result -> result.success && result.updated != null)
69+
.peek(this::writeBack)
70+
.allMatch(result -> result.success);
71+
System.out.println("Hello " + getClass().getSimpleName() + ", abc! Files: " + new TargetResolver(parent.targets).resolveTargets().collect(Collectors.toList()));
72+
System.out.println("success: " + success);
73+
formatterSteps.forEach(step -> System.out.println("Step: " + step));
74+
return 0;
75+
}
76+
}
77+
78+
private Result executeFormatter(Formatter formatter, Path target) {
79+
System.out.println("Formatting file: " + target + " in Thread " + Thread.currentThread().getName());
80+
String targetContent = ThrowingEx.get(() -> Files.readString(target, Charset.defaultCharset())); // TODO charset!
81+
82+
String computed = formatter.compute(targetContent, target.toFile());
83+
// computed is null if file already up to date
84+
return new Result(target, true, computed);
85+
}
86+
87+
private void writeBack(Result result) {
88+
if (result.updated != null) {
89+
ThrowingEx.run(() -> Files.writeString(result.target, result.updated, Charset.defaultCharset())); // TODO charset!
90+
}
91+
// System.out.println("Writing back to file:" + result.target + " with content:\n" + result.updated);
92+
}
93+
94+
private static final class Result {
95+
private final Path target;
96+
private final boolean success;
97+
private final String updated;
98+
99+
public Result(Path target, boolean success, String updated) {
100+
this.target = target;
101+
this.success = success;
102+
this.updated = updated;
103+
}
43104
}
44105
}

cli/src/main/java/com/diffplug/spotless/cli/subcommands/steps/generic/LicenseHeader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ private ThrowingEx.Supplier<String> headerSource() {
8484
}
8585

8686
private String delimiter() {
87+
// TODO (simschla, 01.11.2024): here should somehow be automatically determined which type is needed (e.g. by file extension of files to be formatted)
8788
if (licenseHeaderDelimiterOption == null) {
8889
return DefaultDelimiterType.JAVA.delimiterExpression;
8990
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.cli.version;
17+
18+
import picocli.CommandLine;
19+
20+
public class SpotlessCLIVersionProvider implements CommandLine.IVersionProvider {
21+
22+
@Override
23+
public String[] getVersion() throws Exception {
24+
return new String[]{"Spotless CLI 1.0.0", "TODO"};
25+
}
26+
}

0 commit comments

Comments
 (0)