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