Commons CLI is not honoring my command line setup - java

Using Apache Commons CLI 1.2 here. I have an executable JAR that needs to take 2 runtime options, fizz and buzz; both are strings that require arguments/values. I would like (if at all possible) my app to be executed like so:
java -jar myapp.jar -fizz "Alrighty, then!" -buzz "Take care now, bye bye then!"
In this case, the value for the fizz option would be "Alrighty, then!", etc.
Here's my code:
public class MyApp {
private Options cmdLineOpts = new Options();
private CommandLineParser cmdLineParser = new GnuParser();
private HelpFormatter helpFormatter = new HelpFormatter();
public static void main(String[] args) {
MyApp myapp = new MyApp();
myapp.processArgs(args);
}
private void processArgs(String[] args) {
Option fizzOpt = OptionBuilder
.withArgName("fizz")
.withLongOpt("fizz")
.hasArg()
.withDescription("The fizz argument.")
.create("fizz");
Option buzzOpt = OptionBuilder
.withArgName("buzz")
.withLongOpt("buzz")
.hasArg()
.withDescription("The buzz argument.")
.create("buzz");
cmdLineOpts.addOption(fizzOpt);
cmdLineOpts.addOption(buzzOpt);
CommandLine cmdLine;
try {
cmdLine = cmdLineParser.parse(cmdLineOpts, args);
// Expecting to get a value of "Alright, then!"
String fizz = cmdLine.getOptionValue("fizz");
System.out.println("Fizz is: " + fizz);
} catch(ParseException parseExc) {
helpFormatter.printHelp("myapp", cmdLineOpts, true);
throw parseExc;
}
}
}
When I run this I get the following output:
Fizz is: null
What do I need to do to my code so that my app can be invoked the way I want it to? Or what's the closest I can get to it?
Bonus points: If someone can explain to me the difference between the OptionBuilder's withArgName(...), withLongOpt(...) and create(...) arguments, as I am passing in the same value for them all like so:
Option fizzOpt = OptionBuilder
.withArgName("fizz")
.withLongOpt("fizz") } Why do I have to pass the same value in 3 times to make this work?!?
.create("fizz");

First the .hasArg() on your OptionBuilder tells it that you expect an argument after the paramter flag.
I got it to work with this command line
--fizz "VicFizz is good for you" -b "VicBuzz is also good for you"
Using the following code - I put this in the constructor
Option fizzOpt = OptionBuilder
.withArgName("Fizz")
.withLongOpt("fizz")
.hasArg()
.withDescription("The Fizz Option")
.create("f");
cmdLineOpts.addOption(fizzOpt);
cmdLineOpts.addOption("b", true, "The Buzz Option");
Breakdown
The option settings are necessary in order to provide more usability on the command line, as well as a nice usage message (see below)
.withArgName("Fizz"): Gives your argument a nice title in the usage
(see below)
.withLongOpt("fizz"): allows for --fizz "VicFizz is good for you"
.create("f"): is the main parameter and allows
command line -f "VicFizz is good for you"
Notice that Option b for
fuzz was constructed much simpler, sacrificing readability during
usage
Usage Message
Personally I love CLI programs that print out a nice usage. You can do this with the HelpFormatter. For example:
private void processArgs(String[] args) {
if (args == null || args.length == ) {
helpFormatter.printHelp("Don't you know how to call the Fizz", cmdLineOpts);
...
This will print something usefull like:
usage: Don't you know how to call the Fizz
-b <arg> The Buzz Option
-f,--fizz <Fizz> The Fizz Option
Notice how a the short option -f, the long option --fizz, and a name <Fizz> is displayed, along with the description.
Hope this helps

Related

Picocli: Is it possible to define options with a space in the name?

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
Solution 1: Map Option
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.
Solution 2: Argument Groups
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.

Implementing interactive confirmation in picocli

In a CLI app built using picocli, what is the most appropriate way to implement an interactive confirmation?
The scenario is, when a certain command is run, I need to get a confirmation from the user to do a certain task. I used the interactive option mentioned in the picocli documentation but it's not working as expected.
#CommandLine.Option(names = {"-c", "--copy-contract"},
description = "Do you want to copy the contract in to the project?", interactive = true, arity = "1")
boolean isCopy;
The above option doesn't seem to trigger a user input when the command is run.
Any idea?
The #Option(names = "-c", interactive = true) attribute will cause picocli to prompt the user if (and only if) the -c option is specified.
If the application also needs to prompt the user when the -c option is not specified, it currently needs to be done in the application code.
As of picocli 4.3.2, this can be accomplished as follows:
class MyApp implements Runnable {
#Option(names = {"-c", "--copy-contract"},
description = "Do you want to copy the contract in to the project?",
interactive = true, arity = "1")
Boolean isCopy;
public void run() {
if (isCopy == null) {
String s = System.console().readLine("Copy the contract? y/n: ");
isCopy = Boolean.valueOf(s) || "y".equalsIgnoreCase(s);
}
System.out.printf("isCopy=%s%n", isCopy);
}
public static void main(String... args) {
new CommandLine(new MyApp()).execute(args);
}
}
(There is a feature request to include this in picocli in a future release.)

