From 62b437b211a12e939b324666b3351b403463cfe6 Mon Sep 17 00:00:00 2001 From: Remko Popma Date: Sun, 1 Dec 2019 11:11:44 +0900 Subject: [PATCH] [#889][#885] (DOC) Update of Picocli Programmatic API documentation. --- RELEASE-NOTES.md | 4 +- build.gradle | 4 +- docs/picocli-3.0-programmatic-api.adoc | 385 ------ docs/picocli-3.0-programmatic-api.html | 1043 +---------------- ...api.adoc => picocli-programmatic-api.adoc} | 100 +- ...api.html => picocli-programmatic-api.html} | 110 +- 6 files changed, 155 insertions(+), 1491 deletions(-) delete mode 100644 docs/picocli-3.0-programmatic-api.adoc rename docs/{picocli-4.1-programmatic-api.adoc => picocli-programmatic-api.adoc} (80%) rename docs/{picocli-4.1-programmatic-api.html => picocli-programmatic-api.html} (90%) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 74f2785f5..8a27b251a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -23,8 +23,8 @@ Picocli follows [semantic versioning](http://semver.org/). ## Fixed issues [#884] (Bugfix) Built-in `picocli.AutoComplete.GenerateCompletion` (`generate-completion`) subcommand should omit validation of mandatory options in the parent command. Thanks to [Andreas Deininger](https://github.com/deining) for raising this. -[#883] (DOC) Update of Quick Guide . Thanks to [Andreas Deininger](https://github.com/deining) for the pull request. - +[#883] (DOC) Update of Quick Guide. Thanks to [Andreas Deininger](https://github.com/deining) for the pull request. +[#889][#885] (DOC) Update of Picocli Programmatic API documentation. Thanks to [Andreas Deininger](https://github.com/deining) for the pull request. ## Deprecations No features were deprecated in this release. diff --git a/build.gradle b/build.gradle index 054cf1892..a22d1b736 100644 --- a/build.gradle +++ b/build.gradle @@ -288,7 +288,7 @@ task bumpVersion { fileset(dir: 'docs', includes: 'index.adoc') fileset(dir: 'docs', includes: 'quick-guide.adoc') fileset(dir: 'docs', includes: 'autocomplete.adoc') - fileset(dir: 'docs', includes: 'picocli-3.0-programmatic-api.adoc') + fileset(dir: 'docs', includes: 'picocli-programmatic-api.adoc') } // Annotation Processor section ant.replaceregexp(match: ":picocli-codegen:$projectPreviousVersionRegex", replace: ":picocli-codegen:$version", flags: 'g', byline: true, encoding: 'UTF8') { @@ -318,7 +318,7 @@ task bumpVersion { fileset(dir: 'docs', includes: 'index.adoc') fileset(dir: 'docs', includes: 'quick-guide.adoc') fileset(dir: 'docs', includes: 'autocomplete.adoc') - fileset(dir: 'docs', includes: 'picocli-3.0-programmatic-api.adoc') + fileset(dir: 'docs', includes: 'picocli-programmatic-api.adoc') fileset(dir: 'picocli-codegen', includes: 'README.adoc') } } diff --git a/docs/picocli-3.0-programmatic-api.adoc b/docs/picocli-3.0-programmatic-api.adoc deleted file mode 100644 index 0732b8772..000000000 --- a/docs/picocli-3.0-programmatic-api.adoc +++ /dev/null @@ -1,385 +0,0 @@ -= Programmatic API -//:author: Remko Popma -//:email: rpopma@apache.org -:revnumber: 4.1.2-SNAPSHOT -:revdate: 2019-11-27 -:toc: left -:numbered: -:toclevels: 2 -:source-highlighter: coderay -:icons: font -:imagesdir: images - -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 https://github.com/remkop/picocli/wiki/Picocli-2.0:-Do-More-With-Less[introductory article] and for more details, the http://picocli.info[user manual]. - -Picocli 3.0 offers a programmatic API for creating command line applications, in addition to annotations. The 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. - -== Example - -[source,java] ----- -CommandSpec spec = CommandSpec.create(); -spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options -spec.addOption(OptionSpec.builder("-c", "--count") - .paramLabel("COUNT") - .type(int.class) - .description("number of times to execute").build()); -spec.addPositional(PositionalParamSpec.builder() - .paramLabel("FILES") - .type(List.class) - .auxiliaryTypes(File.class) // List - .description("The files to process").build()); -CommandLine commandLine = new CommandLine(spec); - -// processing parse results can be done manually -// or delegated to a handler -class Handler extends AbstractParseResultHandler { - protected Handler self() { return this; } - - public Integer handle(ParseResult pr) { - int count = pr.matchedOptionValue('c', 1); - List files = pr.matchedPositionalValue(0, Collections.emptyList()); - for (File f : files) { - for (int i = 0; i < count; i++) { - System.out.println(i + " " + f.getName()); - } - } - return files.size(); - } -} - -// using a handler gives fine-grained control -// over which streams to use and exit codes -Integer processed = commandLine.parseWithHandlers( - new Handler().useOut(System.out).andExit(123), - new DefaultExceptionHandler().andExit(567), - args); ----- - -== Configuration -The following classes are the main model classes used to configure the parser: - -* `CommandSpec` -* `OptionSpec` -* `PositionalParamSpec` - - -=== `CommandSpec` - -==== Command Name and Version -`CommandSpec` models a command. It has a name and a version, both of which may be empty. For example: - -[source,java] ----- -CommandSpec cmd = CommandSpec.create() - .name("mycommand") - .version("My Command v1.0"); ----- - -It also has a `UsageMessageSpec` to configure aspects of the usage help message. - -==== Usage Help -[source,java] ----- -cmd.usageMessage() - .headerHeading("Header heading%n") - .header("header line 1", "header line 2") - .descriptionHeading("Description heading%n") - .description("description line 1", "description line 2") - .optionListHeading("Options%n") - .parameterListHeading("Positional Parameters%n"); - .footerHeading("Footer heading%n") - .footer("footer line 1", "footer line 2"); ----- -The `ParserSpec` can be used to control the behaviour of the parser to some extent. - -==== Parser Options -[source,java] ----- -cmd.parser() - .unmatchedArgumentsAllowed(true) - .overwrittenOptionsAllowed(true); ----- - -==== Mixins -`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`. -[source,java] ----- -CommandSpec standardHelpOptions = CommandSpec.create() - .addOption(OptionSpec.builder("-h", "--help") - .usageHelp(true) - .description("Show this help message and exit.").build()) - .addOption(OptionSpec.builder("-V", "--version") - .versionHelp(true) - .description("Print version information and exit.").build()); - -CommandSpec cmd = CommandSpec.create() - .name("mycommand") - .addMixin("standardHelpOptions", standardHelpOptions); ----- -Actually, since these options are extremely common, `CommandSpec` provides a convenience method to quickly add these standard help options: -[source,java] ----- -CommandSpec spec = CommandSpec.create(); -spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options ----- - -==== Subcommands -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` -[source,java] ----- -CommandSpec helpSubcommand = CommandSpec.forAnnotatedObject( - new picocli.CommandLine.HelpCommand()); - -CommandSpec cmd = CommandSpec.create() - .name("mycommand") - .addSubcommand("help", helpSubcommand); ----- - -=== `OptionSpec` -`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 the getter and setter _bindings_. 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)` methods that automatically print help when requested. - -=== `PositionalParamSpec` - -`PositionalParamSpec` objects don’t have names, but have an index range instead. A single `PositionalParamSpec` object can capture multiple positional parameters. The default index range is set to `0..*` (all indices). A command may have multiple `PositionalParamSpec` objects to 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 getter and setter _bindings_. We’ll look at bindings next. - -=== Bindings -Bindings decouple the option and positional parameter specification from the place where their value is held. - -Option specifications and positional parameter specifications created from annotated objects have a `FieldBinding` (and in the near future they can have a `MethodBinding`), so when the value is set on an option specification, the field's value is set (or the setter method is invoked). - -Option specifications and positional parameter specifications created programmatically without annotated object by default have an `ObjectBinding` that simply stores the value in a field of the `ObjectBinding`. - -You may create a custom binding that delegates to some other data structure to retrieve and store the value. - -A binding is either a getter or a setter: -[source,java] ----- -public static interface IGetter { - /** Returns the current value of the binding. For multi-value options and positional - * parameters, this method returns an array, collection or map to add values to. - * @throws PicocliException if a problem occurred while obtaining the current value - * @throws Exception internally, picocli call sites will catch any exceptions - * thrown from here and rethrow them wrapped in a PicocliException */ - T get() throws Exception; -} ----- - -[source,java] ----- -public static interface ISetter { - /** Sets the new value of the option or positional parameter. - * - * @param value the new value of the option or positional parameter - * @param type of the value - * @return the previous value of the binding (if supported by this binding) - * @throws PicocliException if a problem occurred while setting the new value - * @throws Exception internally, picocli call sites will catch any exceptions - * thrown from here and rethrow them wrapped in a PicocliException */ - T set(T value) throws Exception; -} ----- - -For single-value options, picocli will simply invoke the setter when an option or positional parameter is matched on the command line. - -For multi-value options or positional parameters, picocli will call the getter to get the current value, add the newly matched value, and call the setter with the result. -For arrays, this means the existing elements are copied into a new array that is one element larger, and this new array is then set. -For collections and maps, the new value is added to the collection returned by the getter. -If the getter returns `null`, a new array, collection, or map is created. - -== Parse Result -For the below examples, we use the following parser configuration: -[source,java] ----- -CommandSpec spec = CommandSpec.create(); -spec.addOption(OptionSpec.builder("-V", "--verbose").build()); -spec.addOption(OptionSpec.builder("-f", "--file") - .paramLabel("FILES") - .type(List.class) - .auxiliaryTypes(File.class) // this option is of type List - .description("The files to process").build()); -spec.addOption(OptionSpec.builder("-n", "--num") - .paramLabel("COUNT") - .type(int[].class) - .splitRegex(",") - .description("Comma-separated list of integers").build()); -CommandLine commandLine = new CommandLine(spec); ----- - -=== Querying for Options - -The `CommandLine::parseArgs` method returns a `ParseResult` object that allows client code to query which options and positional parameters were matched for a given command. - -[source,java] ----- -String[] args = { "--verbose", "-f", "file1", "--file=file2", "-n1,2,3" }; -ParseResult pr = commandLine.parseArgs(args); - -List originalArgs = pr.originalArgs(); // lists all command line args -assert Arrays.asList(args).equals(originalArgs); - -assert pr.hasMatchedOption("--verbose"); // as specified on command line -assert pr.hasMatchedOption("-V"); // other aliases work also -assert pr.hasMatchedOption('V'); // single-character alias works too -assert pr.hasMatchedOption("verbose"); // and, command name without hyphens ----- - -=== Matched Option Values - -The `matchedOptionValue` method returns the command line value or values, converted to the option's type. This method requires a default value, which will be returned in case the option was not matched on the command line. In the above example, we defined the `--file` option to be of type `List`, so we pass in an empty list as the default value: - -[source,java] ----- -ParseResult pr = commandLine.parseArgs("-f", "file1", "--file=file2", "-n1,2,3"); - -List defaultValue = Collections.emptyList(); -List expected = Arrays.asList(new File("file1"), new File("file2")); - -assert expected.equals(pr.matchedOptionValue('f', defaultValue)); -assert expected.equals(pr.matchedOptionValue("--file", defaultValue)); - -assert Arrays.equals(new int[]{1,2,3}, pr.matchedOptionValue('n', new int[0])); ----- - -=== Original Option Values - -Use the `OptionSpec.stringValues()` or `OptionSpec.originalStringValues()` method to get a list of all values specified on the command line for an option. -The `stringValues()` method returns the arguments after splitting but before type conversion, while -the `originalStringValues()` method returns the matched arguments as specified on the command line (before splitting). - -[source,java] ----- -ParseResult pr = commandLine.parseArgs("-f", "file1", "--file=file2", "-n1,2,3"); - -// Command line arguments after splitting but before type conversion -assert "1".equals(pr.matchedOption('n').stringValues().get(0)); -assert "2".equals(pr.matchedOption('n').stringValues().get(1)); -assert "3".equals(pr.matchedOption('n').stringValues().get(2)); - -// Command line arguments as found on the command line -assert "1,2,3".equals(pr.matchedOption("--num").originalStringValues().get(0)); ----- - - -=== Subcommands - -Use the `hasSubcommand` method to determine whether the command line contained subcommands. The `subcommand` method returns a different `ParseResult` object that can be used to query which options and positional parameters were matched for the subcommand. -[source,java] ----- -class App { - @Option(names = "-x") String x; -} -class Sub { - @Parameters String[] all; -} -CommandLine cmd = new CommandLine(new App()); -cmd.addSubcommand("sub", new Sub()); -ParseResult parseResult = cmd.parseArgs("-x", "xval", "sub", "1", "2", "3"); - -assert parseResult.hasMatchedOption("-x"); -assert "xval".equals(parseResult.matchedOptionValue("-x", "default")); - -assert parseResult.hasSubcommand(); -ParseResult subResult = parseResult.subcommand(); - -assert subResult.hasMatchedPositional(0); -assert subResult.hasMatchedPositional(1); -assert subResult.hasMatchedPositional(2); -assert !subResult.hasMatchedPositional(3); ----- - - -== Parsing and Result Processing - -=== Basic Processing -The most basic way to parse the command line is to call the `CommandLine::parseArgs` method and inspect the resulting `ParseResult` object. - -For example: -[source,java] ----- -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.matchedOptionValue('c', 1); - List files = pr.matchedPositionalValue(0, Collections.emptyList()); - for (File f : files) { - for (int i = 0; i < count; i++) { - System.out.printf("%d: %s%n", i, f); - } - } -} catch (ParameterException invalidInput) { - System.err.println(invalidInput.getMessage()); - invalidInput.getCommandLine().usage(System.err); -} ----- - -=== Convenience Methods - -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 handle invalid input. - - -==== Handlers - -It is possible for the parse result processing logic to return a result. To accomplish this, call the `CommandLine::parseWithHandler` method with a class that extends `AbstractParseResultHandler`. - -Example: - -[source,java] ----- -CommandSpec spec = CommandSpec.create(); -// add options and positional parameters - -CommandLine commandLine = new CommandLine(spec); - -class Handler extends AbstractParseResultHandler { - protected Handler self() { return this; } - - public Integer handle(ParseResult pr) { - int count = pr.matchedOptionValue('c', 1); - List files = pr.matchedPositionalValue(0, Collections.emptyList()); - for (File f : files) { - for (int i = 0; i < count; i++) { - System.out.println(i + " " + f.getName()); - } - } - return files.size(); - } -} - -int processed = commandLine.parseWithHandler(new Handler(), args); -// do something with result... ----- - - -This method also has a variation, `parseWithHandlers`, which additionally takes an `IExceptionHandler2` to customize how invalid input should be handled and optionally set an exit code. - -Example: - -[source,java] ----- -CommandSpec spec = CommandSpec.create(); -// add options and positional parameters - -CommandLine commandLine = new CommandLine(spec); -Integer result = commandLine.parseWithHandlers( - new MyHandler().useOut(System.out).andExit(123), - new DefaultExceptionHandler().andExit(567), - args); -// do something with result... ----- \ No newline at end of file diff --git a/docs/picocli-3.0-programmatic-api.html b/docs/picocli-3.0-programmatic-api.html index 74268fd2b..833c6cbd7 100644 --- a/docs/picocli-3.0-programmatic-api.html +++ b/docs/picocli-3.0-programmatic-api.html @@ -1,1043 +1,10 @@ - - - - -Programmatic API - - - - + Picocli Programmatic API + - - -
-
-
-
- - - - - -
- - -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. The 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.

-
-
-
-
-

1. Example

-
-
-
-
CommandSpec spec = CommandSpec.create();
-spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options
-spec.addOption(OptionSpec.builder("-c", "--count")
-        .paramLabel("COUNT")
-        .type(int.class)
-        .description("number of times to execute").build());
-spec.addPositional(PositionalParamSpec.builder()
-        .paramLabel("FILES")
-        .type(List.class)
-        .auxiliaryTypes(File.class) // List<File>
-        .description("The files to process").build());
-CommandLine commandLine = new CommandLine(spec);
-
-// processing parse results can be done manually
-// or delegated to a handler
-class Handler extends AbstractParseResultHandler<Integer> {
-    protected Handler self() { return this; }
-
-    public Integer handle(ParseResult pr) {
-        int count = pr.matchedOptionValue('c', 1);
-        List<File> files = pr.matchedPositionalValue(0, Collections.<File>emptyList());
-        for (File f : files) {
-            for (int i = 0; i < count; i++) {
-                System.out.println(i + " " + f.getName());
-            }
-        }
-        return files.size();
-    }
-}
-
-// using a handler gives fine-grained control
-// over which streams to use and exit codes
-Integer processed = commandLine.parseWithHandlers(
-        new Handler().useOut(System.out).andExit(123),
-        new DefaultExceptionHandler<Integer>().andExit(567),
-        args);
-
-
-
-
-
-

2. Configuration

-
-
-

The following classes are the main model classes used to configure the parser:

-
-
-
    -
  • -

    CommandSpec

    -
  • -
  • -

    OptionSpec

    -
  • -
  • -

    PositionalParamSpec

    -
  • -
-
-
-

2.1. CommandSpec

-
-

2.1.1. Command Name and Version

-
-

CommandSpec models a command. It has a name and a version, both of which may be empty. For example:

-
-
-
-
CommandSpec cmd = CommandSpec.create()
-    .name("mycommand")
-    .version("My Command v1.0");
-
-
-
-

It also has a UsageMessageSpec to configure aspects of the usage help message.

-
-
-
-

2.1.2. Usage Help

-
-
-
cmd.usageMessage()
-        .headerHeading("Header heading%n")
-        .header("header line 1", "header line 2")
-        .descriptionHeading("Description heading%n")
-        .description("description line 1", "description line 2")
-        .optionListHeading("Options%n")
-        .parameterListHeading("Positional Parameters%n");
-        .footerHeading("Footer heading%n")
-        .footer("footer line 1", "footer line 2");
-
-
-
-

The ParserSpec can be used to control the behaviour of the parser to some extent.

-
-
-
-

2.1.3. Parser Options

-
-
-
cmd.parser()
-        .unmatchedArgumentsAllowed(true)
-        .overwrittenOptionsAllowed(true);
-
-
-
-
-

2.1.4. Mixins

-
-

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.

-
-
-
-
CommandSpec standardHelpOptions = CommandSpec.create()
-    .addOption(OptionSpec.builder("-h", "--help")
-        .usageHelp(true)
-        .description("Show this help message and exit.").build())
-    .addOption(OptionSpec.builder("-V", "--version")
-        .versionHelp(true)
-        .description("Print version information and exit.").build());
-
-CommandSpec cmd = CommandSpec.create()
-    .name("mycommand")
-    .addMixin("standardHelpOptions", standardHelpOptions);
-
-
-
-

Actually, since these options are extremely common, CommandSpec provides a convenience method to quickly add these standard help options:

-
-
-
-
CommandSpec spec = CommandSpec.create();
-spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options
-
-
-
-
-

2.1.5. Subcommands

-
-

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

-
-
-
-
CommandSpec helpSubcommand = CommandSpec.forAnnotatedObject(
-        new picocli.CommandLine.HelpCommand());
-
-CommandSpec cmd = CommandSpec.create()
-    .name("mycommand")
-    .addSubcommand("help", helpSubcommand);
-
-
-
-
-
-

2.2. OptionSpec

-
-

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 the getter and setter bindings. 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) methods that automatically print help when requested.

-
-
-
-

2.3. PositionalParamSpec

-
-

PositionalParamSpec objects don’t have names, but have an index range instead. A single PositionalParamSpec object can capture multiple positional parameters. The default index range is set to 0..* (all indices). A command may have multiple PositionalParamSpec objects to 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 getter and setter bindings. We’ll look at bindings next.

-
-
-
-

2.4. Bindings

-
-

Bindings decouple the option and positional parameter specification from the place where their value is held.

-
-
-

Option specifications and positional parameter specifications created from annotated objects have a FieldBinding (and in the near future they can have a MethodBinding), so when the value is set on an option specification, the field’s value is set (or the setter method is invoked).

-
-
-

Option specifications and positional parameter specifications created programmatically without annotated object by default have an ObjectBinding that simply stores the value in a field of the ObjectBinding.

-
-
-

You may create a custom binding that delegates to some other data structure to retrieve and store the value.

-
-
-

A binding is either a getter or a setter:

-
-
-
-
public static interface IGetter {
-    /** Returns the current value of the binding. For multi-value options and positional
-     * parameters, this method returns an array, collection or map to add values to.
-     * @throws PicocliException if a problem occurred while obtaining the current value
-     * @throws Exception internally, picocli call sites will catch any exceptions
-     *         thrown from here and rethrow them wrapped in a PicocliException */
-    <T> T get() throws Exception;
-}
-
-
-
-
-
public static interface ISetter {
-    /** Sets the new value of the option or positional parameter.
-     *
-     * @param value the new value of the option or positional parameter
-     * @param <T> type of the value
-     * @return the previous value of the binding (if supported by this binding)
-     * @throws PicocliException if a problem occurred while setting the new value
-     * @throws Exception internally, picocli call sites will catch any exceptions
-     *         thrown from here and rethrow them wrapped in a PicocliException */
-    <T> T set(T value) throws Exception;
-}
-
-
-
-

For single-value options, picocli will simply invoke the setter when an option or positional parameter is matched on the command line.

-
-
-

For multi-value options or positional parameters, picocli will call the getter to get the current value, add the newly matched value, and call the setter with the result. -For arrays, this means the existing elements are copied into a new array that is one element larger, and this new array is then set. -For collections and maps, the new value is added to the collection returned by the getter. -If the getter returns null, a new array, collection, or map is created.

-
-
-
-
-
-

3. Parse Result

-
-
-

For the below examples, we use the following parser configuration:

-
-
-
-
CommandSpec spec = CommandSpec.create();
-spec.addOption(OptionSpec.builder("-V", "--verbose").build());
-spec.addOption(OptionSpec.builder("-f", "--file")
-        .paramLabel("FILES")
-        .type(List.class)
-        .auxiliaryTypes(File.class) // this option is of type List<File>
-        .description("The files to process").build());
-spec.addOption(OptionSpec.builder("-n", "--num")
-        .paramLabel("COUNT")
-        .type(int[].class)
-        .splitRegex(",")
-        .description("Comma-separated list of integers").build());
-CommandLine commandLine = new CommandLine(spec);
-
-
-
-

3.1. Querying for Options

-
-

The CommandLine::parseArgs method returns a ParseResult object that allows client code to query which options and positional parameters were matched for a given command.

