diff --git a/picocli-annotation-processing-tests/src/test/java/picocli/Issue1380Test.java b/picocli-annotation-processing-tests/src/test/java/picocli/Issue1380Test.java new file mode 100644 index 000000000..ed9d56a85 --- /dev/null +++ b/picocli-annotation-processing-tests/src/test/java/picocli/Issue1380Test.java @@ -0,0 +1,89 @@ +package picocli; + +import org.junit.Test; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; + +/** + * Testing class for exclusiveOptions + */ +class Issue1380ExclusiveOptions { + @Option(names = {"-s", "--silent"}, description = "Silent mode", required = false) + protected boolean silent; + + @Option(names = {"-v", "--verbose"}, description = "Verbose mode", required = false) + protected boolean verbose; + + @Option(names = {"-j", "--json"}, description = "JSON printing", required = false) + protected boolean json; +} + +/** + * Testing class for creating a commandline with ArgGroup exclusive tree + */ +@Command(requiredOptionMarker = '*') +class TestingClassExclusiveTrue { + + @ArgGroup(exclusive = true, multiplicity = "0..1") + protected Issue1380ExclusiveOptions exclusive; +} + +/** + * Testing class for creating a commandline with ArgGroup exclusive false + */ +@Command(requiredOptionMarker = '*') +class TestingClassExclusiveFalse { + + @ArgGroup(exclusive = false, multiplicity = "0..1") + protected Issue1380ExclusiveOptions exclusive; +} + +/** + * JUnit testing class for issue#1380 // CS427 https://github.com/remkop/picocli/issues/1380 + */ +public class Issue1380Test { + + /** + * JUnit test class for issue#1380 with exclusive set to true // CS427 https://github.com/remkop/picocli/issues/1380 + */ + @Test + public void testingWithExclusiveTrue() { + ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(tempOut); + new CommandLine(new TestingClassExclusiveTrue()).usage(printStream); + + String returnedText = tempOut.toString(); + String expectedText = String.format( + "Usage:
[-s | -v | -j]%n"+ + " -j, --json JSON printing%n"+ + " -s, --silent Silent mode%n"+ + " -v, --verbose Verbose mode%n"); + + assertEquals(expectedText, returnedText); + } + + /** + * JUnit test class for issue#1380 with exclusive set to false// CS427 https://github.com/remkop/picocli/issues/1380 + */ + @Test + public void testingWithExclusiveFalse() { + ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(tempOut); + new CommandLine(new TestingClassExclusiveFalse()).usage(printStream); + + String returnedText = tempOut.toString(); + String expectedText = String.format( + "Usage:
[[-s] [-v] [-j]]%n" + + " -j, --json JSON printing%n" + + " -s, --silent Silent mode%n" + + " -v, --verbose Verbose mode%n"); + + assertEquals(expectedText, returnedText); + } +} diff --git a/picocli-annotation-processing-tests/src/test/java/picocli/Issue1420Test.java b/picocli-annotation-processing-tests/src/test/java/picocli/Issue1420Test.java new file mode 100644 index 000000000..5498e4355 --- /dev/null +++ b/picocli-annotation-processing-tests/src/test/java/picocli/Issue1420Test.java @@ -0,0 +1,58 @@ +package picocli; + +import org.junit.Ignore; +import org.junit.Test; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; + +/** + * Testing class for creating a commandline with resourceBundle + */ +@Command(name = "TestingCommand", resourceBundle = ("picocli.Message")) +class TestingClass { + + @ArgGroup(exclusive = true, multiplicity = "0..1") + protected TestingClass.ExclusiveOptions exclusive; + + public TestingClass.ExclusiveOptions getExclusive() { + return this.exclusive; + } + private static class ExclusiveOptions { + @Option(names = {"-h", "--help"}, descriptionKey = "help.message", required = false) + protected boolean showHelp; + + @Option(names = {"-j", "--json"}, descriptionKey = "json.message", required = false) + protected boolean isJson; + + } +} + +@Ignore +/** + * JUnit testing class for issue#1420 // CS427 https://github.com/remkop/picocli/issues/1420 + */ +public class Issue1420Test { + + /** + * JUnit test class for issue#1420 with resourceBundle // CS427 https://github.com/remkop/picocli/issues/1420 + */ + @Test + public void testingWithResourceBundle1() { + ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(tempOut); + new CommandLine(new TestingClass()).usage(printStream); + + String returnedText = tempOut.toString(); + String expectedText = "Usage: TestingCommand [[-h] | [-j]]\n" + + " -h, --help Show help options\n" + + " -j, --json Set json export-on\n"; + + assertEquals(expectedText, returnedText); + } +} diff --git a/picocli-annotation-processing-tests/src/test/resources/picocli/Message.properties b/picocli-annotation-processing-tests/src/test/resources/picocli/Message.properties new file mode 100644 index 000000000..b1cfad6ff --- /dev/null +++ b/picocli-annotation-processing-tests/src/test/resources/picocli/Message.properties @@ -0,0 +1,6 @@ +/** +* Testing class for // CS427 https://github.com/remkop/picocli/issues/1420 +*/ +help.message=Show help options +json.message =Set json export-on + diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index 8e3fa78ae..4ce7dc545 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -8587,6 +8587,7 @@ public abstract static class ArgSpec { // parser fields private boolean required; + private boolean originallyRequired; private final boolean interactive; private final boolean echo; private final String prompt; @@ -8641,6 +8642,7 @@ private > ArgSpec(Builder builder) { annotatedElement = builder.annotatedElement; defaultValue = NO_DEFAULT_VALUE.equals(builder.defaultValue) ? null : builder.defaultValue; required = builder.required; + originallyRequired = builder.originallyRequired; toString = builder.toString; getter = builder.getter; setter = builder.setter; @@ -8699,6 +8701,13 @@ void applyInitialValue(Tracer tracer) { } } + /** Returns the original value of the option's required attribute, regardless of whether the option is used in an exclusive group or not. + * @since 4.7.0 + * @see Option#required() */ + public boolean originallyRequired(){ + return originallyRequired; + } + /** Returns whether this is a required option or positional parameter without a default value. * If this argument is part of a {@linkplain ArgGroup group}, this method returns whether this argument is required within the group (so it is not necessarily a required argument for the command). * @see Option#required() */ @@ -9251,6 +9260,7 @@ abstract static class Builder> { private String[] description; private String descriptionKey; private boolean required; + private boolean originallyRequired; private boolean interactive; private boolean echo; private String prompt; @@ -9290,6 +9300,7 @@ abstract static class Builder> { description = original.description; descriptionKey = original.descriptionKey; required = original.required; + originallyRequired = original.originallyRequired; interactive = original.interactive; echo = original.echo; prompt = original.prompt; @@ -10198,6 +10209,8 @@ public static class ArgGroupSpec implements IOrdered { if (!arg.required()) { modifiedArgs += sep + (arg.isOption() ? ((OptionSpec) arg).longestName() : (arg.paramLabel() + "[" + ((PositionalParamSpec) arg).index() + "]")); sep = ","; + //Keep initial required as originallyRequired for Issue#1380 https://github.com/remkop/picocli/issues/1380 + arg.originallyRequired = true; arg.required = true; } } @@ -16219,7 +16232,7 @@ public Text[][] render(OptionSpec option, IParamLabelRenderer paramLabelRenderer String longOption = join(names, shortOptionCount, names.length - shortOptionCount, ", "); Text longOptionText = createLongOptionText(option, paramLabelRenderer, scheme, longOption); - String requiredOption = option.required() ? requiredMarker : ""; + String requiredOption = !option.originallyRequired() && option.required() ? requiredMarker : ""; return renderDescriptionLines(option, scheme, requiredOption, shortOption, longOptionText); }