[#1191] Finalize @PicocliScript2 API, update DOCS and tests

This commit is contained in:
Remko Popma
2020-11-09 15:42:50 +09:00
parent 44debc87a2
commit 2726d7831b
8 changed files with 284 additions and 81 deletions

View File

@@ -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.

View File

@@ -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`.

View File

@@ -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 {
/**

View File

@@ -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>
* &#64;Command(name = "myCommand", description = "does something special")
* &#64;Command(name = "greet", description = "Says hello.", mixinStandardHelpOptions = true, version = "greet 0.1")
* &#64;PicocliScript2
* import picocli.groovy.PicocliScript2
* import picocli.CommandLine.Command
* ...
* import picocli.CommandLine.Option
* import groovy.transform.Field
*
* &#64;Option(names = ['-g', '--greeting'], description = 'Type of greeting')
* &#64;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>
* &#64;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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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)
}
}