This is a follow-on to #649. I don't expect that you will add this addition any time soon, and that's fine—I'm finishing up a workaround right now (described at the end). I'm just filing this feature request for completeness and for visibility. The description below is LLM-generated.
Use Case
Picocli's ScopeType.INHERIT is useful for options declared on a top-level command that should normally be accepted by all descendants. Sometimes, however, a subtree should not accept one of those inherited options at all.
Example:
@Command(name = "dbctl", subcommands = {BackupCommand.class, ServerCommand.class})
class DbCtl implements Runnable {
@Option(names = "--database", scope = ScopeType.INHERIT)
String database;
// ...
}
@Command(name = "server", subcommands = {StartCommand.class, StopCommand.class})
class ServerCommand implements Runnable {
// `--database` is inherited syntactically, but it is not meaningful for
// `server` commands because they manage the server process, not a database.
}
Because --database is inherited, all of these are syntactically valid today:
dbctl --database main server start
dbctl server --database main start
dbctl server start --database main
For this use case, the desired behavior is for the entire server subtree to reject --database, regardless of which command level the user typed the inherited option at.
Hypothetical API
A command-level annotation attribute seems like a natural fit, because the semantic rule belongs to the subtree where the inherited option is not applicable:
@Command(
name = "server",
rejectInheritedOptions = "--database",
subcommands = {StartCommand.class, StopCommand.class}
)
class ServerCommand implements Runnable {
// ...
}
Equivalent programmatic API might look like:
serverSpec.rejectInheritedOptions("--database");
The exact API names are only illustrative. Other names such as forbiddenInheritedOptions, excludedInheritedOptions, or a richer annotation would also make sense.
Expected Semantics
If a command rejects inherited option --database, that rejection applies to the command and its descendants.
For the example above, all of these invocations would fail with a ParameterException:
dbctl --database main server start
dbctl server --database main start
dbctl server start --database main
The rejection should be based on the canonical inherited option, not merely string comparison, so aliases and inherited copies are handled consistently.
Possible Implementation Sketch
Picocli already copies ScopeType.INHERIT options into descendant CommandSpecs as inherited OptionSpecs. These inherited copies retain canonical identity via ArgSpec.root().
A native implementation could likely be modeled as command-level metadata on CommandSpec, storing the inherited option names or canonical option roots rejected by that command.
One important detail is that validation probably needs to happen after the full command line has been parsed, not only when an option token is first matched. For example:
dbctl --database main server start
When --database is matched, picocli is still parsing the root dbctl command and has not yet reached the later server start subtree. A post-parse validation pass can see both facts:
- the invoked command path contains
server;
- the inherited
--database option was explicitly matched somewhere in the invocation.
At a high level, such a validation pass could:
- Walk the
ParseResult tree or active command path.
- Collect matched options from each parse level.
- For each command in the invoked path with rejected inherited options:
- resolve the rejected option name against that command's
CommandSpec;
- verify that the resolved option is inherited;
- canonicalize via
ArgSpec.root();
- compare against the canonical root of each matched option.
- Throw
ParameterException if a rejected inherited option was matched anywhere in the invocation.
There are several design details for maintainers to decide: alias handling, negatable options, repeatable subcommands, usage help display, localization of the error message, mixins, method commands, and the exact programmatic API.
Current Workaround
In globalmentor-application we are planning a workaround in BaseCliApplication / BaseCliSubcommand. The workaround has not been committed yet, but the planned approach is to provide runtime validation helpers such as:
rejectIfMatched("--database", "Option `--database` is not applicable to `server` commands.");
These helpers walk the actively invoked command path after parsing and compare matched options by canonical ArgSpec.root() identity, so they can detect inherited options regardless of whether the user typed the option at the root, parent, or leaf command level.
This workaround is useful for application-level validation, including value-dependent checks such as rejecting --color when a positional parameter has a certain value. However, for the inherited-option subtree case, a native picocli feature would be better because it could be declarative, parser-enforced, and subtree-wide without requiring every executable leaf command to remember to call a validation helper.
This is a follow-on to #649. I don't expect that you will add this addition any time soon, and that's fine—I'm finishing up a workaround right now (described at the end). I'm just filing this feature request for completeness and for visibility. The description below is LLM-generated.
Use Case
Picocli's
ScopeType.INHERITis useful for options declared on a top-level command that should normally be accepted by all descendants. Sometimes, however, a subtree should not accept one of those inherited options at all.Example:
Because
--databaseis inherited, all of these are syntactically valid today:For this use case, the desired behavior is for the entire
serversubtree to reject--database, regardless of which command level the user typed the inherited option at.Hypothetical API
A command-level annotation attribute seems like a natural fit, because the semantic rule belongs to the subtree where the inherited option is not applicable:
Equivalent programmatic API might look like:
The exact API names are only illustrative. Other names such as
forbiddenInheritedOptions,excludedInheritedOptions, or a richer annotation would also make sense.Expected Semantics
If a command rejects inherited option
--database, that rejection applies to the command and its descendants.For the example above, all of these invocations would fail with a
ParameterException:The rejection should be based on the canonical inherited option, not merely string comparison, so aliases and inherited copies are handled consistently.
Possible Implementation Sketch
Picocli already copies
ScopeType.INHERIToptions into descendantCommandSpecs as inheritedOptionSpecs. These inherited copies retain canonical identity viaArgSpec.root().A native implementation could likely be modeled as command-level metadata on
CommandSpec, storing the inherited option names or canonical option roots rejected by that command.One important detail is that validation probably needs to happen after the full command line has been parsed, not only when an option token is first matched. For example:
When
--databaseis matched, picocli is still parsing the rootdbctlcommand and has not yet reached the laterserver startsubtree. A post-parse validation pass can see both facts:server;--databaseoption was explicitly matched somewhere in the invocation.At a high level, such a validation pass could:
ParseResulttree or active command path.CommandSpec;ArgSpec.root();ParameterExceptionif a rejected inherited option was matched anywhere in the invocation.There are several design details for maintainers to decide: alias handling, negatable options, repeatable subcommands, usage help display, localization of the error message, mixins, method commands, and the exact programmatic API.
Current Workaround
In
globalmentor-applicationwe are planning a workaround inBaseCliApplication/BaseCliSubcommand. The workaround has not been committed yet, but the planned approach is to provide runtime validation helpers such as:These helpers walk the actively invoked command path after parsing and compare matched options by canonical
ArgSpec.root()identity, so they can detect inherited options regardless of whether the user typed the option at the root, parent, or leaf command level.This workaround is useful for application-level validation, including value-dependent checks such as rejecting
--colorwhen a positional parameter has a certain value. However, for the inherited-option subtree case, a native picocli feature would be better because it could be declarative, parser-enforced, and subtree-wide without requiring every executable leaf command to remember to call a validation helper.