|
10 | 10 | import io.cucumber.messages.NdjsonToMessageIterable;
|
11 | 11 | import io.cucumber.messages.NdjsonToMessageIterable.Deserializer;
|
12 | 12 | import io.cucumber.messages.types.Envelope;
|
13 |
| -import io.cucumber.query.NamingStrategy; |
| 13 | +import io.cucumber.query.NamingStrategy.ExampleName; |
14 | 14 | import picocli.CommandLine;
|
15 | 15 | import picocli.CommandLine.Command;
|
16 | 16 | import picocli.CommandLine.Model.CommandSpec;
|
| 17 | +import picocli.CommandLine.Option; |
| 18 | +import picocli.CommandLine.ParameterException; |
17 | 19 | import picocli.CommandLine.Parameters;
|
18 | 20 | import picocli.CommandLine.Spec;
|
19 | 21 |
|
| 22 | +import java.io.IOException; |
20 | 23 | import java.io.InputStream;
|
21 |
| -import java.io.Writer; |
| 24 | +import java.io.OutputStreamWriter; |
| 25 | +import java.io.PrintWriter; |
| 26 | +import java.nio.charset.StandardCharsets; |
22 | 27 | import java.nio.file.Files;
|
23 | 28 | import java.nio.file.Path;
|
24 | 29 | import java.util.concurrent.Callable;
|
25 | 30 |
|
26 |
| -import static io.cucumber.query.NamingStrategy.ExampleName.NUMBER_AND_PICKLE_IF_PARAMETERIZED; |
| 31 | +import static io.cucumber.junitxmlformatter.cli.JunitXmlFormatter.*; |
| 32 | +import static java.nio.file.Files.newOutputStream; |
| 33 | +import static java.nio.file.StandardOpenOption.CREATE; |
| 34 | +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; |
27 | 35 |
|
28 | 36 | // TODO: Read version from manifest?
|
29 | 37 | @Command(
|
30 |
| - name = "junit-xml-formatter", |
| 38 | + name = JUNIT_XML_FORMATTER_NAME, |
31 | 39 | mixinStandardHelpOptions = true,
|
32 |
| - description = "Converts Cucumber messages to JUnit XML" |
| 40 | + header = "Converts Cucumber messages to JUnit XML", |
| 41 | + versionProvider = ManifestVersionProvider.class |
33 | 42 | )
|
34 | 43 | class JunitXmlFormatter implements Callable<Integer> {
|
| 44 | + static final String JUNIT_XML_FORMATTER_NAME = "junit-xml-formatter"; |
| 45 | + |
35 | 46 | @Spec
|
36 | 47 | private CommandSpec spec;
|
37 | 48 |
|
38 |
| - @Parameters(index = "0", description = "The source file containing Cucumber messages") |
| 49 | + @Parameters( |
| 50 | + index = "0", |
| 51 | + paramLabel = "file", |
| 52 | + description = "The input file containing Cucumber messages. " + |
| 53 | + "Use - to read from the standard input." |
| 54 | + ) |
39 | 55 | private Path source;
|
40 | 56 |
|
| 57 | + @Option( |
| 58 | + names = {"-o", "--output"}, |
| 59 | + arity = "0..1", |
| 60 | + paramLabel = "file", |
| 61 | + description = "The output file containing JUnit XML. " + |
| 62 | + "If file is a directory, a new file be " + |
| 63 | + "created by taking the name of the input file and " + |
| 64 | + "replacing the suffix with '.xml'. If the file is omitted " + |
| 65 | + "the current working directory is used." |
| 66 | + ) |
| 67 | + private Path output; |
| 68 | + |
| 69 | + @Option( |
| 70 | + names = {"-e","--example-naming-strategy"}, |
| 71 | + paramLabel = "strategy", |
| 72 | + description = "How to name examples. Valid values: ${COMPLETION-CANDIDATES}", |
| 73 | + defaultValue = "NUMBER_AND_PICKLE_IF_PARAMETERIZED" |
| 74 | + ) |
| 75 | + private ExampleName exampleNameStrategy; |
| 76 | + |
41 | 77 | @Override
|
42 |
| - public Integer call() throws Exception { |
43 |
| - JsonMapper jsonMapper = JsonMapper.builder() |
| 78 | + public Integer call() throws IOException { |
| 79 | + if (isSourceSystemIn()) { |
| 80 | + if (isDestinationDirectory()) { |
| 81 | + throw new ParameterException( |
| 82 | + spec.commandLine(), |
| 83 | + ("Invalid value '%s' for option '--output': When " + |
| 84 | + "reading from standard input, output can not " + |
| 85 | + "be a directory").formatted(output) |
| 86 | + ); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + try (var envelopes = new NdjsonToMessageIterable(sourceInputStream(), deserializer()); |
| 91 | + var writer = new MessagesToJunitXmlWriter(exampleNameStrategy, outputPrintWriter()) |
| 92 | + ) { |
| 93 | + for (var envelope : envelopes) { |
| 94 | + // TODO: What if exception while writing? |
| 95 | + writer.write(envelope); |
| 96 | + } |
| 97 | + } |
| 98 | + return 0; |
| 99 | + } |
| 100 | + |
| 101 | + public static void main(String... args) { |
| 102 | + var exitCode = new CommandLine(new JunitXmlFormatter()).execute(args); |
| 103 | + System.exit(exitCode); |
| 104 | + } |
| 105 | + |
| 106 | + private boolean isSourceSystemIn() { |
| 107 | + return source.getFileName().toString().equals("-"); |
| 108 | + } |
| 109 | + |
| 110 | + private boolean isDestinationDirectory() { |
| 111 | + return output != null && Files.isDirectory(output); |
| 112 | + } |
| 113 | + |
| 114 | + private static Deserializer deserializer() { |
| 115 | + var jsonMapper = JsonMapper.builder() |
44 | 116 | .addModule(new Jdk8Module())
|
45 | 117 | .addModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))
|
46 | 118 | .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
|
47 | 119 | .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
|
48 | 120 | .enable(DeserializationFeature.USE_LONG_FOR_INTS)
|
49 | 121 | .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
50 | 122 | .build();
|
51 |
| - Deserializer deserializer = json -> jsonMapper.readValue(json, Envelope.class); |
52 |
| - |
53 |
| - // TODO: Use the CLI options. |
54 |
| - NamingStrategy.ExampleName exampleNameStrategy = NUMBER_AND_PICKLE_IF_PARAMETERIZED; |
55 |
| - // TODO: Read from standard in, or read/write from files. |
56 |
| - Writer out = spec.commandLine().getOut(); |
57 |
| - |
58 |
| - try (InputStream in = Files.newInputStream(source)) { |
59 |
| - try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) { |
60 |
| - try (MessagesToJunitXmlWriter writer = new MessagesToJunitXmlWriter(exampleNameStrategy, out)) { |
61 |
| - for (Envelope envelope : envelopes) { |
62 |
| - // TODO: What if exception? |
63 |
| - writer.write(envelope); |
64 |
| - } |
65 |
| - } |
66 |
| - } |
| 123 | + return json -> jsonMapper.readValue(json, Envelope.class); |
| 124 | + } |
| 125 | + |
| 126 | + private PrintWriter outputPrintWriter() { |
| 127 | + if (output == null) { |
| 128 | + return spec.commandLine().getOut(); |
| 129 | + } |
| 130 | + Path path = outputPath(); |
| 131 | + try { |
| 132 | + return new PrintWriter( |
| 133 | + new OutputStreamWriter( |
| 134 | + newOutputStream(path, CREATE, TRUNCATE_EXISTING), |
| 135 | + StandardCharsets.UTF_8 |
| 136 | + ) |
| 137 | + ); |
| 138 | + } catch (IOException e) { |
| 139 | + throw new ParameterException( |
| 140 | + spec.commandLine(), |
| 141 | + ("Invalid value '%s' for option '--output': Could not " + |
| 142 | + "write to '%s'" |
| 143 | + ).formatted(output, path), e); |
67 | 144 | }
|
68 |
| - return 0; |
69 | 145 | }
|
70 | 146 |
|
71 |
| - public static void main(String... args) { |
72 |
| - int exitCode = new CommandLine(new JunitXmlFormatter()).execute(args); |
73 |
| - System.exit(exitCode); |
| 147 | + private Path outputPath() { |
| 148 | + if (!isDestinationDirectory()) { |
| 149 | + return output; |
| 150 | + } |
| 151 | + |
| 152 | + // Given a directory, decide on a file name |
| 153 | + var fileName = source.getFileName().toString(); |
| 154 | + var index = fileName.lastIndexOf("."); |
| 155 | + if (index >= 0) { |
| 156 | + fileName = fileName.substring(0, index); |
| 157 | + } |
| 158 | + var candidate = output.resolve(fileName + ".xml"); |
| 159 | + |
| 160 | + // Avoid overwriting existing files when we decided the file name. |
| 161 | + var counter = 1; |
| 162 | + while(Files.exists(candidate)) { |
| 163 | + candidate = output.resolve(fileName + "." + counter + ".xml"); |
| 164 | + } |
| 165 | + return candidate; |
74 | 166 | }
|
| 167 | + |
| 168 | + private InputStream sourceInputStream() { |
| 169 | + if (isSourceSystemIn()) { |
| 170 | + return System.in; |
| 171 | + } |
| 172 | + try { |
| 173 | + return Files.newInputStream(source); |
| 174 | + } catch (IOException e) { |
| 175 | + throw new ParameterException(spec.commandLine(), "Invalid argument, could not read '%s'".formatted(source), e); |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + |
75 | 180 | }
|
0 commit comments