Reputation: 13
I googled around for a bit and also searched on StackOverflow and of course the Picocli docs but didn't come to any solution.
The company I work at uses a special format for command line parameters in batch programs:
-VAR ARGUMENT1=VALUE -VAR ARGUMENT2=VALUE2 -VAR BOOLEANARG=FALSE
(Don't ask me why this format is used, I already questioned it and didn't get a proper answer.) Now I wanted to use Picocli for command line parsing. However, I can't get it to work with the parameter format we use, because the space makes Picocli think those are two separate arguments and thus it won't recognise them as the ones I defined.
This won't work, obviously:
@CommandLine.Option( names = { "-VAR BOOLEANARG" } )
boolean booleanarg = true;
Calling the program with -VAR BOOLEANARG=FALSE won't have any effect.
Is there any way to custom define those special option names containing spaces? Or how would I go about it? I also am not allowed to collapse multiple arguments as parameters into one -VAR option.
Help is much appreciated. Thanks and best regards, Rosa
Upvotes: 1
Views: 1301
Reputation: 36834
The simplest solution is to make -VAR
a Map option. That could look something like this:
@Command(separator = " ")
class Simple implements Runnable {
enum MyOption {ARGUMENT1, OTHERARG, BOOLEANARG}
@Option(names = "-VAR",
description = "Variable options. Valid keys: ${COMPLETION-CANDIDATES}.")
Map<MyOption, String> options;
@Override
public void run() {
// business logic here
}
public static void main(String[] args) {
new CommandLine(new Simple()).execute(args);
}
}
The usage help for this example would look like this:
Usage: <main class> [-VAR <MyOption=String>]...
-VAR <MyOption=String>
Variable options. Valid keys: ARGUMENT1, OTHERARG, BOOLEANARG.
Note that with this solution all values would have the same type (String
in this example), and you may need to convert to the desired type (boolean
, int
, other...) in the application.
However, this may not be acceptable given this sentence in your post:
I also am not allowed to collapse multiple arguments as parameters into one -VAR option.
One idea for an alternative is to use argument groups: we can make ARGUMENT1
, OTHERARG
, and BOOLEANARG
separate options, and put them in a group so that they must be preceded by the -VAR
option.
The resulting usage help looks something like this:
Usage: group-demo [-VAR (ARGUMENT1=<arg1> | OTHERARG=<otherValue> |
BOOLEANARG=<bool>)]... [-hV]
-VAR Option prefix. Must be followed by one of
ARGUMENT1, OTHERARG or BOOLEANARG
ARGUMENT1=<arg1> An arg. Must be preceded by -VAR.
OTHERARG=<otherValue> Another arg. Must be preceded by -VAR.
BOOLEANARG=<bool> A boolean arg. Must be preceded by -VAR.
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
And the implementation could look something like this:
@Command(name = "group-demo", mixinStandardHelpOptions = true,
sortOptions = false)
class UsingGroups implements Runnable {
static class MyGroup {
@Option(names = "-VAR", required = true,
description = "Option prefix. Must be followed by one of ARGUMENT1, OTHERARG or BOOLEANARG")
boolean ignored;
static class InnerGroup {
@Option(names = "ARGUMENT1", description = "An arg. Must be preceded by -VAR.")
String arg1;
@Option(names = "OTHERARG", description = "Another arg. Must be preceded by -VAR.")
String otherValue;
@Option(names = "BOOLEANARG", arity = "1",
description = "A boolean arg. Must be preceded by -VAR.")
Boolean bool;
}
// exclusive: only one of these options can follow a -VAR option
// multiplicity=1: InnerGroup must occur once
@ArgGroup(multiplicity = "1", exclusive = true)
InnerGroup inner;
}
// non-exclusive means co-occurring, so if -VAR is specified,
// then it must be followed by one of the InnerGroup options
@ArgGroup(multiplicity = "0..*", exclusive = false)
List<MyGroup> groupOccurrences;
@Override
public void run() {
// business logic here
System.out.printf("You specified %d -VAR options.%n", groupOccurrences.size());
for (MyGroup group : groupOccurrences) {
System.out.printf("ARGUMENT1=%s, ARGUMENT2=%s, BOOLEANARG=%s%n",
group.inner.arg1, group.inner.arg2, group.inner.arg3);
}
}
public static void main(String[] args) {
new CommandLine(new UsingGroups()).execute(args);
}
}
Then, invoking with java UsingGroups -VAR ARGUMENT1=abc -VAR BOOLEANARG=true
gives:
You specified 2 -VAR options.
ARGUMENT1=abc, OTHERARG=null, BOOLEANARG=null
ARGUMENT1=null, OTHERARG=null, BOOLEANARG=true
With this approach, you will get a MyGroup
object for every time the end user specifies -VAR
. This MyGroup
object has an InnerGroup
which has many fields, all but one of which will be null
. Only the field that the user specified will be non-null
. That is the disadvantage of this approach: in the application you would need to inspect all fields to find the non-null
one that the user specified. The benefit is that by selecting the right type for the @Option
-annotated field, the values will be automatically converted to the destination type.
Upvotes: 1