How do you parse arguments for a java program?

I'm making a Selenium WebDriver java program. I have 25 application and 4 environments. I need to be able to pass something like -app app1 app2 app3 ... appn -env env1 env2 envn
I need to be able to pass either, neither or both arguments. Right now I have it being able to pass one app and one env in that order but I need to be able to do it in either order and with the either neither or both possibility. Here's what I have so far. With this I can either pass no arguments and runs every app for every environment (which is what I want) or I can pick app1 env1 in that order for that specific test.
public static Application chooseAppTest(String[] args)
{
Application application = null;
switch (Application.valueOf(args[0]))
{
case ACCOUNTINVENTORY:
new AccountInventory(Environment.valueOf(args[1]));
AccountInventory.accountInventoryDatabaseTests(testResults);
break;
if (args.length == 0)
{
LogIn.loginTest(testResults);
DatabaseTest.testResults(testResults);
LinkTest.linkTests(testResults);
}
else
{
// First choose application, then choose environment
Application.chooseAppTest(args);
}
I don't think recursion is needed. You can do something like this:
public static void main (String[] args)
{
List<String> apps = new LinkedList<>();
List<String> envs = new LinkedList<>();
List<String> current = null;
// parse arguments
for (String arg : args)
{
if (arg.equals("-app")) current = apps;
else if (arg.equals("-env")) current = envs;
else if (current != null) // add argument
current.add(arg);
}
// parsing finished
Application.doSomethingWith(apps, envs);
}
It is not necessary or advantagious to use recursion. You can read all the arguments into an array and process them from there. Other than that, I'm not sure how you would proceed. With the arguments arranged in this way, how do you know which environment goes with which application?
As Elliott commented, have you looked at Apache Commons CLI? It's a command line parser.

Using a String in run configurations as an argument and using it in an if-statement

The code below works as long as the argument in the run configuration equals "-output". But when the arguments are empty the compiler throws and ArrayOutOfBoundsException.
The point of this piece of code would eventually be to;
- Perform an action when -output is written in the run configurations arguments
- Perform something else if the arguments are empty or different from -output
I found many problems that looked like this one. But I've been working on a solutions for far to long, so I started a new post. Help is very much appreciated.
...
public static void main(String[] args) {
Version_5 v5 = new Version_5("Test");
{
if(args[0].equals("-output")){
System.out.println("It works");
}
}
}
...
You need to check if you have arguments first, that's all.
if ((args.length > 0) && (args[0].equals("-output")) {
...
You might also consider using an argument-parsing library, of which there are several.
What are the extra brackets for?
As you can see String args[] is an array with a specific size. If you don't pass an argument the size is zero. Before you check what is at args[0] check if args has a size with args.length.
if (args.length>0){
//do something
}
else if (args[0].equals("-output")){
System.out.println("It works");
}

Make parameters available in an application

What is the standard way of storing a command line argument so that it can be accessed when required?
I can see 2 scenarios:
The argument is consumed immediately (A logging level)
The argument is not needed immediately (On failure send email to address X)
With scenario 1 It would seem quite natural to configure it upfront, however when it is a scenario more in the vein of scenario 2 I would prefer to configure that component as and when necessary (IE not up front)
So to phrase the question slightly differently how do I make my configuration options available to my entire application?
You can have a singleton Configuration object, in which all relevant things are stored.
public class Configuration {
private static final Configuration conf = new Configuration();
public static Configuration get() {
return conf;
}
private String failureEmailAddress;
public String getFailureEmailAddress() {
return failureEmailAddress;
}
public void parseCommandLine(String[] args) {
// ...
}
}
You can use something like that (you can store the CommandLine somewhere or use the opions right away):
Options options = createOptions();
CommandLineParser parser = new GnuParser();
CommandLine cmdLine;
int timeoutHumanMove;
try
{
cmdLine = parser.parse(options, args, true);
timeoutHumanMove = getTimeoutOption(cmdLine, "thm", 300);
}
catch(ParseException e)
{
return;
}
private static int getTimeoutOption(CommandLine cmdLine, String opt, int defaultSeconds)
throws ParseException
{
if(cmdLine.hasOption(opt))
{
Number val = (Number)cmdLine.getParsedOptionValue(opt);
return (int)(val.doubleValue() * 1000D);
} else
{
return 1000 * defaultSeconds;
}
}
private static Options createOptions()
{
Options options = new Options();
options.addOption(OptionBuilder.withDescription("print this help and exit").create(OptionBuilder.withLongOpt("help"), 104));
// ...
return options;
}
There is no standard way to store application wide configuration information such as you describe. However, most of the time you store it in a class which is specific to the job (ApplicationContext), and then the instance is passed into the other classes as parameters or in a constructor or something like that. I usually make the ApplicationContext immutable.
Some applications I've come across use a static global context, effectively a global variable. This isn't necessarily a good idea, for the same reason that you should avoid global variables.
However, I would say that you should always verify that the command line options are valid up front. You don't want to do 3 hours of processing and then find out that someone hadn't configured the email address correctly on the command line. This should be a fail-fast situation.
You could store your command args into System properties using System.setProperty() then you can access your properties anywhere via System.getProperty()..

Categories