Search code examples
picocli

picocli example showing the usage of multiple commands


ive got some code that works very well with picocli:

 @Command(name = "parse", sortOptions = false, description = "parse input files and write to database")
class CommandLineArgumentParser {


@Option(names = { "-h", "--help" }, usageHelp = true, description = "display this message")
private boolean helpRequested = false;


@Option(names = { "-s", "--startDate"}, description = "First day at which to parse data",
        converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
public LocalDate start;

@Option(names = { "-e", "--endDate"}, description = "Last day (inclusive) at which to stop parsing",
        converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
public LocalDate end;


private static class GermanDateConverter implements ITypeConverter<LocalDate> {
    @Override
    public LocalDate convert(String value) throws Exception {
        LocalDate result = null;

            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
            result = LocalDate.parse(value, formatter);
            if (result.getYear() < 1900) {
                throw new IllegalArgumentException("year should be after 1900");
            }
        return result;
    }
}


@SpringBootApplication
public class Application implements CommandLineRunner {

public void run(String... args) throws Exception {
    CommandLineArgumentParser commandlineparser = new CommandLineArgumentParser();

    CommandLine commandLine = new CommandLine(commandlineparser);
    try {
        commandLine.parseArgs(args);

    } catch (MissingParameterException e) {
        System.out.println(e);
        System.out.println();
        CommandLine.usage(CommandLineArgumentParser.class, System.out);
        System.exit(1);
    } catch (ParameterException e) {
        System.out.println(e);
        System.out.println();
        CommandLine.usage(CommandLineArgumentParser.class, System.out);
        System.exit(1);
    }

    if (commandLine.isUsageHelpRequested()) {
        commandLine.usage(System.out);
        return;
    } else if (commandLine.isVersionHelpRequested()) {
        commandLine.printVersionHelp(System.out);
        return;
    }

    if (commandlineparser.start == null) {
        log.warn("no start date specified, using: 01.01.2005");
        commandlineparser.start = LocalDate.of(2005, 01, 01);
    }

    if (commandlineparser.end == null) {
        LocalDate timePoint = LocalDate.now();
        log.warn("no end date specified, using today: " + timePoint.toString());
        commandlineparser.end = timePoint;
    }
}

but i did not find a simple example that shows multiple commands used, for example this one:
https://github.com/remkop/picocli/blob/master/src/test/java/picocli/Demo.java

does not compile:

int exitCode = new CommandLine(new Demo()).execute(args);

The method execute(CommandLine, List<Object>) in the type CommandLine is not applicable for the    arguments (String[])

could somebody please post a example on howto use multiple commands?


Solution

  • I suspect you’re using an older version of the library. The execute(String []) : int method was introduced in picocli 4.0.

    Upgrading and using the execute method instead of the parseArgs method will allow you to remove a lot of boilerplate code from the application: handling requests for usage/version help and dealing with invalid input is done automatically with the execute method.

    Your commands should implement Runnable or Callable, and this is where the business logic of each command lives.

    Modifying your example and giving it a subcommand:

    @Command(name = "parse", sortOptions = false,
            mixinStandardHelpOptions = true, version = “1.0”,
            description = "parse input files and write to database",
            subcommands = MySubcommand.class)
    class CommandLineArgumentParser implements Callable<Integer> {
    
        @Option(names = { "-s", "--startDate"}, description = "First day at which to parse data",
                converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
        public LocalDate start;
    
        @Option(names = { "-e", "--endDate"}, description = "Last day (inclusive) at which to stop parsing",
                converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
        public LocalDate end;
    
    
        private static class GermanDateConverter implements ITypeConverter<LocalDate> {
            @Override
            public LocalDate convert(String value) throws Exception {
                LocalDate result = null;
    
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
                    result = LocalDate.parse(value, formatter);
                    if (result.getYear() < 1900) {
                        throw new IllegalArgumentException("year should be after 1900");
                    }
                return result;
            }
        }
    
        @Override
        public Integer call() {
            if (start == null) {
                log.warn("no start date specified, using: 01.01.2005");
                start = LocalDate.of(2005, 01, 01);
            }
    
            if (end == null) {
                LocalDate timePoint = LocalDate.now();
                log.warn("no end date specified, using today: " + timePoint.toString());
                end = timePoint;
            }
    
            // more business logic here ...
    
            // add finally return an exit code 
            int exitCode = ok ? 0 : 1;
            return exitCode;
        }
    }
    
    @Command(name = "foo")
    class MySubcommand implements Callable<Integer> {
        @Override
        public Integer call() {
            System.out.println("hi");
            return 0;
        }
    }
    
    
    @SpringBootApplication
    public class Application implements CommandLineRunner {
    
        public void run(String... args) throws Exception {
            int exitCode = new CommandLine(
                    CommandLineArgumentParser.class,
                    new picocli.spring.PicocliSpringFactory())
                    .execute(args);
            System.exit(exitCode);
         }
    }
    

    Note that when using Spring in combination with picocli subcommands, you need to call the CommandLine constructor with a picocli.spring.PicocliSpringFactory.

    For a more complete Spring example, see the picocli-spring-boot-starter README.