CLI Specification

You use dfh.cli to parse command line arguments. To do this for a particular application, the first thing you need is a specification of what arguments it expects.

Object[][][] spec = {
	{ { "foo" } },                    // boolean option
	{ { "bar", 'b' } },               // boolean with short and long versions
	{ { 'c' }, { "usually false" } }, // boolean with description
	{ },                              // blank line in usage information
	{ { 'd', Integer.class } },       // integer
	{ { 'e', Integer.class, 1 } },    // integer with default
	{ { } },                          // another way to specify a blank line
	// floating point with a description and a name for its argument
	{ { 'f', Double.class }, { "some number", "num" } },
	// float with a restriction
	{ { 'g', Float.class }, {}, { Range.positive() } },
	// set of strings -- option may be repeated -- required to be non-empty
	{ { 'h', String.class }, {}, { Cli.Res.SET, Cli.Res.REQUIRED } },
	// some usage information
	{ { Cli.Opt.USAGE, "short description", "long description" } },
	// name for executable
	{ { Cli.Opt.NAME, "hotstuff" } },
	// argument list description
	{ { Cli.Opt.ARGS, "quux", "corge", "others", Cli.Opt.PLUS } },
};

The specification then is an array of arrays of arrays. You can think of this as an array of specification lines. The lines come in three sorts. Those that define a particular option.

{ { "foo" } },
{ { "bar", 'b' } },
{ { 'f', Double.class }, { "some number", "num" } },
{ { 'g', Float.class }, {}, { Range.positive() } },

Those that insert blank lines into the usage information.

{ },
{ { } },

And those that begin with a Cli.Opt constant.

{ { Cli.Opt.USAGE, "short description", "long description" } },
{ { Cli.Opt.NAME, "hotstuff" } },
{ { Cli.Opt.ARGS, "quux", "corge", "others", Cli.Opt.PLUS } },

The last modify properties of the dfh.cli.Cli object or the usage information it generates. The first and third sorts of specification line are described in greater detail below.

Option Lines

An option line contains one to three sub-arrays.

First Array

The first is required. It specifies option names, type, and any default.

{ { "bar", 'b', 'c' } },           // names
{ { "foo", String.class } },        // type
{ { "quux", Integer.class, 10 } },  // default
	

name

Every option requires at least one name. These can be specified by string or character. One character names, whether strings or characters, will be short options with a single hyphen prefix. Such features may be bundled. For example

foo -bar quux

is the same as

foo -b -a -r quux

which is the same as

foo --b --a --r quux

or

foo --b true -a --r=quux

Basically, you can bundle short options but not long ones. You can assign values using = as a separator with long options but not short ones. The only options that do not require an argument are boolean, though you made provide an argument for these as well.

If you attempt to use the same name for two options an error will be thrown. Any option name can be used to retrieve the options value from the dfh.cli.Cli object after arguments have been parsed.

type

The acceptable option types are

If no type is provided, the option will be boolean. For more concerning these types see here.

default

An options default value, if any, is the last item in the first array. Boolean options always have a default value of false , though you may change this. If you provide an explicit default value, it will be listed in usage information.

	-e        <val>  whole number option; default: 1
	

An option cannot be marked as required if it has a default. See the third array below.

Second Array

The second array in an option line is used only in the construction of usage information. It provides a description of the option and possibly, if the option is non-boolean, a name for its argument.

// only a description, default name "str" used
{ { "foo", String.class }, { "description" } },
// this one has an argument name
{ { "bar", String.class }, { "another description", "baz" } },

produces

	--foo     <str>  description
	--bar     <baz>  another description

If you don't wish to provide any of this information, a simple description will be generated indicating the option type. If you wish to provide some restriction but are satisfied with the default description, you must provide an empty array for the second item in the option specification line.

{ { "foo", Integer.class }, {}, { Range.positive() } }

Third Array

The third array in an option specification line provides restrictions on the possible values of the option and/or indicates that it may be repeated.

{ { 'a', Integer.class }, {}, { Cli.Res.REQUIRED } },
{ { 'b', Integer.class }, {}, { Cli.Res.REPEATABLE } },
{ { 'c', Integer.class }, {}, { Cli.Res.SET } },
{ { 'd', Integer.class }, {}, { Range.nonNegative() } },
{ { 'e', Integer.class }, {}, { Range.nonNegative(), Cli.Res.SHH } },
{ { 'f', Integer.class }, {},
        { Range.nonNegative(), new ValidationRule<Integer>() {
            @Override
            public void test(Integer arg)
                    throws ValidationException {
                double d = Math.sqrt(arg);
                if (Math.floor(d) != d)
                    throw new ValidationException(
                            "not a square");
            }
        } } },

