Real-world usage of Commons CLI to parse command line arguments

The Apache Commons Command Line Interface (CLI), is a handy little toolkit for handling command line arguments.  Java programs, as with almost all other programs running on an OS that supports a CLI, permits passing of arguments from the user as an ordered collection of strings.

The problem with Commons CLI is that it doesn’t appear to manage a scenario that I wanted supported in xml2csv, whereby:

  1. The configuration file option (-c) is mandatory.  xml2csv cannot do anything meaningful without a configuration file, so one must be specified; EXCEPT
  2. When the user requests help (-h) or version information (-v), then obviously a configuration file is not required.
  3. In the usage message, I want to see both –h and –v as available command line options.

I could find a couple of search hits that gave me an answer to 1 & 2, but I needed to make a further change to support 3.

This is how I created the Options objects to model this:

static {
	// mainOptions contains all the options together, including help and version.
	Options mainOptions = new Options();
	Option option = new Option(OPT_CONFIG_FILE, "configuration-file", true, "A single file containing the configuration to use.");
	option.setRequired(true);
	mainOptions.addOption(option);
	option = new Option(OPT_OUT_DIR, "output-directory", true, "The directory to which the output CSV files will be written.  "
				+ "If not specified, current working directory will be used.  Directory must exist and be writeable.");
	mainOptions.addOption(option);
	option = new Option(OPT_TRIM_WHITESPACE, "preserve-whitespace", false,
				"If specified then whitespace will not be removed from the start and end of output fields.");
	mainOptions.addOption(option);
	option = new Option(OPT_APPEND_OUTPUT, "append-output", false,
				"If specified, all output will be appended to any existing output files.  If an existing file is"
				+ " appended to then field names will not be output.");
	mainOptions.addOption(option);

	// helpOptions contains only the help and version options, it's important that these are both optional.
	// Note how both mainOptions and helpOptions contains help and verbose options.
	Options helpOptions = new Options();

	option = new Option(OPT_HELP, "help", false, "Show help on using xml2csv and terminate.");
	mainOptions.addOption(option);
	helpOptions.addOption(option);

	option = new Option(OPT_VERSION, "version", false, "Show version information and terminate.");
	mainOptions.addOption(option);
	helpOptions.addOption(option);

	MAIN_OPTIONS = mainOptions;
	HELP_OPTIONS = helpOptions;
}

Once the options have been created, then the algorithm for using them is as follows:

  1. Parse the arguments against HELP_OPTIONS.
  2. If –v and –h are specified then carry out the relevant actions.  Note that –h needs to print the usage information as generated from MAIN_OPTIONS.
  3. If neither –v nor –h are specified then parse against MAIN_OPTIONS. If mandatory (-c) options are not specified then an error is printed and a usage message is shown.

The code is as follows:

/**
 * Instance entry point for command line invocation.
 *
 * @param args command line arguments
 */
public void execute(String[] args) {
	if (LOG.isInfoEnabled()) {
		LOG.info("xml2csv execute invoked {}", StringUtil.toString(args));
	}
	try {
		if (!showHelpOrVersion(args)) {
			BasicParser parser = new BasicParser();
			CommandLine cmdLine = parser.parse(MAIN_OPTIONS, args);
			LOG.info("Successfully parsed main options.");
			boolean trimWhitespace = Boolean.parseBoolean(cmdLine.getOptionValue(OPT_TRIM_WHITESPACE));
			boolean appendOutput = Boolean.parseBoolean(cmdLine.getOptionValue(OPT_APPEND_OUTPUT));
			String[] xmlInputs = cmdLine.getArgs();
			String outputDirName = cmdLine.getOptionValue(OPT_OUT_DIR);
			String configFileName = cmdLine.getOptionValue(OPT_CONFIG_FILE);
			execute(configFileName, xmlInputs, outputDirName, appendOutput, trimWhitespace);
		}
	} catch (ProgramException pe) {
		LOG.error("A fatal error caused xml2csv to abort", pe);
		// All we can do is print out the error and terminate the program
		System.err.print(getAllCauses(pe));
	} catch (ParseException pe) {
		// Thrown when the command line arguments are invalid
		LOG.debug("Invalid arguments specified: {}", pe.getMessage());
		System.err.println("Invalid arguments specified: " + pe.getMessage());
		printHelp();
	}
}

/**
 * If the arguments contain a request for help or verson information, then show these and return true, otherwise return false.
 *
 * @param args command line arguments.
 * @return true if the help or version options have been specified, false otherwise (i.e. normal processing should resume).
 * @throws ParseException if an option parsing exception occurs.
 */
private boolean showHelpOrVersion(String[] args) throws ParseException {
	CommandLineParser parser = new BasicParser();
	CommandLine cmdLine = parser.parse(HELP_OPTIONS, args, true);
	if (cmdLine.getOptions().length == 0) {
		return false;
	}

	LOG.info("Showing help or version information and terminating.");
	if (cmdLine.hasOption(OPT_HELP)) {
		printHelp();
	} else if (cmdLine.hasOption(OPT_VERSION)) {
		printVersionInfo();
	} else {
		throw new BugException("Options set up is wrong.  Found help or version, but neither believes they have been passed.");
	}
	return true;
}

You can see the code in action in xml2csv, check out Program.java.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.