-
-
-
-
String[] args = { "--verbose", "-f", "file1", "--file=file2", "-n1,2,3" };
-ParseResult pr = commandLine.parseArgs(args);
-
-List<String> originalArgs = pr.originalArgs(); // lists all command line args
-assert Arrays.asList(args).equals(originalArgs);
-
-assert pr.hasMatchedOption("--verbose"); // as specified on command line
-assert pr.hasMatchedOption("-V");        // other aliases work also
-assert pr.hasMatchedOption('V');         // single-character alias works too
-assert pr.hasMatchedOption("verbose");   // and, command name without hyphens
-
-
-
-
-

3.2. Matched Option Values

-
-

The matchedOptionValue method returns the command line value or values, converted to the option’s type. This method requires a default value, which will be returned in case the option was not matched on the command line. In the above example, we defined the --file option to be of type List<File>, so we pass in an empty list as the default value:

-
-
-
-
ParseResult pr = commandLine.parseArgs("-f", "file1", "--file=file2", "-n1,2,3");
-
-List<File> defaultValue = Collections.emptyList();
-List<File> expected     = Arrays.asList(new File("file1"), new File("file2"));
-
-assert expected.equals(pr.matchedOptionValue('f', defaultValue));
-assert expected.equals(pr.matchedOptionValue("--file", defaultValue));
-
-assert Arrays.equals(new int[]{1,2,3}, pr.matchedOptionValue('n', new int[0]));
-
-
-
-
-

