A Spring boot starter for Picocli command line tools. That let you easily write CLI for Spring boot application!
CommandLineRunner
@Command
beans registration@Command
if it implements java.lang.Runnable
or java.lang.Callable
java.lang.Callable
and ExitStatus
Add the Spring boot starter to your project
<dependency>
<groupId>com.kakawait</groupId>
<artifactId>picocli-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
There is multiple ways to define a new picocli commands.
You should start looking sample application to get more advance sample.
@Command
beansFirst and simplest way, is to register a new bean with @Command
annotation inside your Spring context, example:
@Component
@Command(name = "greeting")
class GreetingCommand implements Runnable {
@Parameters(paramLabel = "NAME", description = "name", arity = "0..1")
String name;
@Override
public void run() {
if (StringUtils.hasText(name)) {
System.out.println("Hello " + name + "!");
} else {
System.out.println("Hello world!");
}
}
}
Your bean can implements Runnable
or Callable<ExitStatus>
(in order to control flow) or extends PicocliCommand
or HelpAwarePicocliCommand
(to magically have -h/--help
option to display usage) if you need to execute something when command is called.
In addition, for advance usage PicocliCommand
let you get access to
/**
* Returns result of {@link CommandLine#parse(String...)}.
* @return Picocli parsing result which results on collection of every command involve regarding your input.
*/
protected List<CommandLine> getParsedCommands() {
return parsedCommands;
}
/**
* Returns the current {@link CommandLine}.
* @return current {@link CommandLine} context
*/
protected CommandLine getContext() {
return context;
}
/**
* Returns the root {@link CommandLine}.
* @return root {@link CommandLine} context that must contains (or equals) the {@link #getContext()}.
*/
protected CommandLine getRootContext() {
return rootContext;
}
That might be useful.
Picocli is waiting for a Main command, cf: new CommandLine(mainCommand)
. To determine which @Command
beans will be the Main command, starter will apply the following logic:
Main command will be the first
@Command
(if multiple found) bean with defaultname
argument.
For example
@Command
class MainCommand {}
or
@Command(description = "main command")
class MainCommand {}
But the following example will not be candidate for Main command
@Command(name = "my_command")
class MainCommand {}
Picocli allows nested sub-commands, in order to describe a nested sub-command, starter is offering you nested classes scanning capability.
That means, if you're defining bean structure like following:
@Component
@Command(name = "flyway")
class FlywayCommand extends HelpAwareContainerPicocliCommand {
@Component
@Command(name = "migrate")
class MigrateCommand implements Runnable {
private final Flyway flyway;
public MigrateCommand(Flyway flyway) {
this.flyway = flyway;
}
@Override
public void run() {
flyway.migrate();
}
}
@Component
@Command(name = "repair")
class RepairCommand implements Runnable {
private final Flyway flyway;
public RepairCommand(Flyway flyway) {
this.flyway = flyway;
}
@Override
public void run() {
flyway.repair();
}
}
}
Will generate command line
Commands:
flyway [-h, --help]
migrate
repair
Thus java -jar <name>.jar flyway migrate
will execute Flyway migration.
ATTENTION every classes must be a bean (@Component
) with @Command
annotation without forgetting to file name
attribute.
There is no limitation about nesting level.
If you need to set additional configuration options simply register within Spring application context instance of PicocliConfigurerAdapter
@Configuration
class CustomPicocliConfiguration extends PicocliConfigurerAdapter {
@Override
public void configure(CommandLine commandLine) {
// Here you can configure Picocli commandLine
// You can add additional sub-commands or register converters.
}
}
Otherwise you can define your own bean CommandLine
but attention that will disable automatic @Command
bean registration explained above.
If you defined following command line:
@Component
@Command
class MainCommand extends HelpAwarePicocliCommand {
@Option(names = {"-v", "--version"}, description = "display version info")
boolean versionRequested;
@Override
public void run() {
if (versionRequested) {
System.out.println("0.1.0");
}
}
}
@Component
@Command(name = "greeting")
static class GreetingCommand extends HelpAwarePicocliCommand {
@Parameters(paramLabel = "NAME", description = "name", arity = "0..1")
String name;
@Override
public void run() {
if (StringUtils.hasText(name)) {
System.out.println("Hello " + name + "!");
} else {
System.out.println("Hello world!");
}
}
}
And you execute java -jar <name>.jar -v greeting Thibaud
the output will looks like:
0.1.0
Hello Thibaud!
While you wanted that -v
will break execution and other involved commands not executed. To achieve that you must replace run()
method with ExitStatus call()
.
@Component
@Command
class MainCommand extends HelpAwarePicocliCommand {
@Option(names = {"-v", "--version"}, description = "display version info")
boolean versionRequested;
@Override
public ExitStatus call() {
if (versionRequested) {
System.out.println("0.1.0");
return ExitStatus.TERMINATION;
}
return ExitStatus.OK;
}
}
The main difference is ExitStatus.TERMINATION
that will tell the starter to stop other executions. (ExitStatus.OK
is default status).
Picocli documentation and principle about help
argument is not exactly the same on this starter.
Indeed Picocli only consider option with help
argument like following:
if one of the command line arguments is a "help" option, picocli will stop parsing the remaining arguments and will not check for required options.
While this starter in addition will force displaying usage if help
was requested.
Thus following example from Picocli documentation:
@Option(names = {"-V", "--version"}, help = true, description = "display version info")
boolean versionRequested;
@Option(names = {"-h", "--help"}, help = true, description = "display this help message")
boolean helpRequested;
If you run program with -V
or --version
that will display usage and stop execution. It may not what you need. Thus you have to think about help
argument is to displaying usage and stop execution.
Following example is much more starter compliant:
@Option(names = {"-V", "--version"}, help = false, description = "display version info")
boolean versionRequested;
@Option(names = {"-h", "--help"}, help = true, description = "display this help message")
boolean helpRequested;
MIT License