Skip to content

Commit a0bc3e5

Browse files
committed
Using JCommander
1 parent 7d56545 commit a0bc3e5

File tree

10 files changed

+225
-218
lines changed

10 files changed

+225
-218
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package fr.chuckame.marlinfw.configurator;
2+
3+
public class InvalidUseException extends RuntimeException {
4+
public InvalidUseException(final String format, final Object... args) {
5+
super(String.format(format, args));
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,13 @@
11
package fr.chuckame.marlinfw.configurator;
22

3-
import fr.chuckame.marlinfw.configurator.change.LineChange;
4-
import fr.chuckame.marlinfw.configurator.change.LineChangeFormatter;
5-
import fr.chuckame.marlinfw.configurator.change.LineChangeManager;
6-
import fr.chuckame.marlinfw.configurator.constant.Constant;
7-
import fr.chuckame.marlinfw.configurator.constant.ProfilePropertiesChangeAdapter;
8-
import fr.chuckame.marlinfw.configurator.profile.ProfilePropertiesParser;
9-
import fr.chuckame.marlinfw.configurator.util.FileHelper;
10-
import lombok.RequiredArgsConstructor;
11-
import org.springframework.boot.ApplicationArguments;
12-
import org.springframework.boot.ApplicationRunner;
133
import org.springframework.boot.SpringApplication;
144
import org.springframework.boot.autoconfigure.SpringBootApplication;
15-
import reactor.core.publisher.Flux;
16-
import reactor.core.publisher.Mono;
17-
import reactor.util.function.Tuple2;
18-
19-
import java.nio.file.Path;
20-
import java.util.Collection;
21-
import java.util.List;
22-
import java.util.Map;
23-
import java.util.Scanner;
24-
import java.util.function.Function;
25-
import java.util.function.Predicate;
26-
import java.util.stream.Collectors;
275

286
@SpringBootApplication
29-
@RequiredArgsConstructor
30-
public class MarlinConfigurator implements ApplicationRunner {
7+
public class MarlinConfigurator {
318
public static void main(final String[] args) {
32-
SpringApplication.run(MarlinConfigurator.class, args);
33-
}
34-
35-
private final ProfilePropertiesChangeAdapter changeAdapter;
36-
private final LineChangeManager lineChangeManager;
37-
private final LineChangeFormatter lineChangeFormatter;
38-
private final ProfilePropertiesParser profilePropertiesParser;
39-
private final FileHelper fileHelper;
40-
41-
// get profileProperties
42-
// get lines of file
43-
// prepare profileProperties in memory
44-
// prompt the user all changes (E/E&C/D) and ERRORs
45-
// prompt WARN when missing constants that are defined into profile
46-
// is no error, apply changes into files if user agree (or if --yes arg)
47-
48-
@Override
49-
public void run(final ApplicationArguments args) throws Exception {
50-
try {
51-
final Path profilePath = getRequiredArgPath("profile", args);
52-
final List<Path> filesPath = getRequiredArgPaths("file", args);
53-
final boolean forceApply = args.containsOption("apply");
54-
55-
profilePropertiesParser.parseFromFile(profilePath)
56-
.map(changeAdapter::getWantedConstants)
57-
.flatMap(wantedConstants ->
58-
prepareChanges(filesPath, wantedConstants)
59-
.flatMap(changes -> printChanges(changes)
60-
.then(printUnusedConstants(changes, wantedConstants))
61-
.then(checkIfUserAgree(forceApply))
62-
.then(applyAndSaveChanges(changes)))
63-
)
64-
.blockOptional()
65-
;
66-
} catch (final InvalidArgException e) {
67-
System.err.println(e.getMessage());
68-
System.exit(1);
69-
}
70-
}
71-
72-
private Mono<Void> printChanges(final Map<Path, List<LineChange>> changes) {
73-
return Flux.fromIterable(changes.entrySet())
74-
.concatMap(fileChanges -> Flux.concat(
75-
Flux.just(String.format("%s change(s) for file %s:", fileChanges.getValue().size(), fileChanges.getKey())),
76-
Flux.fromIterable(fileChanges.getValue()).filter(LineChange::isConstant).map(lineChangeFormatter::format),
77-
Flux.just("")
78-
))
79-
.doOnNext(System.out::println)
80-
.then()
81-
;
82-
}
83-
84-
private Mono<Void> printUnusedConstants(final Map<Path, List<LineChange>> changes, final Map<String, Constant> wantedConstants) {
85-
return lineChangeManager.getUnusedWantedConstants(changes.values().stream().flatMap(List::stream).collect(Collectors.toList()), wantedConstants)
86-
.collectList()
87-
.filter(Predicate.not(List::isEmpty))
88-
.doOnNext(unusedConstants -> System.out.printf("Still some unused constants: %s%n", unusedConstants))
89-
.then()
90-
;
91-
}
92-
93-
private Mono<Void> checkIfUserAgree(final boolean forceApply) {
94-
if (forceApply) {
95-
return Mono.empty();
96-
}
97-
return Mono.fromRunnable(() -> System.out.println("Apply changes ? type 'y' to apply changes, or everything else to cancel"))
98-
.then(Mono.fromSupplier(() -> new Scanner(System.in).next()))
99-
.filter("y"::equals)
100-
.switchIfEmpty(Mono.error(() -> new InvalidArgException("User refused to apply")))
101-
.then();
102-
}
103-
104-
public Mono<Map<Path, List<LineChange>>> prepareChanges(final List<Path> filesPath, final Map<String, Constant> wantedConstants) {
105-
return Flux.fromIterable(filesPath)
106-
.flatMap(filePath -> fileHelper.lines(filePath)
107-
.index()
108-
.concatMap(line -> lineChangeManager.prepareChange(line.getT2(), line.getT1().intValue(), wantedConstants))
109-
.collectList()
110-
.zipWith(Mono.just(filePath)))
111-
.collectMap(Tuple2::getT2, Tuple2::getT1);
112-
}
113-
114-
public Mono<Void> applyAndSaveChanges(final Map<Path, List<LineChange>> changes) {
115-
return Flux.fromIterable(changes.entrySet())
116-
.groupBy(Map.Entry::getKey, Map.Entry::getValue)
117-
.flatMap(fileChanges -> fileHelper.write(fileChanges.key(), true, fileChanges.flatMap(this::applyChanges)))
118-
.then();
119-
}
120-
121-
public Flux<String> applyChanges(final Collection<LineChange> changes) {
122-
return Flux.fromIterable(changes).flatMap(lineChangeManager::applyChange);
123-
}
124-
125-
private List<Path> getRequiredArgPaths(final String name, final ApplicationArguments args) {
126-
return Mono.justOrEmpty(args.getOptionValues(name))
127-
.flatMapIterable(Function.identity())
128-
.flatMap(v -> Mono.fromSupplier(() -> Path.of(v))
129-
.doOnError(e -> new InvalidArgException("Invalid value for argument --%s: %s", name, e.getMessage())))
130-
.collectList()
131-
.filter(Predicate.not(List::isEmpty))
132-
.switchIfEmpty(Mono.error(() -> new InvalidArgException("Missing --%s argument", name)))
133-
.block();
134-
}
135-
136-
private Path getRequiredArgPath(final String name, final ApplicationArguments args) {
137-
return Flux.fromIterable(getRequiredArgPaths(name, args))
138-
.single()
139-
.onErrorMap(IndexOutOfBoundsException.class, e -> new InvalidArgException("Only one --%s is allowed", name))
140-
.block();
9+
SpringApplication.run(MarlinConfigurator.class, "apply-profiled", "--save", "--profile", "src/test/resources/profile.yaml", "--file", "src/test/resources/file.h");
10+
//SpringApplication.run(MarlinConfigurator.class, "generate-profile", "--input", "src/test/resources/file.h", "--output", "src/test/resources/profile.yaml");
14111
}
14212
}
14313

144-
class InvalidArgException extends RuntimeException {
145-
public InvalidArgException(final String format, final Object... args) {
146-
super(String.format(format, args));
147-
}
148-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package fr.chuckame.marlinfw.configurator.command;
2+
3+
import com.beust.jcommander.Parameter;
4+
import com.beust.jcommander.Parameters;
5+
import fr.chuckame.marlinfw.configurator.InvalidUseException;
6+
import fr.chuckame.marlinfw.configurator.change.LineChange;
7+
import fr.chuckame.marlinfw.configurator.change.LineChangeFormatter;
8+
import fr.chuckame.marlinfw.configurator.change.LineChangeManager;
9+
import fr.chuckame.marlinfw.configurator.constant.Constant;
10+
import fr.chuckame.marlinfw.configurator.constant.ProfilePropertiesChangeAdapter;
11+
import fr.chuckame.marlinfw.configurator.profile.ProfilePropertiesParser;
12+
import fr.chuckame.marlinfw.configurator.util.FileHelper;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.stereotype.Component;
15+
import reactor.core.publisher.Flux;
16+
import reactor.core.publisher.Mono;
17+
import reactor.util.function.Tuple2;
18+
19+
import java.nio.file.Path;
20+
import java.util.Collection;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Scanner;
24+
import java.util.function.Predicate;
25+
import java.util.stream.Collectors;
26+
27+
@Component
28+
@Parameters(commandNames = "apply-profile")
29+
@RequiredArgsConstructor
30+
public class ApplyProfileCommand implements Command {
31+
@Parameter(names = {"--profile", "-p"}, required = true)
32+
private Path profilePath;
33+
@Parameter(names = {"--file", "--files", "-f"}, required = true)
34+
private List<Path> filesPath;
35+
@Parameter(names = {"--save", "-s"})
36+
private boolean doSave;
37+
@Parameter(names = {"--yes", "-y"})
38+
private boolean applyWithoutPrompt;
39+
40+
private final ProfilePropertiesChangeAdapter changeAdapter;
41+
private final LineChangeManager lineChangeManager;
42+
private final LineChangeFormatter lineChangeFormatter;
43+
private final ProfilePropertiesParser profilePropertiesParser;
44+
private final FileHelper fileHelper;
45+
46+
@Override
47+
public Mono<Void> run() {
48+
return profilePropertiesParser.parseFromFile(profilePath)
49+
.map(changeAdapter::getWantedConstants)
50+
.flatMap(wantedConstants ->
51+
prepareChanges(filesPath, wantedConstants)
52+
.flatMap(changes -> printChanges(changes)
53+
.then(printUnusedConstants(changes, wantedConstants))
54+
.then(doSave ? checkIfUserAgree().then(applyAndSaveChanges(changes)) : Mono.empty()))
55+
);
56+
}
57+
58+
private Mono<Void> printChanges(final Map<Path, List<LineChange>> changes) {
59+
return Flux.fromIterable(changes.entrySet())
60+
.concatMap(fileChanges -> Flux.concat(
61+
Flux.just(String.format("%s change(s) for file %s:", fileChanges.getValue().size(), fileChanges.getKey())),
62+
Flux.fromIterable(fileChanges.getValue()).filter(LineChange::isConstant).map(lineChangeFormatter::format),
63+
Flux.just("")
64+
))
65+
.doOnNext(System.out::println)
66+
.then()
67+
;
68+
}
69+
70+
private Mono<Void> printUnusedConstants(final Map<Path, List<LineChange>> changes, final Map<String, Constant> wantedConstants) {
71+
return lineChangeManager.getUnusedWantedConstants(changes.values().stream().flatMap(List::stream).collect(Collectors.toList()), wantedConstants)
72+
.collectList()
73+
.filter(Predicate.not(List::isEmpty))
74+
.doOnNext(unusedConstants -> System.out.printf("Still some unused constants: %s%n", unusedConstants))
75+
.then();
76+
}
77+
78+
private Mono<Void> checkIfUserAgree() {
79+
if (applyWithoutPrompt) {
80+
return Mono.empty();
81+
}
82+
return Mono.fromRunnable(() -> System.out.println("Apply changes ? type 'y' to apply changes, or everything else to cancel"))
83+
.then(Mono.fromSupplier(() -> new Scanner(System.in).next()))
84+
.filter("y"::equals)
85+
.switchIfEmpty(Mono.error(() -> new InvalidUseException("User refused to apply")))
86+
.then();
87+
}
88+
89+
public Mono<Map<Path, List<LineChange>>> prepareChanges(final List<Path> filesPath, final Map<String, Constant> wantedConstants) {
90+
return Flux.fromIterable(filesPath)
91+
.flatMap(filePath -> fileHelper.lines(filePath)
92+
.index()
93+
.concatMap(line -> lineChangeManager.prepareChange(line.getT2(), line.getT1().intValue(), wantedConstants))
94+
.collectList()
95+
.zipWith(Mono.just(filePath)))
96+
.collectMap(Tuple2::getT2, Tuple2::getT1);
97+
}
98+
99+
public Mono<Void> applyAndSaveChanges(final Map<Path, List<LineChange>> changes) {
100+
return Flux.fromIterable(changes.entrySet())
101+
.groupBy(Map.Entry::getKey, Map.Entry::getValue)
102+
.flatMap(fileChanges -> fileHelper.write(fileChanges.key(), true, fileChanges.flatMap(this::applyChanges)))
103+
.then();
104+
}
105+
106+
public Flux<String> applyChanges(final Collection<LineChange> changes) {
107+
return Flux.fromIterable(changes).flatMap(lineChangeManager::applyChange);
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package fr.chuckame.marlinfw.configurator.command;
2+
3+
import reactor.core.publisher.Mono;
4+
5+
public interface Command {
6+
Mono<Void> run();
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package fr.chuckame.marlinfw.configurator.command;
2+
3+
import com.beust.jcommander.JCommander;
4+
import com.beust.jcommander.ParameterException;
5+
import fr.chuckame.marlinfw.configurator.InvalidUseException;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.boot.CommandLineRunner;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component
11+
@RequiredArgsConstructor
12+
public class CommandRunner implements CommandLineRunner {
13+
private final JCommander jCommander;
14+
15+
@Override
16+
public void run(final String... args) throws Exception {
17+
try {
18+
jCommander.parse(args);
19+
} catch (final ParameterException e) {
20+
System.err.println("Bad argument: " + e.getMessage());
21+
e.usage();
22+
System.exit(1);
23+
}
24+
try {
25+
final Command command = (Command) jCommander.findCommandByAlias(jCommander.getParsedAlias()).getObjects().get(0);
26+
command.run().blockOptional();
27+
} catch (final InvalidUseException e) {
28+
System.err.println(e.getMessage());
29+
System.exit(2);
30+
}
31+
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package fr.chuckame.marlinfw.configurator.command;
2+
3+
import com.beust.jcommander.Parameter;
4+
import com.beust.jcommander.Parameters;
5+
import fr.chuckame.marlinfw.configurator.constant.ConstantLineInterpreter;
6+
import fr.chuckame.marlinfw.configurator.profile.ConstantHelper;
7+
import fr.chuckame.marlinfw.configurator.profile.ProfilePropertiesParser;
8+
import fr.chuckame.marlinfw.configurator.util.FileHelper;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.stereotype.Component;
11+
import reactor.core.publisher.Flux;
12+
import reactor.core.publisher.Mono;
13+
14+
import java.nio.file.Path;
15+
import java.util.List;
16+
17+
@Component
18+
@Parameters(commandNames = "generate-profile")
19+
@RequiredArgsConstructor
20+
public class GenerateProfileCommand implements Command {
21+
@Parameter(names = {"--output", "-o"}, required = true)
22+
private Path profilePath;
23+
@Parameter(names = {"--input", "-i"}, required = true)
24+
private List<Path> filesPath;
25+
26+
private final ProfilePropertiesParser profilePropertiesParser;
27+
private final FileHelper fileHelper;
28+
private final ConstantHelper constantHelper;
29+
private final ConstantLineInterpreter constantLineInterpreter;
30+
31+
// todo: without --output, print to console
32+
33+
@Override
34+
public Mono<Void> run() {
35+
return constantHelper.constantsToProfile(Flux.fromIterable(filesPath)
36+
.flatMap(fileHelper::lines)
37+
.flatMap(constantLineInterpreter::parseLine)
38+
.map(ConstantLineInterpreter.ParsedConstant::getConstant))
39+
.flatMap(profile -> profilePropertiesParser.writeToFile(profile, profilePath));
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package fr.chuckame.marlinfw.configurator.config;
2+
3+
import com.beust.jcommander.JCommander;
4+
import fr.chuckame.marlinfw.configurator.command.Command;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
import java.util.List;
10+
11+
@Configuration
12+
public class JCommanderConfig {
13+
@Bean
14+
public JCommander jCommander(final List<Command> commands, @Value("${command-usage}") final String commandUsage) {
15+
final var jcmd = JCommander.newBuilder();
16+
jcmd.programName(commandUsage);
17+
commands.forEach(jcmd::addCommand);
18+
return jcmd.build();
19+
}
20+
}

0 commit comments

Comments
 (0)