Skip to content

Commit

Permalink
[#639] Add parser API unmatchedOptionsAllowedAsOptionParameters to …
Browse files Browse the repository at this point in the history
…disallow option parameters resembling options
  • Loading branch information
remkop committed Jun 28, 2020
1 parent eecc05c commit d1b2176
Show file tree
Hide file tree
Showing 6 changed files with 502 additions and 132 deletions.
87 changes: 74 additions & 13 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,44 +85,96 @@ When abbreviated options are enabled, user input `-AB` will match the long `-Aaa

#### Option Names as Option Values

Parser fixes and improvements: the parser will no longer assign values that match an option name to options that take a parameter, unless the value is in quotes. For example:
Options that take a parameter previously were able to take option names as the parameter value.
From this release, this is no longer possible.
The parser will no longer assign values that match an option name to an option, unless the value is in quotes. For example:

```java
class App {
@Option(names = "-x") String x;
@Option(names = "-y") String y;

public static void main(String... args) {
App app = new App();
new CommandLine(app).setTrimQuotes(true).parseArgs(args);
System.out.printf("x='%s', y='%s'%n", app.x, app.y);
}
}
```

In previous versions of picocli, the above command would accept input `-x -y`, and the value `-y` would be assigned to the `x` String field. From this release, the above input will be rejected with an error message indicating that the `-x` option requires a parameter.

If it is necessary to accept values that match option names, these values need to be quoted. For example:

```java
class App {
@Option(names = "-x") String x;
@Option(names = "-y") String y;
```
java App -x="-y"
```

This will print the following output:

public static void main(String... args) {
App app = new App();
new CommandLine(app).setTrimQuotes(true).parseArgs("-x=\"-y\"");
assertEquals("-y", app.x);
}
}
```
x='-y', y='null'
```

#### Vararg Positional Parameters No Longer Consume Unmatched Options

Vararg positional arguments no longer consume unmatched options unless `unmatchedOptionsArePositionalParams` is set to `true`. For example:
Vararg positional arguments no longer consume unmatched options unless configured to do so. For example:

```java
class App {
@Parameters(arity = "*") String[] positionals;
}
```

In previous versions of picocli, the above command would reject input `-z 123` with error `"Unknown option: '-z'`, but input `123 -z` would be accepted and the `positionals` String array would contain two values, `123` and `-z`.
In previous versions of picocli, the parser behaviour was not consistent:

* input `-z 123` would be rejected with error `"Unmatched argument: '-z'`
* input `123 -z` would be accepted and the `positionals` String array would contain two values, `123` and `-z`

(Note that this problem only occurred with multi-value positional parameters defined with variable arity: `arity = "*"`.)

From this release, both of the above input sequences will be rejected with an error message indicating that `-z` is an unknown option.
As before, to accept such values as positional parameters, call `CommandLine::setUnmatchedOptionsArePositionalParams` with `true`.

#### Configure Whether Options Should Consume Unknown Options

By default, options accept parameter values that "resemble" (but don't exactly match) an option.

This release introduces a `CommandLine::setUnmatchedOptionsAllowedAsOptionParameters` method that makes it possible to configure the parser to reject values that resemble options as option parameters.
Setting it to `false` will result in values resembling option names being rejected as option values.

For example:

```java
class App {
@Option(names = "-x") String x;
}
```

By default, a value like `-z`, which resembles an option, is accepted as the parameter for `-x`:

```java
App app = new App();
new CommandLine(app).parseArgs("-x", "-z");
assertEquals("-z", app.x);
```

After setting the `unmatchedOptionsAllowedAsOptionParameters` parser option to `false`, values resembling an option are rejected as parameter for `-x`:

```java
new CommandLine(new App())
.setUnmatchedOptionsAllowedAsOptionParameters(false)
.parseArgs("-x", "-z");
```

This will throw an `UnmatchedArgumentException` with message:

```
"Unknown option '-z'; Expected parameter for option '-x' but found '-z'"
```

NOTE: Negative numbers are not considered to be unknown options, so even when `unmatchedOptionsAllowedAsOptionParameters` is set to `false`, option parameters like `-123`, `-NaN`, `-Infinity`, `-#ABC` and `-0xCAFEBABE` will not be rejected for resembling but not matching an option name.



### <a name="4.4.0-gen-manpage-subcommand"></a> ManPageGenerator as Subcommand in Your App
Expand All @@ -140,6 +192,7 @@ To use the `ManPageGenerator` tool as a subcommand, you will need the `picocli-c

## <a name="4.4.0-fixes"></a> Fixed issues
* [#10][#732][#1047] API: Support abbreviated options and commands. Thanks to [NewbieOrange](https://github.com/NewbieOrange) for the pull request.
* [#639] API: Add method `CommandLine::is/setUnmatchedOptionsAllowedAsOptionParameters` to disallow option parameter values resembling option names. Thanks to [Peter Murray-Rust ](https://github.com/petermr) for raising this.
* [#1074][#1075] API: Added method `ParseResult::expandedArgs` to return the list of arguments after `@-file` expansion. Thanks to [Kevin Bedi](https://github.com/mashlol) for the pull request.
* [#1052] API: Show/Hide commands in usage help on specific conditions. Thanks to [Philippe Charles](https://github.com/charphi) for raising this.
* [#1088] API: Add method `Help::allSubcommands` to return all subcommands, including hidden ones. Clarify the semantics of `Help::subcommands`.
Expand Down Expand Up @@ -186,10 +239,18 @@ To use the `ManPageGenerator` tool as a subcommand, you will need the `picocli-c
No features were deprecated in this release.

## <a name="4.4.0-breaking-changes"></a> Potential breaking changes

### Parser Changes
The parser behaviour has changed: picocli will no longer assign values that match an option name to options that take a parameter, unless the value is in quotes.
Applications that rely on this behaviour need to use quoted values.

### Error Message for Unknown Options
Unmatched arguments that look like options now result in an error message `Unknown option: '-unknown'`.

Previously, the error message was: `Unmatched argument: '-unknown'`.


### Usage Help: Synopsis for Arg Groups
This release changes the synopsis for commands with argument groups:
the synopsis now shows the non-group options before argument groups, where previously argument groups were shown first.

Expand Down
140 changes: 92 additions & 48 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2406,9 +2406,16 @@ to force the parser to treat all values following the first positional parameter

When this flag is set, the first positional parameter effectively serves as an "<<Double dash (`--`),end of options>>" marker.

=== Stop At Unmatched
From picocli 2.3, applications can call `CommandLine::setStopAtUnmatched` with `true` to force the parser to stop interpreting
options and positional parameters as soon as it encounters an unmatched argument.

When this flag is set, the first unmatched argument and all subsequent command line arguments are added to the
unmatched arguments list returned by `CommandLine::getUnmatchedArguments`.

=== Unmatched Input
By default, an `UnmatchedArgumentException` is thrown when a command line argument cannot be assigned to
an option or positional parameter. For example:
By default, an `UnmatchedArgumentException` is thrown when a command line argument cannot be assigned to an option or positional parameter.
For example:

[source,java]
----
Expand Down Expand Up @@ -2436,86 +2443,123 @@ If picocli finds a field annotated with `@Unmatched`, it automatically sets `unm
so no `UnmatchedArgumentException` is thrown when a command line argument cannot be assigned to an option or positional parameter.
If no unmatched arguments are found, the value of the field annotated with `@Unmatched` is unchanged.

=== Option Names as Option Values
Since picocli 4.4, the parser will no longer assign values that match an option name to options that take a parameter, unless the value is in quotes.
For example:
=== Unknown Options

==== Unknown Options Definition
A special case of unmatched input are arguments that resemble options but don't match any of the defined options.
Picocli determines if a value "resembles an option" by comparing its leading characters to the prefix characters of the known options.

NOTE: Negative numbers are not considered to be unknown options, so values like `-123`, `-NaN`, `-Infinity`, `-#ABC` and `-0xCAFEBABE` will not be treated specially for resembling an option name.

For example, the value `-z` is considered an unknown option when we have a command that only defines options `-x` and `-y`:

[source,java]
----
class App {
@Option(names = "-x") String x;
@Option(names = "-y") String y;
}
@Option(names = "-x") String x;
@Option(names = "-y") String y;
@Parameters String[] remainder;
----
The above defines options `-x` and `-y`, but no option `-z`. So what should the parser do when the user gives input like this?

----
<command> -z -x XXX
----

In previous versions of picocli, the above command would accept input `-x -y`, and the value `-y` would be assigned to the `x` String field.
From this release, the above input will be rejected with an error message indicating that the `-x` option requires a parameter.

If it is necessary to accept values that match option names, these values need to be quoted.
==== Positional Parameters Resembling Options

One possibility is to silently accept such values as positional parameters, but this is often not desirable.

By default, when the value resembles an option, picocli throws an `UnmatchedArgumentException` rather than treating it as a positional parameter.

Picocli 3.0 introduced the `CommandLine::setUnmatchedOptionsArePositionalParams` method that can be used to force the parser to treat arguments resembling an option as positional parameters.
For example:

----
<command> -z -x XXX
----
When `unmatchedOptionsArePositionalParams` is set to `true`, the unknown option `-z` is treated as a positional parameter.
The next argument `-x` is recognized and processed as a known option like you would expect.

NOTE: An alternative is to call `CommandLine::setUnmatchedArgumentsAllowed` with `true`, this will accept and store such values separately as described in <<Unmatched Input>>.

==== Option Parameters Resembling Options

By default, options accept parameter values that "resemble" (but don't exactly match) an option.

Picocli 4.4 introduced a `CommandLine::setUnmatchedOptionsAllowedAsOptionParameters` method that makes it possible to configure the parser to reject values that resemble options as option parameters.
Setting this to `false` will result in values resembling option names being rejected as option values.

For example:

[source,java]
----
class App {
class MyApp {
@Option(names = "-x") String x;
@Option(names = "-y") String y;
public static void main(String... args) {
App app = new App();
new CommandLine(app).setTrimQuotes(true).parseArgs("-x=\"-y\"");
assertEquals("-y", app.x);
}
}
----

=== Unknown Options
A special case of unmatched input are arguments that resemble options but don't match any of the defined options.
For example:
By default, a value like `-z`, which resembles an option, is accepted as the parameter for `-x`:

[source,java]
----
@Option(names = "-a") String alpha;
@Option(names = "-b") String beta;
@Parameters String[] remainder;
App app = new App();
new CommandLine(app).parseArgs("-x", "-z");
assertEquals("-z", app.x);
----
The above defines options `-a` and `-b`, but what should the parser do with input like this?

After setting the `unmatchedOptionsAllowedAsOptionParameters` parser option to `false`, values resembling an option are rejected as parameter for `-x`:

[source,java]
----
<command> -x -a AAA
new CommandLine(new App())
.setUnmatchedOptionsAllowedAsOptionParameters(false)
.parseArgs("-x", "-z");
----
The `-x` argument "looks like" an option but there is no `-x` option defined...

One possibility is to silently accept such values as positional parameters but this is often not desirable.
From version 1.0, picocli determines if the unmatched argument "resembles an option"
by comparing its leading characters to the prefix characters of the known options.
This will throw an `UnmatchedArgumentException` with message:

When the unmatched value is similar to the known options, picocli throws an `UnmatchedArgumentException`
rather than treating it as a positional parameter.
----
"Unknown option '-z'; Expected parameter for option '-x' but found '-z'"
----

As usual, calling `CommandLine::setUnmatchedArgumentsAllowed` with `true` will accept unmatched input and display a WARN-level message on the console.

=== Option Names as Option Values
Since picocli 4.4, the parser will no longer assign values that match an option name to options that take a parameter, unless the value is in quotes.
For example:

Arguments that are not considered similar to the known options are interpreted as positional parameters:
[source,java]
----
<command> x -a AAA
class App {
@Option(names = "-x") String x;
@Option(names = "-y") String y;
public static void main(String... args) {
App app = new App();
new CommandLine(app).setTrimQuotes(true).parseArgs(args);
System.out.printf("x='%s', y='%s'%n", app.x, app.y);
}
}
----
The above input is treated by the parser as one positional parameter (`x`) followed by the `-a` option and its value.

Picocli 3.0 introduced the `CommandLine::setUnmatchedOptionsArePositionalParams` method that can be used to
force the parser to treat arguments resembling an option as positional parameters. For example:
In previous versions of picocli, the above command would accept input `-x -y`, and the value `-y` would be assigned to the `x` String field.
From picocli 4.4, the above input will be rejected with an error message indicating that the `-x` option requires a parameter.

If it is necessary to accept values that match option names, such values need to be quoted.
For example:

[source,bash]
----
<command> -x -a AAA
java App -x="-y"
----
When `unmatchedOptionsArePositionalParams` is set to `true`, the unknown option `-x` is treated as a positional parameter.
The next argument `-a` is recognized and processed as a known option like you would expect.

=== Stop At Unmatched
From picocli 2.3, applications can call `CommandLine::setStopAtUnmatched` with `true` to force the parser to stop interpreting
options and positional parameters as soon as it encounters an unmatched argument.
This will print the following output:

[source,bash]
----
x='-y', y='null'
----

When this flag is set, the first unmatched argument and all subsequent command line arguments are added to the
unmatched arguments list returned by `CommandLine::getUnmatchedArguments`.

=== 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.
Expand Down
Loading

0 comments on commit d1b2176

Please sign in to comment.