Java CLI Parser
Asked Answered
B

4

10

I know that this question has been asked already, but I'm looking for Java cli parser with a specific functionality. I want it to be able to define command line tree, thus using subcommands (and more than one level depth). Thus i can have 3-4 level of commands before i get to the options. And these subcommands are mutually exclusive. Thanks

Bonin answered 15/4, 2012 at 6:41 Comment(2)
Can you post few examples of commands that you intend to parse?Brubaker
command sub-command sub-sub-command -option1 argument -option2 argumentBonin
S
14

Can be done with JCommander. Every JCommander object is, in its essence, a command with arbitrary number of parameters and/or arbitrary number of nested sub-commands, where the top JCommander object is the root command. Command parameters are always specific to the command they have been declared for and do not interfere with other commands' parameters. The interface for adding a sub-command is not very intuitive but is possible (see the addCommand method())

Here's a proof-of-concept test class:

public class Test{

@Test
public void nestedSubCommandTest() {
    GeneralOptions generalOpts = new GeneralOptions();
    JCommander jc = new JCommander(generalOpts);

    Command command = new Command();
    JCommander jc_command = addCommand(jc, "command", command);

    SubCommand1 subcommand1 = new SubCommand1();
    JCommander jc_subcommand1 = addCommand(jc_command, "subcommand1",
            subcommand1);

    SubCommand2 subcommand2 = new SubCommand2();
    JCommander jc_subcommand2 = addCommand(jc_subcommand1, "subcommand2",
            subcommand2);

    SubCommand3 subcommand3 = new SubCommand3();
    addCommand(jc_subcommand2, "subcommand3", subcommand3);

    jc.parse("--general-opt", 
        "command", "--opt", 
        "subcommand1",
        "subcommand2", "--sub-opt2", 
        "subcommand3", "--sub-opt3");

    assertTrue(generalOpts.opt);// --general-opt was set
    assertTrue(command.opt);// command --opt was set
    assertFalse(subcommand1.opt);// subcommand1 --sub-opt1 was not set
    assertTrue(subcommand2.opt);// subcommand2 --sub-opt2 was set
    assertTrue(subcommand3.opt);// subcommand3 --sub-opt3 was set
}

private static JCommander addCommand(JCommander parentCommand,
        String commandName, Object commandObject) {
    parentCommand.addCommand(commandName, commandObject);
    return parentCommand.getCommands().get(commandName);
}

public static class GeneralOptions {
    @Parameter(names = "--general-opt")
    public boolean opt;
}

@Parameters
public static class Command {
    @Parameter(names = "--opt")
    public boolean opt;
}

@Parameters
public static class SubCommand1 {
    @Parameter(names = "--sub-opt1")
    public boolean opt;
}

@Parameters
public static class SubCommand2 {
    @Parameter(names = "--sub-opt2")
    public boolean opt;
}

@Parameters
public static class SubCommand3 {
    @Parameter(names = "--sub-opt3")
    public boolean opt;
}
}

Edit: How to reuse commands.

Solution 1, use inheritance:

  public class CommonArgs{
    @Parameter(names="--common-opt")
    public boolean isCommonOpt;
  }

  @Parameters(description = "my command 1")
  public class MyCommand1 extends CommonArgs{}

  @Parameters(description = "my command 2")
  public class MyCommand2 extends CommonArgs{}

I think the usage and behaviour is faily obvious for this one. The one drawback is that you can only extend from one class, which may limit reusability in the future.

Solution 2, using composition pattern (see the doc here):

  public class CommonArgs{
    @Parameter(names="--common-opt")
    public boolean isCommonOpt;
  }

  @Parameters(description = "my command 1")
  public class MyCommand1{
    @ParametersDelegate
    public CommonArgs commonArgs = new CommonArgs();
  }

  @Parameters(description = "my command 2")
  public class MyCommand2{
    @ParametersDelegate
    public CommonArgs commonArgs = new CommonArgs();
  }

Here, the nested commonArgs classes' parameters will be treated as if they were direct parameters of the command class. You can add as many delegates as you wish, or even nest delegates inside other delegates and so on. To get the value of the delegated option after parsing, just do myCommand1.commonArgs.isCommonOpt, etc.

Senator answered 15/4, 2012 at 12:22 Comment(5)
Hi. I'm playing with JCommander all day actually. I have few issues with it. The parser's exceptions are not clear at all, i cannot introduce them to the user. Another thing is that i need to create a command class per command ( and i have a lot), i cannot reuse them because the annotations are not generics. Oh and the usage() doesn't really print out all the subcommands and options.Bonin
JCommander was probably not designed with sub-commands as top priority, it's more of a hidden feature, if you will. There aren't that many exceptional cases, so you can probably wrap them in something more helpful. As for usage() there is no official support of sub-commands it seems, so you'll have to do some hacking, or just hand-write it. I cannot agree about reuse, though, reusability is the one thing I love about JCommander. I might be able to help on that if you give me a concrete example.Senator
Ok. Thank you! So for example i have 2 commands that have different naming and different descriptions but they both get same parameters. so in order to set the description, i must have different instances of command class although the parareters inside the class will be the same.Bonin
I've updated the post with two possible solutions. Hope this sheds some light on the problem.Senator
Ah, I've been looking for a while now of how to set a command description. Turns out all I need to is add ` @Parameters(description = "my command 2")` thanks very much!Stingy
U
5

picocli supports nested subcommands to arbitrary depth.

CommandLine commandLine = new CommandLine(new MainCommand())
        .addSubcommand("cmd1", new ChildCommand1()) // 1st level
        .addSubcommand("cmd2", new ChildCommand2())
        .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // 2nd level
                .addSubcommand("cmd3sub1", new GrandChild3Command1())
                .addSubcommand("cmd3sub2", new GrandChild3Command2())
                .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // 3rd
                        .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
                        .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
                                // etc
                )
        );

You may also like its usage help with ANSI styles and colors.

Note that usage help lists the registered subcommands in addition to options and positional parameters.

enter image description here

The usage help is easily customized with annotations.

enter image description here

  • annotation-based
  • git-style subcommands
  • nested sub-subcommands
  • strongly typed option parameters
  • strongly typed positional parameters
  • customizable type conversion
  • multi-value options
  • intuitive model for how many arguments a field consumes
  • fluent API
  • POSIX-style clustered short options
  • GNU style long options
  • allows any option prefix
  • ANSI colors in usage help
  • customizable usage help
  • single source file: include as source to keep your application a single jar
Undergo answered 30/6, 2017 at 5:6 Comment(0)
B
-1

If your command expression is complex then you can think to define the syntax, write its BNF and use libraries like JavaCC or AntLR to create your own parser.

Brubaker answered 15/4, 2012 at 10:35 Comment(0)
U
-1

I think it will be better to split this multi-level commands into several CLI-tools.

Instead of:
program cmd sub-cmd sub-sub-cmd -option1 argument -option2 argument

Append one or two levels to program name:
program-cmd-sub-cmd sub-sub-cmd -option1 argument -option2 argument

Real word example:
svn-add -q -N foo.c

You could keep all required classes in single JAR (and reuse them as much as you like), just add several "main" entry points. For basic CLI-parsing I also recommend JCommander.

Unthoughtof answered 6/5, 2012 at 6:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.