diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index aace3786..5ee43a61 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -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/). ## 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`, 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 ## 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` type for options and positional parameters. Thanks to [Max Rydahl Andersen](https://github.com/maxandersen) for raising this. diff --git a/docs/index.adoc b/docs/index.adoc index 9d2bc719..632c54c2 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -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`, 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`. diff --git a/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript.java b/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript.java index a1c07df1..668cf29e 100644 --- a/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript.java +++ b/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript.java @@ -30,7 +30,8 @@ import java.util.concurrent.Callable; /** *

- * 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}. *

* 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 { /** diff --git a/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript2.java b/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript2.java index 8031b4ad..39833cff 100644 --- a/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript2.java +++ b/picocli-groovy/src/main/java/picocli/groovy/PicocliBaseScript2.java @@ -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; /** *

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

* 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; *

*

Example usage

*
- * @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!"
  * 
*

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

* 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. + *

+ *

Customization

+ *

+ * Scripts can override {@link #beforeParseArgs(CommandLine)} to + * change the parser configuration, or set custom exception handlers etc. + *

+ *

+ * Scripts can override {@link #afterExecution(CommandLine, int, Exception)} to + * call {@code System.exit} or return a custom result. *

*

PicocliBaseScript2 vs PicocliBaseScript

*

@@ -49,9 +64,13 @@ import java.util.concurrent.Callable; *

    *
  • Adds support for {@code @Command}-annotated methods to define subcommands in scripts.
  • *
  • Adds support for {@code help} subcommands (both the built-in {@code CommandLine.HelpCommand} and custom implementations).
  • - *
  • Adds support for exit codes. The return value of the script becomes the exit code.
  • - *
  • 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.
  • + *
  • Adds support for exit codes.
  • + *
  • 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. + *
  • *
* * @author Remko Popma @@ -86,59 +105,102 @@ public abstract class PicocliBaseScript2 extends Script implements Callable *
    - *
  1. A new {@link CommandLine} is created with this script instance as the annotated command object. + *
  2. 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)}.
  3. - *
  4. 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.
  5. - *
  6. 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.
  7. - *
  8. 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.
  9. - *
  10. 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.
  11. - *
  12. If no subcommand was specified, the script body is executed.
  13. - *
  14. If an exception occurs during execution, this exception is rethrown.
  15. + * {@code this.commandLine}). Scripts may override. + *
  16. Call {@link #beforeParseArgs(CommandLine)} to install custom handlers for + * invalid user input or exceptions are installed. + * Scripts may override. + *
  17. + *
  18. Call {@link CommandLine#execute(String...)} method with the script arguments. + * This results in the following: + *
      + *
    • Assuming the user input was valid, this initialises all {@code @Field} + * variables annotated with {@link CommandLine.Option} or {@link CommandLine.Parameters}. + *
    • + *
    • 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.
    • + *
    • 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).
    • + *
    • 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.
    • + *
    • If no subcommand was specified, the script body is executed.
    • + *
    + *
  19. + *
  20. Store the exit code returned by {@code CommandLine.execute} in the {@code exitCode} property of this script.
  21. + *
  22. 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. + *
  23. *
*

Exit Code

- *

After execution, the {@code PicocliBaseScript2} class sets the exit code in the {@code exitCode} property of the script.

*

- * 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: - *

{@code
-     * @Override
-     * public Object run() {
-     *     try {
-     *         super.run();
-     *     } finally {
-     *         System.exit(exitCode);
-     *     }
-     * }
+     * 

+     * @Override
+     * protected Object afterExecution(CommandLine commandLine, int exitCode, Exception exception) {
+     *     exception?.printStackTrace();
+     *     System.exit(exitCode);
      * }
* @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: + *
    + *
  • Call {@code System.exit} with the specified exit code.
  • + *
  • Throw a {@code GroovyRuntimeException} with the specified exception.
  • + *
  • Return the result of the script execution.
  • + *
+ *

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. + *

+ * Scripts may override this method to call {@code System.exit} or do something else. + *

+ * @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 result = new ArrayList(); List 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 + * This method replaces the default {@code IParameterExceptionHandler} with a custom + * one that prints the command line arguments before calling the default parameter exception handler. + *

+ *

+ * 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. + *

+ * @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")) { // 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; diff --git a/picocli-groovy/src/main/java/picocli/groovy/PicocliScript.java b/picocli-groovy/src/main/java/picocli/groovy/PicocliScript.java index b4569c52..189ddd56 100644 --- a/picocli-groovy/src/main/java/picocli/groovy/PicocliScript.java +++ b/picocli-groovy/src/main/java/picocli/groovy/PicocliScript.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; /** *

- * 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; * * * @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; } diff --git a/picocli-groovy/src/main/java/picocli/groovy/PicocliScript2.java b/picocli-groovy/src/main/java/picocli/groovy/PicocliScript2.java index 0e5dbcca..05e64fc9 100644 --- a/picocli-groovy/src/main/java/picocli/groovy/PicocliScript2.java +++ b/picocli-groovy/src/main/java/picocli/groovy/PicocliScript2.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; /** *

- * 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; * *

PicocliBaseScript2 vs PicocliBaseScript

*

- * This class has the following improvements over {@link PicocliScript}: + * See the {@link PicocliBaseScript2} documentation for details. *

- *
    - *
  • Adds support for {@code @Command}-annotated methods to define subcommands in scripts.
  • - *
  • Adds support for {@code help} subcommands (both the built-in {@code CommandLine.HelpCommand} and custom implementations).
  • - *
  • Adds support for exit codes. The return value of the script becomes the exit code.
  • - *
  • 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.
  • - *
* * @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; } diff --git a/picocli-groovy/src/main/java/picocli/groovy/PicocliScriptASTTransformation.java b/picocli-groovy/src/main/java/picocli/groovy/PicocliScriptASTTransformation.java index 251d1be5..6eeb9d45 100644 --- a/picocli-groovy/src/main/java/picocli/groovy/PicocliScriptASTTransformation.java +++ b/picocli-groovy/src/main/java/picocli/groovy/PicocliScriptASTTransformation.java @@ -45,9 +45,11 @@ import picocli.CommandLine; @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class PicocliScriptASTTransformation extends AbstractASTTransformation { + @SuppressWarnings("deprecation") private static final Class MY_CLASS1 = PicocliScript.class; private static final Class MY_CLASS2 = PicocliScript2.class; private static final Class COMMAND_CLASS = CommandLine.Command.class; + @SuppressWarnings("deprecation") private static final Class BASE_SCRIPT1_CLASS = PicocliBaseScript.class; private static final Class BASE_SCRIPT2_CLASS = PicocliBaseScript2.class; private static final ClassNode MY_TYPE1 = ClassHelper.make(MY_CLASS1); diff --git a/picocli-groovy/src/test/groovy/picocli/groovy/PicocliBaseScript2Test.groovy b/picocli-groovy/src/test/groovy/picocli/groovy/PicocliBaseScript2Test.groovy index c8181a56..94fe2923 100644 --- a/picocli-groovy/src/test/groovy/picocli/groovy/PicocliBaseScript2Test.groovy +++ b/picocli-groovy/src/test/groovy/picocli/groovy/PicocliBaseScript2Test.groovy @@ -52,9 +52,6 @@ import picocli.CommandLine @CommandLine.Option(names = ["-cp", "--codepath"]) @Field List 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) + } }