3.3. Original Option Values

-
-

Use the OptionSpec.stringValues() or OptionSpec.originalStringValues() method to get a list of all values specified on the command line for an option. -The stringValues() method returns the arguments after splitting but before type conversion, while -the originalStringValues() method returns the matched arguments as specified on the command line (before splitting).

-
-
-
-
ParseResult pr = commandLine.parseArgs("-f", "file1", "--file=file2", "-n1,2,3");
-
-// Command line arguments after splitting but before type conversion
-assert "1".equals(pr.matchedOption('n').stringValues().get(0));
-assert "2".equals(pr.matchedOption('n').stringValues().get(1));
-assert "3".equals(pr.matchedOption('n').stringValues().get(2));
-
-// Command line arguments as found on the command line
-assert "1,2,3".equals(pr.matchedOption("--num").originalStringValues().get(0));
-
-
-
-
-

3.4. Subcommands

-
-

Use the hasSubcommand method to determine whether the command line contained subcommands. The subcommand method returns a different ParseResult object that can be used to query which options and positional parameters were matched for the subcommand.

-
-
-
-
class App {
-    @Option(names = "-x") String x;
-}
-class Sub {
-    @Parameters String[] all;
-}
-CommandLine cmd = new CommandLine(new App());
-cmd.addSubcommand("sub", new Sub());
-ParseResult parseResult = cmd.parseArgs("-x", "xval", "sub", "1", "2", "3");
-
-assert parseResult.hasMatchedOption("-x");
-assert "xval".equals(parseResult.matchedOptionValue("-x", "default"));
-
-assert parseResult.hasSubcommand();
-ParseResult subResult = parseResult.subcommand();
-
-assert  subResult.hasMatchedPositional(0);
-assert  subResult.hasMatchedPositional(1);
-assert  subResult.hasMatchedPositional(2);
-assert !subResult.hasMatchedPositional(3);
-
-
-
-
-
-
-

