-
Notifications
You must be signed in to change notification settings - Fork 424
Programmatic API
(This page is work in progress)
Tip
|
For most applications the annotations API is a better fit than the programmatic API: the annotation syntax is more compact, easier to read, and easier to maintain. See this introductory article and for more details, the user manual. |
Picocli 3.0 offers a programmatic API for creating command line applications, in addition to annotations. A programmatic API allows applications to dynamically create command line options on the fly, and also makes it possible to create idiomatic domain-specific languages for processing command line arguments, using picocli, in other JVM languages.
CommandSpec spec = CommandSpec.create();
spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options
spec.add(OptionSpec.builder("-c", "--count")
.paramLabel("COUNT")
.type(int.class)
.description("number of times to execute").build());
spec.add(PositionalParamSpec.builder()
.paramLabel("FILES")
.type(List.class)
.auxiliaryTypes(File.class) // List<File>
.description("The files to process").build());
CommandLine commandLine = new CommandLine(spec);
commandLine.parseWithSimpleHandlers(new AbstractSimpleParseResultHandler() {
public void process(ParseResult pr) {
int count = pr.typedOptionValue('c', 1);
List<File> files = pr.typedPositionalValue(0, Collections.<File>emptyList());
for (int i = 0; i < count; i++) {
for (File f : files) {
System.out.printf("%d: %s%n", i, f);
}
}
}
}, args);
The following classes are the main classes in the object model:
-
CommandSpec
-
OptionSpec
-
PositionalParamSpec
-
ParseResult
CommandSpec
models a command. It has a name and a version, both of which may be empty. It also has a UsageMessageSpec
to configure aspects of the usage help message, and a ParserSpec
where the behaviour of the parser can be controlled to some extent.
CommandSpec
has methods to add options (OptionSpec
objects) and positional parameters (PositionalParamSpec
objects). A CommandSpec
can be mixed in with another CommandSpec
, so its options, positional parameters and usage help attributes are merged into the other CommandSpec
.
Finally, CommandSpec
objects can be subcommands of other CommandSpecs
. There is no limit to the depth of a hierarchy of command and subcommands. CommandSpec
also allows registration of type converters that are used while parsing the command line arguments to convert a command line argument string to the strongly typed value of a OptionSpec
or PositionalParamSpec
OptionSpec
models a command option. An OptionSpec
must have at least one name, which is used during parsing to match command line arguments. Other attributes can be left empty and picocli will give them a reasonable default value. This defaulting is why OptionSpec
objects are created with a builder: this allows you to specify only some attributes and let picocli initialise the other attributes. For example, if only the option’s name is specified, picocli assumes the option takes no parameters (arity = 0), and is of type boolean
. Another example, if arity is larger than 1
, picocli sets the type to List
and the auxiliary type
to String
.
Once an OptionSpec
is constructed, its configuration becomes immutable, but its value
can still be modified. Usually the value is set during command line parsing when a command line argument matches one of the option names.
The value is set via a binding. We’ll come back to bindings later in this document.
Similar to the annotation API, OptionSpec
objects have help
, usageHelp
and versionHelp
attributes. When the parser matches an option that was marked with any of these attributes, it will no longer validate that all required arguments exist. See the section below on the parseWithHandler(s)
and parseWithSimpleHandler(s)
methods that automatically print help when requested.
PositionalParamSpec
objects don’t have names, but have an index instead. A single PositionalParamSpec
object can capture multiple positional parameters, and the default index range is set to 0..*
(all indices). It is possible to specify multiple PositionalParamSpec
objects that capture positional parameters at different index ranges. This can be useful if positional parameters at different index ranges have different data types.
Similar to OptionSpec
objects, Once a PositionalParamSpec
is constructed, its configuration becomes immutable, but its value
can still be modified. Usually the value is set during command line parsing when a non-option command line argument is encountered at a position in its index range.
The value is set via a binding. We’ll look at bindings next.
The ParseResult
class…
The most basic way to parse the command line is to call the CommandLine::parseArgs
method and inspect the resulting ParseResult
object.
For example:
CommandSpec spec = CommandSpec.create();
// add options and positional parameters
CommandLine commandLine = new CommandLine(spec);
try {
ParseResult pr = commandLine.parseArgs(args);
if (CommandLine.printHelpIfRequested(pr)) {
return;
}
int count = pr.typedOptionValue('c', 1);
List<File> files = pr.typedPositionalValue(0, Collections.<File>emptyList());
for (int i = 0; i < count; i++) {
for (File f : files) {
System.out.printf("%d: %s%n", i, f);
}
}
} catch (ParseException invalidInput) {
System.err.println(invalidInput.getMessage());
invalidInput.getCommandLine().usage(System.err);
}
There are a number of parseWithHandler
convenience methods to reduce some boilerplate when processing the ParseResult
programmatically. The convenience methods take care of printing help when requested by the user, and exception handling of invalid input.
Call the parseWithSimpleHandler
method with a AbstractSimpleParseResultHandler
subclass to process the parse result without returning a result value. Note the absence of error handling and checking of whether the user requested help. The process
method contains only your business logic.
Example:
CommandSpec spec = CommandSpec.create();
// add options and positional parameters
CommandLine commandLine = new CommandLine(spec);
commandLine.parseWithSimpleHandler(new AbstractSimpleParseResultHandler() {
public void process(ParseResult pr) {
int count = pr.typedOptionValue('c', 1);
List<File> files = pr.typedPositionalValue(0, Collections.<File>emptyList());
for (int i = 0; i < count; i++) {
for (File f : files) {
System.out.printf("%d: %s%n", i, f);
}
}
}
}, args);
A variation of this method, parseWithSimpleHandlers
, additionally takes an IExceptionHandler2<Void>
to customize how invalid input should be handled and optionally set an exit code for when the input was invalid.
Example:
CommandSpec spec = CommandSpec.create();
// add options and positional parameters
CommandLine commandLine = new CommandLine(spec);
commandLine.parseWithSimpleHandlers(new AbstractSimpleParseResultHandler() {
public void process(ParseResult pr) {...}
}.useOut(System.out).andExit(123),
new DefaultExceptionHandler<Void>().andExit(567),
args);
It is possible for the parse result processing logic to return a result. To do this, call the CommandLine::parseWithHandler
method with a class that extends AbstractParseResultHandler
and a prototype return value.
Example:
CommandSpec spec = CommandSpec.create();
// add options and positional parameters
CommandLine commandLine = new CommandLine(spec);
class MyResult {
List<File> files = new ArrayList<File>();
}
class MyHandler extends AbstractParseResultHandler<MyResult> {
public MyResult process(ParseResult pr, MyResult returnValue) {
int count = pr.typedOptionValue('c', 1);
List<File> files = pr.typedPositionalValue(pr.positionalParams().get(0), Collections.<File>emptyList());
for (File f : files) {
for (int i = 0; i < count; i++) {
returnValue.files.add(f);
}
}
return returnValue;
}
protected MyHandler self() { return this; }
}
MyResult result = commandLine.parseWithHandler(new MyHandler(), new MyResult(), args);
// do something with result...
This method also has a variation, parseWithHandlers
, which additionally takes an IExceptionHandler2<MyResult>
to customize how invalid input should be handled and optionally set an exit code.
Example:
CommandSpec spec = CommandSpec.create();
// add options and positional parameters
CommandLine commandLine = new CommandLine(spec);
MyResult result = commandLine.parseWithHandler(
new MyHandler().useOut(System.out).andExit(123),
new MyResult(),
new DefaultExceptionHandler<MyResult>().andExit(567),
args);
// do something with result...