Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add convenience run method that initializes sub commands and type converters #115

Closed
remkop opened this issue Apr 30, 2017 · 2 comments
Closed

Comments

@remkop
Copy link
Owner

remkop commented Apr 30, 2017

For commands implementing Runnable there currently is a CommandLine::run static convenience method that takes care of some boilerplate error handling:

CommandLine.run(new MyApp(), System.err, args);

Unfortunately this static method does not allow us to register subcommands or custom type converters:

  • subcommands and type converters need to be registered on a CommandLine instance
  • the parsing result is a list of recognized commands. The application logic needs to be given this list to process, which cannot be done via the (parameterless) Runnable::run method.

The purpose of this ticket is to explore possibilities for providing a similar convenience API to applications that have subcommands and custom type converters.

@remkop
Copy link
Owner Author

remkop commented May 1, 2017

One idea:

  • add instance method CommandLine.parseWithHandler(IParseResultHandler, OutputStream, String...). This convenience method takes care of error handling during parsing, then delegates to the handler.
  • define IParseResultHandler as a functional interface, so Java 8 users can provide a lambda for custom handlers
  • provide a Run handler implementation that runs the most deeply nested Runnable or Callable subcommand (we could also provide a RunAll implementation that calls run on all parsed subcommands)

The IParseResultHandler interface could look like this:

public static interface IParseResultHandler {
    Object handle(List<CommandLine> parsedCommands, OutputStream out) throws Exception; 
}

Example usage:

CommandLine commandLine = new CommandLine(new MyCommand())
            .addCommand("sub1", new SubCommand1())
            .addCommand("sub2", new SubCommand2())
            .registerConverter(Path.class, s -> Paths.get(s))
            .registerConverter(Duration.class, s -> Duration.parse(s));

Object result = commandLine.parseWithHandler(new CommandLine.Run(), System.err, args);

Run implementation:

public static class Run implements IParseResultHandler {
    public Object handle(List<CommandLine> parsedCommands, OutputStream out) throws Exception {
        for (CommandLine parsed : parsedCommands) {
            if (parsed.isUsageHelpRequested()) {
                parsed.usage(out);
                return null;
            } else if (parsed.isVersionHelpRequested()) {
                parsed.printVersionHelp(out);
                return null;
            }
        }
        Object last = parsedCommands.get(parsedCommands.size() - 1).getCommand();
        if (last instanceof Runnable) {
            ((Runnable) last).run();
            return null;
        } else if (last instanceof Callable) {
            return ((Callable<?>) last).call();
        } else {
            throw new IllegalArgumentException("Last subcommand (" + last +
                    ") is not Runnable or Callable");
        }
    }
}

Users can provide their own handler implementation. Example in Java 8 syntax:

// pass method reference to CommandLine.handle:
Object result = new CommandLine(new MyCommand())
        .registerConverter(Path.class, s -> Paths.get(s))
        .parseWithHandler(MyCommand::handleParsedCommands, System.err, args);//pass method reference

class MyCommand {
    public static Object handleParsedCommands(List<CommandLine> parsedCommands, 
                                              OutputStream out) {...}
}

Here is how the convenience method could be implemented:

// Add this convenience method to picocli.CommandLine:
public Object parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) throws Exception {
    List<CommandLine> result = null;
    try {
        result = parse(args);
    } catch (Exception ex) {
        out.println(ex.getMessage());
        usage(annotatedObject, out);
        return null;
    }
    return handler.handle(result, out); // no exception handling for the business logic
}

@remkop
Copy link
Owner Author

remkop commented Oct 17, 2017

Pushed a change set to master with many javadoc updates.
The run and call methods are now implemented with parseWithHandler and RunFirst.

Still TODO: update user manual and perhaps more unit tests.
Also ensure that the PicocliBaseScript behaviour is consistent with parseWithHandler and RunAll.

remkop added a commit that referenced this issue Oct 18, 2017
remkop added a commit that referenced this issue Oct 19, 2017
remkop added a commit that referenced this issue Oct 19, 2017
remkop added a commit that referenced this issue Oct 19, 2017
… `run` and `call` convenience methods now automatically print usage help
remkop added a commit that referenced this issue Oct 19, 2017
@remkop remkop closed this as completed in ed7281b Oct 21, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant