An opinionated extension to the Apache Commons CLI library which adds support for commands.
Extends the Apache Commons CLI, adding the following features:
- Command arguments parsing and validation
- Simple command execution pattern:
my-cli [OPTIONS] [ARGS]
- Complex command routing patterns, e.g. git-flow style:
git flow feature start <NAME> git flow feature finish <NAME> git flow release start <VERSION> git flow release finish <VERSION> git flow support start <VERSION> <TAG>
- Built-in usage help option for every route and command:
$ my-cli --help usage: my-cli [OPTIONS] My command line tool Options: -h,--help Show this help
<dependency>
<groupId>com.ebay.sd.commons</groupId>
<artifactId>commands-cli</artifactId>
<version>${commands-cli.version}</version>
</dependency>
Define the executable command:
public class MyCommand extends AbstractCommand {
public static final CommandDescriptor DESCRIPTOR = CommandDescriptor.builder("my-cli")
.description("This is my command line tool")
.addOption(Option.builder("v").longOpt("verbose").build())
.addArgument(Argument.builder("FILE").description("The input file").required().build())
.factory(new CommandFactory() {
@Override
Command create(CommandContext commandContext) throws ParseException {
return new MyCommand(commandContext);
}
})
.build();
public MyCommand(CommandContext commandContext) {
super(commandContext);
}
@Override
protected void validate(CommandContext commandContext) throws ParseException {
//Validate the command input if needed
}
@Override
public void execute() throws CommandException {
//Do what ever you want to do, use getCommandContext() to check options, get arguments, etc.
}
}
In the main
method - define and run the program:
public class Main {
public static void main(String[] args) {
CommandsCliMain.builder().mainCommand(MyCommand.DESCRIPTOR).build().main(args);
}
}
The example above defines a simple command line interface which has a single option (flag) -v, --verbose
(defined and implemented by the program) and a single required argument FILE
.
The actual execution is done by the command class MyCommand
.
To invoke it from the command line (assuming my-cli
is an executable/script which invokes main
),
a user shall use the following usage pattern:
my-cli [OPTIONS] FILE
For example, with verbosity flag turned on:
my-cli --verbose path/to/file
Continuing the git-flow example, define the commands, e.g. the git flow feature start <NAME>
command
with an optional --showcommands
option:
public class GitFlowFeatureStartCommand extends AbstractCommand {
public static final CommandDescriptor DESCRIPTOR = CommandDescriptor.builder("start")
.description("Start a feature branch")
.addOption(Option.builder().longOpt("showcommands").desc("Show git commands while executing them").required(false).build())
.addArgument(Argument.builder("NAME").description("Feature name").required().build())
.factory(new CommandFactory() {
@Override
public Command create(CommandContext commandContext) throws ParseException {
return new GitFlowFeatureStartCommand(commandContext);
}
})
.build();
//etc...
}
In the main program, build routing to the commands and the run it, passing the command line arguments.
public class Main {
public static void main(String[] args){
//Define the routes to the commands
RouteDescriptor git = RouteDescriptor.builder("git")
.description("The git command line tool")
.addSubCommand(
RouteDescriptor.builder("flow")
.description("git-flow extensions")
.addSubCommand(
RouteDescriptor.builder("feature")
.description("git-flow feature branch related operations")
.addSubCommand(GitFlowFeatureStartCommand.DESCRIPTOR)
.addSubCommand(GitFlowFeatureFinishCommand.DESCRIPTOR)
.build())
.addSubCommand(
RouteDescriptor.builder("release")
.description("git-flow release branch related operations")
.addSubCommand(GitFlowReleaseStartCommand.DESCRIPTOR)
.addSubCommand(GitFlowReleaseFinishCommand.DESCRIPTOR)
.build())
.addSubCommand(
RouteDescriptor.builder("support")
.description("git-flow support branch related operations")
.addSubCommand(GitFlowSupportStartCommand.DESCRIPTOR)
.build())
.build())
.build();
//Build and run the main program
CommandsCliMain.builder().mainRoute(git).build().main(args);
}
}
By default, a help option is added to each route and command:
-h,--help Show this help
This can be customized using the context data. For example:
public class Main {
public static void main(String[] args){
HelpFormatter myHelpFormatter = createMyHelpFormatter();
Option showHelpOpt = Option.builder()
.longOpt("show-help")
.desc("Show my-cli help")
.build();
Map<String, Object> ctxData = new HashMap<>();
ctxData.put(UsageHelp.CTX_HELP_OPTION, showHelpOpt);
ctxData.put(UsageHelp.CTX_HELP_FORMATTER, myHelpFormatter);
ctxData.put(UsageHelp.CTX_HELP_OPTION_AUTO_ADD, false);
CommandsCliMain cli = CommandsCliMain.builder()
.contextData(ctxData)
.mainRoute(root)
.build();
cli.main(args);
}
//...
}
For instructions on contributing to this project, see CONTRIBUTING.md.
Maintainers - for detailed instructions on how to release a new version, see RELEASING.md.
Copyright 2018 eBay Inc.
Developer: Yinon Avraham
Use of this source code is governed by an Apache-2.0-style
license that can be found in the LICENSE file or at
http://www.apache.org/licenses/LICENSE-2.0.