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 4 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
142 changes: 101 additions & 41 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 @@ -4776,7 +4777,7 @@ class ExecParameterConsumer : IParameterConsumer {
}
----

==== `IParameterPreprocessor` Parser Plugin
==== IParameterPreprocessor Parser Plugin
deining marked this conversation as resolved.
Show resolved Hide resolved

Introduced in picocli 4.6, the `IParameterPreprocessor` is also a parser plugin, similar to `IParameterConsumer`, but more flexible.

Expand All @@ -4798,26 +4799,27 @@ 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 { eclipse, idea, netbeans }
deining marked this conversation as resolved.
Show resolved Hide resolved

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the above, I think it is meaningful to have a defaultEditor choice, so let's keep the original description.

Editor editor = Editor.defaultEditor;
static Editor editor = Editor.eclipse;
deining marked this conversation as resolved.
Show resolved Hide resolved

static class MyPreprocessor implements IParameterPreprocessor {
public boolean preprocess(Stack<String> args,
Expand All @@ -4828,36 +4830,71 @@ class 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());
// act as if the user specified --open=eclipse
args.push(editor.name());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is good to explicitly use the enum constant that represents the default, so Editor.defaultEditor (or Editor.eclipse if Eclipse were the default).

This is mostly for clarity: so that people reading this code example in the documentation would not have to refer back in the code to what the actual value would be.

I realize that in "real" code there is a tension here with the principle of having information in only once place. To be honest, I am not sure what I would do in a "real" application in this case. :-)

But for documentation purposes, I believe that duplicating the default value in the preprocessor is better because it requires less work on the part of the reader.

}
return false; // picocli's internal parsing is resumed for this option
}
}
}
----

With this preprocessor, the following user input gives the following command state:

.Kotlin
[source,kotlin,role="secondary"]
----
# 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: []
class Edit : Runnable {

@Parameters(index = "0", arity = "0..1", description = ["The file to edit."])
var file: File? = null

enum class Editor { eclipse, idea, netbeans }

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

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=eclipse
args.push(argSpec.getValue<Editor>().name)
}
return false // picocli's internal parsing is resumed for this option
}
}
}
----

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 (=`eclipse`) is used:

[grid=cols,cols=4*,options="header"]
|===
| User input | Option `Editor` | Parameter `File` | Unmatched input
| `--open file1 file2` | `eclipse` (default) | `file1` | `file2`
| `--open file1` | `eclipse` (default) | `file1` | none
| `--open=idea file1` | `idea` (user specificed) | `file1` | none
| `--open=idea` | `idea` (user specificed) | `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 +5916,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 +6478,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 +10050,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 +10067,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 +11923,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 +11945,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