CommandLineInterface

The purpose of dfh.cli is to allow you to write a command line utility in Java while spending as little time as possible parsing and validating command line options -- basically, to give Java programmers something like Getopt::Long::Descriptive. It's unlikely that Java code will achieve the concision of Perl, but at least we could spend less time beating around the bush.I have used the Apache Commons CLI library. I found I was spending too much time creating options and such. I wanted to spend still less time mucking with the command line. Also, I wanted it to be easier to generate usage information. So I wrote this.

Example

In dfh.cli you create a command line specification as an Object[][][] . You use this specification, along with some optional modifiers, to create a dfh.cli.Cli object. You initialize this object by passing it the raw arguments. Then you query it to obtain the values relevant to your code. This process is illustrated below.

The following bit of code

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 } },
};
Cli cli = new Cli(spec);
cli.parse("--help");

produces the following usage information.

USAGE: hotstuff [options] <quux> <corge> <others>+

  short description

    --foo            a boolean option
    --bar -b         a boolean option
    -c               usually false

    -d        <int>  integer option
    -e        <int>  integer option; default: 1

    -f        <num>  some number
    -g        <fp>   floating point number option; value must be > 0
    -h        <str>  a set of strings; repeatable; REQUIRED

    --help -?        print usage information

long description

Overview

Most of the essential parts of using dfh.cli are illustrated in the example above. You

  1. specify a command line interface
    Object[][][] spec = {
    	{ { "foo" } }, // boolean option
    	// ...
    	{ { Cli.Opt.ARGS, "quux", "corge", "others", Cli.Opt.PLUS } },
    };
  2. create a dfh.cli.Cli object with the specification, optionally passing in modifiers that add options or change the object's behavior
    Cli cli = new Cli(spec);
  3. and you parse some arguments with this object
    cli.parse("--help");

Two common steps are left out, however. You also generally

  1. extract the parsed values
    boolean foo = cli.bool("foo"), bar = cli.bool("bar");
    Integer d = cli.integer("d");
    Set<String> h = (Set<String>) cli.collection("h");
  2. and do some additional validation beyond what was done during argument parsing
    if (cli.bool("c") && bar)
    	cli.die("Not both -c and --bar!!! We go no further!");
    if (foo && h.size() > 13)
    	cli.error("if --foo, then set -h must be less than 14");
    if (d == null && bar)
    	cli.error("you must specify -d if --bar");
    cli.errorCheck();

Further Details

specification

For how to do this

Object[][][] spec = {
	{ { "foo" } }, // boolean option
	// ...
	{ { Cli.Opt.ARGS, "quux", "corge", "others", Cli.Opt.PLUS } },
};
go here.

modifiers

For more information about stuff like this

Cli cli = new Cli(spec, Cli.Mod.THROW_EXCEPTION);
go here.

extracting option values

For everything you can do here

boolean foo = cli.bool("foo"), bar = cli.bool("bar");
Integer d = cli.integer("d");
Set<String> h = (Set<String>) cli.stringCollection("h");
go here.

validation

And for validation tricks, go here.

Shell Hint

Once you've written a command line application you want to use it from the command line as you do less or grep . This means you type the name and some arguments and it does what you want. What you don't want to do is type this.

java -cp one.jar:another.jar:/usr/share/java/stillAnother.jar big.package.prefix.Class --foo --bar

This is obnoxious. If you write a program which requires someone to type all this you're lucky if they use it. If you're writing a scripting language you can fix most of this with the shebang trick:

#! /usr/bin/env perl -I~/peculiar/library/path
# this works great for Perl, Python, Ruby, Bash, etc.!
# Put this at the top of your script and make it executable and you're golden.
# Not so for Java.

What to do with Java? You create a shell script to invoke your Java class, taking care to protect command line arguments from the shell. For example, the following will work in Bash.

#!/bin/sh

JAVA=/some/path/to/java
CLASSPATH=~/my.jar
CLASS=foo.bar.Quux
$JAVA -cp $CLASSPATH $CLASS "$@"

You can then put the name of this script into the spec like so

{ { Cli.Opt.NAME, "myscript" } },
to ensure the usage information is generated correctly.