These values may be mixed and matched. For example, an option can be required and repeatable, in which case the user must specify one or more values for the option.

Most of the items one puts in the third array will modify the option description. For instance, the description will mention if the option is repeatable, and required options are boldly marked REQUIRED. The Cli.Res.SHH constant, on the other hand, will silence all the additional usage text added via the third specification array.

The fifth example above shows multiple validation rules being applied to an option. Validation rules can be generated ad hoc like this, but a number of useful rules are provided in the package dfh.cli.rules among these are

dfh.cli.rules.Range

This class specifies ranges of the number line that a numeric option's value must fall within. The easiest way to use this is via factory methods that generate various sorts of ranges:

dfh.cli.rules.IntSet

This class is useful if the value must fall within some set not equivalent to a range.

{ { 'a', Integer.class }, {}, { new IntSet(1, 5, 28) } }

dfh.cli.rules.StrSet

This rule validates a string value against a pattern:

{ { 'a', String.class }, {}, { new StrSet("foo", "bar", "quux") } }

Any value provided must be in the specified, case-sensitive set.

dfh.cli.rules.StrRegex

This rule validates a string value against a pattern:

{ { 'a', String.class }, {}, { new StrRegex("[^e]+") } }

Either a string or a java.util.regex.Pattern may be provided to the constructor. The pattern must match against the entire argument.

Silencing Noisy Validation Rules

Many validation rules add text to the option description that appears in the usage information. If you wish to remove the contribution of a particular rule from the description, you can call ValidationRule.shh() , like so:

{ { 'a', Integer.class }, {}, { new IntSet(1, 5, 28).shh() } }

This method sets the rule to be quiet and returns the rule itself.

dfh.cli.Cli.Opt Lines

ARGS

The ARGS line is probably the most useful of the dfh.cli.Cli.Opt lines. You use it to specify what arguments the executable expects after the options. If the arguments on the command line doesn't provide all the required options, a validation error will be thrown. Some examples:

Object[][][] spec = { { { Cli.Opt.ARGS } } };
Cli cli = new Cli(spec);
cli.parse("--help");
USAGE: EXECUTABLE [options]

	--help -? -h    print usage information
Object[][][] spec = { { { Cli.Opt.ARGS, "foo" } } };
Cli cli = new Cli(spec);
cli.parse("--help");
USAGE: EXECUTABLE [options] <foo>

	--help -? -h    print usage information
Object[][][] spec = { { { Cli.Opt.ARGS, "foo", "bar" } } };
Cli cli = new Cli(spec);
cli.parse("--help");
USAGE: EXECUTABLE [options] <foo> <bar>

	--help -? -h    print usage information
Object[][][] spec = { { { Cli.Opt.ARGS, "foo", "bar", Cli.Opt.STAR } } };
Cli cli = new Cli(spec);
cli.parse("--help");
USAGE: EXECUTABLE [options] <foo> <bar>*

	--help -? -h    print usage information
Object[][][] spec = { { { Cli.Opt.ARGS, "foo", Cli.Opt.PLUS } } };
Cli cli = new Cli(spec);
cli.parse("--help");
USAGE: EXECUTABLE [options] <foo>+

	--help -? -h    print usage information

One can retrieve the entire argument list from the dfh.cli.Cli object

List<String> args = cli.argList();
retrieve particular arguments by name (int the case of STAR , or PLUS arguments this is just the first argument in the list)
String arg = cli.argument("foo");
or the list of the arguments slurped into any final argument name
List<String> args = cli.slurpedArguments();

USAGE

A usage line provides text that will only by shown with usage information: an abstract, or short description, that appears right before the option descriptions; and a long description that appears at the end.

Object[][][] spec = { { { Cli.Opt.USAGE, "short description",
		"much longer description" } }, };
Cli cli = new Cli(spec);
cli.parse("--help");
USAGE: EXECUTABLE [options] <arg>*

	short description

	--help -? -h    print usage information

much longer description

If you prefer to keep your long usage text in a separate text file or otherwise apart from your code, you can specify an input stream in a second array. Usage text is formatted according to the following rules:

  1. The right margin for wrapping text is 80 characters, though this can be set with Cli.setMargin(int)
  2. Whitespace inside wrapped text is normalized to a single space. Marginal whitespace is trimmed.
  3. Single line breaks not followed by whitespace are treated like any other whitespace.
  4. Double line breaks mark paragraph boundaries.
  5. Any space at the beginning of the text or after a line break indicates preformatted text. This turns off word wrapping and whitespace normalization until the next line break.

