From 27ab5552fb245a39eef2300ce3cc492ca6f5bc8c Mon Sep 17 00:00:00 2001 From: Remko Popma Date: Mon, 18 Mar 2019 22:32:03 +0900 Subject: [PATCH] Second cut of adding support for argument groups: * mutually exclusive options (#199) * option that must co-occur (#295) * option grouping in the usage help message (#450) * repeating composite arguments (#358 and #635) (this should also cover the use cases presented in #454 and #434 requests for repeatable subcommands) --- build.gradle | 11 +- gradle/jacoco.gradle | 2 +- picocli-codegen/build.gradle | 2 + .../AbstractCommandSpecProcessor.java | 9 + .../src/test/resources/example-reflect.json | 11 + .../src/test/resources/issue622-reflect.json | 7 + src/main/java/picocli/CommandLine.java | 1652 +++++++++++------ src/test/java/picocli/ArgGroupTest.java | 1512 ++++++++------- .../java/picocli/CommandLineArityTest.java | 13 + .../picocli/CommandLineCommandMethodTest.java | 16 +- src/test/java/picocli/CommandLineTest.java | 79 +- src/test/java/picocli/InnerClassFactory.java | 22 +- src/test/java/picocli/ModelArgSpecTest.java | 2 +- .../picocli/ModelCommandReflectionTest.java | 54 +- .../java/picocli/ModelFieldBindingTest.java | 2 +- .../java/picocli/ModelMethodBindingTest.java | 23 +- src/test/java/picocli/ModelTestUtil.java | 2 +- .../java/picocli/ModelTypedMemberTest.java | 2 +- src/test/java/picocli/PicocliTestUtil.java | 2 +- 19 files changed, 2084 insertions(+), 1339 deletions(-) diff --git a/build.gradle b/build.gradle index 242919d0..6ad200c4 100644 --- a/build.gradle +++ b/build.gradle @@ -9,11 +9,12 @@ buildscript { dependencies { classpath "org.asciidoctor:asciidoctor-gradle-plugin:$asciidoctorGradlePluginVersion" + classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.15' classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradleBintrayPluginVersion" } } -apply plugin: 'org.asciidoctor.convert' +apply plugin: 'org.asciidoctor.convert' // version '1.5.8.1' apply plugin: 'distribution' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.bintray' @@ -158,10 +159,18 @@ jar { javadoc.options.overview = "src/main/java/overview.html" javadoc.dependsOn('asciidoctor') +asciidoctorj { + version = '1.5.5' +} asciidoctor { sourceDir = file('docs') outputDir = file('build/docs') logDocuments = true +// backends 'pdf', 'html' +// attributes 'sourcedir': file('docs') //project.sourceSets.main.java.srcDirs[0] +//// attributes 'pdf-stylesdir': 'theme', +//// 'pdf-style': 'custom', +//// 'sourcedir': file('docs') //project.sourceSets.main.java.srcDirs[0] } // jacoco 0.8.2 does not work with Java 13; gradle 4.x has no JavaVersion enum value for Java 12 if (org.gradle.api.JavaVersion.current().isJava11Compatible()) { diff --git a/gradle/jacoco.gradle b/gradle/jacoco.gradle index fabaa834..4758d43b 100644 --- a/gradle/jacoco.gradle +++ b/gradle/jacoco.gradle @@ -14,7 +14,7 @@ jacocoTestCoverageVerification { violationRules { rule { limit { - minimum = 0.98 + minimum = 0.97 } } } diff --git a/picocli-codegen/build.gradle b/picocli-codegen/build.gradle index 061f4f97..35c7465f 100644 --- a/picocli-codegen/build.gradle +++ b/picocli-codegen/build.gradle @@ -8,6 +8,8 @@ plugins { group 'info.picocli' description 'Picocli Code Generation - Tools to generate documentation, configuration, source code and other files from a picocli model.' version "$projectVersion" +sourceCompatibility = 1.6 +targetCompatibility = 1.6 dependencies { compile rootProject diff --git a/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/AbstractCommandSpecProcessor.java b/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/AbstractCommandSpecProcessor.java index 5ac68491..6739d0ff 100644 --- a/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/AbstractCommandSpecProcessor.java +++ b/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/AbstractCommandSpecProcessor.java @@ -1,6 +1,7 @@ package picocli.codegen.annotation.processing; import picocli.CommandLine; +import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.IFactory; import picocli.CommandLine.Mixin; @@ -942,6 +943,7 @@ public abstract class AbstractCommandSpecProcessor extends AbstractProcessor { return false || e.getAnnotation(Option.class) == null || e.getAnnotation(Parameters.class) == null + || e.getAnnotation(ArgGroup.class) == null || e.getAnnotation(Unmatched.class) == null || e.getAnnotation(Mixin.class) == null || e.getAnnotation(Spec.class) == null @@ -995,6 +997,7 @@ public abstract class AbstractCommandSpecProcessor extends AbstractProcessor { public boolean isArgSpec() { return isOption() || isParameter() || isMethodParameter(); } public boolean isOption() { return isAnnotationPresent(Option.class); } public boolean isParameter() { return isAnnotationPresent(Parameters.class); } + public boolean isArgGroup() { return isAnnotationPresent(ArgGroup.class); } public boolean isMixin() { return isAnnotationPresent(Mixin.class); } public boolean isUnmatched() { return isAnnotationPresent(Unmatched.class); } public boolean isInjectSpec() { return isAnnotationPresent(Spec.class); } @@ -1011,6 +1014,12 @@ public abstract class AbstractCommandSpecProcessor extends AbstractProcessor { public boolean hasInitialValue() { return hasInitialValue; } public boolean isMethodParameter() { return position >= 0; } public int getMethodParamPosition() { return position; } + + @Override + public CommandLine.Model.IScope scope() { + return null; // FIXME + } + public String getMixinName() { String annotationName = getAnnotation(Mixin.class).name(); return empty(annotationName) ? getName() : annotationName; diff --git a/picocli-codegen/src/test/resources/example-reflect.json b/picocli-codegen/src/test/resources/example-reflect.json index 76bc7fae..03ae995e 100644 --- a/picocli-codegen/src/test/resources/example-reflect.json +++ b/picocli-codegen/src/test/resources/example-reflect.json @@ -319,6 +319,17 @@ { "name" : "helpRequested" } ] }, + { + "name" : "picocli.CommandLine$Model$ObjectScope", + "allDeclaredConstructors" : true, + "allPublicConstructors" : true, + "allDeclaredMethods" : true, + "allPublicMethods" : true, + "methods" : [ + { "name" : "setMinimum", "parameterTypes" : ["int"] }, + { "name" : "setOtherFiles", "parameterTypes" : ["java.util.List"] } + ] + }, { "name" : "picocli.codegen.aot.graalvm.Example", "allDeclaredConstructors" : true, diff --git a/picocli-codegen/src/test/resources/issue622-reflect.json b/picocli-codegen/src/test/resources/issue622-reflect.json index a86b578d..24a54896 100644 --- a/picocli-codegen/src/test/resources/issue622-reflect.json +++ b/picocli-codegen/src/test/resources/issue622-reflect.json @@ -262,6 +262,13 @@ { "name" : "out" } ] }, + { + "name" : "picocli.CommandLine$Model$ObjectScope", + "allDeclaredConstructors" : true, + "allPublicConstructors" : true, + "allDeclaredMethods" : true, + "allPublicMethods" : true + }, { "name" : "picocli.codegen.aot.graalvm.Issue622AbstractCommand", "allDeclaredConstructors" : true, diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index 5885c903..25df4b23 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -315,12 +315,12 @@ public class CommandLine { /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line. * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}. * @since 0.9.8 */ - public boolean isUsageHelpRequested() { return interpreter.parseResult != null && interpreter.parseResult.usageHelpRequested; } + public boolean isUsageHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.usageHelpRequested; } /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line. * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}. * @since 0.9.8 */ - public boolean isVersionHelpRequested() { return interpreter.parseResult != null && interpreter.parseResult.versionHelpRequested; } + public boolean isVersionHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.versionHelpRequested; } /** Returns the {@code IHelpFactory} that is used to construct the usage help message. * @see #setHelpFactory(IHelpFactory) @@ -748,7 +748,7 @@ public class CommandLine { * @since 0.9.7 */ public List getUnmatchedArguments() { - return interpreter.parseResult == null ? Collections.emptyList() : UnmatchedArgumentException.stripErrorMessage(interpreter.parseResult.unmatched); + return interpreter.parseResultBuilder == null ? Collections.emptyList() : UnmatchedArgumentException.stripErrorMessage(interpreter.parseResultBuilder.unmatched); } /** @@ -833,9 +833,9 @@ public class CommandLine { */ public ParseResult parseArgs(String... args) { interpreter.parse(args); - return interpreter.parseResult.build(); + return getParseResult(); } - public ParseResult getParseResult() { return interpreter.parseResult == null ? null : interpreter.parseResult.build(); } + public ParseResult getParseResult() { return interpreter.parseResultBuilder == null ? null : interpreter.parseResultBuilder.build(); } /** * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully * {@linkplain #parse(String...) parsing} the command line arguments. This is a @@ -2741,11 +2741,32 @@ public class CommandLine { int order() default -1; /** - * Specify one or more {@linkplain ArgGroup groups} that this option is part of. - * @return the name or names of the group(s) that this option belongs to. + * Specify the name of one or more options that this option is mutually exclusive with. + * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and + * any options that the specified options are mutually exclusive with). + *

+ * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions + * where an option is part of multiple groups must be simplified so that the option is in just one group. + * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}. + *

