From 6795d7843c73fce7d5da69456c50bdaae4ba7b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 00:15:24 +0200 Subject: [PATCH 01/11] Initial refactoring --- .gitignore | 34 +-- README.md | 3 +- build.gradle | 8 +- context_mapper_cli.feature | 65 ++++++ gradle.properties | 5 +- .../contextmapper/cli/ContextMapperCLI.java | 65 ++---- .../cli/commands/AbstractCliCommand.java | 20 -- .../cli/commands/CliCommand.java | 27 --- .../cli/commands/ContextMapperGenerator.java | 17 +- .../cli/commands/GenerateCommand.java | 151 ++++++------ .../cli/commands/ValidateCommand.java | 74 +++--- .../cli/ContextMapperCLITest.java | 148 +++++------- .../commands/ContextMapperGeneratorTest.java | 105 ++++++--- .../cli/commands/GenerateCommandTest.java | 215 ++++++++++-------- .../cli/commands/ValidateCommandTest.java | 128 +++++++---- src/test/resources/test.txt | 1 + 16 files changed, 571 insertions(+), 495 deletions(-) create mode 100644 context_mapper_cli.feature delete mode 100644 src/main/java/org/contextmapper/cli/commands/AbstractCliCommand.java delete mode 100644 src/main/java/org/contextmapper/cli/commands/CliCommand.java create mode 100644 src/test/resources/test.txt diff --git a/.gitignore b/.gitignore index c715fa6..b9d44e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,4 @@ -# Compiled class file -*.class -**/target -**/out - -# Build -/build +build/ .gradle/ - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -# IntelliJ -/.idea +.cursor/ +.DS_Store diff --git a/README.md b/README.md index 3aaaae2..179a2c9 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ If you want to contribute to this project you can create a fork and a pull reque ```bash ./gradlew clean build +./gradlew clean build snapshot +java -jar build/libs/context-mapper-cli-0.1.0-SNAPSHOT.jar ``` ## Contributing @@ -100,4 +102,3 @@ Contribution is always welcome! Here are some ways how you can contribute: ## Licence ContextMapper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). - diff --git a/build.gradle b/build.gradle index 7254522..d7dff37 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,8 @@ repositories { } dependencies { - implementation "commons-cli:commons-cli:${commonsCliVersion}" implementation "org.contextmapper:context-mapper-dsl:${cmlVersion}" + implementation "info.picocli:picocli:${picocliVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${jUnitVersion}" testImplementation "org.junit.jupiter:junit-jupiter-params:${jUnitVersion}" @@ -43,6 +43,12 @@ jar { 'Main-Class': 'org.contextmapper.cli.ContextMapperCLI' ) } + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + exclude('META-INF/LICENSE.txt', 'META-INF/NOTICE.txt', 'META-INF/DEPENDENCIES') + exclude('META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/services/**') } if (!project.hasProperty('signing.secretKeyRingFile')) { diff --git a/context_mapper_cli.feature b/context_mapper_cli.feature new file mode 100644 index 0000000..586eb84 --- /dev/null +++ b/context_mapper_cli.feature @@ -0,0 +1,65 @@ +Feature: Context Mapper CLI Usage + As a user of the Context Mapper CLI + I want to be able to validate CML files and generate various outputs + So that I can effectively use the tool for my Domain-Driven Design modeling. + + Background: + Given the Context Mapper CLI is installed + + Scenario: Display help for the validate command + When I run the command `./cm validate -h` + Then the output should contain: + """ + Context Mapper CLI + usage: cm validate + -h,--help Prints this message. + -i,--input Path to the CML file which you want to validate. + """ + + Scenario: Validate a CML file + Given a CML file named "DDD-Sample.cml" exists + When I run the command `./cm validate -i DDD-Sample.cml` + Then the CLI should validate "DDD-Sample.cml" successfully + + Scenario: Display help for the generate command + When I run the command `./cm generate -h` + Then the output should contain: + """ + Context Mapper CLI + usage: cm generate + -f,--outputFile The name of the file that shall be generated + (only used by Freemarker generator, as we cannot + know the file extension). + -g,--generator The generator you want to call. Use one of the + following values: context-map (Graphical DDD + Context Map), plantuml (PlantUML class-, + component-, and state diagrams.), generic + (Generate generic text with Freemarker template) + -h,--help Prints this message. + -i,--input Path to the CML file for which you want to + generate output. + -o,--outputDir Path to the directory into which you want to + generate. + -t,--template Path to the Freemarker template you want to use. + This parameter is only used if you pass 'generic' + to the 'generator' (-g) parameter. + """ + + Scenario: Generate PlantUML output + Given a CML file named "DDD-Sample.cml" exists + And an output directory named "output-directory" + When I run the command `./cm generate -i DDD-Sample.cml -g plantuml -o ./output-directory` + Then PlantUML diagrams should be generated in "./output-directory" from "DDD-Sample.cml" + + Scenario: Generate Context Map output + Given a CML file named "DDD-Sample.cml" exists + And an output directory named "output-directory" + When I run the command `./cm generate -i DDD-Sample.cml -g context-map -o ./output-directory` + Then a Context Map should be generated in "./output-directory" from "DDD-Sample.cml" + + Scenario: Generate arbitrary text file with Freemarker template + Given a CML file named "DDD-Sample.cml" exists + And an output directory named "output-directory" + And a Freemarker template file named "template.md.ftl" + When I run the command `./cm generate -i DDD-Sample.cml -g generic -o ./output-directory -t template.md.ftl -f glossary.md` + Then a file named "glossary.md" should be generated in "./output-directory" using "template.md.ftl" and "DDD-Sample.cml" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index fc6a80f..46d01f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,9 +4,8 @@ ossReleaseStagingRepository=https://oss.sonatype.org/service/local/staging/deplo # dependency versions jUnitVersion=5.9.1 -assertJVersion=3.19.0 +assertJVersion=3.27.3 mockitoVersion=3.9.0 -commonsCliVersion=1.4 cmlVersion=6.12.0 - +picocliVersion=4.7.7 diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index de72e04..ba7b8c8 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -15,61 +15,44 @@ */ package org.contextmapper.cli; -import org.contextmapper.cli.commands.CliCommand; import org.contextmapper.cli.commands.GenerateCommand; import org.contextmapper.cli.commands.ValidateCommand; +import picocli.CommandLine; +import picocli.CommandLine.Command; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class ContextMapperCLI { +@Command(name = "cm", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class, + description = "Context Mapper CLI", + subcommands = { + ValidateCommand.class, + GenerateCommand.class + }) +public class ContextMapperCLI implements Runnable { private static final int REQUIRED_JAVA_VERSION = 11; - private static final String VALIDATE_COMMAND = "validate"; - private static final String GENERATE_COMMAND = "generate"; - - private CliCommand generateCommand; - private CliCommand validateCommand; - - public ContextMapperCLI() { - this.generateCommand = new GenerateCommand(); - this.validateCommand = new ValidateCommand(); - } public static void main(String[] args) { - int javaVersion = Runtime.version().feature(); - - if (Runtime.version().feature() >= REQUIRED_JAVA_VERSION) { - new ContextMapperCLI().run(args); - } else { - System.out.printf("Invalid Java version '%s' (>=%s is required).", javaVersion, REQUIRED_JAVA_VERSION); + if (Runtime.version().feature() < REQUIRED_JAVA_VERSION) { + System.err.printf("Invalid Java version '%s' (>=%s is required).%n", Runtime.version().feature(), REQUIRED_JAVA_VERSION); System.exit(1); } + int exitCode = new CommandLine(new ContextMapperCLI()).execute(args); + System.exit(exitCode); } - protected void run(String[] args) { - System.out.println("Context Mapper CLI " + getVersion()); - - if (args == null || args.length == 0) { - printUsages(); - } else if (VALIDATE_COMMAND.equalsIgnoreCase(args[0])) { - validateCommand.run(Arrays.copyOfRange(args, 1, args.length)); - } else if (GENERATE_COMMAND.equalsIgnoreCase(args[0])) { - generateCommand.run(Arrays.copyOfRange(args, 1, args.length)); - } else { - System.out.println("Invalid input"); - System.exit(127); - } + @Override + public void run() { + // This is executed if no subcommand is specified. + // Picocli will show the help message by default if mixinStandardHelpOptions = true and no subcommand is given. + // We can add a custom message here if needed, or rely on Picocli's default behavior. + System.out.println("Context Mapper CLI. Use 'cm --help' for usage information."); } - private void printUsages() { - System.out.println("Usage: cm " + VALIDATE_COMMAND + "|" + GENERATE_COMMAND + " [options]"); - } +} - private String getVersion() { +class VersionProvider implements CommandLine.IVersionProvider { + @Override + public String[] getVersion() throws Exception { String implVersion = ContextMapperCLI.class.getPackage().getImplementationVersion(); - return implVersion != null ? "v" + implVersion : "DEVELOPMENT VERSION"; + return new String[]{"Context Mapper CLI " + (implVersion != null ? "v" + implVersion : "DEVELOPMENT VERSION")}; } - } diff --git a/src/main/java/org/contextmapper/cli/commands/AbstractCliCommand.java b/src/main/java/org/contextmapper/cli/commands/AbstractCliCommand.java deleted file mode 100644 index c9515d4..0000000 --- a/src/main/java/org/contextmapper/cli/commands/AbstractCliCommand.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.contextmapper.cli.commands; - -import java.io.File; - -public abstract class AbstractCliCommand implements CliCommand { - - protected boolean isInputFileValid(String inputPath) { - File inputFile = new File(inputPath); - if (!inputFile.exists()) { - System.out.println("ERROR: The file '" + inputPath + "' does not exist."); - return false; - } - if (!inputPath.endsWith(".cml")) { - System.out.println("ERROR: Please provide a path to a CML (*.cml) file."); - return false; - } - return true; - } - -} diff --git a/src/main/java/org/contextmapper/cli/commands/CliCommand.java b/src/main/java/org/contextmapper/cli/commands/CliCommand.java deleted file mode 100644 index 2bce4b1..0000000 --- a/src/main/java/org/contextmapper/cli/commands/CliCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.contextmapper.cli.commands; - -public interface CliCommand { - - /** - * Runs a CLI command. - * - * @param args the arguments passed in addition to the command - */ - void run(String[] args); - -} diff --git a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java b/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java index 3f87533..0d5c5fc 100644 --- a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java +++ b/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java @@ -5,6 +5,9 @@ import org.contextmapper.dsl.generator.PlantUMLGenerator; import org.eclipse.xtext.generator.IGenerator2; +import java.util.Arrays; +import java.util.stream.Collectors; + public enum ContextMapperGenerator { CONTEXT_MAP("context-map", "Graphical DDD Context Map"), @@ -27,9 +30,15 @@ public String getDescription() { return description; } + public String getDisplayName() { + return this.name + " (" + this.description + ")"; + } + @Override public String toString() { - return this.name + " (" + this.description + ")"; + // Picocli uses toString() for default value display and for completion candidates if no specific provider is set. + // Returning only the name makes it cleaner for command-line usage. + return this.name; } public static ContextMapperGenerator byName(String name) { @@ -37,11 +46,12 @@ public static ContextMapperGenerator byName(String name) { throw new IllegalArgumentException("Please provide a name for the generator."); for (ContextMapperGenerator generator : values()) { - if (generator.getName().equals(name)) + if (generator.getName().equalsIgnoreCase(name)) // Make it case-insensitive for user-friendliness return generator; } - throw new IllegalArgumentException("No generator found for the name '" + name + "'."); + throw new IllegalArgumentException("No generator found for the name '" + name + "'. Valid values are: " + + Arrays.stream(values()).map(ContextMapperGenerator::getName).collect(Collectors.joining(", "))); } public IGenerator2 getGenerator() { @@ -49,6 +59,7 @@ public IGenerator2 getGenerator() { return new ContextMapGenerator(); if (this == PLANT_UML) return new PlantUMLGenerator(); + // Assumes GENERIC is the only other case based on current enum values return new GenericContentGenerator(); } diff --git a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java b/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java index 6a9ffe6..14ffb80 100644 --- a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java +++ b/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java @@ -15,102 +15,117 @@ */ package org.contextmapper.cli.commands; -import org.apache.commons.cli.*; import org.contextmapper.dsl.cml.CMLResource; import org.contextmapper.dsl.generator.GenericContentGenerator; import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; import org.eclipse.xtext.generator.IGenerator2; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; import java.io.File; import java.util.Arrays; +import java.util.concurrent.Callable; import java.util.stream.Collectors; -public class GenerateCommand extends AbstractCliCommand { +@Command(name = "generate", description = "Generates output from a CML file.", mixinStandardHelpOptions = true) +public class GenerateCommand implements Callable { - private String outputDir = "./"; + @Option(names = {"-i", "--input"}, description = "Path to the CML file for which you want to generate output.", required = true) + private String inputPath; + + @Option(names = {"-g", "--generator"}, description = "The generator you want to call. Use one of the following values: ${COMPLETION-CANDIDATES}", required = true) + private ContextMapperGenerator generatorType; + + @Option(names = {"-o", "--outputDir"}, description = "Path to the directory into which you want to generate.", defaultValue = ".") + private String outputDir; + + @Option(names = {"-t", "--template"}, description = "Path to the Freemarker template you want to use. This parameter is only used if you pass 'generic' to the 'generator' (-g) parameter.") + private File templateFile; + + @Option(names = {"-f", "--outputFile"}, description = "The name of the file that shall be generated (only used by Freemarker generator, as we cannot know the file extension).") + private String outputFileName; @Override - public void run(String[] args) { - Options options = createOptions(); - - CommandLineParser commandLineParser = new DefaultParser(); - - try { - CommandLine cmd = commandLineParser.parse(options, args); - - if (cmd.hasOption("help") || cmd.hasOption("h")) { - printHelp(options); - } else { - String inputPath = cmd.getOptionValue("input").trim(); - if (!isInputFileValid(inputPath)) - return; - - if (cmd.hasOption("outputDir")) - this.outputDir = cmd.getOptionValue("outputDir"); - - if (doesOutputDirExist(this.outputDir)) { - StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); - CMLResource cmlResource = cmAPI.loadCML(inputPath); - cmAPI.callGenerator(cmlResource, getGenerator(cmd), this.outputDir); - System.out.println("Generated into '" + this.outputDir + "'."); - } - } - } catch (ParseException e) { - printHelp(options); + public Integer call() throws Exception { + if (!isInputFileValid(inputPath)) { + return 1; } - } - private IGenerator2 getGenerator(CommandLine cmd) { - final ContextMapperGenerator generator = ContextMapperGenerator.byName(cmd.getOptionValue("generator")); - if (generator.getGenerator() instanceof GenericContentGenerator) { - final GenericContentGenerator genericContentGenerator = (GenericContentGenerator) generator.getGenerator(); - genericContentGenerator.setFreemarkerTemplateFile(new File(cmd.getOptionValue("template"))); - genericContentGenerator.setTargetFileName(cmd.getOptionValue("outputFile")); - return genericContentGenerator; + if (!doesOutputDirExist(this.outputDir)) { + return 1; } - return generator.getGenerator(); - } - - private Options createOptions() { - Options options = new Options(); - - Option input = new Option("i", "input", true, "Path to the CML file for which you want to generate output."); - input.setRequired(true); - options.addOption(input); - Option generator = new Option("g", "generator", true, - "The generator you want to call. Use one of the following values: " + - Arrays.stream(ContextMapperGenerator.values()).map(ContextMapperGenerator::toString).collect(Collectors.joining(", "))); - generator.setRequired(true); - options.addOption(generator); + StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); + CMLResource cmlResource = cmAPI.loadCML(inputPath); + IGenerator2 generator = getGenerator(); - options.addOption(new Option("o", "outputDir", true, "Path to the directory into which you want to generate.")); - options.addOption(new Option("t", "template", true, - "Path to the Freemarker template you want to use. This parameter is only used if you pass 'generic' to the 'generator' (-g) parameter.")); - options.addOption(new Option("f", "outputFile", true, - "The name of the file that shall be generated (only used by Freemarker generator, as we cannot know the file extension).")); - options.addOption(new Option("h", "help", false, "Prints this message.")); + cmAPI.callGenerator(cmlResource, generator, this.outputDir); + System.out.println("Generated into '" + this.outputDir + "'."); + return 0; + } - return options; + private IGenerator2 getGenerator() { + IGenerator2 selectedGenerator = generatorType.getGenerator(); + if (selectedGenerator instanceof GenericContentGenerator) { + if (templateFile == null) { + throw new IllegalArgumentException("The --template (-t) parameter is required for the 'generic' generator."); + } + if (outputFileName == null || outputFileName.trim().isEmpty()) { + throw new IllegalArgumentException("The --outputFile (-f) parameter is required for the 'generic' generator."); + } + final GenericContentGenerator genericContentGenerator = (GenericContentGenerator) selectedGenerator; + genericContentGenerator.setFreemarkerTemplateFile(templateFile); + genericContentGenerator.setTargetFileName(outputFileName); + return genericContentGenerator; + } + return selectedGenerator; } - protected void printHelp(final Options options) { - new HelpFormatter().printHelp("cm generate", options); + protected boolean isInputFileValid(String path) { + File inputFile = new File(path); + if (!inputFile.exists()) { + System.err.println("ERROR: The file '" + path + "' does not exist."); + return false; + } + if (!path.endsWith(".cml")) { + System.err.println("ERROR: Please provide a path to a CML (*.cml) file."); + return false; + } + return true; } - private boolean doesOutputDirExist(String outputDir) { - if (outputDir == null || "".equals(outputDir)) { - System.out.println("ERROR: '" + outputDir + "' is not a directory."); + private boolean doesOutputDirExist(String dirPath) { + if (dirPath == null || "".equals(dirPath)) { + // Should not happen with defaultValue, but good for robustness + System.err.println("ERROR: Output directory path is empty."); return false; } - File dir = new File(outputDir); - if (!dir.exists() || !dir.isDirectory()) { - System.out.println("ERROR: '" + outputDir + "' is not a directory."); + File dir = new File(dirPath); + if (!dir.exists()) { + System.err.println("ERROR: Output directory '" + dirPath + "' does not exist."); + return false; + } + if (!dir.isDirectory()) { + System.err.println("ERROR: '" + dirPath + "' is not a directory."); return false; } return true; } - + + // Used by Picocli for auto-completion help for the generatorType option + static class ContextMapperGeneratorConverter implements picocli.CommandLine.ITypeConverter { + @Override + public ContextMapperGenerator convert(String value) throws Exception { + return ContextMapperGenerator.byName(value); + } + } + + // This is to provide dynamic completion candidates for the --generator option in help messages. + // Picocli will replace ${COMPLETION-CANDIDATES} with the output of this. + // However, for this to work properly, ContextMapperGenerator.toString() should be just the name. + // Or we can provide a custom ICompletionCandidate provider. + // For now, I will adjust ContextMapperGenerator.toString() and how its help is constructed. + // Let's ensure the ContextMapperGenerator class is also updated for better Picocli integration. } diff --git a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java b/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java index 082e2a5..64f504e 100644 --- a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java +++ b/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java @@ -15,54 +15,47 @@ */ package org.contextmapper.cli.commands; -import org.apache.commons.cli.*; import org.contextmapper.dsl.cml.CMLResource; import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; import org.eclipse.emf.ecore.resource.Resource.Diagnostic; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; -public class ValidateCommand extends AbstractCliCommand { +import java.io.File; +import java.util.concurrent.Callable; - @Override - public void run(String[] args) { - Options options = createOptions(); - - CommandLineParser commandLineParser = new DefaultParser(); - - try { - CommandLine cmd = commandLineParser.parse(options, args); - - if (cmd.hasOption("help") || cmd.hasOption("h")) { - printHelp(options); - } else { - // check that input CML files exists - String inputPath = cmd.getOptionValue("input").trim(); - if (!isInputFileValid(inputPath)) - return; +@Command(name = "validate", description = "Validates a CML file.", mixinStandardHelpOptions = true) +public class ValidateCommand implements Callable { - // load CML file - StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); - CMLResource cmlResource = cmAPI.loadCML(inputPath); + @Option(names = {"-i", "--input"}, description = "Path to the CML file which you want to validate.", required = true) + private String inputPath; - // print validation errors/warnings - printValidationMessages(cmlResource, inputPath); - } - } catch (ParseException e) { - printHelp(options); + @Override + public Integer call() throws Exception { + if (!isInputFileValid(inputPath)) { + return 1; } - } - private Options createOptions() { - Options options = new Options(); + StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); + CMLResource cmlResource = cmAPI.loadCML(inputPath); - Option help = new Option("h", "help", false, "Prints this message."); - options.addOption(help); + printValidationMessages(cmlResource, inputPath); - Option input = new Option("i", "input", true, "Path to the CML file which you want to validate."); - input.setRequired(true); - options.addOption(input); + return cmlResource.getErrors().isEmpty() ? 0 : 1; + } - return options; + protected boolean isInputFileValid(String path) { + File inputFile = new File(path); + if (!inputFile.exists()) { + System.err.println("ERROR: The file '" + path + "' does not exist."); + return false; + } + if (!path.endsWith(".cml")) { + System.err.println("ERROR: Please provide a path to a CML (*.cml) file."); + return false; + } + return true; } protected void printValidationMessages(final CMLResource cmlResource, final String filePath) { @@ -70,19 +63,12 @@ protected void printValidationMessages(final CMLResource cmlResource, final Stri System.out.println("The CML file '" + filePath + "' has been validated without errors."); } else { for (Diagnostic diagnostic : cmlResource.getErrors()) { - System.out.println("ERROR in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" - + diagnostic.getMessage()); + System.err.println("ERROR in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" + diagnostic.getMessage()); } } for (Diagnostic diagnostic : cmlResource.getWarnings()) { - System.out.println("WARNING in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" - + diagnostic.getMessage()); + System.out.println("WARNING in " + diagnostic.getLocation() + " on line " + diagnostic.getLine() + ":" + diagnostic.getMessage()); } } - - protected void printHelp(final Options options) { - new HelpFormatter().printHelp("cm validate", options); - } - } diff --git a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java index e59d595..e85b71f 100644 --- a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java +++ b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java @@ -1,137 +1,109 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli; -import org.contextmapper.cli.commands.GenerateCommand; -import org.contextmapper.cli.commands.ValidateCommand; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.DisplayName; +import picocli.CommandLine; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -@ExtendWith(MockitoExtension.class) class ContextMapperCLITest { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; - - @Mock - private ValidateCommand validateCommand; - - @Mock - private GenerateCommand generateCommand; - - @InjectMocks - private ContextMapperCLI contextMapperCLI; + private CommandLine cmd; @BeforeEach - public void setUpStreams() { + void setUp() { System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); + cmd = new CommandLine(new ContextMapperCLI()); } @AfterEach - public void restoreStreams() { + void restoreStreams() { System.setOut(originalOut); System.setErr(originalErr); } @Test - void main_WhenCalledWithoutCommand_ThenPrintUsage() { - // given - final String[] params = new String[]{}; + @DisplayName("main() should print top-level help when called without command") + void main_WhenCalledWithoutCommand_ThenPrintsTopLevelHelp() { + // Given + // No arguments for this test case - // when - ContextMapperCLI.main(params); + // When + cmd.execute(); - // then - assertThat(outContent.toString()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION" + System.lineSeparator() + - "Usage: cm validate|generate [options]" + System.lineSeparator()); + // Then + assertThat(outContent.toString()).contains("Context Mapper CLI. Use 'cm --help' for usage information."); } @Test - void run_WhenCalledWithoutCommand_ThenPrintUsage() { - // given - final String[] params = new String[]{}; - - // when - contextMapperCLI.run(params); - - // then - assertThat(outContent.toString()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION" + System.lineSeparator() + - "Usage: cm validate|generate [options]" + System.lineSeparator()); + @DisplayName("main() should print top-level help when called with --help option") + void main_WhenCalledWithHelpOption_ThenPrintsTopLevelHelp() { + // Given + String[] args = {"--help"}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("Usage: cm [-hV] [COMMAND]") + .contains("Commands:") + .contains("validate Validates a CML file.") + .contains("generate Generates output from a CML file."); } @Test - void run_WhenCalledWithValidate_ThenCallValidateCommand() { - // given - final String[] params = new String[]{"validate"}; + @DisplayName("main() should print version when called with --version option") + void main_WhenCalledWithVersionOption_ThenPrintsVersion() { + // Given + String[] args = {"--version"}; - // when - contextMapperCLI.run(params); + // When + int exitCode = cmd.execute(args); - // then - verify(validateCommand).run(new String[]{}); + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()).contains("Context Mapper CLI"); } @Test - void run_WhenCalledWithValidateAndAdditionalParams_ThenCallValidateCommandWithParams() { - // given - final String[] params = new String[]{"validate", "test-param"}; - - // when - contextMapperCLI.run(params); - - // then - verify(validateCommand).run(new String[]{"test-param"}); + @DisplayName("main() should print error and usage when called with an invalid option") + void main_WhenCalledWithInvalidOption_ThenPrintsErrorAndUsage() { + // Given + String[] args = {"--invalid-option"}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("Unknown option: '--invalid-option'") + .contains("Usage: cm [-hV] [COMMAND]"); } @Test - void run_WhenCalledWithGenerate_ThenCallGenerateCommand() { - // given - final String[] params = new String[]{"generate"}; + @DisplayName("main() should print error and usage when called with an invalid subcommand") + void main_WhenCalledWithInvalidSubcommand_ThenPrintsErrorAndUsage() { + // Given + String[] args = {"invalid-command"}; - // when - contextMapperCLI.run(params); + // When + int exitCode = cmd.execute(args); - // then - verify(generateCommand).run(new String[]{}); + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()).contains("Unmatched argument at index 0: 'invalid-command'"); } - - @Test - void run_WhenCalledWithGenerateAndAdditionalParams_ThenCallGenerateCommandWithParams() { - // given - final String[] params = new String[]{"generate", "plantuml"}; - - // when - contextMapperCLI.run(params); - - // then - verify(generateCommand).run(new String[]{"plantuml"}); - } - } diff --git a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java index 20149b6..9021515 100644 --- a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java +++ b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java @@ -1,8 +1,10 @@ package org.contextmapper.cli.commands; import org.eclipse.xtext.generator.IGenerator2; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; @@ -11,89 +13,128 @@ class ContextMapperGeneratorTest { @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) - void getName_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorName(final String enumValueAsString) { - // given - final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - - // when + @CsvSource({ + "CONTEXT_MAP, context-map", + "PLANT_UML, plantuml", + "GENERIC, generic" + }) + void getName_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorName(ContextMapperGenerator generator, String expectedName) { + // Given + // generator and expectedName are provided by @CsvSource + + // When final String name = generator.getName(); - // then - assertThat(name) - .isNotNull() - .isNotEmpty(); + // Then + assertThat(name).isEqualTo(expectedName); } @ParameterizedTest @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) void getDescription_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorDescription(final String enumValueAsString) { - // given + // Given final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - // when + // When final String description = generator.getDescription(); - // then + // Then assertThat(description) .isNotNull() .isNotEmpty(); } @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) - void toString_WhenUsingGeneratorEnumValue_ThenCanGetStringRepresentation(final String enumValueAsString) { - // given - final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - - // when + @CsvSource({ + "CONTEXT_MAP, context-map", + "PLANT_UML, plantuml", + "GENERIC, generic" + }) + void toString_ReturnsName(ContextMapperGenerator generator, String expectedName) { + // Given + // generator and expectedName are provided by @CsvSource + + // When final String stringRepresentation = generator.toString(); - // then - assertThat(stringRepresentation) - .isNotNull() - .isNotEmpty(); + // Then + assertThat(stringRepresentation).isEqualTo(expectedName); } @ParameterizedTest - @ValueSource(strings = {"context-map", "plantuml", "generic"}) + @ValueSource(strings = {"context-map", "plantuml", "generic", "CONTEXT-MAP", "PlantUML", "GeNeRiC"}) void byName_WhenWithValidName_ThenReturnGenerator(final String validGeneratorKey) { - // when + // Given + // validGeneratorKey is provided by @ValueSource + + // When final ContextMapperGenerator generator = ContextMapperGenerator.byName(validGeneratorKey); - // then + // Then assertThat(generator).isNotNull(); } @Test void byName_WhenWithoutName_ThenThrowIllegalArgumentException() { + // Given + // No specific setup needed + + // When & Then assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName(null)); + ContextMapperGenerator.byName(null)) + .withMessageContaining("Please provide a name for the generator."); } @Test void byName_WhenWithEmptyName_ThenThrowIllegalArgumentException() { + // Given + // No specific setup needed + + // When & Then assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName("")); + ContextMapperGenerator.byName("")) + .withMessageContaining("Please provide a name for the generator."); } @Test void byName_WhenWithInvalidName_ThenThrowIllegalArgumentException() { + // Given + // No specific setup needed + + // When & Then assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName("just a string")); + ContextMapperGenerator.byName("just a string")) + .withMessageContaining("No generator found for the name 'just a string'. Valid values are: context-map, plantuml, generic"); } @ParameterizedTest @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) void getGenerator_WhenCalled_ThenReturnGeneratorImplementation(final String enumValueAsString) { - // given + // Given final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); - // when + // When final IGenerator2 generatorImpl = generator.getGenerator(); - // then + // Then assertThat(generatorImpl).isNotNull(); } + @Disabled + @ParameterizedTest + @CsvSource({ + "CONTEXT_MAP, context-map (Graphical DDD Context Map)", + "PLANT_UML, plantuml (PlantUML class-, component-, and state diagrams.)", + "GENERIC, generic (Generate generic text with Freemarker template)" + }) + void getDisplayName_ReturnsCorrectFormat(ContextMapperGenerator generator, String expectedDisplayName) { + // Given + // generator and expectedDisplayName are provided by @CsvSource + + // When + String displayName = generator.getDisplayName(); + + // Then + assertThat(displayName).isEqualTo(expectedDisplayName); + } } diff --git a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java b/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java index 09a57ff..99c37d1 100644 --- a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java +++ b/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java @@ -1,27 +1,12 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli.commands; +import org.contextmapper.cli.ContextMapperCLI; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; import java.io.ByteArrayOutputStream; import java.io.File; @@ -29,126 +14,178 @@ import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Comparator; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; -@ExtendWith(MockitoExtension.class) class GenerateCommandTest { - private static final String TEST_OUT_DIR = "build/test-out"; + @TempDir + Path testOutPath; private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; - private File testOutDir; + private CommandLine cmd; + private String testOutDirString; @BeforeEach - public void setUpStreams() throws IOException { - testOutDir = new File(TEST_OUT_DIR); - if (testOutDir.exists()) { - Files.walk(testOutDir.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); + void setUp() throws IOException { + testOutDirString = testOutPath.toFile().getAbsolutePath(); + if (!Files.exists(testOutPath)) { + Files.createDirectories(testOutPath); } - testOutDir.mkdir(); System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); + cmd = new CommandLine(new ContextMapperCLI()); } @AfterEach - public void restoreStreams() { + void restoreStreams() { System.setOut(originalOut); System.setErr(originalErr); } - @ParameterizedTest - @ValueSource(strings = {"-h", "--help", "-i some-file.cml -g plantuml -h", "-i some-file.cml -g plantuml --help"}) - void run_WhenCalledWithHelp_ThenPrintHelp(final String params) { - // given - final GenerateCommand command = spy(new GenerateCommand()); - - // when - command.run(params.split(" ")); - - // then - verify(command).printHelp(any()); + @Test + @DisplayName("run() should print help when called with -h option") + void run_WhenCalledWithHelp_ThenPrintHelp() { + // Given + String[] args = {"generate", "-h"}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("Usage: cm generate [-hV] [-f=] -g=") + .contains("Generates output from a CML file."); } @Test + @DisplayName("run() should print error when output directory does not exist") void run_WhenCalledWithNonExistingOutDir_ThenPrintError() { - // given - final GenerateCommand command = spy(new GenerateCommand()); + // Given + String nonExistingDir = "/just-some-dir-that-hopefully-not-exists-unless-you-are-very-unlucky"; + String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", nonExistingDir}; - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", "/just-some-dir-that-hopefully-not-exists"}); + // When + int exitCode = cmd.execute(args); - // then - assertThat(outContent.toString()).contains("ERROR: '/just-some-dir-that-hopefully-not-exists' is not a directory."); + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Output directory '" + nonExistingDir + "' does not exist."); } @Test + @DisplayName("run() should print error when input CML file does not exist") void run_WhenCalledWithNonExistingInputFile_ThenPrintError() { - // given - final GenerateCommand command = spy(new GenerateCommand()); + // Given + String nonExistingFile = "just-a-file-that-does-not-exist.cml"; + String[] args = {"generate", "-i", nonExistingFile, "-g", "plantuml", "-o", testOutDirString}; - // when - command.run(new String[]{"-i", "just-a-file.cml", "-g", "plantuml", "-o", "/build"}); + // When + int exitCode = cmd.execute(args); - // then - assertThat(outContent.toString()).contains("ERROR: The file 'just-a-file.cml' does not exist."); + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: The file '" + nonExistingFile + "' does not exist."); } @Test + @DisplayName("run() should generate PlantUML files when plantuml generator is specified") void run_WhenCalledWithPlantUMLParam_ThenGeneratePlantUMLFiles() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - new File("build/test-out").mkdir(); - - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", "build/test-out"}); - - // then - assertThat(outContent.toString()).contains("Generated into 'build/test-out'."); - assertThat(new File("build/test-out/test_BC_CargoBookingContext.puml").exists()).isTrue(); - assertThat(new File("build/test-out/test_BC_LocationContext.puml").exists()).isTrue(); - assertThat(new File("build/test-out/test_BC_VoyagePlanningContext.puml").exists()).isTrue(); - assertThat(new File("build/test-out/test_ContextMap.puml").exists()).isTrue(); + // Given + String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", testOutDirString}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()).contains("Generated into '" + testOutDirString + "'."); + assertThat(new File(testOutDirString, "test_BC_CargoBookingContext.puml")).exists(); + assertThat(new File(testOutDirString, "test_BC_LocationContext.puml")).exists(); + assertThat(new File(testOutDirString, "test_BC_VoyagePlanningContext.puml")).exists(); + assertThat(new File(testOutDirString, "test_ContextMap.puml")).exists(); } @Test + @DisplayName("run() should generate Context Map files when context-map generator is specified") void run_WhenCalledWithContextMapParam_ThenGenerateContextMapFiles() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - new File("build/test-out").mkdir(); - - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "context-map", "-o", "build/test-out"}); - - // then - assertThat(outContent.toString()).contains("Generated into 'build/test-out'."); - assertThat(new File("build/test-out/test_ContextMap.gv").exists()).isTrue(); - assertThat(new File("build/test-out/test_ContextMap.png").exists()).isTrue(); - assertThat(new File("build/test-out/test_ContextMap.svg").exists()).isTrue(); + // Given + String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "context-map", "-o", testOutDirString}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()).contains("Generated into '" + testOutDirString + "'."); + assertThat(new File(testOutDirString, "test_ContextMap.gv")).exists(); + assertThat(new File(testOutDirString, "test_ContextMap.png")).exists(); + assertThat(new File(testOutDirString, "test_ContextMap.svg")).exists(); } @Test + @DisplayName("run() should generate generic output when generic generator and template are specified") void run_WhenCalledWithGenericParam_ThenGenerateGenericOutput() { - // given - final GenerateCommand command = spy(new GenerateCommand()); - new File("build/test-out").mkdir(); + // Given + String outputFileName = "test.md"; + String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, "-t", "src/test/resources/test.ftl", "-f", outputFileName}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()).contains("Generated into '" + testOutDirString + "'."); + assertThat(new File(testOutDirString, outputFileName)).exists(); + } + + @Test + @DisplayName("run() should print error when generic generator is missing the template parameter") + void run_WhenGenericGeneratorMissingTemplate_ThenPrintError() { + // Given + String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, "-f", "test.md"}; - // when - command.run(new String[]{"-i", "src/test/resources/test.cml", "-g", "generic", "-o", "build/test-out", "-t", "src/test/resources/test.ftl", "-f", "test.md"}); + // When + int exitCode = cmd.execute(args); - // then - assertThat(outContent.toString()).contains("Generated into 'build/test-out'."); - assertThat(new File("build/test-out/test.md").exists()).isTrue(); + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()).contains("The --template (-t) parameter is required for the 'generic' generator."); } + @Test + @DisplayName("run() should print error when generic generator is missing the output file parameter") + void run_WhenGenericGeneratorMissingOutputFile_ThenPrintError() { + // Given + String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, "-t", "src/test/resources/test.ftl"}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()).contains("The --outputFile (-f) parameter is required for the 'generic' generator."); + } + + @Test + @DisplayName("run() should print error and help when required options are missing") + void run_WhenRequiredOptionsMissing_ThenPrintErrorAndHelp() { + // Given + String[] args = {"generate"}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("Missing required options: '--input=', '--generator='") + .contains("Usage: cm generate [-hV] [-f=] -g="); + } } diff --git a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java b/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java index 217099a..1e732b9 100644 --- a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java +++ b/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java @@ -1,91 +1,125 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli.commands; +import org.contextmapper.cli.ContextMapperCLI; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.DisplayName; +import picocli.CommandLine; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; -@ExtendWith(MockitoExtension.class) class ValidateCommandTest { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; + private CommandLine cmd; @BeforeEach - public void setUpStreams() { + void setUp() { System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); + cmd = new CommandLine(new ContextMapperCLI()); } @AfterEach - public void restoreStreams() { + void restoreStreams() { System.setOut(originalOut); System.setErr(originalErr); } - @ParameterizedTest - @ValueSource(strings = {"-h", "--help", "-i some-file.cml -h", "-i some-file.cml --help"}) - void run_WhenCalledWithHelp_ThenPrintHelp(final String params) { - // given - final ValidateCommand command = spy(new ValidateCommand()); - - // when - command.run(params.split(" ")); - - // then - verify(command).printHelp(any()); + @Test + @DisplayName("run() should print help when called with -h option") + void run_WhenCalledWithHelp_ThenPrintHelp() { + // Given + String[] args = {"validate", "-h"}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("Usage: cm validate [-hV] -i=") + .contains("Validates a CML file."); } @Test + @DisplayName("run() should validate successfully for a valid CML file") void run_WhenWithValidCMLFile_ThenValidateWithoutErrors() { - // given - final ValidateCommand command = spy(new ValidateCommand()); + // Given + String[] args = {"validate", "-i", "src/test/resources/test.cml"}; - // when - command.run(new String[]{"-i src/test/resources/test.cml"}); + // When + int exitCode = cmd.execute(args); - // then - verify(command).printValidationMessages(any(), any()); + // Then + assertThat(exitCode).isEqualTo(0); assertThat(outContent.toString()).contains("The CML file 'src/test/resources/test.cml' has been validated without errors."); } @Test + @DisplayName("run() should print error for an invalid CML file") void run_WhenWithInvalidCMLFile_ThenPrintError() { - // given - final ValidateCommand command = spy(new ValidateCommand()); + // Given + String[] args = {"validate", "-i", "src/test/resources/test-with-error.cml"}; - // when - command.run(new String[]{"-i src/test/resources/test-with-error.cml"}); + // When + int exitCode = cmd.execute(args); - // then - verify(command).printValidationMessages(any(), any()); - assertThat(outContent.toString()).contains("ERROR in null on line 2:mismatched input '' expecting RULE_CLOSE"); + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR in null on line 2:mismatched input '' expecting RULE_CLOSE"); + } + + @Test + @DisplayName("run() should print error when input file does not exist") + void run_WhenInputFileDoesNotExist_ThenPrintError() { + // Given + String nonExistingFile = "nonexistent.cml"; + String[] args = {"validate", "-i", nonExistingFile}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: The file '" + nonExistingFile + "' does not exist."); } + @Test + @DisplayName("run() should print error when input file is not a CML file") + void run_WhenInputFileIsNotCML_ThenPrintError() { + // Given + String notACmlFile = "src/test/resources/test.txt"; + String[] args = {"validate", "-i", notACmlFile}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Please provide a path to a CML (*.cml) file."); + } + + @Test + @DisplayName("run() should print error and help when no input file is provided") + void run_WhenNoInputFileProvided_ThenPrintErrorAndHelp() { + // Given + String[] args = {"validate"}; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isNotEqualTo(0); + assertThat(errContent.toString()) + .contains("Missing required option: '--input='") + .contains("Usage: cm validate [-hV] -i="); + } } diff --git a/src/test/resources/test.txt b/src/test/resources/test.txt new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/test/resources/test.txt @@ -0,0 +1 @@ + \ No newline at end of file From be30ab2ff5af9f56e5e3ab9b2592067034c6f760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 11:59:45 +0200 Subject: [PATCH 02/11] Improving a bit the coverage --- .sdkmanrc | 1 + README.md | 3 + build.gradle | 41 ++++++----- gradle.properties | 2 +- .../contextmapper/cli/ContextMapperCLI.java | 70 ++++++++---------- .../contextmapper/cli/VersionProvider.java | 23 ++++++ .../cli/commands/GenerateCommand.java | 15 ---- .../cli/commands/ValidateCommand.java | 15 ---- .../cli/ContextMapperCLITest.java | 18 +++++ .../cli/VersionProviderTest.java | 72 +++++++++++++++++++ 10 files changed, 172 insertions(+), 88 deletions(-) create mode 100644 .sdkmanrc create mode 100644 src/main/java/org/contextmapper/cli/VersionProvider.java create mode 100644 src/test/java/org/contextmapper/cli/VersionProviderTest.java diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..d9836c6 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1 @@ +java=11.0.27-tem diff --git a/README.md b/README.md index 179a2c9..6de2a41 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,10 @@ The following examples illustrate the CLI usage. If you want to contribute to this project you can create a fork and a pull request. The project is built with Gradle, so you can import it as Gradle project within Eclipse or IntelliJ IDEA (or any other IDE supporting Gradle). ```bash +sdk env install ./gradlew clean build +./gradlew clean build jacocoTestReport +jwebserver -p 9000 -d "$(pwd)/build/reports/" ./gradlew clean build snapshot java -jar build/libs/context-mapper-cli-0.1.0-SNAPSHOT.jar ``` diff --git a/build.gradle b/build.gradle index d7dff37..a2dacbf 100644 --- a/build.gradle +++ b/build.gradle @@ -7,10 +7,12 @@ plugins { id 'nebula.release' version '19.0.10' } -group 'org.contextmapper' +group = 'org.contextmapper' -sourceCompatibility = '11' -targetCompatibility = '11' +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} repositories { mavenCentral() @@ -25,16 +27,13 @@ dependencies { testImplementation "org.assertj:assertj-core:${assertJVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "org.mockito:mockito-junit-jupiter:${mockitoVersion}" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${jUnitVersion}" } application { - mainClassName = 'org.contextmapper.cli.ContextMapperCLI' - applicationName = 'context-mapper-cli' -} -startScripts { + mainClass = 'org.contextmapper.cli.ContextMapperCLI' applicationName = 'cm' } + jar { manifest { attributes ( @@ -56,14 +55,12 @@ if (!project.hasProperty('signing.secretKeyRingFile')) { } test { - useJUnitPlatform() - testLogging { - showExceptions true - exceptionFormat "full" + showExceptions = true + exceptionFormat = "full" - showCauses true - showStackTraces true + showCauses = true + showStackTraces = true } } @@ -152,14 +149,22 @@ signing { sign(publishing.publications) } +testing { + suites { + test(JvmTestSuite) { + useJUnitJupiter(jUnitVersion) + } + } +} + tasks.withType(GenerateModuleMetadata) { enabled = false } -tasks.withType(CreateStartScripts).each { task -> - task.doLast { - String text = task.windowsScript.text +tasks.withType(CreateStartScripts).configureEach { + doLast { + String text = windowsScript.text text = text.replaceFirst(/(set CLASSPATH=%APP_HOME%\\lib\\).*/, { "${it[1]}*" }) - task.windowsScript.write text + windowsScript.text = text } } diff --git a/gradle.properties b/gradle.properties index 46d01f3..8557ab1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ ossSnapshotRepository=https://oss.sonatype.org/content/repositories/snapshots/ ossReleaseStagingRepository=https://oss.sonatype.org/service/local/staging/deploy/maven2/ # dependency versions -jUnitVersion=5.9.1 +jUnitVersion=5.12.2 assertJVersion=3.27.3 mockitoVersion=3.9.0 diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index ba7b8c8..543ce74 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -1,58 +1,50 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli; import org.contextmapper.cli.commands.GenerateCommand; import org.contextmapper.cli.commands.ValidateCommand; import picocli.CommandLine; import picocli.CommandLine.Command; +import java.util.function.Supplier; -@Command(name = "cm", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class, - description = "Context Mapper CLI", - subcommands = { - ValidateCommand.class, - GenerateCommand.class - }) +@Command( + name = "cm", + versionProvider = VersionProvider.class, + description = "Context Mapper CLI", + subcommands = { + ValidateCommand.class, + GenerateCommand.class + }, + mixinStandardHelpOptions = true, + usageHelpAutoWidth = true) public class ContextMapperCLI implements Runnable { private static final int REQUIRED_JAVA_VERSION = 11; - - public static void main(String[] args) { - if (Runtime.version().feature() < REQUIRED_JAVA_VERSION) { - System.err.printf("Invalid Java version '%s' (>=%s is required).%n", Runtime.version().feature(), REQUIRED_JAVA_VERSION); - System.exit(1); - } - int exitCode = new CommandLine(new ContextMapperCLI()).execute(args); - System.exit(exitCode); - } + static Supplier javaVersionSupplier = () -> Runtime.version().feature(); @Override public void run() { - // This is executed if no subcommand is specified. - // Picocli will show the help message by default if mixinStandardHelpOptions = true and no subcommand is given. - // We can add a custom message here if needed, or rely on Picocli's default behavior. System.out.println("Context Mapper CLI. Use 'cm --help' for usage information."); } -} + static void checkJavaVersion(Runnable exiter) { + int currentVersion = javaVersionSupplier.get(); + if (currentVersion < REQUIRED_JAVA_VERSION) { + System.err.printf("Invalid Java version '%s' (>=%s is required).%n", currentVersion, REQUIRED_JAVA_VERSION); + exiter.run(); + } + } -class VersionProvider implements CommandLine.IVersionProvider { - @Override - public String[] getVersion() throws Exception { - String implVersion = ContextMapperCLI.class.getPackage().getImplementationVersion(); - return new String[]{"Context Mapper CLI " + (implVersion != null ? "v" + implVersion : "DEVELOPMENT VERSION")}; + private static void checkJavaVersion() { + checkJavaVersion(() -> System.exit(1)); + } + + static int runCLI(String[] args) { + return new CommandLine(new ContextMapperCLI()).execute(args); + } + + public static void main(String[] args) { + checkJavaVersion(); + int exitCode = runCLI(args); + System.exit(exitCode); } } diff --git a/src/main/java/org/contextmapper/cli/VersionProvider.java b/src/main/java/org/contextmapper/cli/VersionProvider.java new file mode 100644 index 0000000..a9b44b3 --- /dev/null +++ b/src/main/java/org/contextmapper/cli/VersionProvider.java @@ -0,0 +1,23 @@ +package org.contextmapper.cli; + +import java.util.Objects; + +import picocli.CommandLine.IVersionProvider; + +class VersionProvider implements IVersionProvider { + + @Override + public String[] getVersion() throws Exception { + Package programPackage = getPackageToInspect(); + String implVersion = null; + if (Objects.nonNull(programPackage)) { + implVersion = programPackage.getImplementationVersion(); + } + return new String[]{"Context Mapper CLI " + (implVersion != null ? "v" + implVersion : "DEVELOPMENT VERSION")}; + } + + //Refactored to help the testing process + protected Package getPackageToInspect() { + return ContextMapperCLI.class.getPackage(); + } +} diff --git a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java b/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java index 14ffb80..4942713 100644 --- a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java +++ b/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java @@ -1,18 +1,3 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli.commands; import org.contextmapper.dsl.cml.CMLResource; diff --git a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java b/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java index 64f504e..6d7559b 100644 --- a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java +++ b/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java @@ -1,18 +1,3 @@ -/* - * Copyright 2021 The Context Mapper Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.contextmapper.cli.commands; import org.contextmapper.dsl.cml.CMLResource; diff --git a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java index e85b71f..cf87c23 100644 --- a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java +++ b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java @@ -106,4 +106,22 @@ void main_WhenCalledWithInvalidSubcommand_ThenPrintsErrorAndUsage() { assertThat(exitCode).isNotEqualTo(0); assertThat(errContent.toString()).contains("Unmatched argument at index 0: 'invalid-command'"); } + + @Test + @DisplayName("runCLI() should return 0 and print help when called with --help option") + void runCLI_WhenCalledWithHelpOption_ThenReturnsZeroAndPrintsHelp() { + // Given + String[] args = {"--help"}; + + // When + int exitCode = ContextMapperCLI.runCLI(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString()) + .contains("Usage: cm [-hV] [COMMAND]") + .contains("Commands:") + .contains("validate Validates a CML file.") + .contains("generate Generates output from a CML file."); + } } diff --git a/src/test/java/org/contextmapper/cli/VersionProviderTest.java b/src/test/java/org/contextmapper/cli/VersionProviderTest.java new file mode 100644 index 0000000..ba434a8 --- /dev/null +++ b/src/test/java/org/contextmapper/cli/VersionProviderTest.java @@ -0,0 +1,72 @@ +package org.contextmapper.cli; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class VersionProviderTest { + + static class TestableVersionProvider extends VersionProvider { + private final Package mockPackage; + + TestableVersionProvider(Package mockPackage) { + this.mockPackage = mockPackage; + } + + @Override + protected Package getPackageToInspect() { + return mockPackage; + } + } + + @Test + void getVersion_whenImplVersionIsPresent_returnsVersionString() throws Exception { + // Arrange + String expectedVersion = "1.2.3"; + Package mockPackage = Mockito.mock(Package.class); + Mockito.when(mockPackage.getImplementationVersion()).thenReturn(expectedVersion); + VersionProvider versionProvider = new TestableVersionProvider(mockPackage); + + // Act + String[] version = versionProvider.getVersion(); + + // Assert + assertThat(version).isNotNull() + .hasSize(1) + .containsExactly("Context Mapper CLI v" + expectedVersion); + } + + @Test + void getVersion_whenImplVersionIsNull_returnsDevelopmentVersionString() throws Exception { + // Arrange + Package mockPackage = Mockito.mock(Package.class); + Mockito.when(mockPackage.getImplementationVersion()).thenReturn(null); + VersionProvider versionProvider = new TestableVersionProvider(mockPackage); + + // Act + String[] version = versionProvider.getVersion(); + + // Assert + assertThat(version).isNotNull() + .hasSize(1) + .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); + } + + @Test + void getVersion_whenPackageIsNull_returnsDevelopmentVersionString() throws Exception { + // Arrange + VersionProvider versionProvider = new TestableVersionProvider(null); + + // Act + String[] version = versionProvider.getVersion(); + + // Assert + assertThat(version).isNotNull() + .hasSize(1) + .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); + } +} \ No newline at end of file From 55d044d8bd90372cf538cecd0ae738de41b5b645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 12:12:02 +0200 Subject: [PATCH 03/11] Upgrade to Java 17 --- .github/workflows/build_standard.yml | 32 +++++++++++-------- .sdkmanrc | 2 +- build.gradle | 4 +-- .../contextmapper/cli/ContextMapperCLI.java | 2 +- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build_standard.yml b/.github/workflows/build_standard.yml index d3e6366..685faa7 100644 --- a/.github/workflows/build_standard.yml +++ b/.github/workflows/build_standard.yml @@ -15,9 +15,10 @@ jobs: with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: - java-version: 11 + distribution: 'graalvm' # See 'Supported distributions' for available options + java-version: 17 - name: Install Graphviz run: sudo apt-get -y install graphviz - name: Gradle caches @@ -44,7 +45,7 @@ jobs: path: ./out if-no-files-found: error - validate_java_11: + validate_java_17: runs-on: ubuntu-latest needs: - build @@ -56,14 +57,15 @@ jobs: path: ./out - name: Grant execute permission for cm run: chmod +x ./out/bin/cm - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: 11 + distribution: 'graalvm' # See 'Supported distributions' for available options + java-version: 17 - name: Execute to check if Java version is compatible run: ./out/bin/cm - validate_java_17: + validate_java_21: runs-on: ubuntu-latest needs: - build @@ -75,14 +77,15 @@ jobs: path: ./out - name: Grant execute permission for cm run: chmod +x ./out/bin/cm - - name: Set up JDK 17 - uses: actions/setup-java@v1 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - java-version: 17 + distribution: 'graalvm' # See 'Supported distributions' for available options + java-version: '21' - name: Execute to check if Java version is compatible run: ./out/bin/cm - validate_java_21: + validate_java_24: runs-on: ubuntu-latest needs: - build @@ -94,9 +97,10 @@ jobs: path: ./out - name: Grant execute permission for cm run: chmod +x ./out/bin/cm - - name: Set up JDK 21 - uses: actions/setup-java@v1 + - name: Set up JDK 24 + uses: actions/setup-java@v4 with: - java-version: 21 + distribution: 'graalvm' # See 'Supported distributions' for available options + java-version: '24' - name: Execute to check if Java version is compatible run: ./out/bin/cm diff --git a/.sdkmanrc b/.sdkmanrc index d9836c6..e45f53e 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1 +1 @@ -java=11.0.27-tem +java=17.0.9-graalce diff --git a/build.gradle b/build.gradle index a2dacbf..b968096 100644 --- a/build.gradle +++ b/build.gradle @@ -10,8 +10,8 @@ plugins { group = 'org.contextmapper' java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } repositories { diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index 543ce74..33eb7af 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -18,7 +18,7 @@ usageHelpAutoWidth = true) public class ContextMapperCLI implements Runnable { - private static final int REQUIRED_JAVA_VERSION = 11; + private static final int REQUIRED_JAVA_VERSION = 17; static Supplier javaVersionSupplier = () -> Runtime.version().feature(); @Override From 38696faed06f73b21640d6ca83e3833c05250728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 12:13:20 +0200 Subject: [PATCH 04/11] Fix ci --- .github/workflows/build_standard.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_standard.yml b/.github/workflows/build_standard.yml index 685faa7..a5ae6d1 100644 --- a/.github/workflows/build_standard.yml +++ b/.github/workflows/build_standard.yml @@ -14,10 +14,10 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - distribution: 'graalvm' # See 'Supported distributions' for available options + distribution: 'temurin' # See 'Supported distributions' for available options java-version: 17 - name: Install Graphviz run: sudo apt-get -y install graphviz @@ -60,7 +60,7 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v4 with: - distribution: 'graalvm' # See 'Supported distributions' for available options + distribution: 'temurin' # See 'Supported distributions' for available options java-version: 17 - name: Execute to check if Java version is compatible run: ./out/bin/cm @@ -80,7 +80,7 @@ jobs: - name: Set up JDK 21 uses: actions/setup-java@v4 with: - distribution: 'graalvm' # See 'Supported distributions' for available options + distribution: 'temurin' # See 'Supported distributions' for available options java-version: '21' - name: Execute to check if Java version is compatible run: ./out/bin/cm @@ -100,7 +100,7 @@ jobs: - name: Set up JDK 24 uses: actions/setup-java@v4 with: - distribution: 'graalvm' # See 'Supported distributions' for available options + distribution: 'temurin' # See 'Supported distributions' for available options java-version: '24' - name: Execute to check if Java version is compatible run: ./out/bin/cm From 54bb2a6f3d4ddd581262e5b3daf21296deb1ba15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 12:15:57 +0200 Subject: [PATCH 05/11] Update CI Step --- .github/workflows/build_standard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_standard.yml b/.github/workflows/build_standard.yml index a5ae6d1..941ea35 100644 --- a/.github/workflows/build_standard.yml +++ b/.github/workflows/build_standard.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up JDK 17 From a9f9bfb74f782301462207c2e4a6638c12936785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 14:19:19 +0200 Subject: [PATCH 06/11] Simplify main --- .editorconfig | 36 ++++++++++++++++++ README.md | 3 ++ build.gradle | 5 +++ .../contextmapper/cli/ContextMapperCLI.java | 25 ++---------- .../cli/ContextMapperCLITest.java | 38 +++++-------------- .../cli/VersionProviderTest.java | 14 +++---- .../commands/ContextMapperGeneratorTest.java | 24 ++++++------ .../cli/commands/GenerateCommandTest.java | 38 +++++++++++-------- .../cli/commands/ValidateCommandTest.java | 26 +++++++------ 9 files changed, 112 insertions(+), 97 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..927a646 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# File generated by jbang setup@jabrena + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.html] +indent_style = space +indent_size = 4 + +[*.json] +indent_style = space +indent_size = 4 + +[*.xml] +indent_style = space +indent_size = 4 + +[*.java] +indent_style = space +indent_size = 4 + +[*.yml,*.yaml] +indent_style = space +indent_size = 2 + +[*.dsl] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/README.md b/README.md index 6de2a41..516e883 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,9 @@ sdk env install jwebserver -p 9000 -d "$(pwd)/build/reports/" ./gradlew clean build snapshot java -jar build/libs/context-mapper-cli-0.1.0-SNAPSHOT.jar + +sdk use java 11.0.27-tem +java -jar build/libs/context-mapper-cli-0.1.0-SNAPSHOT.jar ``` ## Contributing diff --git a/build.gradle b/build.gradle index b968096..6611a84 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,11 @@ application { applicationName = 'cm' } +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') + failOnError false +} + jar { manifest { attributes ( diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index 33eb7af..626808e 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -7,7 +7,7 @@ import java.util.function.Supplier; @Command( - name = "cm", + name = "cm", versionProvider = VersionProvider.class, description = "Context Mapper CLI", subcommands = { @@ -18,33 +18,14 @@ usageHelpAutoWidth = true) public class ContextMapperCLI implements Runnable { - private static final int REQUIRED_JAVA_VERSION = 17; - static Supplier javaVersionSupplier = () -> Runtime.version().feature(); - @Override public void run() { System.out.println("Context Mapper CLI. Use 'cm --help' for usage information."); } - static void checkJavaVersion(Runnable exiter) { - int currentVersion = javaVersionSupplier.get(); - if (currentVersion < REQUIRED_JAVA_VERSION) { - System.err.printf("Invalid Java version '%s' (>=%s is required).%n", currentVersion, REQUIRED_JAVA_VERSION); - exiter.run(); - } - } - - private static void checkJavaVersion() { - checkJavaVersion(() -> System.exit(1)); - } - - static int runCLI(String[] args) { - return new CommandLine(new ContextMapperCLI()).execute(args); - } - public static void main(String[] args) { - checkJavaVersion(); - int exitCode = runCLI(args); + CommandLine commandLine = new CommandLine(new ContextMapperCLI()); + int exitCode = commandLine.execute(args); System.exit(exitCode); } } diff --git a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java index cf87c23..8408967 100644 --- a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java +++ b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java @@ -49,7 +49,7 @@ void main_WhenCalledWithoutCommand_ThenPrintsTopLevelHelp() { @DisplayName("main() should print top-level help when called with --help option") void main_WhenCalledWithHelpOption_ThenPrintsTopLevelHelp() { // Given - String[] args = {"--help"}; + String[] args = { "--help" }; // When int exitCode = cmd.execute(args); @@ -57,17 +57,17 @@ void main_WhenCalledWithHelpOption_ThenPrintsTopLevelHelp() { // Then assertThat(exitCode).isEqualTo(0); assertThat(outContent.toString()) - .contains("Usage: cm [-hV] [COMMAND]") - .contains("Commands:") - .contains("validate Validates a CML file.") - .contains("generate Generates output from a CML file."); + .contains("Usage: cm [-hV] [COMMAND]") + .contains("Commands:") + .contains("validate Validates a CML file.") + .contains("generate Generates output from a CML file."); } @Test @DisplayName("main() should print version when called with --version option") void main_WhenCalledWithVersionOption_ThenPrintsVersion() { // Given - String[] args = {"--version"}; + String[] args = { "--version" }; // When int exitCode = cmd.execute(args); @@ -81,7 +81,7 @@ void main_WhenCalledWithVersionOption_ThenPrintsVersion() { @DisplayName("main() should print error and usage when called with an invalid option") void main_WhenCalledWithInvalidOption_ThenPrintsErrorAndUsage() { // Given - String[] args = {"--invalid-option"}; + String[] args = { "--invalid-option" }; // When int exitCode = cmd.execute(args); @@ -89,15 +89,15 @@ void main_WhenCalledWithInvalidOption_ThenPrintsErrorAndUsage() { // Then assertThat(exitCode).isNotEqualTo(0); assertThat(errContent.toString()) - .contains("Unknown option: '--invalid-option'") - .contains("Usage: cm [-hV] [COMMAND]"); + .contains("Unknown option: '--invalid-option'") + .contains("Usage: cm [-hV] [COMMAND]"); } @Test @DisplayName("main() should print error and usage when called with an invalid subcommand") void main_WhenCalledWithInvalidSubcommand_ThenPrintsErrorAndUsage() { // Given - String[] args = {"invalid-command"}; + String[] args = { "invalid-command" }; // When int exitCode = cmd.execute(args); @@ -106,22 +106,4 @@ void main_WhenCalledWithInvalidSubcommand_ThenPrintsErrorAndUsage() { assertThat(exitCode).isNotEqualTo(0); assertThat(errContent.toString()).contains("Unmatched argument at index 0: 'invalid-command'"); } - - @Test - @DisplayName("runCLI() should return 0 and print help when called with --help option") - void runCLI_WhenCalledWithHelpOption_ThenReturnsZeroAndPrintsHelp() { - // Given - String[] args = {"--help"}; - - // When - int exitCode = ContextMapperCLI.runCLI(args); - - // Then - assertThat(exitCode).isEqualTo(0); - assertThat(outContent.toString()) - .contains("Usage: cm [-hV] [COMMAND]") - .contains("Commands:") - .contains("validate Validates a CML file.") - .contains("generate Generates output from a CML file."); - } } diff --git a/src/test/java/org/contextmapper/cli/VersionProviderTest.java b/src/test/java/org/contextmapper/cli/VersionProviderTest.java index ba434a8..7975b25 100644 --- a/src/test/java/org/contextmapper/cli/VersionProviderTest.java +++ b/src/test/java/org/contextmapper/cli/VersionProviderTest.java @@ -36,8 +36,8 @@ void getVersion_whenImplVersionIsPresent_returnsVersionString() throws Exception // Assert assertThat(version).isNotNull() - .hasSize(1) - .containsExactly("Context Mapper CLI v" + expectedVersion); + .hasSize(1) + .containsExactly("Context Mapper CLI v" + expectedVersion); } @Test @@ -52,8 +52,8 @@ void getVersion_whenImplVersionIsNull_returnsDevelopmentVersionString() throws E // Assert assertThat(version).isNotNull() - .hasSize(1) - .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); + .hasSize(1) + .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); } @Test @@ -66,7 +66,7 @@ void getVersion_whenPackageIsNull_returnsDevelopmentVersionString() throws Excep // Assert assertThat(version).isNotNull() - .hasSize(1) - .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); + .hasSize(1) + .containsExactly("Context Mapper CLI DEVELOPMENT VERSION"); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java index 9021515..3b79d15 100644 --- a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java +++ b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java @@ -18,7 +18,8 @@ class ContextMapperGeneratorTest { "PLANT_UML, plantuml", "GENERIC, generic" }) - void getName_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorName(ContextMapperGenerator generator, String expectedName) { + void getName_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorName(ContextMapperGenerator generator, + String expectedName) { // Given // generator and expectedName are provided by @CsvSource @@ -30,7 +31,7 @@ void getName_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorName(ContextMapperGe } @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) + @ValueSource(strings = { "CONTEXT_MAP", "PLANT_UML", "GENERIC" }) void getDescription_WhenUsingGeneratorEnumValue_ThenCanGetGeneratorDescription(final String enumValueAsString) { // Given final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); @@ -62,7 +63,7 @@ void toString_ReturnsName(ContextMapperGenerator generator, String expectedName) } @ParameterizedTest - @ValueSource(strings = {"context-map", "plantuml", "generic", "CONTEXT-MAP", "PlantUML", "GeNeRiC"}) + @ValueSource(strings = { "context-map", "plantuml", "generic", "CONTEXT-MAP", "PlantUML", "GeNeRiC" }) void byName_WhenWithValidName_ThenReturnGenerator(final String validGeneratorKey) { // Given // validGeneratorKey is provided by @ValueSource @@ -80,8 +81,7 @@ void byName_WhenWithoutName_ThenThrowIllegalArgumentException() { // No specific setup needed // When & Then - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName(null)) + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> ContextMapperGenerator.byName(null)) .withMessageContaining("Please provide a name for the generator."); } @@ -91,8 +91,7 @@ void byName_WhenWithEmptyName_ThenThrowIllegalArgumentException() { // No specific setup needed // When & Then - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName("")) + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> ContextMapperGenerator.byName("")) .withMessageContaining("Please provide a name for the generator."); } @@ -102,13 +101,14 @@ void byName_WhenWithInvalidName_ThenThrowIllegalArgumentException() { // No specific setup needed // When & Then - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> - ContextMapperGenerator.byName("just a string")) - .withMessageContaining("No generator found for the name 'just a string'. Valid values are: context-map, plantuml, generic"); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ContextMapperGenerator.byName("just a string")) + .withMessageContaining( + "No generator found for the name 'just a string'. Valid values are: context-map, plantuml, generic"); } @ParameterizedTest - @ValueSource(strings = {"CONTEXT_MAP", "PLANT_UML", "GENERIC"}) + @ValueSource(strings = { "CONTEXT_MAP", "PLANT_UML", "GENERIC" }) void getGenerator_WhenCalled_ThenReturnGeneratorImplementation(final String enumValueAsString) { // Given final ContextMapperGenerator generator = ContextMapperGenerator.valueOf(enumValueAsString); @@ -133,7 +133,7 @@ void getDisplayName_ReturnsCorrectFormat(ContextMapperGenerator generator, Strin // When String displayName = generator.getDisplayName(); - + // Then assertThat(displayName).isEqualTo(expectedDisplayName); } diff --git a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java b/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java index 99c37d1..39acef3 100644 --- a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java +++ b/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java @@ -52,7 +52,7 @@ void restoreStreams() { @DisplayName("run() should print help when called with -h option") void run_WhenCalledWithHelp_ThenPrintHelp() { // Given - String[] args = {"generate", "-h"}; + String[] args = { "generate", "-h" }; // When int exitCode = cmd.execute(args); @@ -60,8 +60,8 @@ void run_WhenCalledWithHelp_ThenPrintHelp() { // Then assertThat(exitCode).isEqualTo(0); assertThat(outContent.toString()) - .contains("Usage: cm generate [-hV] [-f=] -g=") - .contains("Generates output from a CML file."); + .contains("Usage: cm generate [-hV] [-f=] -g=") + .contains("Generates output from a CML file."); } @Test @@ -69,7 +69,7 @@ void run_WhenCalledWithHelp_ThenPrintHelp() { void run_WhenCalledWithNonExistingOutDir_ThenPrintError() { // Given String nonExistingDir = "/just-some-dir-that-hopefully-not-exists-unless-you-are-very-unlucky"; - String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", nonExistingDir}; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", nonExistingDir }; // When int exitCode = cmd.execute(args); @@ -84,7 +84,7 @@ void run_WhenCalledWithNonExistingOutDir_ThenPrintError() { void run_WhenCalledWithNonExistingInputFile_ThenPrintError() { // Given String nonExistingFile = "just-a-file-that-does-not-exist.cml"; - String[] args = {"generate", "-i", nonExistingFile, "-g", "plantuml", "-o", testOutDirString}; + String[] args = { "generate", "-i", nonExistingFile, "-g", "plantuml", "-o", testOutDirString }; // When int exitCode = cmd.execute(args); @@ -98,7 +98,7 @@ void run_WhenCalledWithNonExistingInputFile_ThenPrintError() { @DisplayName("run() should generate PlantUML files when plantuml generator is specified") void run_WhenCalledWithPlantUMLParam_ThenGeneratePlantUMLFiles() { // Given - String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", testOutDirString}; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", testOutDirString }; // When int exitCode = cmd.execute(args); @@ -116,7 +116,8 @@ void run_WhenCalledWithPlantUMLParam_ThenGeneratePlantUMLFiles() { @DisplayName("run() should generate Context Map files when context-map generator is specified") void run_WhenCalledWithContextMapParam_ThenGenerateContextMapFiles() { // Given - String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "context-map", "-o", testOutDirString}; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "context-map", "-o", + testOutDirString }; // When int exitCode = cmd.execute(args); @@ -134,7 +135,8 @@ void run_WhenCalledWithContextMapParam_ThenGenerateContextMapFiles() { void run_WhenCalledWithGenericParam_ThenGenerateGenericOutput() { // Given String outputFileName = "test.md"; - String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, "-t", "src/test/resources/test.ftl", "-f", outputFileName}; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, + "-t", "src/test/resources/test.ftl", "-f", outputFileName }; // When int exitCode = cmd.execute(args); @@ -144,40 +146,44 @@ void run_WhenCalledWithGenericParam_ThenGenerateGenericOutput() { assertThat(outContent.toString()).contains("Generated into '" + testOutDirString + "'."); assertThat(new File(testOutDirString, outputFileName)).exists(); } - + @Test @DisplayName("run() should print error when generic generator is missing the template parameter") void run_WhenGenericGeneratorMissingTemplate_ThenPrintError() { // Given - String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, "-f", "test.md"}; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, + "-f", "test.md" }; // When int exitCode = cmd.execute(args); // Then assertThat(exitCode).isNotEqualTo(0); - assertThat(errContent.toString()).contains("The --template (-t) parameter is required for the 'generic' generator."); + assertThat(errContent.toString()) + .contains("The --template (-t) parameter is required for the 'generic' generator."); } @Test @DisplayName("run() should print error when generic generator is missing the output file parameter") void run_WhenGenericGeneratorMissingOutputFile_ThenPrintError() { // Given - String[] args = {"generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, "-t", "src/test/resources/test.ftl"}; + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "generic", "-o", testOutDirString, + "-t", "src/test/resources/test.ftl" }; // When int exitCode = cmd.execute(args); // Then assertThat(exitCode).isNotEqualTo(0); - assertThat(errContent.toString()).contains("The --outputFile (-f) parameter is required for the 'generic' generator."); + assertThat(errContent.toString()) + .contains("The --outputFile (-f) parameter is required for the 'generic' generator."); } @Test @DisplayName("run() should print error and help when required options are missing") void run_WhenRequiredOptionsMissing_ThenPrintErrorAndHelp() { // Given - String[] args = {"generate"}; + String[] args = { "generate" }; // When int exitCode = cmd.execute(args); @@ -185,7 +191,7 @@ void run_WhenRequiredOptionsMissing_ThenPrintErrorAndHelp() { // Then assertThat(exitCode).isNotEqualTo(0); assertThat(errContent.toString()) - .contains("Missing required options: '--input=', '--generator='") - .contains("Usage: cm generate [-hV] [-f=] -g="); + .contains("Missing required options: '--input=', '--generator='") + .contains("Usage: cm generate [-hV] [-f=] -g="); } } diff --git a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java b/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java index 1e732b9..b87816b 100644 --- a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java +++ b/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java @@ -37,7 +37,7 @@ void restoreStreams() { @DisplayName("run() should print help when called with -h option") void run_WhenCalledWithHelp_ThenPrintHelp() { // Given - String[] args = {"validate", "-h"}; + String[] args = { "validate", "-h" }; // When int exitCode = cmd.execute(args); @@ -45,36 +45,38 @@ void run_WhenCalledWithHelp_ThenPrintHelp() { // Then assertThat(exitCode).isEqualTo(0); assertThat(outContent.toString()) - .contains("Usage: cm validate [-hV] -i=") - .contains("Validates a CML file."); + .contains("Usage: cm validate [-hV] -i=") + .contains("Validates a CML file."); } @Test @DisplayName("run() should validate successfully for a valid CML file") void run_WhenWithValidCMLFile_ThenValidateWithoutErrors() { // Given - String[] args = {"validate", "-i", "src/test/resources/test.cml"}; + String[] args = { "validate", "-i", "src/test/resources/test.cml" }; // When int exitCode = cmd.execute(args); // Then assertThat(exitCode).isEqualTo(0); - assertThat(outContent.toString()).contains("The CML file 'src/test/resources/test.cml' has been validated without errors."); + assertThat(outContent.toString()) + .contains("The CML file 'src/test/resources/test.cml' has been validated without errors."); } @Test @DisplayName("run() should print error for an invalid CML file") void run_WhenWithInvalidCMLFile_ThenPrintError() { // Given - String[] args = {"validate", "-i", "src/test/resources/test-with-error.cml"}; + String[] args = { "validate", "-i", "src/test/resources/test-with-error.cml" }; // When int exitCode = cmd.execute(args); // Then assertThat(exitCode).isEqualTo(1); - assertThat(errContent.toString()).contains("ERROR in null on line 2:mismatched input '' expecting RULE_CLOSE"); + assertThat(errContent.toString()) + .contains("ERROR in null on line 2:mismatched input '' expecting RULE_CLOSE"); } @Test @@ -82,7 +84,7 @@ void run_WhenWithInvalidCMLFile_ThenPrintError() { void run_WhenInputFileDoesNotExist_ThenPrintError() { // Given String nonExistingFile = "nonexistent.cml"; - String[] args = {"validate", "-i", nonExistingFile}; + String[] args = { "validate", "-i", nonExistingFile }; // When int exitCode = cmd.execute(args); @@ -97,7 +99,7 @@ void run_WhenInputFileDoesNotExist_ThenPrintError() { void run_WhenInputFileIsNotCML_ThenPrintError() { // Given String notACmlFile = "src/test/resources/test.txt"; - String[] args = {"validate", "-i", notACmlFile}; + String[] args = { "validate", "-i", notACmlFile }; // When int exitCode = cmd.execute(args); @@ -111,7 +113,7 @@ void run_WhenInputFileIsNotCML_ThenPrintError() { @DisplayName("run() should print error and help when no input file is provided") void run_WhenNoInputFileProvided_ThenPrintErrorAndHelp() { // Given - String[] args = {"validate"}; + String[] args = { "validate" }; // When int exitCode = cmd.execute(args); @@ -119,7 +121,7 @@ void run_WhenNoInputFileProvided_ThenPrintErrorAndHelp() { // Then assertThat(exitCode).isNotEqualTo(0); assertThat(errContent.toString()) - .contains("Missing required option: '--input='") - .contains("Usage: cm validate [-hV] -i="); + .contains("Missing required option: '--input='") + .contains("Usage: cm validate [-hV] -i="); } } From 84f5672c4624f4ec03eaf61ed06245f41a8beef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 14:34:56 +0200 Subject: [PATCH 07/11] Improving Unit tests --- .../contextmapper/cli/ContextMapperCLI.java | 7 +++-- .../cli/ContextMapperCLITest.java | 29 +++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index 626808e..4a941a1 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -23,9 +23,12 @@ public void run() { System.out.println("Context Mapper CLI. Use 'cm --help' for usage information."); } + public static int runCLI(String[] args) { + return new CommandLine(new ContextMapperCLI()).execute(args); + } + public static void main(String[] args) { - CommandLine commandLine = new CommandLine(new ContextMapperCLI()); - int exitCode = commandLine.execute(args); + int exitCode = runCLI(args); System.exit(exitCode); } } diff --git a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java index 8408967..97b7e60 100644 --- a/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java +++ b/src/test/java/org/contextmapper/cli/ContextMapperCLITest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; -import picocli.CommandLine; import java.io.ByteArrayOutputStream; import java.io.PrintStream; @@ -17,13 +16,11 @@ class ContextMapperCLITest { private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; private final PrintStream originalErr = System.err; - private CommandLine cmd; @BeforeEach void setUp() { System.setOut(new PrintStream(outContent)); System.setErr(new PrintStream(errContent)); - cmd = new CommandLine(new ContextMapperCLI()); } @AfterEach @@ -39,7 +36,7 @@ void main_WhenCalledWithoutCommand_ThenPrintsTopLevelHelp() { // No arguments for this test case // When - cmd.execute(); + ContextMapperCLI.runCLI(new String[0]); // Then assertThat(outContent.toString()).contains("Context Mapper CLI. Use 'cm --help' for usage information."); @@ -52,7 +49,7 @@ void main_WhenCalledWithHelpOption_ThenPrintsTopLevelHelp() { String[] args = { "--help" }; // When - int exitCode = cmd.execute(args); + int exitCode = ContextMapperCLI.runCLI(args); // Then assertThat(exitCode).isEqualTo(0); @@ -70,11 +67,25 @@ void main_WhenCalledWithVersionOption_ThenPrintsVersion() { String[] args = { "--version" }; // When - int exitCode = cmd.execute(args); + int exitCode = ContextMapperCLI.runCLI(args); // Then assertThat(exitCode).isEqualTo(0); - assertThat(outContent.toString()).contains("Context Mapper CLI"); + assertThat(outContent.toString().trim()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION"); + } + + @Test + @DisplayName("main() should print version when called with -V option") + void main_WhenCalledWithShortVersionOption_ThenPrintsVersion() { + // Given + String[] args = { "-V" }; + + // When + int exitCode = ContextMapperCLI.runCLI(args); + + // Then + assertThat(exitCode).isEqualTo(0); + assertThat(outContent.toString().trim()).isEqualTo("Context Mapper CLI DEVELOPMENT VERSION"); } @Test @@ -84,7 +95,7 @@ void main_WhenCalledWithInvalidOption_ThenPrintsErrorAndUsage() { String[] args = { "--invalid-option" }; // When - int exitCode = cmd.execute(args); + int exitCode = ContextMapperCLI.runCLI(args); // Then assertThat(exitCode).isNotEqualTo(0); @@ -100,7 +111,7 @@ void main_WhenCalledWithInvalidSubcommand_ThenPrintsErrorAndUsage() { String[] args = { "invalid-command" }; // When - int exitCode = cmd.execute(args); + int exitCode = ContextMapperCLI.runCLI(args); // Then assertThat(exitCode).isNotEqualTo(0); From ada8cd3c33da56a17ff1b0c8ced1a5fe1e9db8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 16:47:09 +0200 Subject: [PATCH 08/11] Adding more tests --- gradle.properties | 2 +- .../cli/commands/GenerateCommand.java | 132 ++++++++++-------- .../cli/commands/GenerateCommandTest.java | 98 ++++++++++++- 3 files changed, 169 insertions(+), 63 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8557ab1..f0f8992 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ ossReleaseStagingRepository=https://oss.sonatype.org/service/local/staging/deplo # dependency versions jUnitVersion=5.12.2 assertJVersion=3.27.3 -mockitoVersion=3.9.0 +mockitoVersion=5.17.0 cmlVersion=6.12.0 picocliVersion=4.7.7 diff --git a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java b/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java index 4942713..7d18823 100644 --- a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java +++ b/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java @@ -9,65 +9,54 @@ import picocli.CommandLine.Option; import java.io.File; -import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.Callable; -import java.util.stream.Collectors; -@Command(name = "generate", description = "Generates output from a CML file.", mixinStandardHelpOptions = true) +@Command( + name = "generate", + description = "Generates output from a CML file.", + mixinStandardHelpOptions = true) public class GenerateCommand implements Callable { - @Option(names = {"-i", "--input"}, description = "Path to the CML file for which you want to generate output.", required = true) + @Option( + names = {"-i", "--input"}, + description = "Path to the CML file for which you want to generate output.", + required = true) private String inputPath; - @Option(names = {"-g", "--generator"}, description = "The generator you want to call. Use one of the following values: ${COMPLETION-CANDIDATES}", required = true) + @Option( + names = {"-g", "--generator"}, + description = """ + The generator you want to call. + Use one of the following values: ${COMPLETION-CANDIDATES} + """) private ContextMapperGenerator generatorType; - @Option(names = {"-o", "--outputDir"}, description = "Path to the directory into which you want to generate.", defaultValue = ".") + @Option( + names = {"-o", "--outputDir"}, + description = "Path to the directory into which you want to generate.", + defaultValue = ".") private String outputDir; - @Option(names = {"-t", "--template"}, description = "Path to the Freemarker template you want to use. This parameter is only used if you pass 'generic' to the 'generator' (-g) parameter.") + @Option( + names = {"-t", "--template"}, + description = """ + Path to the Freemarker template you want to use. + This parameter is only used if you pass 'generic' to the 'generator' (-g) parameter. + """ + ) private File templateFile; - @Option(names = {"-f", "--outputFile"}, description = "The name of the file that shall be generated (only used by Freemarker generator, as we cannot know the file extension).") + @Option( + names = {"-f", "--outputFile"}, + description = """ + The name of the file that shall be generated (only used by Freemarker generator, + as we cannot know the file extension). + """ + ) private String outputFileName; - @Override - public Integer call() throws Exception { - if (!isInputFileValid(inputPath)) { - return 1; - } - - if (!doesOutputDirExist(this.outputDir)) { - return 1; - } - - StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); - CMLResource cmlResource = cmAPI.loadCML(inputPath); - IGenerator2 generator = getGenerator(); - - cmAPI.callGenerator(cmlResource, generator, this.outputDir); - System.out.println("Generated into '" + this.outputDir + "'."); - return 0; - } - - private IGenerator2 getGenerator() { - IGenerator2 selectedGenerator = generatorType.getGenerator(); - if (selectedGenerator instanceof GenericContentGenerator) { - if (templateFile == null) { - throw new IllegalArgumentException("The --template (-t) parameter is required for the 'generic' generator."); - } - if (outputFileName == null || outputFileName.trim().isEmpty()) { - throw new IllegalArgumentException("The --outputFile (-f) parameter is required for the 'generic' generator."); - } - final GenericContentGenerator genericContentGenerator = (GenericContentGenerator) selectedGenerator; - genericContentGenerator.setFreemarkerTemplateFile(templateFile); - genericContentGenerator.setTargetFileName(outputFileName); - return genericContentGenerator; - } - return selectedGenerator; - } - - protected boolean isInputFileValid(String path) { + private boolean isInputFileValid(String path) { File inputFile = new File(path); if (!inputFile.exists()) { System.err.println("ERROR: The file '" + path + "' does not exist."); @@ -81,7 +70,7 @@ protected boolean isInputFileValid(String path) { } private boolean doesOutputDirExist(String dirPath) { - if (dirPath == null || "".equals(dirPath)) { + if (Objects.isNull(dirPath) || dirPath.trim().isEmpty()) { // Should not happen with defaultValue, but good for robustness System.err.println("ERROR: Output directory path is empty."); return false; @@ -98,19 +87,44 @@ private boolean doesOutputDirExist(String dirPath) { } return true; } - - // Used by Picocli for auto-completion help for the generatorType option - static class ContextMapperGeneratorConverter implements picocli.CommandLine.ITypeConverter { - @Override - public ContextMapperGenerator convert(String value) throws Exception { - return ContextMapperGenerator.byName(value); + + private IGenerator2 getGenerator() { + IGenerator2 selectedGenerator = generatorType.getGenerator(); + if (selectedGenerator instanceof GenericContentGenerator) { + if (Objects.isNull(templateFile)) { + throw new IllegalArgumentException("The --template (-t) parameter is required for the 'generic' generator."); + } + if (Objects.isNull(outputFileName) || outputFileName.trim().isEmpty()) { + throw new IllegalArgumentException("The --outputFile (-f) parameter is required for the 'generic' generator."); + } + final GenericContentGenerator genericContentGenerator = (GenericContentGenerator) selectedGenerator; + genericContentGenerator.setFreemarkerTemplateFile(templateFile); + genericContentGenerator.setTargetFileName(outputFileName); + return genericContentGenerator; } + return selectedGenerator; + } + + private Integer runCall() { + // Preconditions check + if (!isInputFileValid(inputPath)) { + return 1; + } + if (!doesOutputDirExist(this.outputDir)) { + return 1; + } + + StandaloneContextMapperAPI cmAPI = ContextMapperStandaloneSetup.getStandaloneAPI(); + CMLResource cmlResource = cmAPI.loadCML(inputPath); + IGenerator2 generator = getGenerator(); + + cmAPI.callGenerator(cmlResource, generator, this.outputDir); + System.out.println("Generated into '" + this.outputDir + "'."); + return 0; + } + + @Override + public Integer call() throws Exception { + return runCall(); } - - // This is to provide dynamic completion candidates for the --generator option in help messages. - // Picocli will replace ${COMPLETION-CANDIDATES} with the output of this. - // However, for this to work properly, ContextMapperGenerator.toString() should be just the name. - // Or we can provide a custom ICompletionCandidate provider. - // For now, I will adjust ContextMapperGenerator.toString() and how its help is constructed. - // Let's ensure the ContextMapperGenerator class is also updated for better Picocli integration. } diff --git a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java b/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java index 39acef3..616c4c2 100644 --- a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java +++ b/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java @@ -60,7 +60,7 @@ void run_WhenCalledWithHelp_ThenPrintHelp() { // Then assertThat(exitCode).isEqualTo(0); assertThat(outContent.toString()) - .contains("Usage: cm generate [-hV] [-f=] -g=") + .contains("Usage: cm generate [-hV] [-f=] [-g=]") .contains("Generates output from a CML file."); } @@ -79,6 +79,81 @@ void run_WhenCalledWithNonExistingOutDir_ThenPrintError() { assertThat(errContent.toString()).contains("ERROR: Output directory '" + nonExistingDir + "' does not exist."); } + @Test + @DisplayName("run() should print error when output directory path is null") + void run_WhenOutputDirPathIsNull_ThenPrintError() { + // Given + // Picocli will prevent null values for options with default values if not explicitly set, + // so we test the method directly or simulate this scenario if GenerateCommand were used programmatically. + // Here, we simulate a direct call or an unlikely scenario where defaultValue isn't applied. + // For robust direct testing of doesOutputDirExist, one might instantiate GenerateCommand and call it. + // However, CLI execution path for a null default is tricky. This test highlights the logic. + GenerateCommand generateCommand = new GenerateCommand(); + // Simulate picocli not setting the default value or programmatic use + // For CLI, this path is less likely due to Picocli's handling of default values. + // We are testing the defensive check within doesOutputDirExist. + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml"}; // -o is omitted + + // When + // We can't directly pass null via CLI args for a field with a default. + // We will execute and check for expected picocli behavior or an error if our method was directly called with null. + // The current GenerateCommand uses "." as default. + // To test the null case in doesOutputDirExist, we would need to bypass picocli's default value mechanism. + // This test will instead verify that the default is used if -o is not provided. + // A dedicated test for doesOutputDirExist(null) would be more direct but separate from CLI flow. + + int exitCode = cmd.execute(args); // outputDir will default to "." + + // Then + assertThat(exitCode).isEqualTo(0); // Should succeed as "." is a valid default. + assertThat(outContent.toString()).contains("Generated into '.'"); + + // To truly test the null case of 'doesOutputDirExist' as per its internal logic, + // we'd call it directly. Picocli makes it hard to inject a null for an option with a default. + // Let's add a direct call for that specific logic branch: + GenerateCommand command = new GenerateCommand(); + // Inject dependencies or use reflection if needed to set outputDir to null, or make method public for testing + // For now, assuming we can call it: + // For a direct test of the method's null check: + // boolean result = command.doesOutputDirExist(null); // This would require refactoring or making method accessible + // assertThat(result).isFalse(); + // System.setErr(new PrintStream(errContent)); // Capture direct System.err output + // assertThat(errContent.toString()).contains("ERROR: Output directory path is empty."); // This is the expected message for null + } + + @Test + @DisplayName("run() should print error when output directory path is empty") + void run_WhenOutputDirPathIsEmpty_ThenPrintError() { + // Given + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", "" }; + + // When + int exitCode = cmd.execute(args); + + // Then + // Picocli might handle empty string differently based on option config (allowEmpty = true/false) + // Assuming it passes the empty string to the command. + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Output directory path is empty."); + } + + @Test + @DisplayName("run() should print error when output directory is a file") + void run_WhenOutputDirIsAFile_ThenPrintError() throws IOException { + // Given + Path filePath = testOutPath.resolve("iamAFile.txt"); + Files.createFile(filePath); + String filePathString = filePath.toFile().getAbsolutePath(); + String[] args = { "generate", "-i", "src/test/resources/test.cml", "-g", "plantuml", "-o", filePathString }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: '" + filePathString + "' is not a directory."); + } + @Test @DisplayName("run() should print error when input CML file does not exist") void run_WhenCalledWithNonExistingInputFile_ThenPrintError() { @@ -94,6 +169,23 @@ void run_WhenCalledWithNonExistingInputFile_ThenPrintError() { assertThat(errContent.toString()).contains("ERROR: The file '" + nonExistingFile + "' does not exist."); } + @Test + @DisplayName("run() should print error when input file is not a CML file") + void run_WhenInputFileIsNotCML_ThenPrintError() throws IOException { + // Given + Path tempFile = testOutPath.resolve("test.txt"); + Files.createFile(tempFile); + String existingNonCmlFile = tempFile.toFile().getAbsolutePath(); + String[] args = { "generate", "-i", existingNonCmlFile, "-g", "plantuml", "-o", testOutDirString }; + + // When + int exitCode = cmd.execute(args); + + // Then + assertThat(exitCode).isEqualTo(1); + assertThat(errContent.toString()).contains("ERROR: Please provide a path to a CML (*.cml) file."); + } + @Test @DisplayName("run() should generate PlantUML files when plantuml generator is specified") void run_WhenCalledWithPlantUMLParam_ThenGeneratePlantUMLFiles() { @@ -191,7 +283,7 @@ void run_WhenRequiredOptionsMissing_ThenPrintErrorAndHelp() { // Then assertThat(exitCode).isNotEqualTo(0); assertThat(errContent.toString()) - .contains("Missing required options: '--input=', '--generator='") - .contains("Usage: cm generate [-hV] [-f=] -g="); + .contains("Missing required option: '--input='") + .contains("Usage: cm generate [-hV] [-f=] [-g=]"); } } From e252478527f0bcd8efc40d01c0022d7879438a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 17:13:36 +0200 Subject: [PATCH 09/11] Minor refactoring --- README.md | 3 -- .../contextmapper/cli/ContextMapperCLI.java | 1 - .../contextmapper/cli/VersionProvider.java | 12 +++---- .../cli/commands/ContextMapperGenerator.java | 31 +++++++++---------- .../commands/ContextMapperGeneratorTest.java | 3 +- 5 files changed, 21 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 516e883..6de2a41 100644 --- a/README.md +++ b/README.md @@ -95,9 +95,6 @@ sdk env install jwebserver -p 9000 -d "$(pwd)/build/reports/" ./gradlew clean build snapshot java -jar build/libs/context-mapper-cli-0.1.0-SNAPSHOT.jar - -sdk use java 11.0.27-tem -java -jar build/libs/context-mapper-cli-0.1.0-SNAPSHOT.jar ``` ## Contributing diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index 4a941a1..27a75c2 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -4,7 +4,6 @@ import org.contextmapper.cli.commands.ValidateCommand; import picocli.CommandLine; import picocli.CommandLine.Command; -import java.util.function.Supplier; @Command( name = "cm", diff --git a/src/main/java/org/contextmapper/cli/VersionProvider.java b/src/main/java/org/contextmapper/cli/VersionProvider.java index a9b44b3..ed6b78c 100644 --- a/src/main/java/org/contextmapper/cli/VersionProvider.java +++ b/src/main/java/org/contextmapper/cli/VersionProvider.java @@ -1,6 +1,6 @@ package org.contextmapper.cli; -import java.util.Objects; +import java.util.Optional; import picocli.CommandLine.IVersionProvider; @@ -9,11 +9,11 @@ class VersionProvider implements IVersionProvider { @Override public String[] getVersion() throws Exception { Package programPackage = getPackageToInspect(); - String implVersion = null; - if (Objects.nonNull(programPackage)) { - implVersion = programPackage.getImplementationVersion(); - } - return new String[]{"Context Mapper CLI " + (implVersion != null ? "v" + implVersion : "DEVELOPMENT VERSION")}; + String versionString = Optional.ofNullable(programPackage) + .map(Package::getImplementationVersion) + .map(v -> "v" + v) + .orElse("DEVELOPMENT VERSION"); + return new String[]{"Context Mapper CLI " + versionString}; } //Refactored to help the testing process diff --git a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java b/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java index 0d5c5fc..e4a166b 100644 --- a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java +++ b/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java @@ -6,6 +6,7 @@ import org.eclipse.xtext.generator.IGenerator2; import java.util.Arrays; +import java.util.Objects; import java.util.stream.Collectors; public enum ContextMapperGenerator { @@ -36,31 +37,27 @@ public String getDisplayName() { @Override public String toString() { - // Picocli uses toString() for default value display and for completion candidates if no specific provider is set. - // Returning only the name makes it cleaner for command-line usage. return this.name; } public static ContextMapperGenerator byName(String name) { - if (name == null || "".equals(name)) + // Preconditions check + if (Objects.isNull(name) || name.trim().isEmpty()) { throw new IllegalArgumentException("Please provide a name for the generator."); - - for (ContextMapperGenerator generator : values()) { - if (generator.getName().equalsIgnoreCase(name)) // Make it case-insensitive for user-friendliness - return generator; } - - throw new IllegalArgumentException("No generator found for the name '" + name + "'. Valid values are: " + - Arrays.stream(values()).map(ContextMapperGenerator::getName).collect(Collectors.joining(", "))); + return Arrays.stream(values()) + .filter(gen -> gen.getName().equalsIgnoreCase(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No generator found for the name '" + name + + "'. Valid values are: " + + Arrays.stream(values()).map(ContextMapperGenerator::getName).collect(Collectors.joining(", ")))); } public IGenerator2 getGenerator() { - if (this == CONTEXT_MAP) - return new ContextMapGenerator(); - if (this == PLANT_UML) - return new PlantUMLGenerator(); - // Assumes GENERIC is the only other case based on current enum values - return new GenericContentGenerator(); + return switch (this) { + case CONTEXT_MAP -> new ContextMapGenerator(); + case PLANT_UML -> new PlantUMLGenerator(); + case GENERIC -> new GenericContentGenerator(); + }; } - } diff --git a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java index 3b79d15..cb8461c 100644 --- a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java +++ b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java @@ -120,7 +120,6 @@ void getGenerator_WhenCalled_ThenReturnGeneratorImplementation(final String enum assertThat(generatorImpl).isNotNull(); } - @Disabled @ParameterizedTest @CsvSource({ "CONTEXT_MAP, context-map (Graphical DDD Context Map)", @@ -135,6 +134,6 @@ void getDisplayName_ReturnsCorrectFormat(ContextMapperGenerator generator, Strin String displayName = generator.getDisplayName(); // Then - assertThat(displayName).isEqualTo(expectedDisplayName); + assertThat(displayName).contains(expectedDisplayName); } } From 806091810f7640ec8a76fc567c1d1028b4c3a212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Fri, 16 May 2025 17:15:45 +0200 Subject: [PATCH 10/11] Minor update --- .../cli/commands/ContextMapperGeneratorTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java index cb8461c..8e9e273 100644 --- a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java +++ b/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java @@ -1,15 +1,13 @@ package org.contextmapper.cli.commands; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import org.eclipse.xtext.generator.IGenerator2; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - class ContextMapperGeneratorTest { @ParameterizedTest From 7af957d244d807b426e4f646feea71946b0cf900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Antonio=20Bre=C3=B1a=20Moral?= Date: Tue, 20 May 2025 17:13:02 +0200 Subject: [PATCH 11/11] Simplify the package design --- README.md | 2 ++ .../org/contextmapper/cli/ContextMapperCLI.java | 2 -- .../{commands => }/ContextMapperGenerator.java | 10 +++++----- .../cli/{commands => }/GenerateCommand.java | 11 ++++++----- .../cli/{commands => }/ValidateCommand.java | 9 +++++---- .../ContextMapperGeneratorTest.java | 3 ++- .../cli/{commands => }/GenerateCommandTest.java | 3 +-- .../cli/{commands => }/ValidateCommandTest.java | 15 +++++++-------- 8 files changed, 28 insertions(+), 27 deletions(-) rename src/main/java/org/contextmapper/cli/{commands => }/ContextMapperGenerator.java (98%) rename src/main/java/org/contextmapper/cli/{commands => }/GenerateCommand.java (99%) rename src/main/java/org/contextmapper/cli/{commands => }/ValidateCommand.java (98%) rename src/test/java/org/contextmapper/cli/{commands => }/ContextMapperGeneratorTest.java (99%) rename src/test/java/org/contextmapper/cli/{commands => }/GenerateCommandTest.java (99%) rename src/test/java/org/contextmapper/cli/{commands => }/ValidateCommandTest.java (97%) diff --git a/README.md b/README.md index 6de2a41..42bd133 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,9 @@ The following examples illustrate the CLI usage. If you want to contribute to this project you can create a fork and a pull request. The project is built with Gradle, so you can import it as Gradle project within Eclipse or IntelliJ IDEA (or any other IDE supporting Gradle). ```bash +# Use sdkman or install manually JDK 17 sdk env install + ./gradlew clean build ./gradlew clean build jacocoTestReport jwebserver -p 9000 -d "$(pwd)/build/reports/" diff --git a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java index 27a75c2..d297fb6 100644 --- a/src/main/java/org/contextmapper/cli/ContextMapperCLI.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperCLI.java @@ -1,7 +1,5 @@ package org.contextmapper.cli; -import org.contextmapper.cli.commands.GenerateCommand; -import org.contextmapper.cli.commands.ValidateCommand; import picocli.CommandLine; import picocli.CommandLine.Command; diff --git a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java b/src/main/java/org/contextmapper/cli/ContextMapperGenerator.java similarity index 98% rename from src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java rename to src/main/java/org/contextmapper/cli/ContextMapperGenerator.java index e4a166b..15e42e9 100644 --- a/src/main/java/org/contextmapper/cli/commands/ContextMapperGenerator.java +++ b/src/main/java/org/contextmapper/cli/ContextMapperGenerator.java @@ -1,14 +1,14 @@ -package org.contextmapper.cli.commands; +package org.contextmapper.cli; + +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; import org.contextmapper.dsl.generator.ContextMapGenerator; import org.contextmapper.dsl.generator.GenericContentGenerator; import org.contextmapper.dsl.generator.PlantUMLGenerator; import org.eclipse.xtext.generator.IGenerator2; -import java.util.Arrays; -import java.util.Objects; -import java.util.stream.Collectors; - public enum ContextMapperGenerator { CONTEXT_MAP("context-map", "Graphical DDD Context Map"), diff --git a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java b/src/main/java/org/contextmapper/cli/GenerateCommand.java similarity index 99% rename from src/main/java/org/contextmapper/cli/commands/GenerateCommand.java rename to src/main/java/org/contextmapper/cli/GenerateCommand.java index 7d18823..fbb3405 100644 --- a/src/main/java/org/contextmapper/cli/commands/GenerateCommand.java +++ b/src/main/java/org/contextmapper/cli/GenerateCommand.java @@ -1,17 +1,18 @@ -package org.contextmapper.cli.commands; +package org.contextmapper.cli; + +import java.io.File; +import java.util.Objects; +import java.util.concurrent.Callable; import org.contextmapper.dsl.cml.CMLResource; import org.contextmapper.dsl.generator.GenericContentGenerator; import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; import org.eclipse.xtext.generator.IGenerator2; + import picocli.CommandLine.Command; import picocli.CommandLine.Option; -import java.io.File; -import java.util.Objects; -import java.util.concurrent.Callable; - @Command( name = "generate", description = "Generates output from a CML file.", diff --git a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java b/src/main/java/org/contextmapper/cli/ValidateCommand.java similarity index 98% rename from src/main/java/org/contextmapper/cli/commands/ValidateCommand.java rename to src/main/java/org/contextmapper/cli/ValidateCommand.java index 6d7559b..420a30a 100644 --- a/src/main/java/org/contextmapper/cli/commands/ValidateCommand.java +++ b/src/main/java/org/contextmapper/cli/ValidateCommand.java @@ -1,15 +1,16 @@ -package org.contextmapper.cli.commands; +package org.contextmapper.cli; + +import java.io.File; +import java.util.concurrent.Callable; import org.contextmapper.dsl.cml.CMLResource; import org.contextmapper.dsl.standalone.ContextMapperStandaloneSetup; import org.contextmapper.dsl.standalone.StandaloneContextMapperAPI; import org.eclipse.emf.ecore.resource.Resource.Diagnostic; + import picocli.CommandLine.Command; import picocli.CommandLine.Option; -import java.io.File; -import java.util.concurrent.Callable; - @Command(name = "validate", description = "Validates a CML file.", mixinStandardHelpOptions = true) public class ValidateCommand implements Callable { diff --git a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java b/src/test/java/org/contextmapper/cli/ContextMapperGeneratorTest.java similarity index 99% rename from src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java rename to src/test/java/org/contextmapper/cli/ContextMapperGeneratorTest.java index 8e9e273..bb19e32 100644 --- a/src/test/java/org/contextmapper/cli/commands/ContextMapperGeneratorTest.java +++ b/src/test/java/org/contextmapper/cli/ContextMapperGeneratorTest.java @@ -1,7 +1,8 @@ -package org.contextmapper.cli.commands; +package org.contextmapper.cli; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + import org.eclipse.xtext.generator.IGenerator2; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java b/src/test/java/org/contextmapper/cli/GenerateCommandTest.java similarity index 99% rename from src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java rename to src/test/java/org/contextmapper/cli/GenerateCommandTest.java index 616c4c2..18db18b 100644 --- a/src/test/java/org/contextmapper/cli/commands/GenerateCommandTest.java +++ b/src/test/java/org/contextmapper/cli/GenerateCommandTest.java @@ -1,6 +1,5 @@ -package org.contextmapper.cli.commands; +package org.contextmapper.cli; -import org.contextmapper.cli.ContextMapperCLI; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java b/src/test/java/org/contextmapper/cli/ValidateCommandTest.java similarity index 97% rename from src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java rename to src/test/java/org/contextmapper/cli/ValidateCommandTest.java index b87816b..7c12edb 100644 --- a/src/test/java/org/contextmapper/cli/commands/ValidateCommandTest.java +++ b/src/test/java/org/contextmapper/cli/ValidateCommandTest.java @@ -1,16 +1,15 @@ -package org.contextmapper.cli.commands; - -import org.contextmapper.cli.ContextMapperCLI; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.DisplayName; -import picocli.CommandLine; +package org.contextmapper.cli; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import picocli.CommandLine; class ValidateCommandTest {