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
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.
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 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.
The usage help is easily customized with annotations.
- 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
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
.
© 2022 - 2024 — McMap. All rights reserved.