diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 561999116..de3a8532d 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -33,6 +33,7 @@ No features have been promoted in this picocli release.
- [#223] New feature: Added `examples` subproject containing running examples. Thanks to [aadrian](https://github.com/aadrian) and [RobertZenz](https://github.com/RobertZenz).
- [#68] Enhancement: Reject private final primitive fields annotated with @Option or @Parameters: because compile-time constants are inlined, updates by picocli to such fields would not be visible to the application.
- [#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).
## Deprecations
diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java
index 7ed961d28..9ec5e3f0b 100644
--- a/src/main/java/picocli/CommandLine.java
+++ b/src/main/java/picocli/CommandLine.java
@@ -2220,14 +2220,13 @@ private void processClusteredShortOptions(Collection required,
// arity may be >= 1, or
// arity <= 0 && !cluster.startsWith(separator)
// e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE"
- args.push(cluster); // interpret remainder as option parameter (CAUTION: may be empty string!)
- 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 (CAUTION: may be empty string!)
}
int argCount = args.size();
int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription);
// if cluster was consumed as a parameter or if this field was the last in the cluster we're done; otherwise continue do-while loop
- if (args.isEmpty() || args.size() < argCount) {
+ if (empty(cluster) || args.isEmpty() || args.size() < argCount) {
return;
}
cluster = args.pop();
diff --git a/src/test/java/picocli/CommandLineTest.java b/src/test/java/picocli/CommandLineTest.java
index c9ac6eb2f..db6003a4e 100644
--- a/src/test/java/picocli/CommandLineTest.java
+++ b/src/test/java/picocli/CommandLineTest.java
@@ -466,6 +466,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"));
@@ -523,7 +526,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() {
@@ -531,11 +534,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);
}