Relentless Coding

A Developer’s Blog

How to Use Groovy's CliBuilder

Let’s use Groovy’s CliBuilder to create CLI programs that can take flags.

Intro

I write quite some scripts at work, either for myself or for my team. I used to write these scripts in Bash, but since we are using Windows at work, it made sense to switch to a more general scripting language. Because we’re a Java shop, Groovy seemed like a natural choice. Now, it’s often very convenient to have named command-line arguments (e.g. tool --output out.ext --input in.ext) as opposed to positional arguments (e.g. tool out.ext in.ext): named arguments allow for putting the arguments in any order you want, and you can leave out optional arguments and use the defaults. Groovy’s CliBuilder makes things especially easy.

A sample program

weather.groovy

@Grab(group='commons-cli', module='commons-cli', version='1.4')

class Main {
    static main(args) {
        CommandLineInterface cli = CommandLineInterface.INSTANCE
        cli.parse(args)
    }
}

enum CommandLineInterface {
    INSTANCE

    CliBuilder cliBuilder

    CommandLineInterface() {
        cliBuilder = new CliBuilder(
                usage: 'weather [<options>]',
                header: 'Options:',
                footer: 'And here we put footer text.'
        )
        // set the amount of columns the usage message will be wide
        cliBuilder.width = 80  // default is 74
        cliBuilder.with {
            h longOpt: 'help', 'Print this help text and exit.'
            n(longOpt: 'max-count', args: 1, argName: 'number',
                    'Limit the number of days shown')
            '1' longOpt: 'one', "Show today's forecast"
            '2' longOpt: 'two', "Show today's and tomorrow's forecast"
            _(longOpt: 'url', args: 1, argName: 'URL',
                    'Use given URL to query for weather.')
            D(args: 2, valueSeparator: '=', argName: 'property=value',
                    'Use value for given property.')
        }
    }

    void parse(args) {
        OptionAccessor options = cliBuilder.parse(args)

        if (!options) {
            System.err << 'Error while parsing command-line options.\n'
            System.exit 1
        }

        if (options.h) {
            cliBuilder.usage()
            System.exit 0
        }

        if (options.n) {
            // handle user's input
        }
        if (options.'1') {
            // show weather for one day
        }
        if (options.url) {
            URI uri = new URI(options.url)
        }
        if (options.Ds) {
            assert options.Ds.class == ArrayList.class
        }
    }
}

When invoked with groovy weather.groovy --help, the program will output:

usage: weather []
Options:
 -1,--one           Show today's forecast
 -2,--two           Show today's and tomorrow's forecast
 -D                 Use value for given property.
 -h,--help          Print this help text and exit.
 -n,--max-count     Limit the number of days shown
    --url           Use given URL to query for weather.
And here can put footer text.

You can specify short and long names: cliBuilder.n specifies that the program will accept an argument -n. To also specify a long name, we add longOpt: max-count. To use only a long name, we can replace the short name by _: cliBuilder._(longName: 'wow-such-long'). Note that cliBuilder._ can be reused several times (in contrast to other short names).

The number of arguments can be indicated by using the args parameter. If you use more than one parameter, a valueSeparator must be indicated, which is used to split up the argument. I found that the number assigned to args is not very strict: when called with argument -Dmyprop, the following is valid.

assert options.Ds == ['myprop']

Note how we append an s to the option name (options.Ds) to get a list of properties and values. A normal invocation groovy weather.groovy -Dpancakes=yesplease would result in:

assert options.Ds == ['pancakes', 'yesplease']

When required: true is set, the program will shout at you if the argument is not used. For example, if we specify cliBuilder.q(required: true) but fail to provide the -q argument on the command line, the program will exit with error: Missing required option: q.

One thing to remember is that, if you want to use digits as argument names, you have to stringify them first by putting quotation marks around them: cliBuilder.'1'.

More on CliBuilder

Check the Groovy documentation on CliBuilder for more information.

Dependencies

Groovy’s CliBuilder depends on cli-commons:

build.gradle

dependencies {
    compile group: 'commons-cli', name: 'commons-cli', version: '1.4'
}