How do I build a Spring Boot 2.3 multi-command CLI application that can be run with single command, an @script, and interactively in picocli? It should behave like this:
manager -u <user> -p <pass> [list|create|delete] # run and exit
manager -u <user> -p <pass> @script # run and exit
manager -u <user> -p <pass> # run shell
The username -u
and password -p
are required, and the three commands (list
, create
, and delete
) each have different options and parameters.
The Spring Boot application is trivial:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
System.exit(SpringApplication.exit(
SpringApplication.run(Application.class, args))
);
}
}
And the Spring Boot CommandLineRunner
with a return value is also simple and calls into picocli's CommandLine
to parse and execute commands:
@Component
public class ApplicationRunner implements CommandLineRunner, ExitCodeGenerator {
private int exitCode;
@Override
public void run(String... args) throws Exception {
exitCode = new CommandLine(new ConnectCommand()).execute(args);
}
@Override
public int getExitCode() {
return exitCode;
}
}
The ConnectCommand
has showAtFileInUsageHelp = true
which enables picocli's @-file support and mixinStandardHelpOptions
which enable help and version information with "standard" options (-h
, --help
, etc.):
@Command(
name = "manager",
description = "The manager description",
showAtFileInUsageHelp = true,
mixinStandardHelpOptions = true,
subcommands = {
ListCommand.class,
CreateCommand.class,
DeleteCommand.class
})
@Component
public class ConnectCommand implements Runnable, ExitCodeGenerator {
@Option(
names = {"-u", "--username"},
description = "The username")
private String username;
@Option(
names = {"-p", "--password"},
description = "The password")
private String password;
private int exitCode;
@Override
public void run() {
// WIP: kick-off shell
}
@Override
public int getExitCode() {
return exitCode;
}
}
And the (sub-)commands all take this form (sprinkle in picocli's @Option
and @Parameters
as necessary):
@Command(
name = "list",
mixinStandardHelpOptions = true,
header = "list stuff")
@Component
class ListCommand implements Runnable{
@Override
public void run() {
System.out.println("listing...");
}
}
With this, the help now looks like:
Usage: manager [-hV] [-u=username] [-p=password] [@<filename>...] [COMMAND]
The manager description
[@<filename>...] One or more argument files containing options.
-u, --username=name The username
-p, --password=pass The password
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Commands:
list list stuff
create create stuff
delete delete stuff
And running a single command works:
java -jar manager.jar -u=myname -p=mypass list
listing...
And running an @-file containing 'list' also works:
java -jar manager.jar -u=myname -p=mypass @listing
listing...
Here's a sample repository. Now we need to fold-in the shell...