Skip to content

Commit

Permalink
[#359] Make end-of-options delimiter configurable.
Browse files Browse the repository at this point in the history
Closes #359
  • Loading branch information
remkop committed Aug 6, 2018
1 parent 52d4b9c commit ecc2e41
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 12 deletions.
4 changes: 2 additions & 2 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The picocli community is pleased to announce picocli 3.4.1.

This release contains new features, bugfixes and enhancements.

From this release, the comment character in @-files (argument files) is configurable
From this release, the comment character in @-files (argument files) and the end-of-options delimiter (`--` by default) are configurable.


This is the thirty-sixth public release.
Expand Down Expand Up @@ -57,7 +57,7 @@ No features have been promoted in this picocli release.
- [#430] Bugfix: formatting was incorrect (did not break on embedded newlines) in the subcommands list descriptions. Thanks to [Benny Bottema](https:/bbottema) for the bug report.
- [#431] Better support for validation in setter methods: cleaner stack trace.
- [#432] Make comment character in @-files (argument files) configurable.

- [#359] Make end-of-options delimiter configurable.

## <a name="3.4.1-deprecated"></a> Deprecations
No features were deprecated in this release.
Expand Down
5 changes: 5 additions & 0 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ assert demo.files == null;
assert demo.params.equals(Arrays.asList("-files", "file1", "file2"));
----

A custom delimiter can be configured with `CommandLine.setEndOfOptionsDelimiter(String)`.

[[AtFiles]]
=== @-files
Users sometimes run into system limitations on the length of a command line when creating a
Expand All @@ -283,6 +285,9 @@ If an argument contains embedded whitespace, put the whole argument in double or
(`"-f=My Files\Stuff.java"`).

Lines starting with `#` are comments and are ignored.
The comment character can be configured with `CommandLine.setAtFileCommentChar(Character)`,
and comments can be switched off by setting the comment character to `null`.

The file may itself contain additional @-file arguments; any such arguments will be processed recursively.

If the file does not exist, or cannot be read, then the argument will be treated literally, and not removed.
Expand Down
45 changes: 37 additions & 8 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ public boolean isToggleBooleanFlags() {
return getCommandSpec().parser().toggleBooleanFlags();
}

/** Sets whether the value of boolean flag options should be "toggled" when the option is matched.
/** Sets whether the value of boolean flag options should be "toggled" when the option is matched. The default is {@code true}.
* <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
* subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
* later will have the default setting. To ensure a setting is applied to all
Expand Down Expand Up @@ -323,6 +323,7 @@ public boolean isOverwrittenOptionsAllowed() {
}

/** Sets whether options for single-value fields can be specified multiple times on the command line without a {@link OverwrittenOptionException} being thrown.
* The default is {@code false}.
* <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
* subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
* later will have the default setting. To ensure a setting is applied to all
Expand All @@ -344,7 +345,7 @@ public CommandLine setOverwrittenOptionsAllowed(boolean newValue) {
* @since 3.0 */
public boolean isPosixClusteredShortOptionsAllowed() { return getCommandSpec().parser().posixClusteredShortOptionsAllowed(); }

/** Sets whether short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}.
/** Sets whether short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}. The default is {@code true}.
* <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
* subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
* later will have the default setting. To ensure a setting is applied to all
Expand All @@ -368,8 +369,8 @@ public CommandLine setPosixClusteredShortOptionsAllowed(boolean newValue) {
* @since 3.4 */
public boolean isCaseInsensitiveEnumValuesAllowed() { return getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(); }

/** Sets whether the parser should ignore case when converting arguments to {@code enum} values.
* E.g., for an option of type <a href="https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html">java.time.DayOfWeek</a>,
/** Sets whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}.
* When set to true, for example, for an option of type <a href="https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html">java.time.DayOfWeek</a>,
* values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}.
* <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
* subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
Expand All @@ -387,6 +388,23 @@ public CommandLine setCaseInsensitiveEnumValuesAllowed(boolean newValue) {
return this;
}

/** Returns the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
* @return the end-of-options delimiter. The default is {@code "--"}.
* @since 3.5 */
public String getEndOfOptionsDelimiter() { return getCommandSpec().parser().endOfOptionsDelimiter(); }

/** Sets the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
* @param delimiter the end-of-options delimiter; must not be {@code null}. The default is {@code "--"}.
* @return this {@code CommandLine} object, to allow method chaining
* @since 3.5 */
public CommandLine setEndOfOptionsDelimiter(String delimiter) {
getCommandSpec().parser().endOfOptionsDelimiter(delimiter);
for (CommandLine command : getCommandSpec().subcommands().values()) {
command.setEndOfOptionsDelimiter(delimiter);
}
return this;
}

/** Returns whether the parser interprets the first positional parameter as "end of options" so the remaining
* arguments are all treated as positional parameters. The default is {@code false}.
* @return {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise
Expand Down Expand Up @@ -461,6 +479,7 @@ public boolean isUnmatchedOptionsArePositionalParams() {
}

/** Sets whether arguments on the command line that resemble an option should be treated as positional parameters.
* The default is {@code false}.
* <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
* subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
* later will have the default setting. To ensure a setting is applied to all
Expand Down Expand Up @@ -491,6 +510,7 @@ public boolean isUnmatchedArgumentsAllowed() {
}

/** Sets whether the end user may specify unmatched arguments on the command line without a {@link UnmatchedArgumentException} being thrown.
* The default is {@code false}.
* <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
* subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
* later will have the default setting. To ensure a setting is applied to all
Expand Down Expand Up @@ -1846,7 +1866,7 @@ public CommandLine setSeparator(String separator) {
return this;
}

/** Returns the maximum width of the usage help message.
/** Returns the maximum width of the usage help message. The default is 80.
* @see UsageMessageSpec#width() */
public int getUsageHelpWidth() { return getCommandSpec().usageMessage().width(); }

Expand Down Expand Up @@ -3743,6 +3763,7 @@ public static class ParserSpec {
private String separator;
private boolean stopAtUnmatched = false;
private boolean stopAtPositional = false;
private String endOfOptionsDelimiter = "--";
private boolean toggleBooleanFlags = true;
private boolean overwrittenOptionsAllowed = false;
private boolean unmatchedArgumentsAllowed = false;
Expand All @@ -3763,6 +3784,9 @@ public static class ParserSpec {
public boolean stopAtUnmatched() { return stopAtUnmatched; }
/** @see CommandLine#isStopAtPositional() */
public boolean stopAtPositional() { return stopAtPositional; }
/** @see CommandLine#getEndOfOptionsDelimiter()
* @since 3.5 */
public String endOfOptionsDelimiter() { return endOfOptionsDelimiter; }
/** @see CommandLine#isToggleBooleanFlags() */
public boolean toggleBooleanFlags() { return toggleBooleanFlags; }
/** @see CommandLine#isOverwrittenOptionsAllowed() */
Expand Down Expand Up @@ -3799,6 +3823,9 @@ public static class ParserSpec {
public ParserSpec stopAtUnmatched(boolean stopAtUnmatched) { this.stopAtUnmatched = stopAtUnmatched; return this; }
/** @see CommandLine#setStopAtPositional(boolean) */
public ParserSpec stopAtPositional(boolean stopAtPositional) { this.stopAtPositional = stopAtPositional; return this; }
/** @see CommandLine#setEndOfOptionsDelimiter(String)
* @since 3.5 */
public ParserSpec endOfOptionsDelimiter(String delimiter) { this.endOfOptionsDelimiter = Assert.notNull(delimiter, "end-of-options delimiter"); return this; }
/** @see CommandLine#setToggleBooleanFlags(boolean) */
public ParserSpec toggleBooleanFlags(boolean toggleBooleanFlags) { this.toggleBooleanFlags = toggleBooleanFlags; return this; }
/** @see CommandLine#setOverwrittenOptionsAllowed(boolean) */
Expand Down Expand Up @@ -3832,20 +3859,22 @@ public static class ParserSpec {
public String toString() {
return String.format("posixClusteredShortOptionsAllowed=%s, stopAtPositional=%s, stopAtUnmatched=%s, " +
"separator=%s, overwrittenOptionsAllowed=%s, unmatchedArgumentsAllowed=%s, expandAtFiles=%s, " +
"limitSplit=%s, aritySatisfiedByAttachedOptionParam=%s",
"atFileCommentChar=%s, endOfOptionsDelimiter=%s, limitSplit=%s, aritySatisfiedByAttachedOptionParam=%s",
posixClusteredShortOptionsAllowed, stopAtPositional, stopAtUnmatched,
separator, overwrittenOptionsAllowed, unmatchedArgumentsAllowed, expandAtFiles,
limitSplit, aritySatisfiedByAttachedOptionParam);
atFileCommentChar, endOfOptionsDelimiter, limitSplit, aritySatisfiedByAttachedOptionParam);
}

void initFrom(ParserSpec settings) {
separator = settings.separator;
stopAtUnmatched = settings.stopAtUnmatched;
stopAtPositional = settings.stopAtPositional;
endOfOptionsDelimiter = settings.endOfOptionsDelimiter;
toggleBooleanFlags = settings.toggleBooleanFlags;
overwrittenOptionsAllowed = settings.overwrittenOptionsAllowed;
unmatchedArgumentsAllowed = settings.unmatchedArgumentsAllowed;
expandAtFiles = settings.expandAtFiles;
atFileCommentChar = settings.atFileCommentChar;
posixClusteredShortOptionsAllowed = settings.posixClusteredShortOptionsAllowed;
unmatchedOptionsArePositionalParams = settings.unmatchedOptionsArePositionalParams;
limitSplit = settings.limitSplit;
Expand Down Expand Up @@ -5637,7 +5666,7 @@ private void processArguments(List<CommandLine> parsedCommands,

// Double-dash separates options from positional arguments.
// If found, then interpret the remaining args as positional parameters.
if ("--".equals(arg)) {
if (commandSpec.parser.endOfOptionsDelimiter().equals(arg)) {
tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n");
endOfOptions = true;
processRemainderAsPositionalParameters(required, initialized, args);
Expand Down
23 changes: 21 additions & 2 deletions src/test/java/picocli/CommandLineTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,25 @@ public void testDoubleDashSeparatesPositionalParameters() {
verifyCompact(compact, false, false, "out", fileArray("-r", "-v", "p1", "p2"));
}

@Test
public void testEndOfOptionsSeparatorConfigurable() {
CompactFields compact = new CompactFields();
CommandLine cmd = new CommandLine(compact);
cmd.setEndOfOptionsDelimiter(";;");
cmd.parse("-oout ;; ;; -- -r -v p1 p2".split(" "));
verifyCompact(compact, false, false, "out", fileArray(";;", "--","-r", "-v", "p1", "p2"));
}

@Test
public void testEndOfOptionsSeparatorCannotBeNull() {
try {
new CommandLine(new CompactFields()).setEndOfOptionsDelimiter(null);
fail("Expected exception");
} catch (Exception ok) {
assertEquals("java.lang.NullPointerException: end-of-options delimiter", ok.toString());
}
}

private static void clearBuiltInTracingCache() throws Exception {
Field field = Class.forName("picocli.CommandLine$BuiltIn").getDeclaredField("traced");
field.setAccessible(true);
Expand Down Expand Up @@ -1050,7 +1069,7 @@ public void testDebugOutputForDoubleDashSeparatesPositionalParameters() throws E
String expected = String.format("" +
"[picocli DEBUG] Creating CommandSpec for object of class picocli.CommandLineTest$CompactFields with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli INFO] Parsing 6 command line args [-oout, --, -r, -v, p1, p2]%n" +
"[picocli DEBUG] Parser configuration: posixClusteredShortOptionsAllowed=true, stopAtPositional=false, stopAtUnmatched=false, separator=null, overwrittenOptionsAllowed=false, unmatchedArgumentsAllowed=false, expandAtFiles=true, limitSplit=false, aritySatisfiedByAttachedOptionParam=false%n" +
"[picocli DEBUG] Parser configuration: posixClusteredShortOptionsAllowed=true, stopAtPositional=false, stopAtUnmatched=false, separator=null, overwrittenOptionsAllowed=false, unmatchedArgumentsAllowed=false, expandAtFiles=true, atFileCommentChar=#, endOfOptionsDelimiter=--, limitSplit=false, aritySatisfiedByAttachedOptionParam=false%n" +
"[picocli DEBUG] Set initial value for field boolean picocli.CommandLineTest$CompactFields.verbose of type boolean to false.%n" +
"[picocli DEBUG] Set initial value for field boolean picocli.CommandLineTest$CompactFields.recursive of type boolean to false.%n" +
"[picocli DEBUG] Set initial value for field java.io.File picocli.CommandLineTest$CompactFields.outputFile of type class java.io.File to null.%n" +
Expand Down Expand Up @@ -2034,7 +2053,7 @@ public void testTracingDebugWithSubCommands() throws Exception {
"[picocli DEBUG] Creating CommandSpec for object of class picocli.Demo$GitRebase with factory picocli.CommandLine$DefaultFactory%n" +
"[picocli DEBUG] Creating CommandSpec for object of class picocli.Demo$GitTag with factory picocli.CommandLine$DefaultFactory%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: posixClusteredShortOptionsAllowed=true, stopAtPositional=false, stopAtUnmatched=false, separator=null, overwrittenOptionsAllowed=false, unmatchedArgumentsAllowed=false, expandAtFiles=true, limitSplit=false, aritySatisfiedByAttachedOptionParam=false%n" +
"[picocli DEBUG] Parser configuration: posixClusteredShortOptionsAllowed=true, stopAtPositional=false, stopAtUnmatched=false, separator=null, overwrittenOptionsAllowed=false, unmatchedArgumentsAllowed=false, expandAtFiles=true, atFileCommentChar=#, endOfOptionsDelimiter=--, limitSplit=false, aritySatisfiedByAttachedOptionParam=false%n" +
"[picocli DEBUG] Set initial value for field java.io.File picocli.Demo$Git.gitDir of type class java.io.File to null.%n" +
"[picocli DEBUG] Set initial value for field boolean picocli.CommandLine$AutoHelpMixin.helpRequested of type boolean to false.%n" +
"[picocli DEBUG] Set initial value for field boolean picocli.CommandLine$AutoHelpMixin.versionRequested of type boolean to false.%n" +
Expand Down

0 comments on commit ecc2e41

Please sign in to comment.