When I pass arguments to my program to be parsed by the CommandLineParser, it doesn't take my arguments from passed to the main method.
The code:
public class Main {
private static final Option ARG_HELP = new Option("h", "help", false, "HELP - Prints command line arguments and their uses");
private static final Option ARG_SERVER = new Option("s", "server", true, "SERVER_URL - Full HTTP(s) url of the server");
private static final Option ARG_USER = new Option("u", "user", true, "USERNAME - Username used for authentication");
private static final Option ARG_PASSWORD = new Option("p", "password", true, "PASSWORD - Password used for authentication");
private static final Option ARG_TARGET = new Option("t", "target", true, "TARGET_DIR - Base directory used for pushing or pulling files");
private static final Option ARG_PUSH = new Option("push", "push", false, "PUSH - Push xml config to server");
private static final Option ARG_PULL = new Option("pull", "pull", false, "PULL - pull xml config from server");
public static void main(String[] args) {
CommandLineParser clp = new DefaultParser();
Options options = new Options();
options.addOption(ARG_HELP);
options.addOption(ARG_SERVER);
options.addOption(ARG_USER);
options.addOption(ARG_PASSWORD);
options.addOption(ARG_TARGET);
options.addOption(ARG_PUSH);
options.addOption(ARG_PULL);
try{
CommandLine cl = clp.parse(options, args);
if(cl.getArgList().size() < 5){
ArrayList<String> arguments = new ArrayList<String>(cl.getArgList());
}
else{
printHelp(options);
System.exit(0);
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
System.exit(0);
}
}
When I set a breakpoint at the if statement and check my cl object, the args are 0. But when I look at args passed in the clp.parse(options, args) method they're the arguments that I passed when I ran the program.
Example arguments passed: -s https://localhost:8443 -u admin -p admin -t C:\users\admin -pull
That is the correct behaviour.
According to the CommandLine.getArgList() JavaDoc:
Retrieve any left-over non-recognized options and arguments
Since all the arguments from your command line are recognized as options and arguments there are no left-over arguments.
Related
I have two options (-n and -t) under a command where if -n is used, then -t is required, but bothare not required. However, I keep getting an error about
I am trying to send the options as a parameter to another method (with business logic) as a parameter.
Valid Usage:
agent.bat install -n -t <blahblah>
agent.bat install -t <blahblah> -n
agent.bat install -t <blah blah>
agent.bat install -t <----This is on interactive so it would ask for a parameter later
Invalid Usage:
agent.bat install -n
agent.bat install -n -t
Current output with valid usage:
agent.bat install -t
Missing required parameter: '<arg0>'
Usage: agent install [-hV] <arg0>
Setup or update the agent service program by install token.
<arg0>
public class Agent implements Callable<Integer> {
static class InstallArgs {
#Option(names = {"-t", "--token"},
order = 0,
arity = "0..1",
interactive = true,
description = "The agent install token.",
required = true) String installToken ;
#Option(names = {"-n", "--noninteractive"},
order = 1,
description = "Sets installation to non-interactive",
required = false) boolean nonInteractive ;
public String toString() {
return String.format("%s,%s", installToken, nonInteractive);
}
}
private static String[] programArgs;
#ArgGroup(exclusive = false, multiplicity = "1")
#CommandLine.Command(name = AgentCommand.INSTALL_COMMAND, mixinStandardHelpOptions = true,
description = "Setup or update the agent service program by install token.")
void install(InstallArgs installArgs) {
String[] installArgsValues = installArgs.toString().split(",");
String installToken = installArgsValues[0];
boolean nonInteractive = Boolean.parseBoolean(installArgsValues[1]);
IcbProgram.initProgramMode(ProgramMode.INSTALL);
MainService mainService = MainService.createInstallInstance(configFile, agentUserFile, backupAgentUserFile, installToken, nonInteractive);
}
public static void main(String... args) {
if (ArgumentValidator.validateArgument(args)) {
programArgs = args;
int exitCode = new CommandLine(new Agent()).execute(args);
System.exit(exitCode);
} else
//Exit with usage error
System.exit(ExitCode.USAGE);
}
}
Can you try using arity=1 for installToken?
static class InstallArgs {
#Option(names = {"-t", "--token"},
order = 0,
arity = "1",
interactive = true,
description = "The agent install token.",
required = true) String installToken ;
I want to execute a command with the terminal in linux. Now in order to do this I can't hard code cmd like I do windows. How do I get the terminal name programatically as String from java?
new ProcessBuilder(new String[] {"xfce4-terminal", "--title="+windowTitle, "--hold", "-x", "java", "-jar", decodedPath, "run"}).start();
Notice the string "xfce4-terminal". This changes depending on what distribution of linux they have. What reliable way is there to get the terminal exe for java commands. In my opinion it should by System.getProperty("os.terminal") but, that doesn't exist.
here is a cross platform way to determine the os terminal. supports windows, mac and linux
public static String osName = System.getProperty("os.name");
public static String[] windows_terminals = new String[]
{
"cmd",
"powershell",//seems to freak out and seems to be beta even in 2020 with all it's bugs
};
public static String[] mac_terminals = new String[]
{
"bin/bash"
};
public static String[] linux_terminals = new String[]
{
"/usr/bin/gcm-calibrate",
"/usr/bin/gnome-terminal",
"/usr/bin/mosh-client",
"/usr/bin/mosh-server",
"/usr/bin/mrxvt",
"/usr/bin/mrxvt-full",
"/usr/bin/roxterm",
"/usr/bin/rxvt-unicode",
"/usr/bin/urxvt",
"/usr/bin/urxvtd",
"/usr/bin/vinagre",
"/usr/bin/x-terminal-emulator",
"/usr/bin/xfce4-terminal",
"/usr/bin/xterm",
"/usr/bin/aterm",
"/usr/bin/guake",
"/usr/bin/Kuake",
"/usr/bin/rxvt",
"/usr/bin/rxvt-unicode",
"/usr/bin/Terminator",
"/usr/bin/Terminology",
"/usr/bin/tilda",
"/usr/bin/wterm",
"/usr/bin/Yakuake",
"/usr/bin/Eterm",
"/usr/bin/gnome-terminal.wrapper",
"/usr/bin/koi8rxterm",
"/usr/bin/konsole",
"/usr/bin/lxterm",
"/usr/bin/mlterm",
"/usr/bin/mrxvt-full",
"/usr/bin/roxterm",
"/usr/bin/rxvt-xpm",
"/usr/bin/rxvt-xterm",
"/usr/bin/urxvt",
"/usr/bin/uxterm",
"/usr/bin/xfce4-terminal.wrapper",
"/usr/bin/xterm",
"/usr/bin/xvt"
};
public static String getTerminal()
{
String[] cmds = getTerminals(osName);
for(String cmd : cmds)
{
try
{
Runtime.getRuntime().exec(cmd + " cd " + System.getProperty("user.dir"));
return cmd;
}
catch (Throwable e) {}
}
return null;
}
public static String[] getTerminals(String os)
{
return os.contains("windows") ? windows_terminals : os.contains("mac") ? mac_terminals : os.contains("linux") ? linux_terminals : null;
}
I would like to parse options with picocli in the following format:
application -mode CLIENT -c aaaa -d bbbb
application -mode SERVER -e xxxx -f yyyy
mode is an enum with values { CLIENT, SERVER }
If mode == CLIENT, -c and -d options are mandatory, and -e, -f must not be used.
If mode == SERVER, -e and -f options are mandatory, and -c, -d must not be used.
In other words, I would like to choose the required options based on a key option. Is this possible in picocli?
Yes, this is possible. One way is simple programmatic validation:
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.Spec;
import java.util.Objects;
import java.util.function.Predicate;
#Command(name = "application", mixinStandardHelpOptions = true)
public class MyApp implements Runnable {
enum Mode {CLIENT, SERVER}
#Option(names = "-mode", required = true)
Mode mode;
#Option(names = "-c") String c;
#Option(names = "-d") String d;
#Option(names = "-e") String e;
#Option(names = "-f") String f;
#Spec CommandSpec spec;
public static void main(String[] args) {
System.exit(new CommandLine(new MyApp()).execute(args));
}
#Override
public void run() {
validateInput();
// business logic here...
}
private void validateInput() {
String INVALID = "Error: option(s) %s cannot be used in %s mode";
String REQUIRED = "Error: option(s) %s are required in %s mode";
if (mode == Mode.CLIENT) {
check(INVALID, "CLIENT", Objects::isNull, e, "-e", f, "-f");
check(REQUIRED, "CLIENT", Objects::nonNull, c, "-c", d, "-d");
} else if (mode == Mode.SERVER) {
check(INVALID, "SERVER", Objects::isNull, c, "-c", d, "-d");
check(REQUIRED, "SERVER", Objects::nonNull, e, "-e", f, "-f");
}
}
private void check(String msg, String param, Predicate<String> validator, String... valuesAndLabels) {
String desc = "";
String sep = "";
for (int i = 0; i < valuesAndLabels.length; i += 2) {
if (validator.test(valuesAndLabels[i])) {
desc = sep + valuesAndLabels[i + 1];
sep = ", ";
}
}
if (desc.length() > 0) {
throw new ParameterException(spec.commandLine(), String.format(msg, desc, param));
}
}
}
Alternatively, if you are willing to change your requirements a little bit, we can use picocli's argument groups for a more declarative approach:
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
#Command(name = "application", mixinStandardHelpOptions = true)
public class MyApp2 implements Runnable {
static class ClientArgs {
#Option(names = "-clientMode", required = true) boolean clientMode;
#Option(names = "-c", required = true) String c;
#Option(names = "-d", required = true) String d;
}
static class ServerArgs {
#Option(names = "-serverMode", required = true) boolean serverMode;
#Option(names = "-e", required = true) String e;
#Option(names = "-f", required = true) String f;
}
static class Args {
#ArgGroup(exclusive = false, multiplicity = "1", heading = "CLIENT mode args%n")
ClientArgs clientArgs;
#ArgGroup(exclusive = false, multiplicity = "1", heading = "SERVER mode args%n")
ServerArgs serverArgs;
}
#ArgGroup(exclusive = true, multiplicity = "1")
Args args;
public static void main(String[] args) {
System.exit(new CommandLine(new MyApp2()).execute(args));
}
#Override
public void run() {
// business logic here...
}
}
When invoked with just -serverMode, this second example will show this error message, followed by the usage help message:
Error: Missing required argument(s): -e=<e>, -f=<f>
Usage: application ((-clientMode -c=<c> -d=<d>) | (-serverMode -e=<e> -f=<f>))
...
Note that this declarative approach cannot be achieved with a single -mode option: each argument group needs its own option (I chose -clientMode and -serverMode in this example). This allows the picocli parser to figure out which options must occur together and which are mutually exclusive.
I would like to provide a search string for my program like:
cmd.execute("getDevices", "-h 1.2.3.4", "-p myPSW", "-u myUser", "-n red|blue&black,-nonprod");
I want to create predicates to search for hostNames that contain red OR blue AND Black, but NOT nonprod. It is unclear to me how to go about parsing this the logical operators along with the Strings in Picocli to create a Predicate. Is there a simple and Straight forward way to parse a String to a predicate?
My CLI is set up as follows:
#Command(name = "HostMagicCLI", mixinStandardHelpOptions = true,
version = "1.0",
description = "Do Stuff With Hosts"
,
subcommands = {TufinDevices.class}
)
public class HostMagicCLI implements Runnable {
public static void main(String[] args) {
CommandLine cmd = new CommandLine(new InterfaceMagicCLI());
cmd.setExecutionStrategy(new RunAll());
cmd.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, new MyCommandListRenderer());
cmd.usage(System.out);
cmd.execute("getDevices", "-h1.2.3.4", "-p myPSW", "-u myUser", "-n red|blue&black");
}
#Override
public void run() {
System.out.println("Running..");
}
}
#Command(name = "getDevices", aliases = {"l"}, description = "SpecifyTufin Credentials", subcommands = {InterfaceCommand.class})
class TufinDevices implements Runnable {
.
.//Options to collect user,psw, host etc.
.
#CommandLine.Option(names = {"-n", "--n"}, split = ",", arity = "0..*", description = "Hostname Contains")
String[] hostNameContains;
private void filter(TufinDeviceCollection<TufinDevice> devices) {
if (hostNameContains != null) {
Predicate< ? super TufinDevice> deviceFilter = device -> Arrays.stream(hostNameContains)
.allMatch(input -> device.getHostName().toLowerCase().contains(input.toLowerCase()));
devices = devices.stream()
.sequential()
.filter(deviceFilter)
.collect(Collectors.toCollection(TufinDeviceCollection<TufinDevice>::new));
}
#Override
public void run() {
try {
TufinDeviceCollection<TufinDevice> FETCH_DEVICES = Tufin.FETCH_DEVICES(user.trim(), password.trim(), hostName.trim());
this.filter(FETCH_DEVICES);
} catch (IOException | NoSuchAlgorithmException | KeyManagementException | IPConverter.InvalidIPException ex) {
Logger.getLogger(TufinDevices.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
I suspect you may want to use a library for parsing the string that the end user specifies as the filter expression (the -n parameter). It may be an idea to look at libraries like Spring Expression Language, OGNL, JXPath, there may be others. Alternatively, if it is easy to write such a filter in Groovy or BeanShell, these languages can be called from Java, so you can call that filter from the Java command.
CAUTION:
I notice the example passes parameter to the picocli parser like this:
cmd.execute("getDevices", "-h 1.2.3.4", "-p myPSW", "-u myUser", "-n red|blue&black,-nonprod");
This will probably give an error explaining that "there is no -p myPSW option defined".
In your testing, if you call the execute method directly, make sure to pass parameters separately like this:
cmd.execute("getDevices", "-h", "1.2.3.4", "-p", "myPSW", "-u", "myUser", "-n", "red|blue&black,-nonprod");
I'm adding options for the Parser in following way:
options = new Options()
.addOption(Option.builder(CONFIG_PARAM)
.required()
.hasArg(true)
.argName(CONFIG_PARAM_NAME + "_path")
.desc(CONFIG_PARAM_DESC)
.longOpt(CONFIG_PARAM_NAME)
.build())
(...)
.addOption(Option.builder(HELP_PARAM)
.hasArg(false)
.longOpt(HELP_PARAM_NAME)
.desc(HELP_PARAM_DESC)
.build());
Now, I would like to allow the user to use only the help command, for ex.
mypreciousapp --help
With the above solution, it is impossible - I'm receiving the information about missing required parameters
Missing required options: c
Is there any way to flag the help parameter so it can override the required parameters, and allow its usage alone? I can do this manually, but first I would like to know if there's such option in CLI lib.
It seems commons-cli does not support that currently, so I would create a 2nd Options object where the param is not required and parse/check that first, before doing the full parse, something like this:
public static void main(String[] args) {
// define the options with required arguments as needed
Options opts = new Options()
.addOption(Option.builder("p")
.required()
.hasArg(true)
.argName("arg")
.desc("description ")
.longOpt("param")
.build())
.addOption(Option.builder("h")
.hasArg(false)
.longOpt("help")
.desc("help description")
.build());
// first check if usage-output was requested
if (handleHelp(args, opts)) {
return;
}
// now handle the full options
CommandLineParser parser = new DefaultParser();
final CommandLine cmdLine;
try {
cmdLine = parser.parse(opts, args);
} catch (ParseException ex) {
System.out.println("Syntax error: " + ex.getMessage());
printHelp(opts);
return;
}
// now handle options and do your work
}
private boolean handleHelp(String[] args, Options opts) {
Options helpOpts = new Options()
.addOption(Option.builder("p")
//.required()
.hasArg(true)
.argName("arg")
.desc("description ")
.longOpt("param")
.build())
.addOption(Option.builder("h")
.hasArg(false)
.longOpt("help")
.desc("help description")
.build());
CommandLineParser helpParser = new DefaultParser();
final CommandLine cmdLine;
try {
cmdLine = helpParser.parse(helpOpts, args);
} catch (ParseException ex) {
System.out.println("Syntax error: " + ex.getMessage());
printHelp(opts);
return true;
}
if(cmdLine.hasOption("h")) {
printHelp(opts);
return true;
}
return false;
}
private void printHelp(Options opts) {
try (PrintWriter pw = new PrintWriter(System.out)) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(pw, 80, "myapp", "test-header", opts,
formatter.getLeftPadding(), formatter.getDescPadding(), "test-footer", true);
}
}
There is a fluent wrapper for the commons-cli library: https://github.com/bogdanovmn/java-cmdline-app
The -h option is already built-in. You don't need to manage it in your own code.
A better solution can be the following:
public static void main(String[] args) {
// define the options with required arguments as needed
Options opts = new Options()
.addOption(Option.builder("p")
.required()
.hasArg(true)
.argName("arg")
.desc("description ")
.longOpt("param")
.build())
.addOption(Option.builder("h")
.hasArg(false)
.longOpt("help")
.desc("help description")
.build());
// first check if usage-output was requested
if (Arrays.asList(args).contains("--help")) {
HelpFormatter help = new HelpFormatter();
help.printHelp("betgenius-stream", opts);
System.exit(0);
}
CommandLineParser parser = new DefaultParser();
CommandLine cli = parser.parse(options, args);
// now handle options and do your work
}
In this way it is not necessary to rewrite the options.