+ * @return the name or names of the option(s) that this option is mutually exclusive with. * @since 4.0 */ - String[] groups() default {}; + String[] excludes() default {}; + + /** + * Specify the name of one or more options that this option must co-occur with. + * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and + * any options that the specified options must co-occur with). + *

+ * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions + * where an option is part of multiple groups must be simplified so that the option is in just one group. + * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}. + *

+ * @return the name or names of the option(s) that this option must co-occur with. + * @since 4.0 + */ + String[] needs() default {}; } /** *

@@ -2927,11 +2948,32 @@ public class CommandLine { String descriptionKey() default ""; /** - * Specify one or more {@linkplain ArgGroup groups} that this positional parameter is part of. - * @return the name or names of the group(s) that this positional parameter belongs to. + * Specify the name of one or more options that this positional parameter is mutually exclusive with. + * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and + * any options and positional parameters that the specified options are mutually exclusive with). + *

+ * An option or positional parameter cannot be part of multiple groups to avoid ambiguity for the parser. Constructions + * where an option is part of multiple groups must be simplified so that the option is in just one group. + * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}. + *

+ * @return the name or names of the option(s) that this positional parameter is mutually exclusive with. * @since 4.0 */ - String[] groups() default {}; + String[] excludes() default {}; + + /** + * Specify the name of one or more options that this option must co-occur with. + * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and + * any options that the specified options must co-occur with). + *

+ * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions + * where an option is part of multiple groups must be simplified so that the option is in just one group. + * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}. + *

+ * @return the name or names of the option(s) that this option must co-occur with. + * @since 4.0 + */ + String[] needs() default {}; } /** @@ -3304,11 +3346,6 @@ public class CommandLine { * @since 3.7 */ int usageHelpWidth() default 80; - - /** Optionally define argument {@linkplain ArgGroup groups}; groups can be used to define mutually exclusive arguments, - * arguments that must co-occur, or to customize the usage help message. - * @since 4.0 */ - ArgGroup[] argGroups() default {}; } /** A {@code Command} may define one or more {@code ArgGroups}: a group of options, positional parameters or a mixture of the two. * Groups can be used: @@ -3328,7 +3365,7 @@ public class CommandLine { * For a group of mutually exclusive arguments, making the group required means that one of the arguments in the group must appear on the command line, or a {@link MissingParameterException MissingParameterException} is thrown. * For a group of co-occurring arguments, all arguments in the group must appear on the command line. *

- *

Groups can be composed by specifying {@linkplain #subgroups() subgroups} for validation purposes:

+ *

Groups can be composed for validation purposes:

*