Picocli: Allow catch(Exception ex) throw new ExecutionException to be turned off

Created on 27 May 2019  路  11Comments  路  Source: remkop/picocli

question

All 11 comments

Hi! Happy to help if there鈥檚 any issue.

Hi @remkop

In picocli.CommandLine the execute method on line 1200 try catches hide any exceptions from code trying to use the command line.

This is a (fairly minor) issue for me, as it's swallowing up any validation exceptions i was wanting to throw.

can work around this by just handling the exceptions within each command line, so it's not a real problem

Hi @pdaulbyscottlogic thanks for the feedback! This is good timing, please read on!

The reason for wrapping the business exception in an ExecutionException is to capture the CommandLine instance of the subcommand where the exception occurred. This allows the caller to do things like invoking commandLine.usage() to print the usage help for the subcommand where the problem occurred.

However, the solution in picocli 3.x is a bit clunky, and you're not the first to mention this.

I'm trying to improve this in picocli 4.0. It would be great if you can take a look at the latest pre-release (4.0-alpha-3) and let me know your thoughts on the new execute API:

  • there's a new execute public method that returns an int exit code. This method is guaranteed to never throw an exception
  • applications that _want_ to deal with the business exception can install a IExecutionExceptionHandler (example below).
  • applications don't have to deal with ExecutionException anymore. Picocli will unwrap it and pass the business exception to the IExecutionExceptionHandler

For example:

// define error handler
IExecutionExceptionHandler errorHandler = new IExecutionExceptionHandler() {
    public int handleExecutionException(Exception ex, 
                                        CommandLine commandLine, 
                                        ParseResult parseResult) {
        //ex.printStackTrace();                        // no stack trace
        commandLine.getErr().println(ex.getMessage()); // print message to STDERR
        commandLine.usage(commandLine.getErr());       // print usage to STDERR
        return commandLine.getCommandSpec().exitCodeOnExecutionException();
    }
};
int exitCode = new CommandLine(new App())
        .setExecutionExceptionHandler(errorHandler) // install error handler
        .execute(args);                             // and run the application

馃憤 tried it. it works great.

though it's made me realise that i'm throwing some checked exceptions so i'm going to need a bit of a rethink before i can actually start using it...

Not sure I follow, what is the problem with checked exceptions?
If your commands implement Callable<Integer> they can throw any exception... then you can have centralized exception handling in the IExecutionExceptionHandler if you want. Or am I missing something?

If there's anything you cannot do with the new execute API, please let me know. Now is still a good time to make changes. After 4.0 GA it may be a bit harder. :-)

i've been implementing runnable.

I'll try it with callable

Works like a charm. thanks for the help

Ok cool. Don't hesitate to let me know if there's any issue.
(And please star the project if you like it. :-) )

commandLine.getErr().println(ex.getMessage());

When business exception why is message not red, even if I define color schema and set it to CommandLine

IExecutionExceptionHandler errorHandler = (Exception ex, CommandLine commandLine, ParseResult parseResult) -> {
            commandLine.getErr().println(ex.getMessage()); // print message to STDERR
            commandLine.usage(commandLine.getErr());       // print usage to STDERR
            return commandLine.getCommandSpec().exitCodeOnExecutionException();
        };

        ColorScheme colorScheme = createColorScheme();
        CommandLine commands = new CommandLine(newUserCommands())
                                                              .setColorScheme(colorScheme)
                                                              .setExecutionExceptionHandler(errorHandler);

        System.exit(commands.execute(args));
....

private static ColorScheme createColorScheme() {
        return new ColorScheme.Builder()
                .commands(Style.bold, Style.underline) // combine multiple styles
                .options(Style.fg_yellow) // yellow foreground color
                .parameters(Style.fg_yellow)
                .optionParams(Style.italic)
                .errors(Style.fg_red, Style.bold)
                .stackTraces(Style.italic)
                .build();
    }

@sysmat Hi, thanks for your question!

command.getErr() only returns a writer that prints to the standard error stream (where getOut() returns a writer that prints to the standard output stream).

To print a message that uses the errors style of a color scheme, call the ColorScheme::errorText method. For example:

IExecutionExceptionHandler errorHandler = (Exception ex, CommandLine commandLine, ParseResult parseResult) -> {
    commandLine.getErr().println(commandLine.getColorScheme().errorText(ex.getMessage())); // bold red
    commandLine.usage(commandLine.getErr());       // print usage to STDERR
    return commandLine.getCommandSpec().exitCodeOnExecutionException();
};

I will update the example in the manual, I did the IParameterExceptionHandler, but not the IExecutionExceptionHandler, thanks for reminding me!

To get custom colors, use Ansi::string or Ansi::text:

ColorScheme cs = commandLine.getColorScheme();
String colored = cs.ansi().string("@|bold,green,underline Hello, colored world!|@");
Was this page helpful?
0 / 5 - 0 ratings