From ccfd487fdab383e659f0ecc8eb5de5377b06bab1 Mon Sep 17 00:00:00 2001 From: Remko Popma Date: Tue, 20 Dec 2022 19:15:27 +0900 Subject: [PATCH] [#1855] Add section Executing Commands > Rare Use Cases --- docs/index.adoc | 104 ++++++++++++++ docs/index.html | 374 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 391 insertions(+), 87 deletions(-) diff --git a/docs/index.adoc b/docs/index.adoc index 5ec41a649..64db27581 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -4273,6 +4273,110 @@ class PrintExceptionMessageHandler : IExecutionExceptionHandler { } ---- +=== Rare Use Cases + +The `CommandLine::execute` method is the recommended way to execute your command line application, as it provides configurable exception handling, handles user requests for usage help or version information, results in short and simple application code, and never throws an exception. + +However, there may be use cases for which the `execute` method is not a good match. +The alternative is to use `CommandLine::parseArgs` and handle the resulting `ParseResult` object in your application. +The <> section shows what is involved in doing so. + +The `parseArgs` method may be useful when writing parser test code, or when your application's `main` method is called by another application. The following sections go into some detail. + +==== Parser Test Code Example +The `parseArgs` method is useful in test code that only exercises the parsing logic, without involving the business logic. +For example: + +.Java +[source,java,role="primary"] +---- +MyApp app = new MyApp(); +new CommandLine(app).parseArgs("--some --options and parameters".split(" ")); +assertTrue(app.some); +---- + +.Groovy +[source,groovy,role="secondary"] +---- +MyApp app = new MyApp() +new CommandLine(app).parseArgs('--some --options and parameters'.split(' ')) +assert app.some +---- + +==== Percolating Exceptions Up +The `execute` method never throws an exception, and for some applications this is undesirable. + +The `parseArgs` method can also be useful when the `main` method of your application is called by another application, and this other application is responsible for error handling. + +An common use case is when your application is called as part of the build. +For example, Maven provides the https://www.mojohaus.org/exec-maven-plugin/[`exec-maven-plugin`] with https://www.mojohaus.org/exec-maven-plugin/java-mojo.html[`exec:java` goal], and Gradle similarly provides the https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html[Exec] and https://docs.gradle.org/current/dsl/org.gradle.api.tasks.JavaExec.html[JavaExec] tasks. + +The Maven `exec:java` goal invokes the target class in the same Maven process. +In this case, we don't want to call `System.exit`, because it would stop the entire Maven process, and additionally, we want the exceptions thrown by the command line application to be handled by Maven. + +One idea is to provide a separate `main` method that uses `parseArgs` instead of `execute`. +For example: + +.Java +[source,java,role="primary"] +---- +public class MyApp implements Callable { + /** Calls System.exit when called from the command line. */ + public static void main(String... args) throws Exception { + System.exit(new CommandLine(new MyApp()).execute(args)); + } + + /** + * Nested helper class that can be used safely from the build tool: + * it does not call System.exit and it percolates exceptions up + * for handling by the caller. + */ + public static class NoSystemExit { + public static void main(String... args) throws Exception { + MyApp app = new MyApp(); + ParseResult pr = new CommandLine(app).parseArgs(args); + if (CommandLine.executeHelpRequest(pr) != null) { return; } // help was requested + app.call(); // execute business logic, which may also throw an exception + } + } + //... +---- + +Then, in the build configuration, invoke nested class `MyApp.NoSystemExit` instead of `MyApp` to let the build tool handle any exceptions and avoid calling `System.exit`. + +==== System.exit or not? +An alternative to the above solution is to decide at runtime whether to call `System.exit` or not. + +The example implementation below demonstrates how to use system properties to determine whether to call `System.exit` or not: + +.Java +[source,java,role="primary"] +---- +public static void main(String... args) { + int exitCode = new CommandLine(new App()).execute(args); + if ((exitCode == CommandLine.ExitCode.OK && exitOnSuccess()) + || (exitCode != CommandLine.ExitCode.OK && exitOnError())) { + System.exit(exitCode); + } +} + +private static boolean exitOnSuccess() { + return syspropDefinedAndNotFalse("systemExitOnSuccess"); +} + +private static boolean exitOnError() { + return syspropDefinedAndNotFalse("systemExitOnError"); +} + +private static boolean syspropDefinedAndNotFalse(String key) { + String value = System.getProperty(key); + return value != null && !"false".equalsIgnoreCase(value); +} +---- + +Picocli's own <> tool uses this method: +when this tool is called with a link:autocomplete.html#_maven_example[specific system property] (`picocli.autocomplete.systemExitOnError`) it will call `System.exit` when an error occurs. + == Validation === Built-in Validation diff --git a/docs/index.html b/docs/index.html index 8420f72f2..39d0083d8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1709,81 +1709,156 @@ }); }); - - - + + + + + + @@ -1885,6 +1960,7 @@

picocli - a mighty tiny command line interface

  • 9.6. Migration
  • 9.7. DIY Command Execution
  • 9.8. Handling Errors
  • +
  • 9.9. Rare Use Cases
  • 10. Validation @@ -4506,7 +4582,7 @@

    5. Defaul

    It is possible to define a default value for an option or positional parameter, that is assigned when the user did not specify this option or positional parameter on the command line.

    -

    Configuring a default value guarantees that the @Option or @Parameters-annotated field will get set, annotated method will get called, and, when using the programmatic API, that the ArgSpec.setValue method will get invoked, even when the option or positional parameter was not specified on the command line.

    +

    Configuring a default value guarantees that the @Option or @Parameters-annotated field will get set, annotated method will get called, and, when using the programmatic API, that the ArgSpec.setValue method will get invoked, even when the option or positional parameter was not specified on the command line.

    5.1. defaultValue Annotation

    @@ -4749,12 +4825,12 @@

    format.

    -

    For options, the key is either the descriptionKey, -or the option’s longest name, without the prefix. So, for an option --verbose, the key would be verbose, and for an option /F, the key would be F.

    +

    For options, the key is either the descriptionKey, +or the option’s longest name, without the prefix. So, for an option --verbose, the key would be verbose, and for an option /F, the key would be F.

    -

    For positional parameters, the key is either the descriptionKey, -or the positional parameter’s param label.

    +

    For positional parameters, the key is either the descriptionKey, +or the positional parameter’s param label.

    End users may not know what the descriptionKey of your options and positional parameters are, so be sure to document that with your application.

    @@ -7360,7 +7436,7 @@

    9

    The default parameter exception handler prints an error message describing the problem, -followed by either suggested alternatives +followed by either suggested alternatives for mistyped options, or the full usage help message of the problematic command. Finally, the handler returns an exit code. This is sufficient for most applications.

    @@ -7530,6 +7606,130 @@

    +

    9.9. Rare Use Cases

    +
    +

    The CommandLine::execute method is the recommended way to execute your command line application, as it provides configurable exception handling, handles user requests for usage help or version information, results in short and simple application code, and never throws an exception.

    +
    +
    +

    However, there may be use cases for which the execute method is not a good match. +The alternative is to use CommandLine::parseArgs and handle the resulting ParseResult object in your application. +The DIY Command Execution section shows what is involved in doing so.

    +
    +
    +

    The parseArgs method may be useful when writing parser test code, or when your application’s main method is called by another application. The following sections go into some detail.

    +
    +
    +

    9.9.1. Parser Test Code Example

    +
    +

    The parseArgs method is useful in test code that only exercises the parsing logic, without involving the business logic. +For example:

    +
    +
    +
    Java
    +
    +
    MyApp app = new MyApp();
    +new CommandLine(app).parseArgs("--some --options and parameters".split(" "));
    +assertTrue(app.some);
    +
    +
    +
    +
    Groovy
    +
    +
    MyApp app = new MyApp()
    +new CommandLine(app).parseArgs('--some --options and parameters'.split(' '))
    +assert app.some
    +
    +
    +
    +
    +

    9.9.2. Percolating Exceptions Up

    +
    +

    The execute method never throws an exception, and for some applications this is undesirable.

    +
    +
    +

    The parseArgs method can also be useful when the main method of your application is called by another application, and this other application is responsible for error handling.

    +
    +
    +

    An common use case is when your application is called as part of the build. +For example, Maven provides the exec-maven-plugin with exec:java goal, and Gradle similarly provides the Exec and JavaExec tasks.

    +
    +
    +

    The Maven exec:java goal invokes the target class in the same Maven process. +In this case, we don’t want to call System.exit, because it would stop the entire Maven process, and additionally, we want the exceptions thrown by the command line application to be handled by Maven.

    +
    +
    +

    One idea is to provide a separate main method that uses parseArgs instead of execute. +For example:

    +
    +
    +
    Java
    +
    +
    public class MyApp implements Callable {
    +    /** Calls System.exit when called from the command line. */
    +    public static void main(String... args) throws Exception {
    +        System.exit(new CommandLine(new MyApp()).execute(args));
    +    }
    +
    +    /**
    +     * Nested helper class that can be used safely from the build tool:
    +     * it does not call System.exit and it percolates exceptions up
    +     * for handling by the caller.
    +     */
    +    public static class NoSystemExit {
    +        public static void main(String... args) throws Exception {
    +            MyApp app = new MyApp();
    +            ParseResult pr = new CommandLine(app).parseArgs(args);
    +            if (CommandLine.executeHelpRequest(pr) != null) { return; } // help was requested
    +            app.call(); // execute business logic, which may also throw an exception
    +        }
    +    }
    +    //...
    +
    +
    +
    +

    Then, in the build configuration, invoke nested class MyApp.NoSystemExit instead of MyApp to let the build tool handle any exceptions and avoid calling System.exit.

    +
    +
    +
    +

    9.9.3. System.exit or not?

    +
    +

    An alternative to the above solution is to decide at runtime whether to call System.exit or not.

    +
    +
    +

    The example implementation below demonstrates how to use system properties to determine whether to call System.exit or not:

    +
    +
    +
    Java
    +
    +
    public static void main(String... args) {
    +    int exitCode = new CommandLine(new App()).execute(args);
    +    if ((exitCode == CommandLine.ExitCode.OK && exitOnSuccess())
    +    || (exitCode != CommandLine.ExitCode.OK && exitOnError())) {
    +        System.exit(exitCode);
    +    }
    +}
    +
    +private static boolean exitOnSuccess() {
    +    return syspropDefinedAndNotFalse("systemExitOnSuccess");
    +}
    +
    +private static boolean exitOnError() {
    +    return syspropDefinedAndNotFalse("systemExitOnError");
    +}
    +
    +private static boolean syspropDefinedAndNotFalse(String key) {
    +    String value = System.getProperty(key);
    +    return value != null && !"false".equalsIgnoreCase(value);
    +}
    +
    +
    +
    +

    Picocli’s own Bash and ZSH completion script generator tool uses this method: +when this tool is called with a specific system property (picocli.autocomplete.systemExitOnError) it will call System.exit when an error occurs.

    +
    +
    +

    @@ -8873,7 +9073,7 @@

    -

    By default, a set of regular expressions is used to control the above. +

    By default, a set of regular expressions is used to control the above. Use CommandLine::setNegatableOptionTransformer to replace the INegatableOptionTransformer with a custom version. See the JavaDoc for details.

    @@ -13271,7 +13471,7 @@

    -

    is invoked, only the last two sub-subcommands subsub-B1 and subsub-B2 (who both have parent command subcmd-B) are executed by default. You can set a different execution strategy if this does not meet your needs.

    +

    is invoked, only the last two sub-subcommands subsub-B1 and subsub-B2 (who both have parent command subcmd-B) are executed by default. You can set a different execution strategy if this does not meet your needs.

    @@ -14532,7 +14732,7 @@

    -

    When the descriptionKey is omitted, the fallback for options is any option name without the leading dashes, for example:

    +

    When the descriptionKey is omitted, the fallback for options is any option name without the leading dashes, for example:

    Java
    @@ -14556,7 +14756,7 @@

    -

    For positional parameters the fallback key is the paramLabel + [ index ], for example:

    +

    For positional parameters the fallback key is the paramLabel + [ index ], for example:

    Java
    @@ -17603,7 +17803,7 @@

    -

    This assumes that the application uses the PrintWriter provided by CommandLine.getOut or CommandLine.getErr. +

    This assumes that the application uses the PrintWriter provided by CommandLine.getOut or CommandLine.getErr. Applications can get these writers via the @Spec annotation:

    @@ -18910,4 +19110,4 @@

    37.2. Source

    handleTocOnResize(); - \ No newline at end of file +