groovypicocli

Exception Handling in a groovy script annotated with @PicocliScript2


I have a PicocliScript2 annoatated groovy script. I have been trying hard to figure out how to handle exceptions and haven't exeprienced luck thus far.

PicocliScript2 swallows all exceptions.

Going by the documentaion it seems simple to implement with java/groovy classes. There are tons of usecases/customizations be it parsing errors or execution error, but it gets trickier everytime I try to implement in a script.

This is such a versatile cli tool, but I believe I am being limited by the choice of implementation here/or perhaps my lack of knowledge thereof. Currently I would want to implement it via script only.

EDIT 1 : START

Background : When a picocli script is executed by passing incomplete set of arguments or no arguments at all, the script does not throw error. It nicely tells the user that the script is missing arguments, like

args: []
Missing required options: '--first-name=firstName', '--last-name=lastName'

This is quite helpful to the human user.

But not so much when some other program(like script, service, tool like Jenkins) calls it, unless there is a way to return some exit codes/throw some exception. This is possible in the class based implementations(both java/groovy/kotlin) using rich apis of picoli but not as much help is available when it comes to implementing via a groovy script.

Trying to call the script with wrong commandline arguments from a Jenkins job will still make the job green because Jenkins does not see any exception being thrown by the script.

There has to be a way to deal with this in groovy script based implementation too.

The picocli documentation is so rich, and I blame my own lacking for not being able extract the required info.

Link to mcve : SAMPLE-PICOCLI

The script on the master branch can be used to recreate this issue.

Picocli : 4.7.5

Groovy : 3.0.19

EDIT 1 : END


Solution

  • The picocli user manual has a section on Groovy Scripts: https://picocli.info/#_groovy_scripts

    I believe the information you are looking for is in the "Comparison of PicocliBaseScript2 and PicocliBaseScript script base classes" table:

    For PicocliBaseScript2, Custom handling of invalid user input can be accomplished as follows:

    Scripts can override beforeParseArgs(CommandLine) to install a custom IParameterExceptionHandler.

    See the manual's section on Invalid User Input for what such a handler looks like.

    Putting it all together:

    // example custom handler
    class ShortErrorMessageHandler implements IParameterExceptionHandler {
    
        public int handleParseException(ParameterException ex, String[] args) {
            CommandLine cmd = ex.getCommandLine();
            PrintWriter err = cmd.getErr();
    
            // if tracing at DEBUG level, show the location of the issue
            if ("DEBUG".equalsIgnoreCase(System.getProperty("picocli.trace"))) {
                err.println(cmd.getColorScheme().stackTraceText(ex));
            }
    
            err.println(cmd.getColorScheme().errorText(ex.getMessage())); // bold red
            UnmatchedArgumentException.printSuggestions(ex, err);
            err.print(cmd.getHelp().fullSynopsis());
    
            CommandSpec spec = cmd.getCommandSpec();
            err.printf("Try '%s --help' for more information.%n", spec.qualifiedName());
    
            return cmd.getExitCodeExceptionMapper() != null
                        ? cmd.getExitCodeExceptionMapper().getExitCode(ex)
                        : spec.exitCodeOnInvalidInput();
        }
    }
    
    // in your script, add this to install this custom handler:
    @Override
    protected CommandLine beforeParseArgs(CommandLine customizable) {
        customizable.setParameterExceptionHandler(new ShortErrorMessageHandler());
        return super.beforeParseArgs(customizable)
    }
    

    (For custom handling of errors that occur during your script's execution, override beforeParseArgs(CommandLine) to install a custom IExecutionExceptionHandler.)

    This test has some examples.