Mark
Mark

Reputation: 2148

Using Picocli, how do I require a positional argument and then optional arguments depending on the value of the positional argument

I have a situation where I need to have three mandatory arguments (field1, field2 and field3. I then want the user to enter a command name (mandatory, values can be create, list, etc). The command name must be entered, and must be singular (only one of them can be entered).

Some of the commands will have arguments, some of them will not. How do I handle that?

I tried the following, but I get an error:

ArgGroup has no options or positional parameters, and no subgroups
public class CliParserArgs {
    @Option(names = {"--field1"}, required = true)
    private String field1;

    @Option(names = {"--field2"}, required = true)
    String field2;

    @Option(names={"--field3"}, required = true)
    String field3;

    @Option(names = {"-h", "--help"}, usageHelp = true) boolean help;

    class Create {
        private final String val;
        public Create(final String val) {
            this.val = val;
        }
    }

    class ListObjects {
        private final String val;
        public ListObjects(final String val) {
            this.val = val;
        }
    }

    @ArgGroup(heading = "Command", exclusive = true, multiplicity = "1")
    Create create;
    ListObjects listObjects;

    public static void main(String[] args) {
        CliParserArgs cliParserArgs = new CliParserArgs();
        CommandLine cmd = new CommandLine(cliParserArgs);
        CommandLine.ParseResult parseResult = cmd.parseArgs(args);
        System.err.println("parse results: " + parseResult.matchedArgs().toString());

        try {
        if (cmd.isUsageHelpRequested()) {
            cmd.usage(System.out);
        }
        } catch (CommandLine.ParameterException e) {
            System.err.println("error: " + e.getMessage());
            System.err.println(e.getStackTrace());

        }
    }
}

Upvotes: 2

Views: 1119

Answers (1)

Remko Popma
Remko Popma

Reputation: 36754

It sounds like you want to create a command with subcommands. You can do this in picocli by either marking a method with the @Command annotation or by creating a separate command class and registering it as a subcommand of your parent command. If your subcommand has many options, you probably want to create a separate class for it.

Once you have created subcommands, you want to call the logic for the subcommand that the user specified. You can do this manually, using the CommandLine.parseArgs method, but this is a lot of work. I would recommend using the CommandLine.execute method instead.

The execute method will parse the user input, handle --help and --version requests, handle invalid user input, and finally (if the user input was valid) invoke the business logic of the subcommand that the user specified. It will also return an exit code.

The execute method requires subcommands to be either a @Command-annotated method or a @Command-annotated class that implements Runnable or Callable.

Below is an example based on your example code, implemented as subcommands.

@Command(name = "cli", version = "1.0",
    mixinStandardHelpOptions = true,
    subcommands = {Create.class, ListObjects.class})
public class Cli implements Runnable {
    @Option(names = {"--field1"}, required = true)
    private String field1;

    @Option(names = {"--field2"}, required = true)
    String field2;

    @Option(names={"--field3"}, required = true)
    String field3;

    // not needed because we have mixinStandardHelpOptions=true
    //@Option(names = {"-h", "--help"}, usageHelp = true) boolean help;

    public void run() {
        // business logic of the top-level cmd here
        System.out.println("hi, field1="+field1);
    }

    public static void main(String[] args) {
        int exitCode = new CommandLine(new Cli()).execute(args);
        System.exit(exitCode);
    }
}

@Command(name = "create", description = "create ...",
        mixinStandardHelpOptions = true, version = "1.0")
class Create implements Callable<Integer> {
    @Option(names = {"-x", "--times"}, description = "...")
    int x;

    @Override
    public Integer call() {
        // business logic for "create" here...
        return ok ? 0 : 1; // exit code support
    }
}

@Command(name = "list", description = "create ...",
        mixinStandardHelpOptions = true, version = "1.0")
class ListObjects implements Runnable {
    @Option(names = {"-x", "--times"}, description = "...")
    int x;

    @Override
    public void run() {
        // business logic for "list" here...
    }
}

Upvotes: 5

Related Questions