Saturday, February 19, 2011

Customizing Groovy's CliBuilder Usage Statements

There are numerous online examples that use Groovy's baked-in Apache Commons CLI based command-line parsing support. These examples include Groovy Command Line Options, Chris Mahns's Groovy Script: Get SSL Cipher and CLIBuilder Example, Groovy Goodness: Parsing Commandline Arguments with CliBuilder, and some of my own posts such as Explicitly Specifying 'args' Property with Groovy CliBuilder and Using Groovy to Check Seventh Grade Homework. Most of these examples do a nice job of showing how to use Groovy's CliBuilder to accept and properly process command-line arguments, but most of them do not fully demonstrate the granular control one can have over the usage statement provided by CliBuilder. This post addresses that by looking at some of the options one can use to granularly control the usage statement. I also look at nuances involved in specifying multiple values for a single command-line flag/argument.

The following Groovy code listing demonstrates using Groovy's built-in command-line parsing support to obtain city, state, and zip code details. It demonstrates how to handle multiple values for a single argument and how to use the "header" and "footer" options in CliBuilder to add more details to the printed usage statement.

#!/usr/bin/env groovy
/*
 * This is a simple script that demonstrates Groovy's built-in support for
 * Apache Commons CLI command-line argument parsing.
 */

// Many examples of using CliBuilder do so by instantiating it with a single
// 'usage' parameter. This example goes past that.
//    * 'header': Displayed after usage but before options.
//    * 'footer': Displayed after usage options.
//    * Multiple values for same command-line argument/flag.
//
def cli = new CliBuilder(
   usage: 'demoGroovyCliBuilder -c cityName -s stateName -z zipCodes',
   header: '\nAvailable options (use -h for help):\n',
   footer: '\nInformation provided via above options is used to generate printed string.\n')
import org.apache.commons.cli.Option
cli.with
{
   h(longOpt: 'help', 'Usage Information', required: false)
   c(longOpt: 'city', 'City Name', args: 1, required: true)
   s(longOpt: 'state', 'State Name', args: 1, required: true)
   z(longOpt: 'zip', 'Zip Codes (separated by comma)', required: true, args: Option.UNLIMITED_VALUES, valueSeparator: ',')
   o(longOpt: 'options', 'Print Options', required: false)
}
def opt = cli.parse(args)

if (!opt) return
if (opt.h) cli.usage()

def cityName = opt.c
def stateName = opt.s
def zipCodes = opt.zs   // append 's' to end of opt.z to get more than first
def printOptions = opt.o
print "The city ${cityName}, ${stateName} has the following zip codes: "
zipCodes.each
{
   print "${it} "
}
if (printOptions) println cli.options


In the above listing, I provided the CliBuilder constructor with three arguments: the oft-used usage argument along with header and footer arguments. The usage string is shown first, followed by the header string, followed by presentation of the options, followed by the footer. This is demonstrated in the next screen snapshot which shows the output of the script when the usage is displayed because no required options are specified and includes a successful running of the script to demonstrate the correct parsing of the multiple comma-separated zip codes provided to the -z/--zip option.


The "header" and "footer" are convenient because they allow for further customization of the usage statement beyond simple specifying a single Usage string and the individual options' descriptions. Various details about the options or about using the script can be provided via the header and footer mechanisms.

The use of multiple values for a single argument can also be highly useful. The direct use of Apache Commons CLI's Option class (and specifically its UNLIMITED_VALUES constant field) allows the developer to communicate to CliBuilder that there is a variable number of values that need to be parsed for this option. The character that separates these multiple values (a common in this example) must also be specified by specifying the character via "valueSeparator." It is also important to note that multiple values for the same argument are accessed by adding an "s" to the end of the option name when retrieving it from the instance of Groovy's OptionAccessor (variable named 'opt' in code listing above.)

Conclusion

Groovy's built-in command-line parsing is nice to use and can be customized via the usage, header, and footer settings. It also supports specification of multiple values for a single argument.

No comments: