I could swear I had remembered Spring Boot already supporting something like this, but I can't find it. So..
I haven't actually used it yet, but picocli/ looks awesome. here's the problem I see with current solution
- Manually generate help
- not typesafe out of the box
- convoluted code has to be written for conditional things
- no subcommands
- no colored help output
- no command completion
- ...
picocli seems to solve these, suggested spring boot interface
package com.xenoterracide.ppm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import picocli.CommandLine;
import java.util.function.Consumer;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run( Application.class, args );
}
@Component
static class Printer implements Consumer<String> {
@Override
public void accept( String s ) {
System.out.println("Hello, " + s);
}
}
@CommandLine.Command // auto register as component
static class RootCommand implements Runnable {
private final Printer printer;
@CommandLine.Parameters(index = "0" )
private String to;
RootCommand( Printer printer ) {
this.printer = printer;
}
@Override
public void run() {
printer.accept( to );
}
}
}
would be equivalent to
package com.xenoterracide.ppm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import picocli.CommandLine;
import java.util.function.Consumer;
// CHECKSTYLE:OFF HideUtilityClassConstructor FinalClass
@SpringBootApplication
public class Application {
// CHECKSTYLE:ON
public static void main(String[] args) {
var ctx = SpringApplication.run( Application.class, args );
var rootCommand = ctx.getBean( RootCommand.class );
new CommandLine( rootCommand ).execute( args );
}
@Component
static class Printer implements Consumer<String> {
@Override
public void accept( String s ) {
System.out.println("Hello, " + s);
}
}
@Component
@CommandLine.Command
static class RootCommand implements Runnable {
private final Printer printer;
@CommandLine.Parameters(index = "0" )
private String to;
RootCommand( Printer printer ) {
this.printer = printer;
}
@Override
public void run() {
printer.accept( to );
}
}
}
obviously in this example it's fairly trivial to workaround the command not being a bean by default, but if you get into subcommands declared declaratively, if they aren't all wired up they aren't going to work. Possible, even probable you'd have to have a @Commands
spring annotation that could replace this with a similar api, but would ensure autowiring of all those classes, all the way down.
@Command(subcommands = {
GitStatus.class,
GitCommit.class,
GitAdd.class,
GitBranch.class,
GitCheckout.class,
GitClone.class,
GitDiff.class,
GitMerge.class,
GitPush.class,
GitRebase.class,
GitTag.class
})
public class Git { /* ... */ }
honestly, I think that you should probably use this thing for other spring commands, just 'cause pretty, and dry. Just an idea, to be honest I don't need this at the moment due to not writing cli apps with spring boot very often. But if I were writing a java cli app, this is how I'd want to do it.
Comment From: snicoll
Thanks for the suggestion but we do not plan to make any enhancements to Spring Boot’s CLI at this time.
Comment From: xenoterracide
just to be clear, I totally mean the primary use case of this to be for a consumer API's. I suppose though if the time comes when I need it I can just create my own spring module, I was just hoping it would be there instead, because when I did use the existing API and needed some conditional option it got painful real fast.