4. Parsing and Result Processing

-
-
-

4.1. Basic Processing

-
-

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.matchedOptionValue('c', 1);
-    List<File> files = pr.matchedPositionalValue(0, Collections.<File>emptyList());
-    for (File f : files) {
-        for (int i = 0; i < count; i++) {
-            System.out.printf("%d: %s%n", i, f);
-        }
-    }
-} catch (ParameterException invalidInput) {
-    System.err.println(invalidInput.getMessage());
-    invalidInput.getCommandLine().usage(System.err);
-}
-
-
-
-
-

4.2. Convenience Methods

-
-

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 handle invalid input.

-
-
-

4.2.1. Handlers

-
-

It is possible for the parse result processing logic to return a result. To accomplish this, call the CommandLine::parseWithHandler method with a class that extends AbstractParseResultHandler.

-
-
-

Example:

-
-
-
-
CommandSpec spec = CommandSpec.create();
-// add options and positional parameters
-
-CommandLine commandLine = new CommandLine(spec);
-
-class Handler extends AbstractParseResultHandler<Integer> {
-    protected Handler self() { return this; }
-
-    public Integer handle(ParseResult pr) {
-        int count = pr.matchedOptionValue('c', 1);
-        List<File> files = pr.matchedPositionalValue(0, Collections.<File>emptyList());
-        for (File f : files) {
-            for (int i = 0; i < count; i++) {
-                System.out.println(i + " " + f.getName());
-            }
-        }
-        return files.size();
-    }
-}
-
-int processed = commandLine.parseWithHandler(new Handler(), args);
-// do something with result...
-
-
-
-

This method also has a variation, parseWithHandlers, which additionally takes an IExceptionHandler2 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);
-Integer result = commandLine.parseWithHandlers(
-        new MyHandler().useOut(System.out).andExit(123),
-        new DefaultExceptionHandler<Integer>().andExit(567),
-        args);
-// do something with result...
-
-
-
-
-
-
-
- + +

Redirecting to https://picocli.info/picocli-programmatic-api.html...

