Mixing multiplicity or required + non-required options in an exclusive ArgGroup gives unexpected results
See original GitHub issueRelated to #870
Tested with latest master. Using multiplicity=“0…*” and exclusive = true
with an ArgGroup, or exclusive = true
with an ArgGroup that has both optional and required parameters gives unexpected results.
class Group {
@Option(names = "--name", required = true) String name;
@Option(names = "--id", required = true) String id;
@Option(names = "--opt") String opt;
}
@ArgGroup(exclusive = true, multiplicity = "0..*") List<Group> groups;
-
cli --opt=1
results in one group:{name=null, opt=1}
. Even though no required options are provided, validation passes. -
cli --name=foo --opt=1
givesError: --name=<name>, --opt=<opt> are mutually exclusive (specify only one)
. Even though --opt is optional, it’s still exclusive with --name. -
cli --opt=1 --name=foo
works and results in[{name=null, opt=1},{name=foo, opt=null}]
. Whoa, switching the arg order makes it work? It still seems to be exclusive, but for some reason now it knows to create a second group. -
cli --name=foo --id=bar
givesError: --name=<name>, --id=<id> are mutually exclusive (specify only one)
. This seems like a bug. Those should be two valid groups, right?{name=foo, id=null}
and{name=null, id=bar}
? Why doesn’t it create a second group now?
I’d think, either (1) only the required arguments are exclusive, and it’s still an error to specify an ArgGroup option without exactly one of the required args, as in #870, or maybe (2) picocli should just immediately error out if using non-required options w/ exclusive = True. I’m leaning heavily toward (1).
In general, I think the logic should be:
(1) Attempt to fit an arg into the previous group, unless it makes the group invalid. *
(2) If a new arg cannot be fit into the previous group, start a new group with it, and validate the (now completed) previous group.
(3) Now that a new group is started, check if the new number of groups > max multiplicity.
(4) exclusive
simply changes the validation of required options in a group. exclusive=true
means exactly 1 of the required options per group, and exclusive=false
means all of the required options per group. Not specifying any required options should always be invalid.
Max multiplicity=1 can be a special case, where we know not to start a new group so the error message is clearer: “–foo, --bar are mutually exclusive”
* Consider making it so that only required args can start a new group. Optional args would always be pushed into the previous group, triggering a validation error if, for example, providing too many of that optional arg, and it would be illegal to start with an optional arg. This makes it always obvious which group an optional arg belongs to:
cli --req --opt --opt --req --req --opt --req
-> always {req, opt, opt}, {req}, {req, opt}, {req}
. Currently, it’s {req, opt}, {opt, req}, {req, opt}, {req}
if opt is singular, but {req, opt, opt}, {req}, {req, opt}, {req}
if opt is repeatable.
Issue Analytics
- State:
- Created 4 years ago
- Comments:6 (3 by maintainers)
@hanslovsky I suspect that Paintera is a good use case for repeating subcommands. #454 is on the todo list for picocli 4.2.
@wjohnson5 sorry for my late reply, I had other commitments that took my time. Looking at it again now, I have trouble wrapping my head around it all…
In the context of mutually dependent groups (where all options must co-occur), the notion of optional and required options makes sense. It is useful to be able to define a group as
(-a -b [-c])...
so that both-a -b -c
and-a -b
are valid on the command line, but not-a -c
for example.However, what does it mean if an option is not required in an exclusive group? Using the corresponding example:
(-a | -b | [-c])...
, what behaviour are we trying to achieve by defining the group this way?I think the simplest thing to do, both from an implementation/maintenance perspective, but also from a usability perspective, is to make all options required in exclusive groups. To ease migration, we can silently make the default to be
required=true
in exclusive groups, and throw anInitializationException
for options that are explicitly defined asrequired=false
in exclusive groups. (The annotation processor can make this a compilation error.)The example you mention in example 4 looks like a bug. If the max multiplicity is greater than 1, a new group should be created for each option in the exclusive group.
No problem at all, please take your time! Again I really appreciate your work here 😃