I'm currently using picocli 4.7.0-SNAPSHOT to great effect in a Java 11 application that has a complex set of options, so I am making use of the @-file functionality.
What I am trying to get to work is an option specified directly on the command line to override the same option if it exists in the @-file. So options specified on the command line take precedence over the @-file. Is that possible.
When I try to run my test application, heavily based on the picocli example, with both a command line option and an @-file, I get the following error from picocli along with the expected usage:
myapp --sourceDatabaseType=MySQL @.\myapp.options
option '--sourceDatabaseType' (<sourceDatabaseType>) should be specified only once
and then the expected usage information.
Let me paraphrase the question to see if I understand it correctly:
If the end user specifies an option directly on the command line, the command line value should be used, while if that option is not specified on the command line, the value in a file should be used.
Essentially, you are using an @-file with the intention to define default values for one or more options. However, that is not what @-files were designed for: picocli cannot distinguish between arguments that came from the command line and arguments that came from the @-file.
I would suggest using picocli's default provider mechanism instead.
One idea is to use the built-in PropertiesDefaultProvider
:
import picocli.CommandLine.PropertiesDefaultProvider;
@Command(name = "myapp", defaultValueProvider = PropertiesDefaultProvider.class)
class MyApp { }
PropertiesDefaultProvider
also uses a file, and the values in that file are only used for options that were not specified on the command line.
The tricky bit is the location of this file. PropertiesDefaultProvider
looks for the file in the following locations:
picocli.defaults.${COMMAND-NAME}.path
.${COMMAND-NAME}.properties
in the end user's user home directory(Replace ${COMMAND-NAME}
by the name of the command, so for a command named myapp
, the system property is picocli.defaults.myapp.path
)
To give end users the ability to specify the location of the file, we need to set the system property before picocli completes parsing the command line arguments.
We can do that with an @Option
-annotated setter method. For example:
class MyApp {
@Option(names = "-D")
void setSystemProperty(Map<String, String> properties) {
System.getProperties().putAll(properties);
}
}
This would allow end users to invoke the command with something like this:
myapp --sourceDatabaseType=MySQL -Dpicocli.defaults.myapp.path=.\myapp.options
If this is too verbose, you could go one step further and create a special -@
option, to allow users to invoke the command with something like this:
myapp --sourceDatabaseType=MySQL -@.\myapp.options
The implementation for this would be an annotated setter method, similar to the above:
class MyApp {
@Spec CommandSpec spec; // injected by picocli
@Option(names = "-@")
void setDefaultProviderPath(File path) {
// you could do some validation here:
if (!path.canRead()) {
String msg = String.format("ERROR: file not found: %s", path);
throw new ParameterException(spec.commandLine(), msg);
}
// only set the system property if the file exists
System.setProperty("picocli.defaults.myapp.path", path.toString());
}
}