kohsuke/args4j — Issue #5: Add ability to recurse parsing sub-commands
Author: raphaeljolivet
Created: 2011-11-17 10:15:23+00:00
State: open

Following the request found on JIRA (http://java.net/jira/browse/ARGS4J-20), I have made some fix to support basic sub-command parsing.

I copy here my answer made on JIRA

---

I have extended the method parseArgument() to accept a "strict" mode.
If set to false, Args4j will not complain about trailing arguments/options, and will return a list of unused arguments /options instead.

I have one class per sub-command, with proper arg4j annotations.
I take the first remaining argument, which should be the "command" and switch on it to choose the proper sub-command handler.

Then, I instantiate the sub-command class and use args4j to parse the remaining arguments against it.

Here is the piece of code that does the trick :

```java
/**
* Parses the command line arguments and set them to the option bean
* given in the constructor.
*
* @param args arguments to parse
*
* @throws CmdLineException
* if there's any error parsing arguments, or if
* {@link Option#required() required} option was not given.
*/

public String[] parseArgument(boolean strict, final String... args) throws CmdLineException {
CmdLineImpl cmdLine = new CmdLineImpl(args);

Set present = new HashSet();
int argIndex = 0;

ArrayList remainingArgs = new ArrayList();

while( cmdLine.hasMore() ) {
String arg = cmdLine.getCurrentToken();

if( isOption(arg) ) {
boolean isKeyValuePair = arg.indexOf('=')!=-1;
// parse this as an option.
this.currentOptionHandler = isKeyValuePair ? findOptionHandler(arg) : findOptionByName(arg);

if (this.currentOptionHandler==null) {
if (strict) {
throw new CmdLineException(Messages.UNDEFINED_OPTION.format(arg));
} else {
remainingArgs.add(arg);
cmdLine.proceed(1);
continue;
}
}

// known option; skip its name
cmdLine.proceed(1);

} else {
if (argIndex >= this.arguments.size()) {
if (strict) {
Messages msg = this.arguments.size() == 0 ? Messages.NO_ARGUMENT_ALLOWED : Messages.TOO_MANY_ARGUMENTS;
throw new CmdLineException(msg.format(arg));
} else {
remainingArgs.add(arg);
cmdLine.proceed(1);
continue;
}
}

// known argument
this.currentOptionHandler = this.arguments.get(argIndex);
if (!this.currentOptionHandler.option.isMultiValued())
argIndex++;
}
int diff = this.currentOptionHandler.parseArguments(cmdLine);
cmdLine.proceed(diff);
present.add(this.currentOptionHandler);

} // End of loop on input arguments

// make sure that all mandatory options are present
for (OptionHandler handler : this.options)
if(handler.option.required() && !present.contains(handler))
throw new CmdLineException(Messages.REQUIRED_OPTION_MISSING.format(handler.option.toString()));

// make sure that all mandatory arguments are present
for (OptionHandler handler : this.arguments)
if(handler.option.required() && !present.contains(handler))
throw new CmdLineException(Messages.REQUIRED_ARGUMENT_MISSING.format(handler.option.toString()));

return remainingArgs.toArray(new String[0]);
}

/** Strict mode by default */
public void parseArgument(final String... args) throws CmdLineException
{
this.parseArgument(true, args);
}
```

I think this could be done more nicely, with an annotation like :

```java
@Command(name="help", class=HelpCommand.class)
@Command(name="del", class=DelCommand.class);
@Command(name="foo", class=FooCommand.class);
@Command(name="bar", class=BarCommand.class);
AbstractSubCommand command;
```

This way, the framework could process additionnal checks (like ensuring that the parent options does not conflict with sub-commands options). It could also produce a nice "usage" display, with all sub-commands options.
But this would require much more work.
Hope this helps.

Raphael