Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docu, new code samples: Kotlin versions, minor changes #1296

Merged
merged 5 commits into from
Jan 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 120 additions & 55 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2346,6 +2346,7 @@ When `-x VALUE` is specified on the command line, this results in an error: `Mis
Users can use the <<Double dash (`--`),end-of-options delimiter>> and disambiguate the input with
`-x &dash;&dash; VALUE`, but this may not be obvious to many users.
One idea is to <<Show End of Options,show the end-of-options>> delimiter in the usage help.
Another idea is to make use of the <<IParameterPreprocessor_Parser_Plugin,IParameterPreprocessor Parser Plugin>> introduced with picocli 4.6.

An alternative is to avoid the use of optional parameters and use the default arity in this scenario to eliminate the ambiguity altogether.

Expand Down Expand Up @@ -4707,6 +4708,8 @@ See the javadoc for details.

=== Custom Parameter Processing

As of version 4.6, picocli offers two different parser plugins for custom parameter processing: while the `IParameterPreprocessor` is a powerful and flexible tool, the `IParameterConsumer` serves as a simpler alternative.

==== `IParameterConsumer` Parser Plugin

Options or positional parameters can be assigned a `IParameterConsumer` that implements
Expand Down Expand Up @@ -4776,6 +4779,7 @@ class ExecParameterConsumer : IParameterConsumer {
}
----

[[IParameterPreprocessor_Parser_Plugin]]
==== `IParameterPreprocessor` Parser Plugin

Introduced in picocli 4.6, the `IParameterPreprocessor` is also a parser plugin, similar to `IParameterConsumer`, but more flexible.
Expand All @@ -4798,66 +4802,104 @@ edit [--open[=<editor>]] <file>
One of the <<Optional Parameter Limitations, limitations of options with an optional parameter>> is that they are difficult to combine with positional parameters.

With a custom parser plugin, we can customize the parser, such that `VALUE` in `--option=VALUE` is interpreted as the option parameter, and in `--option VALUE` (without the `=` separator), VALUE is interpreted as the positional parameter.
The code below demonstrates:
The code below demonstrates this:

.Java
[source,java,role="primary"]
----
@Command(name = "edit")
class Edit {

@Parameters(index = "0", description = "The file to edit.")
@Parameters(index = "0", arity="0..1", description = "The file to edit.")
File file;

enum Editor { defaultEditor, eclipse, idea, netbeans }
enum Editor { defaultEditor, eclipse, idea, netbeans }

@Option(names = "--open", arity = "0..1", preprocessor = Edit.MyPreprocessor.class,
description = {
"Optionally specify the editor to use; if omitted the default editor is used. ",
@Option(names = "--open", arity = "0..1", preprocessor = Edit.MyPreprocessor.class,
description = {
"Optionally specify the editor to use (${COMPLETION-CANDIDATES}). " +
"If omitted the default editor is used. ",
"Example: edit --open=idea FILE opens IntelliJ IDEA (notice the '=' separator)",
" edit --open FILE opens the specified file in the default editor"
})
Editor editor = Editor.defaultEditor;
})
Editor editor = Editor.defaultEditor;

static class MyPreprocessor implements IParameterPreprocessor {
public boolean preprocess(Stack<String> args,
CommandSpec commandSpec,
ArgSpec argSpec,
Map<String, Object> info) {
// we need to decide whether the next arg is the file to edit
// or the name of the editor to use...
if (" ".equals(info.get("separator"))) { // parameter was not attached to option

// act as if the user specified --open=defaultEditor
args.push(Editor.defaultEditor.name());
}
return false; // picocli's internal parsing is resumed for this option
}
}
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
class Edit : Runnable {

static class MyPreprocessor implements IParameterPreprocessor {
public boolean preprocess(Stack<String> args,
CommandSpec commandSpec,
ArgSpec argSpec,
Map<String, Object> info) {
// we need to decide whether the next arg is the file to edit
// or the name of the editor to use...
if (" ".equals(info.get("separator"))) { // parameter was not attached to option
@Parameters(index = "0", arity = "0..1", description = ["The file to edit."])
var file: File? = null

// act as if the user specified --open=defaultEditor
args.push(Editor.defaultEditor.name());
}
return false; // picocli's internal parsing is resumed for this option
}
}
enum class Editor { defaultEditor, eclipse, idea, netbeans }

@Option(names = ["--open"], arity = "0..1", preprocessor = MyPreprocessor::class,
description = ["Optionally specify the editor to use (\${COMPLETION-CANDIDATES}). " +
"If omitted the default editor is used. ",
"Example: edit --open=idea FILE ",
" opens file in IntelliJ IDEA (notice the '=' separator)",
" edit --open FILE",
" opens the specified file in the default editor"])
var editor = Editor.defaultEditor

class MyPreprocessor : IParameterPreprocessor {
override fun preprocess(args: Stack<String>,
commandSpec: CommandSpec,
argSpec: ArgSpec,
info: Map<String, Any>): Boolean {
// we need to decide whether the next arg is the file to edit
// or the name of the editor to use ...
if (" " == info["separator"]) { // parameter was not attached to option

// act as if the user specified --open=defaultEditor
args.push(Editor.defaultEditor.name)
}
return false // picocli's internal parsing is resumed for this option
}
}
}
----

With this preprocessor, the following user input gives the following command state:
With this preprocessor in place the user can now specify his editor of choice (e.g. `--open=idea`). If no editor is given, the default editor is used:

----
# User input # Command State
# --------------------------
--open A B # editor: defaultEditor, file: A, unmatched: [B]
--open A # editor: defaultEditor, file: A, unmatched: []
--open=A B # editor: A, file: B, unmatched: []
--open=A # editor: A, file: null, unmatched: []
----
[grid=cols,cols=4*,options="header"]
|===
| User input | Option `Editor` | Parameter `File` | Unmatched input
| `--open file1 file2` | `defaultEditor` | `file1` | `file2`
| `--open file1` | `defaultEditor` | `file1` | none
| `--open=idea file1` | `idea` (user specified) | `file1` | none
| `--open=idea` | `idea` (user specified) | `null` | none
|===

==== Parser Plugin Comparison

.Differences between the `IParameterPreprocessor` and `IParameterConsumer` parser plugins.
[grid=cols,cols=2*,options="header"]
.Comparison of `IParameterPreprocessor` and `IParameterConsumer` parser plugins.
[grid=cols,cols=3*,options="header"]
|===
| `IParameterPreprocessor` | `IParameterConsumer`
| Either augment (return `false`) or replace (return `true`) picocli parsing logic for the matched element. | Replaces picocli parsing logic for the matched element.
| Commands as well as options and positional parameters. | Options and positional parameters only.
| Receives information on parser internal state (the `info` Map).| No equivalent.
| May communicate back to parser (by modifying the `info` Map).| No equivalent.
| | `IParameterPreprocessor` | `IParameterConsumer`
| *Principle* | Either augment (return `false`) or replace (return `true`) picocli parsing logic for the matched element. | Replaces picocli parsing logic for the matched element.
| *Scope* | Commands as well as options and positional parameters. | Options and positional parameters only.
| *Information on parser internal state* | Yes, information may received via the `info` Map | No
| *Communication back to parser* | Yes, via modifying the `info` Map | No
|===


Expand Down Expand Up @@ -5879,7 +5921,7 @@ Usage: <main class> <host> <port> [<files>...]

=== Show At Files

From picocli 4.2, an entry for `@<filename>` can be shown in the options and parameters list of the usage help message of a command with the `@Command(showAtFileInUsageHelp = true)` annotation.
As of picocli 4.2, an entry for `@<filename>` can be shown in the options and parameters list of the usage help message of a command with the `@Command(showAtFileInUsageHelp = true)` annotation.

==== Example

Expand Down Expand Up @@ -6441,7 +6483,7 @@ class GitCommit { /* ... */ }

CAUTION: Markup styles cannot be nested, for example: `@|bold this @|underline that|@|@` will not work. You can achieve the same by combining styles, for example: `@|bold this|@ @|bold,underline that|@` will work fine.

From picocli 4.2, custom markup like `@|bold mytext|@`, `@|italic mytext|@` etc. can also be converted to custom markup like `<b>mytext</b>` and `<i>mytext</i>` in HTML, or `pass:c[*mytext*]` and `pass:c[_mytext_]` in lightweight markup languages like AsciiDoc.
As of picocli 4.2, custom markup like `@|bold mytext|@`, `@|italic mytext|@` etc. can also be converted to custom markup like `<b>mytext</b>` and `<i>mytext</i>` in HTML, or `pass:c[*mytext*]` and `pass:c[_mytext_]` in lightweight markup languages like AsciiDoc.
Applications can control this by setting a `ColorScheme` with a custom markup map.
This feature is used to generate man page documentation.

Expand Down Expand Up @@ -10013,12 +10055,13 @@ interface IModelTransformer {

This allows applications to dynamically add or remove options, positional parameters or subcommands, or modify the command in any other way, based on some runtime condition.

[source,java]
.Java
[source,java,role="primary"]
----
@Command(modelTransformer = Dynamic.SubCmdFilter.class)
class Dynamic {

private static class SubCmdFilter implements IModelTransformer {
static class SubCmdFilter implements IModelTransformer {
public CommandSpec transform(CommandSpec commandSpec) {
if (Boolean.getBoolean("disable_sub")) {
commandSpec.removeSubcommand("sub");
Expand All @@ -10029,7 +10072,29 @@ class Dynamic {

@Command
private void sub() {
// subcommand business logic
// subcommand, business logic
}
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
@Command(modelTransformer = Dynamic.SubCmdFilter::class)
class Dynamic {

class SubCmdFilter : CommandLine.IModelTransformer {
override fun transform(commandSpec: CommandSpec): CommandSpec {
if (Boolean.getBoolean("disable_sub")) {
commandSpec.removeSubcommand("sub")
}
return commandSpec
}
}

@Command
private fun sub() {
// subcommand, business logic
}
}
----
Expand Down Expand Up @@ -11863,19 +11928,19 @@ assert this.commandLine.commandName == "myScript"

The older `@picocli.groovy.PicocliScript` annotation is deprecated from picocli 4.6.
New scripts should use the `@picocli.groovy.PicocliScript2` annotation (and associated `picocli.groovy.PicocliBaseScript2` base class) instead.
The table below lists the differences between these base classes.
The table below compares these two base classes.

.Differences between the `PicocliBaseScript2` and `PicocliBaseScript` script base classes.
[grid=cols,cols=2*,options="header"]
.Comparison of `PicocliBaseScript2` and `PicocliBaseScript` script base classes.
[grid=cols,cols=3*,options="header"]
|===
| `PicocliBaseScript2` | `PicocliBaseScript`
| Subcommands can be defined as `@Command`-annotated methods in the script. | No support for `@Command`-annotated methods.
| Support for `help` subcommands (both the built-in one and custom ones). | No support for `help` subcommands.
| Exit code support: scripts can override `afterExecution(CommandLine, int, Exception)` to call `System.exit`.| No support for exit code.
| Invokes https://picocli.info/#execute[`CommandLine::execute`]. Scripts can override `beforeParseArgs(CommandLine)` to install a custom `IExecutionStrategy`.| Execution after parsing is defined in `PicocliBaseScript::run` and is not easy to customize. Any subcommand _and_ the main script are _both_ executed.
| Scripts can override `beforeParseArgs(CommandLine)` to install a custom `IParameterExceptionHandler`. | Invalid input handling can be customized by overriding `PicocliBaseScript::handleParameterException`.
| Scripts can override `beforeParseArgs(CommandLine)` to install a custom `IExecutionExceptionHandler`. | Runtime exception handling can be customized by overriding `PicocliBaseScript::handleExecutionException`.
| Implements `Callable<Object>`, script body is transformed to the `call` method. | Script body is transformed to the `runScriptBody` method.
| | `PicocliBaseScript2` | `PicocliBaseScript`
| *Subcommands as methods* | Supported, subcommands can be defined as `@Command`-annotated methods in the script. | Unsupported
| *`help` subcommands* | Supported, both the built-in one and custom ones. | Unsupported
| *Exit codes* | Supported: scripts can override `afterExecution(CommandLine, int, Exception)` to call `System.exit`.| Unsupported
| *Command execution* | Via invocation of https://picocli.info/#execute[`CommandLine::execute`]. Scripts can override `beforeParseArgs(CommandLine)` to install a custom `IExecutionStrategy`. | Execution after parsing is defined in `PicocliBaseScript::run` and is not easy to customize. Any subcommand _and_ the main script are _both_ executed.
| *Custom handling of invalid user input* | Scripts can override `beforeParseArgs(CommandLine)` to install a custom `IParameterExceptionHandler`. | Invalid input handling can be customized by overriding `PicocliBaseScript::handleParameterException`.
| *Custom error handling* | Scripts can override `beforeParseArgs(CommandLine)` to install a custom `IExecutionExceptionHandler`. | Runtime exception handling can be customized by overriding `PicocliBaseScript::handleExecutionException`.
| *Principle* | Implements `Callable<Object>`, script body is transformed to the `call` method. | Script body is transformed to the `runScriptBody` method.
|===


Expand All @@ -11885,7 +11950,7 @@ Scripts should use `@Grab('info.picocli:picocli-groovy:4.6.2-SNAPSHOT')`. The ol
because the `@picocli.groovy.PicocliScript` annotation class and supporting classes have been moved into a separate module, `picocli-groovy`.

==== Closures in Annotations
From picocli 4.6, Groovy programs can use closures in the picocli annotations instead of specifying a class.
As of picocli 4.6, Groovy programs can use closures in the picocli annotations instead of specifying a class.
This can be especially useful in Groovy scripts, where one cannot define a static inner class.

Example:
Expand Down
4 changes: 2 additions & 2 deletions picocli-examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
}

dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21"
}
}

Expand Down Expand Up @@ -38,7 +38,7 @@ dependencies {
"org.hibernate.validator:hibernate-validator-annotation-processor:6.1.2.Final",
"javax.el:javax.el-api:3.0.0",
"org.glassfish.web:javax.el:2.2.6"
implementation "org.jetbrains.kotlin:kotlin-script-runtime:1.4.10"
implementation "org.jetbrains.kotlin:kotlin-script-runtime:1.4.21"
}
tasks.withType(GroovyCompile) {
// this, and the `configurations {ivy}` section, are a workaround for the dreaded
Expand Down