I'm trying to parse some command line arguments in any order. Two of them are single-value and mandatory and the other one is an optional comma-separated list:
usage:
-mo <value1,value2,...,valueN>
-sm1 <value>
-sm2 <value>
Using any of the old parsers (BasicParser
, PosixParser
and GnuParser
) the code works fine but if I use DefaultParser
instead, a MissingOptionException
is thrown.
import java.util.Arrays;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
public class Foo {
public static void main(String[] args) throws Exception {
Option singleMandatory1 = Option.builder("sm1")
.argName("value")
.hasArg()
.required()
.build();
Option singleMandatory2 = Option.builder("sm2")
.argName("value")
.hasArg()
.required()
.build();
Option multipleOptional = Option.builder("mo")
.argName("value1,value2,...,valueN")
.hasArgs()
.valueSeparator(',')
.build();
Options options = new Options();
options.addOption(singleMandatory1);
options.addOption(singleMandatory2);
options.addOption(multipleOptional);
CommandLineParser parser = new DefaultParser();
CommandLine line = parser.parse(options, args);
for (Option o : line.getOptions()) {
System.out.println(o.getOpt() + '\t'
+ Arrays.toString(o.getValues()));
}
}
}
-sm1 Alice -sm2 Bob -mo Charles,David
works
-sm1 Alice -mo Charles,David -sm2 Bob
works only using the old (and now deprecated) parsers
Am I missing something? I'm using commons-cli-1.4-SNAPSHOT
.
Thanks for any help.
I think this is a bug in DefaultParser
. Ultimately it boils down to this method:
/**
* Tells if the token looks like a short option.
*
* @param token
*/
private boolean isShortOption(String token)
{
// short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
return token.startsWith("-") && token.length() >= 2 &&
options.hasShortOption(token.substring(1, 2));
}
(Line broken to make it easier to read on SO).
Unfortunately this will always return false
for "short options" that are more than a single character, because of the final clause options.hasShortOption(token.substring(1, 2))
. It will certainly fail on items 2, 3 and 4 in the comment immediately preceding the return statement, which leads me to believe it is a bug. I may be misinterpreting the intention behind the comment, so please ignore previous statement.
A fix might look something like this:
/**
* Tells if the token looks like a short option.
*
* @param token
*/
private boolean isShortOption(String token)
{
// short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
// extended to handle short options of more than one character
if (token.startsWith("-") && token.length() >= 2)
{
return options.hasShortOption(token.substring(1, 2)) ||
options.hasShortOption(extractShortOption(token));
}
return false;
}
/**
* Extract option from token. Assume the token starts with '-'.
*/
private String extractShortOption(String token)
{
int index = token.indexOf('=');
return (index == -1) ? token.substring(1) : token.substring(1, index);
}
Unfortunately there is no nice way to get this into DefaultParser
as the methods are private, the calling methods are private (isOption
, isArgument
and handleToken
) and DefaultParser
relies on package local methods in Options
.
The way I tested a fix was to copy/plaster DefaultParser
into my local project, move into org.apache.commons.cli
package and make the changes above.
As a dodgy work around for the specific case in the question, you could add a dummy short option "s"
, which would trick isShortOption(...)
into returning true for sm1
and/or sm2
options. Something like this:
Option singleMandatory1 = Option.builder("sm1")
.argName("value")
.hasArg()
.required()
.build();
Option singleMandatory2 = Option.builder("sm2")
.argName("value")
.hasArg()
.required()
.build();
Option multipleOptional = Option.builder("mo")
.argName("value1,value2,...,valueN")
.hasArgs()
.valueSeparator(',')
.build();
Option dummyOptional = Option.builder("s")
.build();
Options options = new Options();
options.addOption(singleMandatory1);
options.addOption(singleMandatory2);
options.addOption(multipleOptional);
options.addOption(dummyOptional);
CommandLineParser parser = new DefaultParser();
CommandLine line = parser.parse(options, args);
This issue on ASF JIRA appears to capture the issue, albeit with a slightly different trigger case: https://issues.apache.org/jira/browse/CLI-265