So for example, the following textThis is from the beginning of the second chapter of Vaarallinen juhannus, by Tove Jansson. I was going to use the standard lorem ipsum text, but then I figured why not branch out. Also, this example shows that the word wrap algorithm can handle any character set. It doesn't attempt to find syllable boundaries but only breaks on whitespace.

Se syttyi kapeasta juovasta, joka hapuili kauan pitkin taivaanrantaa
ennen kuin uskalsi nousta korkeammalle.

Oli tyyni ja kaunis ilma.

Mutta laineet vyöryivät kiihkeänä sekamelskana yli uusien rantojen,
jotka eivät koskaan ennen olleet kohdanneet merta.
Tulta syöksevä vuori, joka oli saanut kaiken tämän aikaan, oli nut rauhoittunut.

will wrap to

Se syttyi kapeasta juovasta, joka hapuili kauan pitkin taivaanrantaa ennen kuin 
uskalsi nousta korkeammalle.

Oli tyyni ja kaunis ilma.

Mutta laineet vyöryivät kiihkeänä sekamelskana yli uusien rantojen, jotka eivät 
koskaan ennen olleet kohdanneet merta. Tulta syöksevä vuori, joka oli saanut 
kaiken tämän aikaan, oli nut rauhoittunut.

and

Se syttyi kapeasta juovasta, joka hapuili kauan pitkin taivaanrantaa
ennen kuin uskalsi nousta korkeammalle.

   Oli  tyyni 
      ja kaunis            ilma.
Mutta laineet vyöryivät kiihkeänä sekamelskana yli uusien rantojen,
jotka eivät koskaan ennen olleet kohdanneet merta.
Tulta syöksevä vuori, joka oli saanut kaiken tämän aikaan, oli nut rauhoittunut.

will wrap to

Se syttyi kapeasta juovasta, joka hapuili kauan pitkin taivaanrantaa ennen kuin 
uskalsi nousta korkeammalle.

   Oli  tyyni 
      ja kaunis            ilma.
Mutta laineet vyöryivät kiihkeänä sekamelskana yli uusien rantojen, jotka eivät 
koskaan ennen olleet kohdanneet merta. Tulta syöksevä vuori, joka oli saanut 
kaiken tämän aikaan, oli nut rauhoittunut.

load usage via dfh.cli.Cli.class.getClassLoader()

If you specify the resource as a string in the second array, dfh.cli.Cli will attempt to use its class loader to load the text as a resource. This is particularly handy if you want to pack the long usage text into a jar file.

Object[][][] spec = { { { Cli.Opt.USAGE, "short description"},
		{ "usage.txt" } }, };

load usage via java.io.InputStream

If you provide an InputStream object in the second array, it will attempt to load your usage text from here.

InputStream is = new FileInputStream(f);
Object[][][] spec = { { { Cli.Opt.USAGE, "short description"}, { is } }, };

TEXT

A TEXT line is useful for breaking up a long sequence of options into functional blocks. The string following Cli.Opt.TEXT will be inserted into the list of option descriptions in the usage text.

Object[][][] spec = {
	{ { "foo" } },
	{ { "bar" } },
	{ { "baz" } },
	{ { Cli.Opt.TEXT } },
	{ { Cli.Opt.TEXT, "and now for something completely different" } },
	{ { Cli.Opt.TEXT } },
	{ { "quux", Integer.class } },
	{ { "corge", Integer.class } },
	};
Cli cli = new Cli(spec);
cli.parse("--help");
USAGE: EXECUTABLE [options] <arg>*

	--foo               a boolean option
	--bar               a boolean option
	--baz               a boolean option

    and now for something completely different

	--quux       <val>  whole number option
	--corge      <val>  whole number option

Note that the left margin is indented to line up with the options being described. The normal text wrapping rules will apply to this text. Also, { { Cli.Opt.TEXT } } is functionally equivalent to the much more concise { } .

NAME

A NAME line simply tells the Cli object what your executable is called. Ideally, this is some simple executable on your binary path -- whatever the user typed to see the usage information. Here's something that will work if you haven't wrapped your Java executable in something more comfortable on the command line:

{ { Cli.Opt.NAME, "java " + MyClass.class.getName() } }

If you provide no NAME line, dfh.cli will call your program “EXECUTABLE”.

VERSION

If you wish the user to be able to discover the version of your application from the command line, use the VERSION constant. The next argument will be stringified to produce a version number.

{ { Cli.Opt.VERSION, 1.5 } }
or
{ { Cli.Opt.VERSION, "1.5.1" } }

dfh.cli will automatically add an option to retrieve this version number. It will seek a name for this version in the set {version,v} , using both if both are available, throwing an error if neither is available.