diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index d3ade404f..f261afe20 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -4,6 +4,7 @@ ## Fixed issues - [#230] Enhancement: Support embedded newlines in usage help sections like header or descriptions. Thanks to [ddimtirov](https://github.com/ddimtirov). +- [#233] Bugfix: Parser bug: first argument following clustered options is treated as a positional parameter. Thanks to [mgrossmann](https://github.com/mgrossmann). # Picocli 2.0.2 diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index 60fa05b18..538dca926 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -2157,16 +2157,15 @@ private void processClusteredShortOptions(Collection required, if (arity.min > 0 && !empty(cluster)) { if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);} } - args.push(cluster); // interpret remainder as option parameter (CAUTION: may be empty string!) // arity may be >= 1, or // arity <= 0 && !cluster.startsWith(separator) // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE" - if (!args.isEmpty() && args.peek().length() == 0 && !paramAttachedToOption) { - args.pop(); // throw out empty string we get at the end of a group of clustered short options + if (!empty(cluster)) { + args.push(cluster); // interpret remainder as option parameter } int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription); // only return if cluster (and maybe more) was consumed, otherwise continue do-while loop - if (consumed > 0 || args.isEmpty()) { + if (empty(cluster) || consumed > 0 || args.isEmpty()) { return; } cluster = args.pop(); diff --git a/src/test/java/picocli/CommandLineTest.java b/src/test/java/picocli/CommandLineTest.java index 756e4e585..ebf49a44c 100644 --- a/src/test/java/picocli/CommandLineTest.java +++ b/src/test/java/picocli/CommandLineTest.java @@ -955,6 +955,9 @@ public void testCompactFieldsAnyOrder() { compact = CommandLine.populateCommand(new CompactFields(), "-r -v -oout p1 p2".split(" ")); verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + compact = CommandLine.populateCommand(new CompactFields(), "-rv -o out p1 p2".split(" ")); //#233 + verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); + compact = CommandLine.populateCommand(new CompactFields(), "-oout -r -v p1 p2".split(" ")); verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); @@ -1012,7 +1015,7 @@ public void testOptionsMixedWithParameters() { @Test public void testShortOptionsWithSeparatorButNoValueAssignsEmptyStringEvenIfNotLast() { CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-ro= -v".split(" ")); - verifyCompact(compact, true, true, "", null); + verifyCompact(compact, false, true, "-v", null); } @Test public void testShortOptionsWithColonSeparatorButNoValueAssignsEmptyStringEvenIfNotLast() { @@ -1020,11 +1023,33 @@ public void testShortOptionsWithColonSeparatorButNoValueAssignsEmptyStringEvenIf CommandLine cmd = new CommandLine(compact); cmd.setSeparator(":"); cmd.parse("-ro: -v".split(" ")); + verifyCompact(compact, false, true, "-v", null); + } + @Test + public void testShortOptionsWithSeparatorButNoValueFailsIfValueRequired() { + try { + CommandLine.populateCommand(new CompactFields(), "-rvo=".split(" ")); + fail("Expected exception"); + } catch (ParameterException ex) { + assertEquals("Missing required parameter for option '-o' ()", ex.getMessage()); + } + } + @Test + public void testShortOptionsWithSeparatorButNoValueAssignsQuotedEmptyStringEvenIfNotLast() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-ro=\"\" -v".split(" ")); + verifyCompact(compact, true, true, "", null); + } + @Test + public void testShortOptionsWithColonSeparatorButNoValueAssignsQuotedEmptyStringEvenIfNotLast() { + CompactFields compact = new CompactFields(); + CommandLine cmd = new CommandLine(compact); + cmd.setSeparator(":"); + cmd.parse("-ro:\"\" -v".split(" ")); verifyCompact(compact, true, true, "", null); } @Test - public void testShortOptionsWithSeparatorButNoValueAssignsEmptyStringIfLast() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvo=".split(" ")); + public void testShortOptionsWithSeparatorButNoValueAssignsEmptyQuotedStringIfLast() { + CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvo=\"\"".split(" ")); verifyCompact(compact, true, true, "", null); }