The picocli community is pleased to announce picocli 4.0.0-alpha-1.
This release adds support for argument groups (incubating). Argument groups enable the following:
See the New and Noteworthy section below for more details.
Please try this and provide feedback. We can still make changes.
What do you think of the annotations API? What about the programmatic API? Does it work as expected? Are the input validation error messages correct and clear? Is the documentation clear and complete? Anything you want to change or improve? Any other feedback?
Many thanks to the picocli community members who contributed!
This is the fifty-first public release.
Picocli follows semantic versioning.
This release introduces a new @ArgGroup
annotation and its ArgGroupSpec
programmatic equivalent.
Argument Groups can be used to define:
To create a group using the annotations API, annotate a field or method with @ArgGroup
.
The field's type refers to the class containing the options and positional parameters in the group.
(For annotated interface methods this would be the return type, for annotated setter methods in a concrete class this would be the setter's parameter type.)
Picocli will instantiate this class as necessary to capture command line argument values in the @Option
and @Parameters
-annotated fields and methods of this class.
Annotate a field or method with @ArgGroup(exclusive = true)
to create a group of mutually exclusive options and positional parameters. For example:
@Command(name = "exclusivedemo") public class MutuallyExclusiveOptionsDemo { @ArgGroup(exclusive = true, multiplicity = "1") Exclusive exclusive; static class Exclusive { @Option(names = "-a", required = true) int a; @Option(names = "-b", required = true) int b; @Option(names = "-c", required = true) int c; } }
The above example defines a command with mutually exclusive options -a
, -b
and -c
.
The group itself has a multiplicity
attribute that defines how many times the group may be specified within the command.
The default is multiplicity = "0..1"
, meaning that by default a group may be omitted or specified once.
In this example the group has multiplicity = "1"
, so the group must occur once: one of the exclusive options must occur on the command line.
The synopsis of this command is exclusivedemo (-a=<a> | -b=<b> | -c=<c>)
.
Note that the options are defined as required = true
; this means required within the group, not required within the command.
Picocli will validate the arguments and throw a MutuallyExclusiveArgsException
if multiple mutually exclusive arguments were specified. For example:
MutuallyExclusiveOptionsDemo example = new MutuallyExclusiveOptionsDemo(); CommandLine cmd = new CommandLine(example); try { cmd.parseArgs("-a=1", "-b=2"); } catch (MutuallyExclusiveArgsException ex) { assert "Error: -a=<a>, -b=<b> are mutually exclusive (specify only one)" .equals(ex.getMessage()); }
For the above group, only one of the options can be specified. Any other combination of options, or the absence of options, is invalid.
Annotate a field or method with @ArgGroup(exclusive = false)
to create a group of dependent options and positional parameters that must co-occur. For example:
@Command(name = "co-occur") public class DependentOptionsDemo { @ArgGroup(exclusive = false) Dependent dependent; static class Dependent { @Option(names = "-a", required = true) int a; @Option(names = "-b", required = true) int b; @Option(names = "-c", required = true) int c; } }
The above example defines a command with dependent options -a
, -b
and -c
that must co-occur.
The group itself has a multiplicity
attribute that defines how many times the group may be specified within the command.
In this example the group uses the default multiplicity, multiplicity = "0..1"
, meaning that the group may be omitted or specified once.
The synopsis of this command is co-occur [-a=<a> -b=<b> -c=<c>]
.
Note that the options are defined as required = true
; this means required within the group, not required within the command.
Picocli will validate the arguments and throw a MissingParameterException
if not all dependent arguments were specified. For example:
DependentOptionsDemo example = new DependentOptionsDemo(); CommandLine cmd = new CommandLine(example); try { cmd.parseArgs("-a=1", "-b=2"); } catch (MissingParameterException ex) { assert "Error: Missing required argument(s): -c=<c>".equals(ex.getMessage()); }
The example below uses groups to define options sections in the usage help.
When a group has a non-null heading
(or headingKey
), the options in the group are given the specified heading in the usage help message.
The headingKey
attribute can be used to get the heading text from the command's resource bundle.
This works for mutually exclusive or co-occurring groups, but it is also possible to define a group that does no validation but only creates an option section in the usage help.
Annotate a field or method with @ArgGroup(validate = false)
to create a group for display purposes only. For example:
@Command(name = "sectiondemo", description = "Section demo") public class OptionSectionDemo { @ArgGroup(validate = false, heading = "This is the first section%n") Section1 section1; static class Section1 { @Option(names = "-a", description = "Option A") int a; @Option(names = "-b", description = "Option B") int b; @Option(names = "-c", description = "Option C") int c; } @ArgGroup(validate = false, heading = "This is the second section%n") Section2 section2; static class Section2 { @Option(names = "-x", description = "Option X") int x; @Option(names = "-y", description = "Option Y") int y; @Option(names = "-z", description = "Option X") int z; } public static void main(String[] args) { new CommandLine(new OptionSectionDemo()).usage(System.out); } }
This prints the following usage help message:
Usage: sectiondemo [-a=<a>] [-b=<b>] [-c=<c>] [-x=<x>] [-y=<y>] [-z=<z>]
Section demo
This is the first section
-a=<a> Option A
-b=<b> Option B
-c=<c> Option C
This is the second section
-x=<x> Option X
-y=<y> Option Y
-z=<z> Option X
Note that the heading text must end with %n
to insert a newline between the heading text and the first option.
This is for consistency with other headings in the usage help, like @Command(headerHeading = "Usage:%n", optionListHeading = "%nOptions:%n")
.
The below example shows how groups can be composed of other groups, and how arrays and collections can be used to capture repeating groups (with a multiplicity
greater than one):
public class CompositeGroupDemo { @ArgGroup(exclusive = false, multiplicity = "1..*") List<Composite> composites; static class Composite { @ArgGroup(exclusive = false, multiplicity = "1") Dependent dependent; @ArgGroup(exclusive = true, multiplicity = "1") Exclusive exclusive; } static class Dependent { @Option(names = "-a", required = true) int a; @Option(names = "-b", required = true) int b; @Option(names = "-c", required = true) int c; } static class Exclusive { @Option(names = "-x", required = true) boolean x; @Option(names = "-y", required = true) boolean y; @Option(names = "-z", required = true) boolean z; } }
In the above example, the annotated composites
field defines a composite group that must be specified at least once, and may be specified many times, on the command line.
Each time the group is matched, picocli creates an instance of the Composite
class and adds it to the composites
list.
The Composite
class itself contains two groups: a group of dependent options that must co-occur, and another group of mutually exclusive options.
Both of these subgroups have multiplicity = "1"
, so they can occur exactly once for each multiple of the Composite
group. The below example illustrates:
CompositeGroupDemo example = new CompositeGroupDemo(); CommandLine cmd = new CommandLine(example); cmd.parseArgs("-x", "-a=1", "-b=1", "-c=1", "-a=2", "-b=2", "-c=2", "-y"); assert example.composites.size() == 2; Composite c1 = example.composites.get(0); assert c1.exclusive.x; assert c1.dependent.a == 1; assert c1.dependent.b == 1; assert c1.dependent.c == 1; Composite c2 = example.composites.get(1); assert c2.exclusive.y; assert c2.dependent.a == 2; assert c2.dependent.b == 2; assert c2.dependent.c == 2;
When a @Parameters
positional parameter is part of a group, its index
is the index within the group, not within the command.
%
to %%
when using ${DEFAULT-VALUE}
in option description. Thanks to Steffen Rehbergfor the pull request.No features were deprecated in this release.
No breaking changes in this release.