I am using Pico CLI v4.0.0-alpha-3 & jline v3. I have the following class (using annotations). When I run the main class, I cannot seem to run the command and get the callable invoked. I can invoke the callable if I simply pass in the parameters.
@CommandLine.Command(name = "Test CLI",
description = "CLI tool",
header = "%n@|green test cli |@",
footer = {"",
"@|cyan Press Ctrl-D to exit the CLI.|@",
""},
version = "1.0.0",
showDefaultValues = true,
optionListHeading = "@|bold %nOptions|@:%n",
subcommands = {
Sync.class,
Status.class
})
public class Tester implements Callable<Integer> {
private static final String PROMPT = "Test CLI> ";
private LineReaderImpl lineReader;
private PrintWriter out;
@Option(names = {"-u", "--username"}, required = true, description = "user name")
private String userName;
@Option(names = {"-p", "--password"}, required = true, description = "password")
private String password;
final Tester tester = new Tester();
final CommandLine cmd = prepareCommand(tester);
final int exitCode = cmd.execute(args);
When I run the java application, and run the command with the parameters, the callable does not get invoked. When I simply pass in the parameters, the callable gets invoked. Any thoughts on how to fix this so that the CLI user has to pass in the command followed by the parameters.
Command Line Utilities
I think that the way that java applications are packaged is what causes the confusion.
If your application was a single executable file, let's say myapp.exe
, then you would invoke the app on the command line like this:
myapp -u xxx
If your application is a Java class, you need to invoke it like this:
java -cp mylib.jar com.myorg.MyApp -u xxx
So, the top-level command is the com.myorg.MyApp
class itself, there is no need to specify myapp
as an argument to the com.myorg.MyApp
class on the command line.
Now imagine that your myapp
application has a subcommand, called sub
. Again, if your application was a single executable file, you would invoke the subcommand on the command line like this:
myapp sub --subcommand-options
As a Java class, you need to invoke it like below. Note that here, you do need to specify the subcommand name as a command line argument to the com.myorg.MyApp
class:
java -cp mylib.jar com.myorg.MyApp sub --subcommand-options
Side note: What people often do is create startup scripts for Windows and unix that allow you to execute your application as
myapp
instead ofjava -cp mylib.jar com.myorg.MyApp
. One of the reasons that people are enthusiastic about GraalVM is that GraalVM native images solve this packaging problem and allow Java developers to distribute their applications as a single executable file.
Picocli's main use case is to facilitate creating command line utilities in Java. So it is very easy to do the above with code like this:
package com.myorg;
@Command(name = "myapp")
public class MyApp implements Runnable {
@Option(names = {"-u", "--username"}, description = "user name")
private String userName;
@Command // subcommand "sub"
public void sub(@Option(names = "--subcommand-options") String option) {
System.out.printf("sub says hello %s!%n", userName);
}
@Override
public void run() {
System.out.printf("myapp says hi %s!%n", userName);
}
public static void main(String[] args) {
int exitCode = new CommandLine(new MyApp()).execute(args);
}
}
Interactive Command Line Applications
When you create an interactive CLI shell application with JLine, you need to keep in mind that the top-level command is not specified on the command line.
Therefore, all the commands that you want users to be able to type in the shell should be subcommands, like you describe in your answer.
The top-level command can still be useful to provide help to the users. One idea is to print a friendly help message followed by the usage help of the top-level command when the user enters an invalid command (or just hits enter without parameters).