mirror of
https://github.com/jlengrand/picocli.git
synced 2026-03-10 08:41:17 +00:00
[#1191] Finalize @PicocliScript2 API, update DOCS and tests
This commit is contained in:
@@ -6,6 +6,8 @@ The picocli community is pleased to announce picocli 4.6.0.
|
||||
|
||||
This release contains bug fixes and enhancements.
|
||||
|
||||
The `picocli-groovy` module gets a facelift. This release introduces a new `@PicocliScript2` annotation that adds support for exit codes and `@Command`-annotated methods to define subcommands.
|
||||
|
||||
From this release, Map options accept key-only parameters, so end users can specify `-Dkey` as well as `-Dkey=value`.
|
||||
There is a new `mapFallbackValue` attribute that enables this, which can be used to control the value that is put into the map when only a key was specified on the command line.
|
||||
|
||||
@@ -26,6 +28,22 @@ Picocli follows [semantic versioning](http://semver.org/).
|
||||
|
||||
## <a name="4.6.0-new"></a> New and Noteworthy
|
||||
|
||||
### New `@PicocliScript2` annotation
|
||||
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 the `PicocliBaseScript2` and `PicocliBaseScript` script base classes.
|
||||
|
||||
| `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 `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.
|
||||
|
||||
|
||||
### Key-only map parameters
|
||||
By default, picocli expects Map options and positional parameters to look like `key=value`, that is, the option parameter or positional parameter is expected to have a key part and a value part, separated by a `=` character.
|
||||
|
||||
@@ -88,6 +106,7 @@ only single-value types, and the values in a `Map` (but not the keys!) can be wr
|
||||
|
||||
|
||||
## <a name="4.6.0-fixes"></a> Fixed issues
|
||||
* [#1191] API: Add `@PicocliScript2` annotation to support subcommand methods in Groovy scripts. Thanks to [Mattias Andersson](https://github.com/attiand) for raising this.
|
||||
* [#1241] API: Add `mapFallbackValue` attribute to `@Options` and `@Parameters` annotations, and corresponding `ArgSpec.mapFallbackValue()`.
|
||||
* [#1184] API: Added public methods `Help.Layout::colorScheme`, `Help.Layout::textTable`, `Help.Layout::optionRenderer`, `Help.Layout::parameterRenderer`, and `Help::calcLongOptionColumnWidth`.
|
||||
* [#1108] Enhancement: Support `Optional<T>` type for options and positional parameters. Thanks to [Max Rydahl Andersen](https://github.com/maxandersen) for raising this.
|
||||
|
||||
@@ -11386,13 +11386,15 @@ class MyApp implements Runnable {
|
||||
----
|
||||
|
||||
==== Groovy Scripts
|
||||
Picocli 2.0 introduced special support for Groovy scripts. (From picocli 4.0 this was moved into a separate module, `picocli-groovy`.)
|
||||
Picocli 2.0 introduced special support for Groovy scripts.
|
||||
From picocli 4.0 this was moved into a separate module, `picocli-groovy`.
|
||||
In picocli 4.6, `@PicocliScript` was deprecated in favor of `@PicocliScript2`.
|
||||
|
||||
Scripts annotated with `@picocli.groovy.PicocliScript` are automatically transformed to use
|
||||
`picocli.groovy.PicocliBaseScript` as their base class and can also use the `@Command` annotation to
|
||||
Scripts annotated with `@picocli.groovy.PicocliScript2` are automatically transformed to use
|
||||
`picocli.groovy.PicocliBaseScript2` as their base class and can also use the `@Command` annotation to
|
||||
customize parts of the usage message like command name, description, headers, footers etc.
|
||||
|
||||
Before the script body is executed, the `PicocliBaseScript` base class parses the command line and initializes
|
||||
Before the script body is executed, the `PicocliBaseScript2` base class parses the command line and initializes
|
||||
`@Field` variables annotated with `@Option` or `@Parameters`.
|
||||
The script body is executed if the user input was valid and did not request usage help or version information.
|
||||
|
||||
@@ -11403,14 +11405,14 @@ The script body is executed if the user input was valid and did not request usag
|
||||
@Command(name = "myScript",
|
||||
mixinStandardHelpOptions = true, // add --help and --version options
|
||||
description = "@|bold Groovy script|@ @|underline picocli|@ example")
|
||||
@picocli.groovy.PicocliScript
|
||||
@picocli.groovy.PicocliScript2
|
||||
import groovy.transform.Field
|
||||
import static picocli.CommandLine.*
|
||||
|
||||
@Option(names = ["-c", "--count"], description = "number of repetitions")
|
||||
@Field int count = 1;
|
||||
|
||||
// PicocliBaseScript prints usage help or version if requested by the user
|
||||
// PicocliBaseScript2 prints usage help or version if requested by the user
|
||||
|
||||
count.times {
|
||||
println "hi"
|
||||
@@ -11419,7 +11421,26 @@ count.times {
|
||||
assert this.commandLine.commandName == "myScript"
|
||||
----
|
||||
|
||||
WARNING: When upgrading scripts to picocli 4.0, just changing the version number is not enough!
|
||||
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.
|
||||
|
||||
.Differences between the `PicocliBaseScript2` and `PicocliBaseScript` script base classes.
|
||||
[grid=cols,cols=2*,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.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
WARNING: When upgrading scripts from picocli versions older than 4.0, just changing the version number is not enough!
|
||||
Scripts should use `@Grab('info.picocli:picocli-groovy:4.5.3-SNAPSHOT')`. The old artifact id `@Grab('info.picocli:picocli:4.5.3-SNAPSHOT')` will not work,
|
||||
because the `@picocli.groovy.PicocliScript` annotation class and supporting classes have been moved into a separate module, `picocli-groovy`.
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Base script class that provides picocli declarative (annotation-based) command line argument processing for Groovy scripts.
|
||||
* Base script class that provides picocli declarative (annotation-based) command line
|
||||
* argument processing for Groovy scripts, superseded by {@link PicocliBaseScript2}.
|
||||
* </p><p>
|
||||
* Scripts may install this base script via the {@link PicocliScript} annotation or via the standard Groovy
|
||||
* {@code @groovy.transform.BaseScript(picocli.groovy.PicocliBaseScript)} annotation, but
|
||||
@@ -57,6 +58,8 @@ import java.util.concurrent.Callable;
|
||||
* @author Jim White
|
||||
* @author Remko Popma
|
||||
* @since 2.0
|
||||
* @see PicocliBaseScript2
|
||||
* @deprecated Use {@link PicocliBaseScript2} instead.
|
||||
*/
|
||||
abstract public class PicocliBaseScript extends Script {
|
||||
/**
|
||||
|
||||
@@ -4,10 +4,9 @@ import groovy.lang.GroovyRuntimeException;
|
||||
import groovy.lang.MissingPropertyException;
|
||||
import groovy.lang.Script;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
import picocli.CommandLine.IExecutionExceptionHandler;
|
||||
import picocli.CommandLine.IParameterExceptionHandler;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.ParameterException;
|
||||
import picocli.CommandLine.ParseResult;
|
||||
|
||||
@@ -18,7 +17,8 @@ import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Base script class that provides picocli declarative (annotation-based) command line argument processing for Groovy scripts.
|
||||
* Base script class that provides picocli declarative (annotation-based) command line argument
|
||||
* processing for Groovy scripts, updated for picocli version 4 and greater.
|
||||
* </p><p>
|
||||
* Scripts may install this base script via the {@link PicocliScript2} annotation or via the standard Groovy
|
||||
* {@code @groovy.transform.BaseScript(picocli.groovy.PicocliBaseScript2)} annotation, but
|
||||
@@ -27,11 +27,17 @@ import java.util.concurrent.Callable;
|
||||
* </p>
|
||||
* <h1>Example usage</h1>
|
||||
* <pre>
|
||||
* @Command(name = "myCommand", description = "does something special")
|
||||
* @Command(name = "greet", description = "Says hello.", mixinStandardHelpOptions = true, version = "greet 0.1")
|
||||
* @PicocliScript2
|
||||
* import picocli.groovy.PicocliScript2
|
||||
* import picocli.CommandLine.Command
|
||||
* ...
|
||||
* import picocli.CommandLine.Option
|
||||
* import groovy.transform.Field
|
||||
*
|
||||
* @Option(names = ['-g', '--greeting'], description = 'Type of greeting')
|
||||
* @Field String greeting = 'Hello'
|
||||
*
|
||||
* println "${greeting} world!"
|
||||
* </pre>
|
||||
* <p>
|
||||
* Before the script body is executed,
|
||||
@@ -40,7 +46,16 @@ import java.util.concurrent.Callable;
|
||||
* It also takes care of error handling and common use cases like requests for usage help.
|
||||
* </p><p>
|
||||
* See the {@link #run()} method for a detailed break-down of the steps the base class takes
|
||||
* before the statements in the script body are executed.
|
||||
* before and after the statements in the script body are executed.
|
||||
* </p>
|
||||
* <h1>Customization</h1>
|
||||
* <p>
|
||||
* Scripts can override {@link #beforeParseArgs(CommandLine)} to
|
||||
* change the parser configuration, or set custom exception handlers etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* Scripts can override {@link #afterExecution(CommandLine, int, Exception)} to
|
||||
* call {@code System.exit} or return a custom result.
|
||||
* </p>
|
||||
* <h1>PicocliBaseScript2 vs PicocliBaseScript</h1>
|
||||
* <p>
|
||||
@@ -49,9 +64,13 @@ import java.util.concurrent.Callable;
|
||||
* <ul>
|
||||
* <li>Adds support for {@code @Command}-annotated methods to define subcommands in scripts.</li>
|
||||
* <li>Adds support for {@code help} subcommands (both the built-in {@code CommandLine.HelpCommand} and custom implementations).</li>
|
||||
* <li>Adds support for exit codes. The return value of the script becomes the exit code.</li>
|
||||
* <li>Consistency with Java picocli. This implementation delegates to the {@code CommandLine} class,
|
||||
* while {@code PicocliBaseScript} re-implemented features, and this re-implementation got out of sync over time.</li>
|
||||
* <li>Adds support for exit codes.</li>
|
||||
* <li>Consistency with Java picocli. The new {@code PicocliBaseScript2} base class delegates to the
|
||||
* {@code CommandLine::execute} method introduced in picocli 4.0. This allows scripts to
|
||||
* leverage new features of the picocli library, as well as future enhancements, more easily.
|
||||
* By contrast, the old {@code PicocliBaseScript} base class implemented its own execution strategy,
|
||||
* which over time fell behind the core picocli library.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Remko Popma
|
||||
@@ -86,59 +105,102 @@ public abstract class PicocliBaseScript2 extends Script implements Callable<Obje
|
||||
* Here is a break-down of the steps the base class takes before the statements in the script body are executed:
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>A new {@link CommandLine} is created with this script instance as the annotated command object.
|
||||
* <li>Call {@link #getOrCreateCommandLine()} to create a new {@link CommandLine} with this
|
||||
* script instance as the annotated command object.
|
||||
* The {@code CommandLine} instance is cached in the {@code commandLine} property
|
||||
* (so it can be referred to in script code with
|
||||
* {@code this.commandLine}). {@code CommandLine} creation and initialization may be
|
||||
* customized by overriding {@link #createCommandLine()} or {@link #customize(CommandLine)}.</li>
|
||||
* <li>The {@link CommandLine#execute(String...)} method is called with the script arguments.
|
||||
* This initialises all {@code @Field} variables annotated with {@link CommandLine.Option} or
|
||||
* {@link CommandLine.Parameters}, unless the user input was invalid.</li>
|
||||
* <li>If the user input was invalid, the command line arguments are printed to standard err, followed by
|
||||
* an error message and the usage message.
|
||||
* Then, the script exits.
|
||||
* This may be customized by overriding {@link #customize(CommandLine)} and setting a custom
|
||||
* {@link IParameterExceptionHandler} on the {@code CommandLine} instance.</li>
|
||||
* <li>Otherwise, if the user input requested version help or usage help, the version string or usage help message is
|
||||
* printed to standard err and the script exits.</li>
|
||||
* <li>The script may support subcommands. In that case only the last specified subcommand is invoked.
|
||||
* This may be customized by overriding
|
||||
* {@link #customize(CommandLine)} and setting a custom
|
||||
* {@link picocli.CommandLine.IExecutionStrategy} on the {@code CommandLine} instance.</li>
|
||||
* <li>If no subcommand was specified, the script body is executed.</li>
|
||||
* <li>If an exception occurs during execution, this exception is rethrown.</li>
|
||||
* {@code this.commandLine}). Scripts may override.</li>
|
||||
* <li>Call {@link #beforeParseArgs(CommandLine)} to install custom handlers for
|
||||
* invalid user input or exceptions are installed.
|
||||
* Scripts may override.
|
||||
* </li>
|
||||
* <li>Call {@link CommandLine#execute(String...)} method with the script arguments.
|
||||
* This results in the following:
|
||||
* <ul>
|
||||
* <li>Assuming the user input was valid, this initialises all {@code @Field}
|
||||
* variables annotated with {@link CommandLine.Option} or {@link CommandLine.Parameters}.
|
||||
* </li>
|
||||
* <li>Otherwise, if the user input was invalid, the command line arguments are printed
|
||||
* to standard err, followed by an error message and the usage message.
|
||||
* Then, the script exits (see step 4 below).
|
||||
* This may be customized by overriding {@link #beforeParseArgs(CommandLine)} and setting a custom
|
||||
* {@link IParameterExceptionHandler} on the {@code CommandLine} instance.</li>
|
||||
* <li>Otherwise, if the user input requested version help or usage help, the version string or usage help message is
|
||||
* printed to standard err and the script exits (see step 4 below).</li>
|
||||
* <li>The script may define subcommands. In that case only the last specified subcommand is invoked.
|
||||
* This may be customized by overriding
|
||||
* {@link #beforeParseArgs(CommandLine)} and setting a custom
|
||||
* {@link picocli.CommandLine.IExecutionStrategy} on the {@code CommandLine} instance.</li>
|
||||
* <li>If no subcommand was specified, the script body is executed.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Store the exit code returned by {@code CommandLine.execute} in the {@code exitCode} property of this script.</li>
|
||||
* <li>Call {@link #afterExecution(CommandLine, int, Exception)} to handle script exceptions and return the script result:
|
||||
* If an exception occurred during execution, this exception is rethrown, wrapped in a {@code GroovyRuntimeException}.
|
||||
* Otherwise, the result of the script is returned, either as a list (in case of multiple results), or as a single object.
|
||||
* </li>
|
||||
* </ol>
|
||||
* <h1>Exit Code</h1>
|
||||
* <p>After execution, the {@code PicocliBaseScript2} class sets the exit code in the {@code exitCode} property of the script.</p>
|
||||
* <p>
|
||||
* Scripts that want to control the exit code of the process executing the script
|
||||
* need to override the {@link #run()} method and call {@code System.exit}.
|
||||
* Scripts that want to control the exit code of the process that executed the script
|
||||
* can override the {@link #afterExecution(CommandLine, int, Exception)} method and call {@code System.exit}.
|
||||
* For example:
|
||||
* </p><pre>{@code
|
||||
* @Override
|
||||
* public Object run() {
|
||||
* try {
|
||||
* super.run();
|
||||
* } finally {
|
||||
* System.exit(exitCode);
|
||||
* }
|
||||
* }
|
||||
* </p><pre>
|
||||
* @Override
|
||||
* protected Object afterExecution(CommandLine commandLine, int exitCode, Exception exception) {
|
||||
* exception?.printStackTrace();
|
||||
* System.exit(exitCode);
|
||||
* }</pre>
|
||||
* @return The result of the script evaluation.
|
||||
*/
|
||||
@Override
|
||||
public Object run() {
|
||||
String[] args = getScriptArguments();
|
||||
CommandLine commandLine = getOrCreateCommandLine();
|
||||
CommandLine commandLine = beforeParseArgs(getOrCreateCommandLine());
|
||||
exitCode = commandLine.execute(args);
|
||||
return afterExecution(commandLine, exitCode, exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after the script has been executed, and may do one of three things:
|
||||
* <ul>
|
||||
* <li>Call {@code System.exit} with the specified exit code.</li>
|
||||
* <li>Throw a {@code GroovyRuntimeException} with the specified exception.</li>
|
||||
* <li>Return the result of the script execution.</li>
|
||||
* </ul>
|
||||
* <p>By default, this method will throw a {@code GroovyRuntimeException} if the specified
|
||||
* exception is not {@code null}, and otherwise returns the result of the script execution.
|
||||
* </p><p>
|
||||
* Scripts may override this method to call {@code System.exit} or do something else.
|
||||
* </p>
|
||||
* @param commandLine encapsulates the picocli model of the command and subcommands
|
||||
* @param exitCode the exit code that resulted from executing the script's command and/or subcommand(s)
|
||||
* @param exception {@code null} if the script executed successfully without throwing any exceptions,
|
||||
* otherwise this is the exception thrown by the script
|
||||
* @return the script result; this may be a list of results, a single object, or {@code null}.
|
||||
* If multiple commands/subcommands were executed, this method may return a {@code List},
|
||||
* which may contain some {@code null} values.
|
||||
* For a single command, this method will return the return value of the script,
|
||||
* which is often the value of the last expression in the script.
|
||||
*/
|
||||
protected Object afterExecution(CommandLine commandLine, int exitCode, Exception exception) {
|
||||
if (exception != null) {
|
||||
throw new GroovyRuntimeException(exception);
|
||||
}
|
||||
return scriptResult(commandLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script result; this may be a list of results, a single object, or {@code null}.
|
||||
* If multiple commands/subcommands were executed, this method may return a {@code List}, which may contain some {@code null} values.
|
||||
* For a single command, this method will return the return value of the script,
|
||||
* which is often the value of the last expression in the script.
|
||||
*/
|
||||
private Object scriptResult(CommandLine commandLine) {
|
||||
List<Object> result = new ArrayList<Object>();
|
||||
List<CommandLine> commandLines = commandLine.getParseResult().asCommandLineList();
|
||||
for (CommandLine cl : commandLines) {
|
||||
if (cl.getExecutionResult() != null || !result.isEmpty()) {
|
||||
if (cl.getExecutionResult() != null || !result.isEmpty()) { // if multiple results, some may be null
|
||||
result.add(cl.getExecutionResult());
|
||||
}
|
||||
}
|
||||
@@ -146,7 +208,7 @@ public abstract class PicocliBaseScript2 extends Script implements Callable<Obje
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the script arguments as an array of strings.
|
||||
* Returns the script arguments as an array of strings.
|
||||
* The default implementation is to get the "args" property.
|
||||
*
|
||||
* @return the script arguments as an array of strings.
|
||||
@@ -156,8 +218,8 @@ public abstract class PicocliBaseScript2 extends Script implements Callable<Obje
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CommandLine for this script.
|
||||
* If there isn't one already, then create it using {@link #createCommandLine()}.
|
||||
* Returns the CommandLine for this script.
|
||||
* If there isn't one already, then this method returns the result of the {@link #createCommandLine()} method.
|
||||
*
|
||||
* @return the CommandLine for this script.
|
||||
*/
|
||||
@@ -174,16 +236,35 @@ public abstract class PicocliBaseScript2 extends Script implements Callable<Obje
|
||||
// the property has a setter before creating a new script binding.
|
||||
this.getMetaClass().setProperty(this, COMMAND_LINE, commandLine);
|
||||
}
|
||||
return customize(commandLine);
|
||||
return commandLine;
|
||||
} catch (MissingPropertyException mpe) {
|
||||
CommandLine commandLine = createCommandLine();
|
||||
// Since no property or binding already exists, we can use plain old setProperty here.
|
||||
setProperty(COMMAND_LINE, commandLine);
|
||||
return customize(commandLine);
|
||||
return commandLine;
|
||||
}
|
||||
}
|
||||
|
||||
protected CommandLine customize(final CommandLine customizable) {
|
||||
/**
|
||||
* Customizes the specified {@code CommandLine} instance to set a custom
|
||||
* {@code IParameterExceptionHandler} and a custom {@code IExecutionExceptionHandler},
|
||||
* subclasses can override to customize further.
|
||||
* <p>
|
||||
* This method replaces the default {@code IParameterExceptionHandler} with a custom
|
||||
* one that prints the command line arguments before calling the default parameter exception handler.
|
||||
* </p>
|
||||
* <p>
|
||||
* This method replaces the default {@code IExecutionExceptionHandler} with a custom
|
||||
* one that stores any exception that occurred during execution into the {@code exception}
|
||||
* property of this script, and returns the
|
||||
* {@link CommandSpec#exitCodeOnExecutionException() configured exit code}.
|
||||
* This exception is later passed to the {@link #afterExecution(CommandLine, int, Exception)} method.
|
||||
* </p>
|
||||
* @param customizable the {@code CommandLine} instance that models this command and its subcommands
|
||||
* @return the customized {@code CommandLine} instance (usually, but not necessarily,
|
||||
* the same instance as the method parameter)
|
||||
*/
|
||||
protected CommandLine beforeParseArgs(final CommandLine customizable) {
|
||||
final IParameterExceptionHandler original = customizable.getParameterExceptionHandler();
|
||||
customizable.setParameterExceptionHandler(new IParameterExceptionHandler() {
|
||||
public int handleParseException(ParameterException ex, String[] args) throws Exception {
|
||||
@@ -193,8 +274,8 @@ public abstract class PicocliBaseScript2 extends Script implements Callable<Obje
|
||||
});
|
||||
customizable.setExecutionExceptionHandler(new IExecutionExceptionHandler() {
|
||||
public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult) throws Exception {
|
||||
exception = ex; // any exception will be rethrown from run()
|
||||
return CommandLine.ExitCode.SOFTWARE;
|
||||
exception = ex; // any exception will be rethrown from afterExecution()
|
||||
return customizable.getCommandSpec().exitCodeOnExecutionException();
|
||||
}
|
||||
});
|
||||
return customizable;
|
||||
@@ -212,7 +293,7 @@ public abstract class PicocliBaseScript2 extends Script implements Callable<Obje
|
||||
*/
|
||||
public CommandLine createCommandLine() {
|
||||
CommandLine commandLine = new CommandLine(this);
|
||||
if (commandLine.getCommandName().equals("<main class>")) { // only if user did not specify @Command(name) attribute
|
||||
if (CommandSpec.DEFAULT_COMMAND_NAME.equals(commandLine.getCommandName())) { // only if user did not specify @Command(name) attribute
|
||||
commandLine.setCommandName(this.getClass().getSimpleName());
|
||||
}
|
||||
return commandLine;
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Annotation to give Groovy scripts convenient access to picocli functionality.
|
||||
* Annotation to give Groovy scripts convenient access to picocli functionality, superseded by {@link PicocliScript2}.
|
||||
* Scripts may annotate the package statement, an import statement or a local variable with
|
||||
* {@code @PicocliScript} and the script base class will be {@link PicocliScriptASTTransformation transformed} to
|
||||
* {@link picocli.groovy.PicocliBaseScript}.
|
||||
@@ -80,13 +80,16 @@ import java.lang.annotation.Target;
|
||||
* </pre>
|
||||
*
|
||||
* @see PicocliScriptASTTransformation
|
||||
* @see PicocliScript2
|
||||
* @author Remko Popma
|
||||
* @since 2.0
|
||||
* @deprecated Use {@link PicocliScript2} instead.
|
||||
*/
|
||||
@java.lang.annotation.Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.LOCAL_VARIABLE, ElementType.PACKAGE, ElementType.TYPE, ElementType.FIELD /*, ElementType.IMPORT*/})
|
||||
@GroovyASTTransformationClass("picocli.groovy.PicocliScriptASTTransformation")
|
||||
public @interface PicocliScript {
|
||||
@SuppressWarnings("deprecation")
|
||||
Class<?> value() default PicocliBaseScript.class;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Annotation to give Groovy scripts convenient access to picocli functionality.
|
||||
* Annotation to give Groovy scripts convenient access to picocli functionality, updated for picocli version 4 and greater.
|
||||
* Scripts may annotate the package statement, an import statement or a local variable with
|
||||
* {@code @PicocliScript2} and the script base class will be {@link PicocliScriptASTTransformation transformed} to
|
||||
* {@link PicocliBaseScript2}.
|
||||
@@ -81,16 +81,8 @@ import java.lang.annotation.Target;
|
||||
* </pre>
|
||||
* <h1>PicocliBaseScript2 vs PicocliBaseScript</h1>
|
||||
* <p>
|
||||
* This class has the following improvements over {@link PicocliScript}:
|
||||
* See the {@link PicocliBaseScript2} documentation for details.
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Adds support for {@code @Command}-annotated methods to define subcommands in scripts.</li>
|
||||
* <li>Adds support for {@code help} subcommands (both the built-in {@code CommandLine.HelpCommand} and custom implementations).</li>
|
||||
* <li>Adds support for exit codes. The return value of the script becomes the exit code.</li>
|
||||
* <li>Consistency with Java picocli.
|
||||
* The new {@link PicocliBaseScript2} implementation delegates to the {@code CommandLine} class,
|
||||
* while {@link PicocliBaseScript} re-implemented features, and this re-implementation got out of sync over time.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see PicocliScriptASTTransformation
|
||||
* @author Remko Popma
|
||||
@@ -101,5 +93,5 @@ import java.lang.annotation.Target;
|
||||
@Target({ElementType.LOCAL_VARIABLE, ElementType.PACKAGE, ElementType.TYPE, ElementType.FIELD /*, ElementType.IMPORT*/})
|
||||
@GroovyASTTransformationClass("picocli.groovy.PicocliScriptASTTransformation")
|
||||
public @interface PicocliScript2 {
|
||||
Class<?> value() default PicocliBaseScript.class;
|
||||
Class<?> value() default PicocliBaseScript2.class;
|
||||
}
|
||||
|
||||
@@ -45,9 +45,11 @@ import picocli.CommandLine;
|
||||
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
|
||||
public class PicocliScriptASTTransformation extends AbstractASTTransformation {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static final Class<PicocliScript> MY_CLASS1 = PicocliScript.class;
|
||||
private static final Class<PicocliScript2> MY_CLASS2 = PicocliScript2.class;
|
||||
private static final Class<CommandLine.Command> COMMAND_CLASS = CommandLine.Command.class;
|
||||
@SuppressWarnings("deprecation")
|
||||
private static final Class<PicocliBaseScript> BASE_SCRIPT1_CLASS = PicocliBaseScript.class;
|
||||
private static final Class<PicocliBaseScript2> BASE_SCRIPT2_CLASS = PicocliBaseScript2.class;
|
||||
private static final ClassNode MY_TYPE1 = ClassHelper.make(MY_CLASS1);
|
||||
|
||||
@@ -52,9 +52,6 @@ import picocli.CommandLine
|
||||
@CommandLine.Option(names = ["-cp", "--codepath"])
|
||||
@Field List<String> codepath = []
|
||||
|
||||
//println parameters
|
||||
//println codepath
|
||||
|
||||
assert parameters == ['placeholder', 'another']
|
||||
assert codepath == ['/usr/x.jar', '/bin/y.jar', 'z']
|
||||
|
||||
@@ -196,9 +193,6 @@ import picocli.CommandLine
|
||||
|
||||
@Test
|
||||
void testScriptExecutionExceptionWrapsException() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
System.setErr(new PrintStream(baos))
|
||||
|
||||
String script = '''
|
||||
@picocli.CommandLine.Command
|
||||
@picocli.groovy.PicocliScript2
|
||||
@@ -375,7 +369,7 @@ class Message {
|
||||
String target
|
||||
}
|
||||
'''
|
||||
GroovyShell shell = new GroovyShell(new Binding())
|
||||
GroovyShell shell = new GroovyShell()
|
||||
shell.context.setVariable('args', ["-g", "Hi"] as String[])
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
@@ -409,7 +403,7 @@ println "done"
|
||||
@Test
|
||||
void testSubcommandMethods1191() {
|
||||
|
||||
GroovyShell shell = new GroovyShell(new Binding())
|
||||
GroovyShell shell = new GroovyShell()
|
||||
shell.context.setVariable('args', ["commit", "picocli.groovy"] as String[])
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
@@ -421,7 +415,7 @@ println "done"
|
||||
|
||||
@Test
|
||||
void testSubcommandMethodHelp() {
|
||||
GroovyShell shell = new GroovyShell(new Binding())
|
||||
GroovyShell shell = new GroovyShell()
|
||||
shell.context.setVariable('args', ["help", "commit"] as String[])
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
@@ -439,7 +433,7 @@ println "done"
|
||||
|
||||
@Test
|
||||
void testMultipleSubcommandMethodsHelp() {
|
||||
GroovyShell shell = new GroovyShell(new Binding())
|
||||
GroovyShell shell = new GroovyShell()
|
||||
shell.context.setVariable('args', ["--help"] as String[])
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
@@ -456,4 +450,92 @@ println "done"
|
||||
assertEquals(String.format(expected), baos.toString().trim())
|
||||
assertEquals(null, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeforeParseArgs() {
|
||||
String script = '''
|
||||
import picocli.CommandLine.Model.CommandSpec
|
||||
import picocli.CommandLine
|
||||
import static picocli.CommandLine.*
|
||||
|
||||
@Command(name="testBeforeParseArgs")
|
||||
@picocli.groovy.PicocliScript2
|
||||
import groovy.transform.Field
|
||||
|
||||
@picocli.CommandLine.Spec
|
||||
@Field CommandSpec spec;
|
||||
|
||||
@Option(names = ['-g', '--greeting'], description = 'Type of greeting')
|
||||
@Field String greeting = 'Hello'
|
||||
|
||||
println "${greeting} ${spec.name()} world!"
|
||||
|
||||
@Override
|
||||
protected CommandLine beforeParseArgs(CommandLine customizable) {
|
||||
customizable.setCommandName("modifiedName")
|
||||
customizable.setOptionsCaseInsensitive(true)
|
||||
return super.beforeParseArgs(customizable)
|
||||
}
|
||||
'''
|
||||
GroovyShell shell = new GroovyShell()
|
||||
shell.context.setVariable('args', ["--GREETING", "Hi"] as String[])
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
System.setOut(new PrintStream(baos))
|
||||
shell.evaluate script
|
||||
assertEquals("Hi modifiedName world!", baos.toString().trim())
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAfterExecutionHandlesExceptionAndReturnsResult() {
|
||||
String script = '''
|
||||
@Command(name="testAfterExecution")
|
||||
@picocli.groovy.PicocliScript2
|
||||
import picocli.CommandLine
|
||||
import static picocli.CommandLine.*
|
||||
|
||||
throw new IllegalStateException("I am illegal!!")
|
||||
|
||||
@Override
|
||||
protected Object afterExecution(CommandLine commandLine, int exitCode, Exception exception) {
|
||||
println("exitCode=${exitCode}, exceptionMessage=${exception.getMessage()}")
|
||||
12345
|
||||
}
|
||||
'''
|
||||
GroovyShell shell = new GroovyShell()
|
||||
shell.context.setVariable('args', [] as String[])
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
System.setOut(new PrintStream(baos))
|
||||
def scriptResult = shell.evaluate script
|
||||
assertEquals("exitCode=1, exceptionMessage=I am illegal!!", baos.toString().trim())
|
||||
assertEquals(12345, scriptResult)
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAfterExecutionMayCallSystemExit() {
|
||||
String script = '''
|
||||
@Command(name="testAfterExecution", exitCodeOnExecutionException = 54321)
|
||||
@picocli.groovy.PicocliScript2
|
||||
import picocli.CommandLine
|
||||
import static picocli.CommandLine.*
|
||||
|
||||
throw new IllegalStateException("I am illegal!!")
|
||||
|
||||
@Override
|
||||
protected Object afterExecution(CommandLine commandLine, int exitCode, Exception exception) {
|
||||
println("System.exit(${exitCode})")
|
||||
//System.exit(exitCode)
|
||||
exitCode
|
||||
}
|
||||
'''
|
||||
GroovyShell shell = new GroovyShell()
|
||||
shell.context.setVariable('args', [] as String[])
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
System.setOut(new PrintStream(baos))
|
||||
def scriptResult = shell.evaluate script
|
||||
assertEquals("System.exit(54321)", baos.toString().trim())
|
||||
assertEquals(54321, scriptResult)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user