Vert.x Core provides an API for parsing command line arguments passed to programs. It’s also able to print help
messages detailing the options available for a command line tool. Even if such features are far from
the Vert.x core topics, this API is used in the Launcher
class that you can use in fat-jar
and in the vertx
command line tools. In addition, it’s polyglot (can be used from any supported language) and is
used in Vert.x Shell.
Vert.x CLI provides a model to describe your command line interface, but also a parser. This parser supports different types of syntax:
-
POSIX like options (ie.
tar -zxvf foo.tar.gz
) -
GNU like long options (ie.
du --human-readable --max-depth=1
) -
Java like properties (ie.
java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo
) -
Short options with value attached (ie.
gcc -O2 foo.c
) -
Long options with single hyphen (ie.
ant -projecthelp
)
Using the CLI api is a 3-steps process:
-
The definition of the command line interface
-
The parsing of the user command line
-
The query / interrogation
Definition Stage
Each command line interface must define the set of options and arguments that will be used. It also requires a
name. The CLI API uses the Option
and Argument
classes to
describe options and arguments:
CLI cli = CLI.create("copy")
.setSummary("A command line interface to copy files.")
.addOption(new Option()
.setLongName("directory")
.setShortName("R")
.setDescription("enables directory support")
.setFlag(true))
.addArgument(new Argument()
.setIndex(0)
.setDescription("The source")
.setArgName("source"))
.addArgument(new Argument()
.setIndex(1)
.setDescription("The destination")
.setArgName("target"));
As you can see, you can create a new CLI
using
CLI.create
. The passed string is the name of the CLI. Once created you
can set the summary and description. The summary is intended to be short (one line), while the description can
contain more details. Each option and argument are also added on the CLI
object using the
addArgument
and
addOption
methods.
Options
An Option
is a command line parameter identified by a key present in the user command
line. Options must have at least a long name or a short name. Long name are generally used using a --
prefix,
while short names are used with a single -
. Names are case-sensitive; however, case-insensitive name matching
will be used during the Query / Interrogation Stage if no exact match is found.
Options can get a description displayed in the usage (see below). Options can receive 0, 1 or several values. An
option receiving 0 values is a flag
, and must be declared using
setFlag
. By default, options receive a single value, however, you can
configure the option to receive several values using setMultiValued
:
CLI cli = CLI.create("some-name")
.setSummary("A command line interface illustrating the options valuation.")
.addOption(new Option()
.setLongName("flag").setShortName("f").setFlag(true).setDescription("a flag"))
.addOption(new Option()
.setLongName("single").setShortName("s").setDescription("a single-valued option"))
.addOption(new Option()
.setLongName("multiple").setShortName("m").setMultiValued(true)
.setDescription("a multi-valued option"));
Options can be marked as mandatory. A mandatory option not set in the user command line throws an exception during the parsing:
CLI cli = CLI.create("some-name")
.addOption(new Option()
.setLongName("mandatory")
.setRequired(true)
.setDescription("a mandatory option"));
Non-mandatory options can have a default value. This value would be used if the user does not set the option in the command line:
CLI cli = CLI.create("some-name")
.addOption(new Option()
.setLongName("optional")
.setDefaultValue("hello")
.setDescription("an optional option with a default value"));
An option can be hidden using the setHidden
method. Hidden option are
not listed in the usage, but can still be used in the user command line (for power-users).
If the option value is contrained to a fixed set, you can set the different acceptable choices:
CLI cli = CLI.create("some-name")
.addOption(new Option()
.setLongName("color")
.setDefaultValue("green")
.addChoice("blue").addChoice("red").addChoice("green")
.setDescription("a color"));
Options can also be instantiated from their JSON form.
Arguments
Unlike options, arguments do not have a key and are identified by their index. For example, in
java com.acme.Foo
, com.acme.Foo
is an argument.
Arguments do not have a name, there are identified using a 0-based index. The first parameter has the
index 0
:
CLI cli = CLI.create("some-name")
.addArgument(new Argument()
.setIndex(0)
.setDescription("the first argument")
.setArgName("arg1"))
.addArgument(new Argument()
.setIndex(1)
.setDescription("the second argument")
.setArgName("arg2"));
If you don’t set the argument indexes, it computes it automatically by using the declaration order.
CLI cli = CLI.create("some-name")
// will have the index 0
.addArgument(new Argument()
.setDescription("the first argument")
.setArgName("arg1"))
// will have the index 1
.addArgument(new Argument()
.setDescription("the second argument")
.setArgName("arg2"));
The argName
is optional and used in the usage message.
As options, Argument
can:
-
be hidden using
setHidden
-
be mandatory using
setRequired
-
have a default value using
setDefaultValue
-
receive several values using
setMultiValued
- only the last argument can be multi-valued.
Arguments can also be instantiated from their JSON form.
Usage generation
Once your CLI
instance is configured, you can generate the usage message:
CLI cli = CLI.create("copy")
.setSummary("A command line interface to copy files.")
.addOption(new Option()
.setLongName("directory")
.setShortName("R")
.setDescription("enables directory support")
.setFlag(true))
.addArgument(new Argument()
.setIndex(0)
.setDescription("The source")
.setArgName("source"))
.addArgument(new Argument()
.setIndex(0)
.setDescription("The destination")
.setArgName("target"));
StringBuilder builder = new StringBuilder();
cli.usage(builder);
It generates an usage message like this one:
Usage: copy [-R] source target
A command line interface to copy files.
-R,--directory enables directory support
If you need to tune the usage message, check the UsageMessageFormatter
class.
Parsing Stage
Once your CLI
instance is configured, you can parse the user command line to evaluate
each option and argument:
CommandLine commandLine = cli.parse(userCommandLineArguments);
The parse
method returns a CommandLine
object containing the values. By default, it validates the user command line and checks that each mandatory options
and arguments have been set as well as the number of values received by each option. You can disable the
validation by passing false
as second parameter of parse
.
This is useful if you want to check an argument or option is present even if the parsed command line is invalid.
You can check whether or not the
CommandLine
is valid using isValid
.
Query / Interrogation Stage
Once parsed, you can retrieve the values of the options and arguments from the
CommandLine
object returned by the parse
method:
CommandLine commandLine = cli.parse(userCommandLineArguments);
String opt = commandLine.getOptionValue("my-option");
boolean flag = commandLine.isFlagEnabled("my-flag");
String arg0 = commandLine.getArgumentValue(0);
One of your options can be marked as "help". If a user command line enabled a "help" option, the validation won’t fail, but you have the opportunity to check if the user asks for help:
CLI cli = CLI.create("test")
.addOption(
new Option().setLongName("help").setShortName("h").setFlag(true).setHelp(true))
.addOption(
new Option().setLongName("mandatory").setRequired(true));
CommandLine line = cli.parse(Collections.singletonList("-h"));
// The parsing does not fail and let you do:
if (!line.isValid() && line.isAskingForHelp()) {
StringBuilder builder = new StringBuilder();
cli.usage(builder);
stream.print(builder.toString());
}
Typed options and arguments
The described Option
and Argument
classes are untyped,
meaning that the only get String values.
TypedOption
and TypedArgument
let you specify a type, so the
(String) raw value is converted to the specified type.
Instead of
Option
and Argument
, use TypedOption
and TypedArgument
in the CLI
definition:
CLI cli = CLI.create("copy")
.setSummary("A command line interface to copy files.")
.addOption(new TypedOption<Boolean>()
.setType(Boolean.class)
.setLongName("directory")
.setShortName("R")
.setDescription("enables directory support")
.setFlag(true))
.addArgument(new TypedArgument<File>()
.setType(File.class)
.setIndex(0)
.setDescription("The source")
.setArgName("source"))
.addArgument(new TypedArgument<File>()
.setType(File.class)
.setIndex(0)
.setDescription("The destination")
.setArgName("target"));
Then you can retrieve the converted values as follows:
CommandLine commandLine = cli.parse(userCommandLineArguments);
boolean flag = commandLine.getOptionValue("R");
File source = commandLine.getArgumentValue("source");
File target = commandLine.getArgumentValue("target");
The vert.x CLI is able to convert to classes:
-
having a constructor with a single
String
argument, such asFile
orJsonObject
-
with a static
from
orfromString
method -
with a static
valueOf
method, such as primitive types and enumeration
In addition, you can implement your own Converter
and instruct the CLI to use
this converter:
CLI cli = CLI.create("some-name")
.addOption(new TypedOption<Person>()
.setType(Person.class)
.setConverter(new PersonConverter())
.setLongName("person"));
For booleans, the boolean values are evaluated to true
: on
, yes
, 1
, true
.
If one of your option has an enum
as type, it computes the set of choices automatically.
Using annotations
You can also define your CLI using annotations. Definition is done using annotation on the class and on setter methods:
@Name("some-name")
@Summary("some short summary.")
@Description("some long description")
public class AnnotatedCli {
private boolean flag;
private String name;
private String arg;
@Option(shortName = "f", flag = true)
public void setFlag(boolean flag) {
this.flag = flag;
}
@Option(longName = "name")
public void setName(String name) {
this.name = name;
}
@Argument(index = 0)
public void setArg(String arg) {
this.arg = arg;
}
}
Once annotated, you can define the CLI
and inject the values using:
CLI cli = CLI.create(AnnotatedCli.class);
CommandLine commandLine = cli.parse(userCommandLineArguments);
AnnotatedCli instance = new AnnotatedCli();
CLIConfigurator.inject(commandLine, instance);