- \ No newline at end of file + diff --git a/docs/picocli-4.1-programmatic-api.adoc b/docs/picocli-programmatic-api.adoc similarity index 80% rename from docs/picocli-4.1-programmatic-api.adoc rename to docs/picocli-programmatic-api.adoc index 0cf6d917e..db4e290a5 100644 --- a/docs/picocli-4.1-programmatic-api.adoc +++ b/docs/picocli-programmatic-api.adoc @@ -12,7 +12,9 @@ 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 https://picocli.info/quick-guide.html[quick guide] and for more details, the http://picocli.info[user manual]. -Picocli 4.1 offers a programmatic API for creating command line applications, in addition to annotations. The 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. +TIP: This document covers the programmatic API for picocli 4.0 and higher. For older versions, check link:https://picocli.info/man/3.x/picocli-3.0-programmatic-api.html[picocli-3.0-programmatic-api.html]. + +Picocli offers a programmatic API for creating command line applications, in addition to annotations. The 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. == Example @@ -23,33 +25,21 @@ public class ProgrammaticCommand { public static void main(String[] args) { CommandSpec spec = CommandSpec.create(); spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options - spec.addOption(OptionSpec.builder("-c", "--count").paramLabel("COUNT").type(int.class) + spec.addOption(OptionSpec.builder("-c", "--count").paramLabel("COUNT") + .type(int.class) .description("number of times to execute").build()); - spec.addPositional(PositionalParamSpec.builder().paramLabel("FILES").type(List.class).auxiliaryTypes(File.class) // List + spec.addPositional(PositionalParamSpec.builder().paramLabel("FILES") + .type(List.class).auxiliaryTypes(File.class) // List .description("The files to process").build()); CommandLine commandLine = new CommandLine(spec); - - // set the execution strategy and control other aspects, - // like streams and handlers to be used and exit codes - commandLine.setExecutionStrategy(new Strategy()) - .setCaseInsensitiveEnumValuesAllowed(true) // configure a non-default parser option - .setOut(System.outmyOutWriter()) // configure an alternative to System.out - .setErr(myErrWriter()) // configure an alternative to System.err - .setColorScheme(myColorScheme()) // configure a custom color scheme - .setParameterExceptionHandler(myHandler()) // configure a custom handler - .setExecutionExceptionHandler(myHandler()) // configure a custom handler - .setExitCodeExceptionMapper(myMapper()); // map exception to exit code + + // set an execution strategy so we can call CommandLine.execute(args) + commandLine.setExecutionStrategy(ProgrammaticCommand::run); int exitCode = commandLine.execute(args); System.exit(exitCode); } -} -// processing parse results can be done manually -// or delegated to a configured execution strategy. -class Strategy implements IExecutionStrategy { - - @Override - public int execute(ParseResult pr) throws ExecutionException, ParameterException { + static int run(ParseResult pr) { int count = pr.matchedOptionValue('c', 1); List files = pr.matchedPositionalValue(0, Collections.emptyList()); for (File f : files) { @@ -343,15 +333,61 @@ For example: [source,java] ---- -CommandLine commandLine = new CommandLine(spec); -commandLine.setExecutionStrategy(new Strategy()) - .setCaseInsensitiveEnumValuesAllowed(true) // configure a non-default parser option - .setOut(System.outmyOutWriter()) // configure an alternative to System.out - .setErr(myErrWriter()) // configure an alternative to System.err - .setColorScheme(myColorScheme()) // configure a custom color scheme - .setParameterExceptionHandler(myHandler()) // configure a custom handler - .setExecutionExceptionHandler(myHandler()) // configure a custom handler - .setExitCodeExceptionMapper(myMapper()); // map exception to exit code -int exitCode = commandLine.execute(args); -System.exit(exitCode); +public class ProgrammaticCommand { + + public static void main(String[] args) { + CommandSpec spec = CommandSpec.create(); + spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options + spec.addOption(OptionSpec.builder("-c", "--count").paramLabel("COUNT") + .type(int.class) + .description("number of times to execute").build()); + spec.addPositional(PositionalParamSpec.builder().paramLabel("FILES") + .type(List.class).auxiliaryTypes(File.class) // List + .description("The files to process").build()); + CommandLine commandLine = new CommandLine(spec); + + // optionally configure streams and handlers to be used + commandLine.setCaseInsensitiveEnumValuesAllowed(true) // configure a non-default parser option + .setOut(myOutWriter()) // configure an alternative to System.out + .setErr(myErrWriter()) // configure an alternative to System.err + .setColorScheme(myColorScheme()) // configure a custom color scheme + .setExitCodeExceptionMapper(myMapper()) // map exception to exit code + .setParameterExceptionHandler(Tmp::invalidUserInput) // configure a custom handler + .setExecutionExceptionHandler(Tmp::runtimeException) // configure a custom handler + ; + // set an execution strategy so we can call CommandLine.execute(args) + commandLine.setExecutionStrategy(ProgrammaticCommand::run); + int exitCode = commandLine.execute(args); + System.exit(exitCode); + } + + static int run(ParseResult pr) { + int count = pr.matchedOptionValue('c', 1); + List files = pr.matchedPositionalValue(0, Collections.emptyList()); + for (File f : files) { + for (int i = 0; i < count; i++) { + System.out.println(i + " " + f.getName()); + } + } + return files.size(); + } + + // custom handler for runtime errors that does not print a stack trace + static int runtimeException(Exception e, + CommandLine commandLine, + ParseResult parseResult) { + commandLine.getErr().println("INTERNAL ERROR: " + e.getMessage()); + return CommandLine.ExitCode.SOFTWARE; + } + + // custom handler for invalid input that does not print usage help + static int invalidUserInput(ParameterException e, String[] strings) { + CommandLine commandLine = e.getCommandLine(); + commandLine.getErr().println("ERROR: " + e.getMessage()); + commandLine.getErr().println("Try '" + + commandLine.getCommandSpec().qualifiedName() + + " --help' for more information."); + return CommandLine.ExitCode.USAGE; + } +} ---- diff --git a/docs/picocli-4.1-programmatic-api.html b/docs/picocli-programmatic-api.html similarity index 90% rename from docs/picocli-4.1-programmatic-api.html rename to docs/picocli-programmatic-api.html index 33aeb3bac..d585d2a6d 100644 --- a/docs/picocli-4.1-programmatic-api.html +++ b/docs/picocli-programmatic-api.html @@ -573,8 +573,20 @@

Programmatic API

+
+ + + + + +
+ + +This document covers the programmatic API for picocli 4.0 and higher. For older versions, check picocli-3.0-programmatic-api.html. +
+
-

Picocli 4.1 offers a programmatic API for creating command line applications, in addition to annotations. The 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.

+

Picocli offers a programmatic API for creating command line applications, in addition to annotations. The 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.

@@ -588,33 +600,21 @@

1. Example

