Friday, July 30, 2010

Explicitly Specifying 'args' Property with Groovy CliBuilder

As I have blogged on previously, Groovy's CliBuilder makes parsing with Apache Commons CLI very straightforward.  However, there are a few minor things to be aware of when using CliBuilder.  One of these observations is the importance of explicitly specifying the args property for options that have more than the default (zero) arguments.  In this blog post, I look at what can happen when this is not done.

The following script, groovyArgsHelloWorld.groovy, demonstrates this with the typical "Hello World" type example.

groovyArgsHelloWorld.groovy
#!/usr/bin/env groovy

//
// groovyArgsHelloWorld.groovy
//
// Simple Groovy script that demonstrates importance of declaring args property
// explicitly in CliBuilder when the number of arguments for the option is
// different than the default of zero arguments.
//

def cli = new CliBuilder()
cli.with
{
   h(longOpt: 'help', 'Help - Usage Information')
   n(longOpt: 'name', 'Name to say hello to', type: String, required: true)
}
def opt = cli.parse(args)
if (!opt) return
if (opt.h) cli.usage()
println "Hello ${opt.n}"

At first glance, it might appear that the above script will print out "Hello" followed by the name specified with the -n option. However, because no 'args' property was specified for the "n" option, the default of zero is assumed and any value provided after the -n flag is ignored. The specification of the -n flag results only in "true" (because the -n flag is present) when printed in this case as shown in the next screen snapshot.


This is easily fixed by explicitly specifying one argument in the option.  The revised version of the script is shown next.

groovyArgsHelloWorld.groovy (Revised)
#!/usr/bin/env groovy

//
// groovyArgsHelloWorld.groovy
//
// Simple Groovy script that demonstrates importance of declaring args property
// explicitly in CliBuilder when the number of arguments for the option is
// different than the default of zero arguments.
//

def cli = new CliBuilder()
cli.with
{
   h(longOpt: 'help', 'Help - Usage Information')
   n(longOpt: 'name', 'Name to say hello to', args: 1, type: String, required: true)
}
def opt = cli.parse(args)
if (!opt) return
if (opt.h) cli.usage()
println "Hello ${opt.n}"

The simple addition of "args: 1" makes a big difference as shown in the next screen snapshot. The output indicates that the value for the -n option is now processed.


Not specifying the args property for certain options when there are multiple required options can lead the Groovy script's CliBuilder instance to think that some of the options are not specified.  In the following script, the three options other than help do not have their args explicitly specified.

#!/usr/bin/env groovy

//
// groovyArgsMultiples.groovy
//
// Simple Groovy script that demonstrates importance of declaring args property
// explicitly in CliBuilder when the number of arguments for the option is
// different than the default of zero arguments.
//

def cli = new CliBuilder()
cli.with
{
   h(longOpt: 'help', 'Help - Usage Information')
   c(longOpt: 'city', 'City', type: String, required: true)
   y(longOpt: 'county', 'County', type: String, required: true) 
   s(longOpt: 'state', 'State', type: String, required: true)
}
def opt = cli.parse(args)
if (!opt) return
if (opt.h) cli.usage()
println "The location is ${opt.c}, ${opt.y}, ${opt.s}."

Because the number of options for the -c, -y, and -s options are not specified explicitly, this script does not work as hoped. Its output is shown in the next screen snapshot.


As the output demonstrates, the script's CliBuilder support cannot process the provided options because it doesn't expect there to be any values for the -c, -y, and -s options.  Again, fixing this is easy by simply specifying that each option has 1 option rather than letting the zero arguments default confuse the issue.  The revised script is shown next.

#!/usr/bin/env groovy

//
// groovyArgsMultiples.groovy
//
// Simple Groovy script that demonstrates importance of declaring args property
// explicitly in CliBuilder when the number of arguments for the option is
// different than the default of zero arguments.
//

def cli = new CliBuilder()
cli.with
{
   h(longOpt: 'help', 'Help - Usage Information')
   c(longOpt: 'city', 'City', type: String, args: 1, required: true)
   y(longOpt: 'county', 'County', type: String, args: 1, required: true) 
   s(longOpt: 'state', 'State', type: String, args: 1, required: true)
}
def opt = cli.parse(args)
if (!opt) return
if (opt.h) cli.usage()
println "The location is ${opt.c}, ${opt.y}, ${opt.s}."

The additional explicit specification of "args: 1" for each of the three options does the trick!


In all these scripts, I did not need to explicitly specify the args for "help" because that flag was one for which no value was expected to be specified.

Groovy's CliBuilder supports makes it easy to provide more elegant command-line handling than is normally available via raw handling of Java arguments.  This is especially useful when writing Groovy scripts with heavy command-line interaction.

3 comments:

Dustin said...

The GroovyDoc for CliBuilder provides significant comments on how to use CliBuilder, including how to set things like header, footer, and usage.

help required said...

When I used this code in an existing groovy script for arguments, my groovy script failed saying:

java.lang.NoClassDefFoundError: org/apache/commons/cli/ParseException

@DustinMarx said...

help required,

I have only used this with standalone scripts and it has worked fine for me. You might try some of the recommendations at http://stackoverflow.com/questions/13763112/running-groovy-script-from-gradle-using-groovyshell-exception-in-thread-main.