From 311ab392c5f40f6e0d77c1aca096aaa868068250 Mon Sep 17 00:00:00 2001
From: Derek Sharpe
Date: Mon, 18 Sep 2023 17:16:42 -0500
Subject: [PATCH 1/6] Create new feature parameterAllowedBeforeEndOfOptions to
allow restricting positional parameters until after EndOfOptions
---
src/main/java/picocli/CommandLine.java | 40 ++++++++++++-
src/test/java/picocli/Issue2103.java | 77 ++++++++++++++++++++++++++
2 files changed, 115 insertions(+), 2 deletions(-)
create mode 100644 src/test/java/picocli/Issue2103.java
diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java
index d51f83492..f06f413fc 100644
--- a/src/main/java/picocli/CommandLine.java
+++ b/src/main/java/picocli/CommandLine.java
@@ -1104,6 +1104,31 @@ public CommandLine setUnmatchedOptionsArePositionalParams(boolean newValue) {
return this;
}
+ /** Returns whether positional parameters on the command line are allowed to occur before the special End of Options delimiter.
+ * The default is {@code true}.
+ * @return {@code true} positional parameters may occur anywhere on the command line, {@code false} if they must follow End of Options.
+ */
+ public boolean isParameterAllowedBeforeEndOfOptions() {
+ return getCommandSpec().parser().parameterAllowedBeforeEndOfOptions();
+ }
+
+ /** Sets whether positional parameters on the command line are allowed to occur before the special End of Options delimiter.
+ * The default is {@code true}.
+ * The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
+ * subcommands and nested sub-subcommands at the moment this method is called. Subcommands added
+ * later will have the default setting. To ensure a setting is applied to all
+ * subcommands, call the setter last, after adding subcommands.
+ * @param newValue the new setting. When {@code false}, positional parameters must follow the special End of Options delimiter.
+ * @return this {@code CommandLine} object, to allow method chaining
+ */
+ public CommandLine setParameterAllowedBeforeEndOfOptions(boolean newValue) {
+ getCommandSpec().parser().parameterAllowedBeforeEndOfOptions(newValue);
+ for (CommandLine command : getCommandSpec().subcommands().values()) {
+ command.setParameterAllowedBeforeEndOfOptions(newValue);
+ }
+ return this;
+ }
+
/** Returns whether the end user may specify arguments on the command line that are not matched to any option or parameter fields.
* The default is {@code false} and a {@link UnmatchedArgumentException} is thrown if this happens.
* When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
@@ -8546,6 +8571,7 @@ public static class ParserSpec {
private boolean unmatchedArgumentsAllowed = false;
private boolean unmatchedOptionsAllowedAsOptionParameters = true;
private boolean unmatchedOptionsArePositionalParams = false;
+ private boolean parameterAllowedBeforeEndOfOptions = true;
private boolean useSimplifiedAtFiles = false;
/** Returns the String to use as the separator between options and option parameters. {@code "="} by default,
@@ -8596,6 +8622,7 @@ public boolean useSimplifiedAtFiles() {
public boolean splitQuotedStrings() { return splitQuotedStrings; }
/** @see CommandLine#isUnmatchedOptionsArePositionalParams() */
public boolean unmatchedOptionsArePositionalParams() { return unmatchedOptionsArePositionalParams; }
+ public boolean parameterAllowedBeforeEndOfOptions() { return parameterAllowedBeforeEndOfOptions; }
/**
* @see CommandLine#isUnmatchedOptionsAllowedAsOptionParameters()
* @since 4.4 */
@@ -8664,6 +8691,8 @@ public boolean useSimplifiedAtFiles() {
public ParserSpec unmatchedOptionsAllowedAsOptionParameters(boolean unmatchedOptionsAllowedAsOptionParameters) { this.unmatchedOptionsAllowedAsOptionParameters = unmatchedOptionsAllowedAsOptionParameters; return this; }
/** @see CommandLine#setUnmatchedOptionsArePositionalParams(boolean) */
public ParserSpec unmatchedOptionsArePositionalParams(boolean unmatchedOptionsArePositionalParams) { this.unmatchedOptionsArePositionalParams = unmatchedOptionsArePositionalParams; return this; }
+ /** @see CommandLine#setParameterAllowedBeforeEndOfOptions(boolean) */
+ public ParserSpec parameterAllowedBeforeEndOfOptions(boolean allowParametersBeforeEndOfOptions) { this.parameterAllowedBeforeEndOfOptions = allowParametersBeforeEndOfOptions; return this; }
/**
* @see CommandLine#setAllowSubcommandsAsOptionParameters(boolean)
* @since 4.7.6-SNAPSHOT */
@@ -8699,14 +8728,16 @@ public String toString() {
"limitSplit=%s, overwrittenOptionsAllowed=%s, posixClusteredShortOptionsAllowed=%s, " +
"separator=%s, splitQuotedStrings=%s, stopAtPositional=%s, stopAtUnmatched=%s, " +
"toggleBooleanFlags=%s, trimQuotes=%s, " +
- "unmatchedArgumentsAllowed=%s, unmatchedOptionsAllowedAsOptionParameters=%s, unmatchedOptionsArePositionalParams=%s, useSimplifiedAtFiles=%s",
+ "unmatchedArgumentsAllowed=%s, unmatchedOptionsAllowedAsOptionParameters=%s, " +
+ "unmatchedOptionsArePositionalParams=%s, allowParametersBeforeEndOfOptions=%s, useSimplifiedAtFiles=%s",
abbreviatedOptionsAllowed, abbreviatedSubcommandsAllowed, allowOptionsAsOptionParameters,
allowSubcommandsAsOptionParameters, aritySatisfiedByAttachedOptionParam, atFileCommentChar,
caseInsensitiveEnumValuesAllowed, collectErrors, endOfOptionsDelimiter, expandAtFiles,
limitSplit, overwrittenOptionsAllowed, posixClusteredShortOptionsAllowed,
separator, splitQuotedStrings, stopAtPositional, stopAtUnmatched,
toggleBooleanFlags, trimQuotes,
- unmatchedArgumentsAllowed, unmatchedOptionsAllowedAsOptionParameters, unmatchedOptionsArePositionalParams, useSimplifiedAtFiles);
+ unmatchedArgumentsAllowed, unmatchedOptionsAllowedAsOptionParameters,
+ unmatchedOptionsArePositionalParams, parameterAllowedBeforeEndOfOptions, useSimplifiedAtFiles);
}
void initFrom(ParserSpec settings) {
@@ -8732,6 +8763,7 @@ void initFrom(ParserSpec settings) {
unmatchedArgumentsAllowed = settings.unmatchedArgumentsAllowed;
unmatchedOptionsAllowedAsOptionParameters = settings.unmatchedOptionsAllowedAsOptionParameters;
unmatchedOptionsArePositionalParams = settings.unmatchedOptionsArePositionalParams;
+ parameterAllowedBeforeEndOfOptions = settings.parameterAllowedBeforeEndOfOptions;
useSimplifiedAtFiles = settings.useSimplifiedAtFiles;
}
}
@@ -13932,6 +13964,10 @@ private void processPositionalParameter(Collection required, Set list = new ArrayList();
+ }
+
+ App app = CommandLine.populateCommand(new App(), "--optA joe a b -- --optB c d".split(" "));
+ assertEquals("joe", app.optA);
+ assertEquals(Arrays.asList("a", "b", "--optB", "c", "d"), app.list);
+ }
+
+ /**
+ * When ParameterAllowedBeforeEndOfOptions is disabled, the exit code for USAGE should be returned
+ * when positional parameters are found before EndOfOptions delimiter.
+ */
+ @Test
+ public void testTriggerUsage() {
+ class App implements Runnable {
+ @CommandLine.Option(names = "--optA") String optA;
+ @CommandLine.Parameters()
+ final List list = new ArrayList();
+
+ public void run() { }
+ }
+
+ App app = new App();
+ int exitCode = new CommandLine(app)
+ .setParameterAllowedBeforeEndOfOptions(false)
+ .execute("--optA joe a b -- --optB c d".split(" "));
+ assertEquals(2, exitCode); // Should exit with USAGE since a and b are unmatched arguments
+ assertEquals("joe", app.optA);
+ assertEquals(Arrays.asList("--optB", "c", "d"), app.list);
+ }
+
+ /**
+ * Using a valid command line with ParameterAllowedBeforeEndOfOptions disabled, should correctly parse the options
+ * after the EndOfOptions delimiter as well as the valid options before the delimiter.
+ */
+ @Test
+ public void testParameterAllowedBeforeEndOfOptions() {
+ class App implements Runnable {
+ @CommandLine.Option(names = "--optA") String optA;
+ @CommandLine.Parameters()
+ final List list = new ArrayList();
+
+ public void run() { }
+ }
+
+ App app = new App();
+ int exitCode = new CommandLine(app)
+ .setParameterAllowedBeforeEndOfOptions(false)
+ .execute("--optA joe -- --optB c d".split(" "));
+ assertEquals(0, exitCode);
+ assertEquals("joe", app.optA);
+ assertEquals(Arrays.asList("--optB", "c", "d"), app.list);
+ }
+}
From c90bbfb17adec0343e90bf6c6e11d92f44748870 Mon Sep 17 00:00:00 2001
From: Derek Sharpe
Date: Tue, 19 Dec 2023 17:55:30 -0600
Subject: [PATCH 2/6] added javadoc since notations to all new API methods for
this feature.
---
src/main/java/picocli/CommandLine.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java
index d5ae2eafc..00df67ca5 100644
--- a/src/main/java/picocli/CommandLine.java
+++ b/src/main/java/picocli/CommandLine.java
@@ -1107,6 +1107,7 @@ public CommandLine setUnmatchedOptionsArePositionalParams(boolean newValue) {
/** Returns whether positional parameters on the command line are allowed to occur before the special End of Options delimiter.
* The default is {@code true}.
* @return {@code true} positional parameters may occur anywhere on the command line, {@code false} if they must follow End of Options.
+ * @since 4.8.0
*/
public boolean isParameterAllowedBeforeEndOfOptions() {
return getCommandSpec().parser().parameterAllowedBeforeEndOfOptions();
@@ -1120,6 +1121,7 @@ public boolean isParameterAllowedBeforeEndOfOptions() {
* subcommands, call the setter last, after adding subcommands.
* @param newValue the new setting. When {@code false}, positional parameters must follow the special End of Options delimiter.
* @return this {@code CommandLine} object, to allow method chaining
+ * @since 4.8.0
*/
public CommandLine setParameterAllowedBeforeEndOfOptions(boolean newValue) {
getCommandSpec().parser().parameterAllowedBeforeEndOfOptions(newValue);
@@ -8691,7 +8693,9 @@ public boolean useSimplifiedAtFiles() {
public ParserSpec unmatchedOptionsAllowedAsOptionParameters(boolean unmatchedOptionsAllowedAsOptionParameters) { this.unmatchedOptionsAllowedAsOptionParameters = unmatchedOptionsAllowedAsOptionParameters; return this; }
/** @see CommandLine#setUnmatchedOptionsArePositionalParams(boolean) */
public ParserSpec unmatchedOptionsArePositionalParams(boolean unmatchedOptionsArePositionalParams) { this.unmatchedOptionsArePositionalParams = unmatchedOptionsArePositionalParams; return this; }
- /** @see CommandLine#setParameterAllowedBeforeEndOfOptions(boolean) */
+ /**
+ * @see CommandLine#setParameterAllowedBeforeEndOfOptions(boolean)
+ * @since 4.8.0*/
public ParserSpec parameterAllowedBeforeEndOfOptions(boolean allowParametersBeforeEndOfOptions) { this.parameterAllowedBeforeEndOfOptions = allowParametersBeforeEndOfOptions; return this; }
/**
* @see CommandLine#setAllowSubcommandsAsOptionParameters(boolean)
From 6df1aa759ea6e955018e8e71bb9cf60c7098e85c Mon Sep 17 00:00:00 2001
From: Derek Sharpe
Date: Thu, 21 Dec 2023 16:49:15 -0600
Subject: [PATCH 3/6] additional unit tests for subcommand behavior with
parameterAllowedBeforeEndOfOptions
---
src/test/java/picocli/Issue2103.java | 100 +++++++++++++++++++++------
1 file changed, 79 insertions(+), 21 deletions(-)
diff --git a/src/test/java/picocli/Issue2103.java b/src/test/java/picocli/Issue2103.java
index 456cbea9d..fba376f58 100644
--- a/src/test/java/picocli/Issue2103.java
+++ b/src/test/java/picocli/Issue2103.java
@@ -7,42 +7,62 @@
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* Enhancement from issue 2103 enables or disables positional parameters before the EndOfOptions delimiter (such as "--").
*/
public class Issue2103 {
+ static class App implements Runnable {
+ @CommandLine.Option(names = "--optA") String optA;
+ @CommandLine.Parameters()
+ final List list = new ArrayList();
+ public void run() { }
+ }
+
+ static class SubCommand implements Runnable {
+ @CommandLine.Option(names = "--optB") String optB;
+ @CommandLine.Parameters()
+ final List list = new ArrayList();
+ public void run() { }
+ }
+
/**
* Original behavior allows positional parameters before and after EndOfOptions delimiter.
*/
@Test
public void testOriginalBehavior() {
- class App {
- @CommandLine.Option(names = "--optA") String optA;
- @CommandLine.Parameters()
- final List list = new ArrayList();
- }
-
App app = CommandLine.populateCommand(new App(), "--optA joe a b -- --optB c d".split(" "));
assertEquals("joe", app.optA);
assertEquals(Arrays.asList("a", "b", "--optB", "c", "d"), app.list);
}
+ /**
+ * The default value for allowing parameters prior to the End Of Options delimiter should be true
+ * in order to maintain backward compatibility with previous releases.
+ */
+ @Test
+ public void testOriginalDefault() {
+ App app = new App();
+ CommandLine c = new CommandLine(app);
+ assertTrue(c.isParameterAllowedBeforeEndOfOptions());
+ //verify ParserSpec getter (should return same value as CommandLine setting
+ assertTrue(c.getCommandSpec().parser().parameterAllowedBeforeEndOfOptions());
+
+ // Toggle value for setting and verify (tests setter, and verifies getters)
+ c.getCommandSpec().parser().parameterAllowedBeforeEndOfOptions(false);
+ assertFalse(c.getCommandSpec().parser().parameterAllowedBeforeEndOfOptions());
+ assertFalse(c.isParameterAllowedBeforeEndOfOptions());
+ }
+
/**
* When ParameterAllowedBeforeEndOfOptions is disabled, the exit code for USAGE should be returned
* when positional parameters are found before EndOfOptions delimiter.
*/
@Test
public void testTriggerUsage() {
- class App implements Runnable {
- @CommandLine.Option(names = "--optA") String optA;
- @CommandLine.Parameters()
- final List list = new ArrayList();
-
- public void run() { }
- }
-
App app = new App();
int exitCode = new CommandLine(app)
.setParameterAllowedBeforeEndOfOptions(false)
@@ -58,20 +78,58 @@ public void run() { }
*/
@Test
public void testParameterAllowedBeforeEndOfOptions() {
- class App implements Runnable {
- @CommandLine.Option(names = "--optA") String optA;
+ App app = new App();
+ int exitCode = new CommandLine(app)
+ .setParameterAllowedBeforeEndOfOptions(false)
+ .execute("--optA joe -- --optB c d".split(" "));
+ assertEquals(0, exitCode);
+ assertEquals("joe", app.optA);
+ assertEquals(Arrays.asList("--optB", "c", "d"), app.list);
+ }
+
+ /**
+ * Subcommand tests for ParameterAllowedBeforeEndOfOptions.
+ */
+ @Test
+ public void testParameterAllowedBeforeEndOfOptionsSubCommand1() {
+ class SubCommandZ implements Runnable {
+ @CommandLine.Option(names = "--optZ") String optZ;
@CommandLine.Parameters()
final List list = new ArrayList();
-
public void run() { }
}
+ CommandLine cl = new CommandLine(new App())
+ .addSubcommand("cmdA", new SubCommand())
+ .setParameterAllowedBeforeEndOfOptions(false)
+ .addSubcommand("cmdZ", new SubCommandZ());
+
+ // The ParameterAllowedBeforeEndOfOptions should apply to both main command and subcommands
+ // The extra "a1" after "jack" should be rejected.
+ assertEquals(2, cl.execute("--optA jill cmdA --optB jack a1 -- --optC c d".split(" ")));
+ // The extra "a2" after "jill" should be rejected
+ assertEquals(2, cl.execute("--optA jill a2 cmdA --optB jack -- --optC c d".split(" ")));
+ /* The extra "a3" after "hill" should NOT be rejected since the setParameterAllowedBeforeEndOfOptions was
+ called before the subcommand was added. */
+ assertEquals(0, cl.execute("--optA jill cmdZ --optZ hill a3 -- --optC c d".split(" ")));
+ }
+
+ /**
+ * Subcommand tests for ParameterAllowedBeforeEndOfOptions.
+ */
+ @Test
+ public void testParameterAllowedBeforeEndOfOptionsSubCommand2() {
App app = new App();
+ SubCommand sub = new SubCommand();
int exitCode = new CommandLine(app)
- .setParameterAllowedBeforeEndOfOptions(false)
- .execute("--optA joe -- --optB c d".split(" "));
+ .addSubcommand("cmdA", sub)
+ .execute("--optA jill cmdA --optB jack a -- --optC c d".split(" "));
+ // the extra "a" after "jack" should be accepted, because ParameterAllowedBeforeEndOfOptions was not set
assertEquals(0, exitCode);
- assertEquals("joe", app.optA);
- assertEquals(Arrays.asList("--optB", "c", "d"), app.list);
+ assertEquals("jill", app.optA);
+ assertEquals("jack", sub.optB);
+ assertTrue(app.list.isEmpty());
+ assertEquals(Arrays.asList("a", "--optC", "c", "d"), sub.list);
}
+
}
From 97dd03b15e2a4a556539d46d8bcd92a85b815cc5 Mon Sep 17 00:00:00 2001
From: Derek Sharpe
Date: Thu, 21 Dec 2023 16:57:26 -0600
Subject: [PATCH 4/6] fix TracerTest unit tests where output was changed by the
addition of the new allowParametersBeforeEndOfOptions feature
---
src/test/java/picocli/TracerTest.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/test/java/picocli/TracerTest.java b/src/test/java/picocli/TracerTest.java
index c73444ae4..aaf1a965a 100644
--- a/src/test/java/picocli/TracerTest.java
+++ b/src/test/java/picocli/TracerTest.java
@@ -63,7 +63,7 @@ public void testDebugOutputForDoubleDashSeparatesPositionalParameters() throws E
"[picocli DEBUG] Creating CommandSpec for picocli.CommandLineTest$CompactFields@20f5239f with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli INFO] Picocli version: %3$s%n" +
"[picocli INFO] Parsing 6 command line args [-oout, --, -r, -v, p1, p2]%n" +
- "[picocli DEBUG] Parser configuration: optionsCaseInsensitive=false, subcommandsCaseInsensitive=false, abbreviatedOptionsAllowed=false, abbreviatedSubcommandsAllowed=false, allowOptionsAsOptionParameters=false, allowSubcommandsAsOptionParameters=false, aritySatisfiedByAttachedOptionParam=false, atFileCommentChar=#, caseInsensitiveEnumValuesAllowed=false, collectErrors=false, endOfOptionsDelimiter=--, expandAtFiles=true, limitSplit=false, overwrittenOptionsAllowed=false, posixClusteredShortOptionsAllowed=true, separator=null, splitQuotedStrings=false, stopAtPositional=false, stopAtUnmatched=false, toggleBooleanFlags=false, trimQuotes=false, unmatchedArgumentsAllowed=false, unmatchedOptionsAllowedAsOptionParameters=true, unmatchedOptionsArePositionalParams=false, useSimplifiedAtFiles=false%n" +
+ "[picocli DEBUG] Parser configuration: optionsCaseInsensitive=false, subcommandsCaseInsensitive=false, abbreviatedOptionsAllowed=false, abbreviatedSubcommandsAllowed=false, allowOptionsAsOptionParameters=false, allowSubcommandsAsOptionParameters=false, aritySatisfiedByAttachedOptionParam=false, atFileCommentChar=#, caseInsensitiveEnumValuesAllowed=false, collectErrors=false, endOfOptionsDelimiter=--, expandAtFiles=true, limitSplit=false, overwrittenOptionsAllowed=false, posixClusteredShortOptionsAllowed=true, separator=null, splitQuotedStrings=false, stopAtPositional=false, stopAtUnmatched=false, toggleBooleanFlags=false, trimQuotes=false, unmatchedArgumentsAllowed=false, unmatchedOptionsAllowedAsOptionParameters=true, unmatchedOptionsArePositionalParams=false, allowParametersBeforeEndOfOptions=true, useSimplifiedAtFiles=false%n" +
"[picocli DEBUG] (ANSI is disabled by default: ...)%n" +
"[picocli DEBUG] Initializing command 'null' (user object: picocli.CommandLineTest$CompactFields@20f5239f): 3 options, 1 positional parameters, 0 required, 0 groups, 0 subcommands.%n" +
"[picocli DEBUG] Set initial value for field boolean picocli.CommandLineTest$CompactFields.verbose of type boolean to false.%n" +
@@ -293,7 +293,7 @@ public void testTracingDebugWithSubCommands() throws Exception {
"[picocli DEBUG] Adding subcommand 'tag' to 'git'%n" +
"[picocli INFO] Picocli version: %3$s%n" +
"[picocli INFO] Parsing 8 command line args [--git-dir=/home/rpopma/picocli, commit, -m, \"Fixed typos\", --, src1.java, src2.java, src3.java]%n" +
- "[picocli DEBUG] Parser configuration: optionsCaseInsensitive=false, subcommandsCaseInsensitive=false, abbreviatedOptionsAllowed=false, abbreviatedSubcommandsAllowed=false, allowOptionsAsOptionParameters=false, allowSubcommandsAsOptionParameters=false, aritySatisfiedByAttachedOptionParam=false, atFileCommentChar=#, caseInsensitiveEnumValuesAllowed=false, collectErrors=false, endOfOptionsDelimiter=--, expandAtFiles=true, limitSplit=false, overwrittenOptionsAllowed=false, posixClusteredShortOptionsAllowed=true, separator=null, splitQuotedStrings=false, stopAtPositional=false, stopAtUnmatched=false, toggleBooleanFlags=false, trimQuotes=false, unmatchedArgumentsAllowed=false, unmatchedOptionsAllowedAsOptionParameters=true, unmatchedOptionsArePositionalParams=false, useSimplifiedAtFiles=false%n" +
+ "[picocli DEBUG] Parser configuration: optionsCaseInsensitive=false, subcommandsCaseInsensitive=false, abbreviatedOptionsAllowed=false, abbreviatedSubcommandsAllowed=false, allowOptionsAsOptionParameters=false, allowSubcommandsAsOptionParameters=false, aritySatisfiedByAttachedOptionParam=false, atFileCommentChar=#, caseInsensitiveEnumValuesAllowed=false, collectErrors=false, endOfOptionsDelimiter=--, expandAtFiles=true, limitSplit=false, overwrittenOptionsAllowed=false, posixClusteredShortOptionsAllowed=true, separator=null, splitQuotedStrings=false, stopAtPositional=false, stopAtUnmatched=false, toggleBooleanFlags=false, trimQuotes=false, unmatchedArgumentsAllowed=false, unmatchedOptionsAllowedAsOptionParameters=true, unmatchedOptionsArePositionalParams=false, allowParametersBeforeEndOfOptions=true, useSimplifiedAtFiles=false%n" +
"[picocli DEBUG] (ANSI is disabled by default: ...)%n" +
"[picocli DEBUG] Initializing command 'git' (user object: picocli.Demo$Git@75d4a5c2): 3 options, 0 positional parameters, 0 required, 0 groups, 12 subcommands.%n" +
"[picocli DEBUG] Set initial value for field java.io.File picocli.Demo$Git.gitDir of type class java.io.File to null.%n" +
From eca3fc40630addc8d244d04d651cb31c572087c1 Mon Sep 17 00:00:00 2001
From: Derek Sharpe
Date: Wed, 3 Jan 2024 15:59:49 -0600
Subject: [PATCH 5/6] Added documentation for new parser configuration option
---
docs/index.adoc | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/docs/index.adoc b/docs/index.adoc
index e28761c65..8d0ad0573 100644
--- a/docs/index.adoc
+++ b/docs/index.adoc
@@ -1039,6 +1039,9 @@ assert(mixed.positional == Arrays.asList("param0", "param1", "param2", "param3")
assert(mixed.options == Arrays.asList("AAA", "BBB"))
----
+Note that the mixing of positional parameters and options is configurable, see <>.
+
+
=== Double dash (`--`)
When one of the command line arguments is just two dashes without any characters attached (`--`),
picocli interprets all following arguments as positional parameters, even arguments that match an option name.
@@ -5261,6 +5264,12 @@ java App -x -y -y=123
----
+=== End of Options Behaviour
+Since picocli 2.0, positional parameters can be specified anywhere on the command line and no longer need to follow the options.
+Starting with v4.8.0, `CommandLine::setParameterAllowedBeforeEndOfOptions` can be set to false in order to restrict
+the mixing of options and positional parameters. This option forces positional parameters to follow the <>.
+
+
=== Toggle Boolean Flags
When a flag option is specified on the command line picocli will set its value to the opposite of its _default_ value.
From 64b147028cf209321b2fcbe139dbb3f76dda315c Mon Sep 17 00:00:00 2001
From: Derek Sharpe
Date: Wed, 3 Jan 2024 18:20:36 -0600
Subject: [PATCH 6/6] Altered error message for UnmatchedArgumentException for
new parser option, with corresponding unit tests.
---
src/main/java/picocli/CommandLine.java | 6 ++++-
src/test/java/picocli/Issue2103.java | 36 ++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java
index 00df67ca5..903b9eeb7 100644
--- a/src/main/java/picocli/CommandLine.java
+++ b/src/main/java/picocli/CommandLine.java
@@ -13695,7 +13695,11 @@ private void validateConstraints(Stack argumentStack, List requ
for (UnmatchedArgsBinding unmatchedArgsBinding : getCommandSpec().unmatchedArgsBindings()) {
unmatchedArgsBinding.addAll(unmatched.clone());
}
- if (!isUnmatchedArgumentsAllowed()) { maybeThrow(new UnmatchedArgumentException(CommandLine.this, Collections.unmodifiableList(parseResultBuilder.unmatched))); }
+ if (!isUnmatchedArgumentsAllowed()) {
+ String extraMsg = "";
+ if (!isParameterAllowedBeforeEndOfOptions()) { extraMsg = ". Positional parameters must follow the EndOfOptions delimiter '" + getEndOfOptionsDelimiter() + "'."; }
+ maybeThrow(new UnmatchedArgumentException(CommandLine.this, Collections.unmodifiableList(parseResultBuilder.unmatched), extraMsg));
+ }
Tracer tracer = CommandLine.tracer();
if (tracer.isInfo()) { tracer.info("Unmatched arguments: %s", parseResultBuilder.unmatched); }
}
diff --git a/src/test/java/picocli/Issue2103.java b/src/test/java/picocli/Issue2103.java
index fba376f58..510290d62 100644
--- a/src/test/java/picocli/Issue2103.java
+++ b/src/test/java/picocli/Issue2103.java
@@ -9,6 +9,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* Enhancement from issue 2103 enables or disables positional parameters before the EndOfOptions delimiter (such as "--").
@@ -132,4 +133,39 @@ public void testParameterAllowedBeforeEndOfOptionsSubCommand2() {
assertEquals(Arrays.asList("a", "--optC", "c", "d"), sub.list);
}
+ /**
+ * Validate that the setParameterAllowedBeforeEndOfOptions triggers a new message for unmatched positional arguments.
+ */
+ @Test
+ public void testUnmatchedArgumentMessageAsFalse() {
+ App app = new App();
+ CommandLine cl = new CommandLine(app)
+ .setParameterAllowedBeforeEndOfOptions(false);
+ try {
+ CommandLine.populateCommand(cl, "--optA joe a1 -- --optB c d".split(" "));
+ fail("Unmatched positional argument should have thrown exception");
+ } catch (CommandLine.UnmatchedArgumentException ex) {
+ assertEquals("Unmatched argument at index 2: 'a1'. Positional parameters must follow the EndOfOptions delimiter '--'.", ex.getMessage());
+ }
+ }
+
+ /**
+ * Verify that the setParameterAllowedBeforeEndOfOptions triggers the original message when not set.
+ */
+ @Test
+ public void testUnmatchedArgumentMessageAsDefault() {
+ class UnmatchedApp implements Runnable {
+ @CommandLine.Option(names = "--optA") String optA;
+ public void run() { }
+ }
+
+ UnmatchedApp app = new UnmatchedApp();
+ CommandLine cl = new CommandLine(app);
+ try {
+ CommandLine.populateCommand(cl, "--optA joe a1".split(" "));
+ fail("Unmatched positional argument should have thrown exception");
+ } catch (CommandLine.UnmatchedArgumentException ex) {
+ assertEquals("Unmatched argument at index 2: 'a1'", ex.getMessage());
+ }
+ }
}