public static void main(String[] args) { CommandSpec spec = CommandSpec.create(); spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options - spec.addOption(OptionSpec.builder("-c", "--count").paramLabel("COUNT").type(int.class) + spec.addOption(OptionSpec.builder("-c", "--count").paramLabel("COUNT") + .type(int.class) .description("number of times to execute").build()); - spec.addPositional(PositionalParamSpec.builder().paramLabel("FILES").type(List.class).auxiliaryTypes(File.class) // List<File> + spec.addPositional(PositionalParamSpec.builder().paramLabel("FILES") + .type(List.class).auxiliaryTypes(File.class) // List<File> .description("The files to process").build()); CommandLine commandLine = new CommandLine(spec); - // set the execution strategy and control other aspects, - // like streams and handlers to be used and exit codes - commandLine.setExecutionStrategy(new Strategy()) - .setCaseInsensitiveEnumValuesAllowed(true) // configure a non-default parser option - .setOut(System.outmyOutWriter()) // configure an alternative to System.out - .setErr(myErrWriter()) // configure an alternative to System.err - .setColorScheme(myColorScheme()) // configure a custom color scheme - .setParameterExceptionHandler(myHandler()) // configure a custom handler - .setExecutionExceptionHandler(myHandler()) // configure a custom handler - .setExitCodeExceptionMapper(myMapper()); // map exception to exit code + // set an execution strategy so we can call CommandLine.execute(args) + commandLine.setExecutionStrategy(ProgrammaticCommand::run); int exitCode = commandLine.execute(args); System.exit(exitCode); } -} -// processing parse results can be done manually -// or delegated to a configured execution strategy. -class Strategy implements IExecutionStrategy { - - @Override - public int execute(ParseResult pr) throws ExecutionException, ParameterException { + static int run(ParseResult pr) { int count = pr.matchedOptionValue('c', 1); List<File> files = pr.matchedPositionalValue(0, Collections.<File>emptyList()); for (File f : files) { @@ -985,17 +985,63 @@

4.2. Execute Convenience Metho
-
CommandLine commandLine = new CommandLine(spec);
-commandLine.setExecutionStrategy(new Strategy())
-    .setCaseInsensitiveEnumValuesAllowed(true) // configure a non-default parser option
-    .setOut(System.outmyOutWriter()) // configure an alternative to System.out
-    .setErr(myErrWriter()) // configure an alternative to System.err
-    .setColorScheme(myColorScheme()) // configure a custom color scheme
-    .setParameterExceptionHandler(myHandler()) // configure a custom handler
-    .setExecutionExceptionHandler(myHandler()) // configure a custom handler
-    .setExitCodeExceptionMapper(myMapper()); // map exception to exit code
-int exitCode = commandLine.execute(args);
-System.exit(exitCode);
+
public class ProgrammaticCommand {
+
+    public static void main(String[] args) {
+        CommandSpec spec = CommandSpec.create();
+        spec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options
+        spec.addOption(OptionSpec.builder("-c", "--count").paramLabel("COUNT")
+                .type(int.class)
+                .description("number of times to execute").build());
+        spec.addPositional(PositionalParamSpec.builder().paramLabel("FILES")
+                .type(List.class).auxiliaryTypes(File.class) // List<File>
+                .description("The files to process").build());
+        CommandLine commandLine = new CommandLine(spec);
+
+        // optionally configure streams and handlers to be used
+        commandLine.setCaseInsensitiveEnumValuesAllowed(true) // configure a non-default parser option
+            .setOut(myOutWriter()) // configure an alternative to System.out
+            .setErr(myErrWriter()) // configure an alternative to System.err
+            .setColorScheme(myColorScheme()) // configure a custom color scheme
+            .setExitCodeExceptionMapper(myMapper()) //  map exception to exit code
+            .setParameterExceptionHandler(Tmp::invalidUserInput) // configure a custom handler
+            .setExecutionExceptionHandler(Tmp::runtimeException) // configure a custom handler
+        ;
+        // set an execution strategy so we can call CommandLine.execute(args)
+        commandLine.setExecutionStrategy(ProgrammaticCommand::run);
+        int exitCode = commandLine.execute(args);
+        System.exit(exitCode);
+    }
+
+    static int run(ParseResult pr) {
+        int count = pr.matchedOptionValue('c', 1);
+        List<File> files = pr.matchedPositionalValue(0, Collections.<File>emptyList());
+        for (File f : files) {
+            for (int i = 0; i < count; i++) {
+                System.out.println(i + " " + f.getName());
+            }
+        }
+        return files.size();
+    }
+
+    // custom handler for runtime errors that does not print a stack trace
+    static int runtimeException(Exception e,
+                                CommandLine commandLine,
+                                ParseResult parseResult) {
+        commandLine.getErr().println("INTERNAL ERROR: " + e.getMessage());
+        return CommandLine.ExitCode.SOFTWARE;
+    }
+
+    // custom handler for invalid input that does not print usage help
+    static int invalidUserInput(ParameterException e, String[] strings) {
+        CommandLine commandLine = e.getCommandLine();
+        commandLine.getErr().println("ERROR: " + e.getMessage());
+        commandLine.getErr().println("Try '"
+                + commandLine.getCommandSpec().qualifiedName()
+                + " --help' for more information.");
+        return CommandLine.ExitCode.USAGE;
+    }
+}
@@ -1005,7 +1051,7 @@

4.2. Execute Convenience Metho