CLI с Picocli: вызов основной команды до вызова подкоманды
Я перешел с Apache Commons CLI на Picocli из-за поддержки подкоманд (и объявления на основе аннотаций).
Рассмотрим инструмент командной строки, как git
, с подкомандами вроде push
, Git есть главный выключатель --verbose
или же -v
для включения подробного режима во всех подкомандах. Как я могу реализовать главный переключатель, который выполняется перед любыми подкомандами?
Это мой тест
@CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Callable<Void> {
@Override
public Void call() throws Exception {
System.out.println("#PushCommand.call");
return null;
}
}
@CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp implements Callable<Void> {
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
@CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "--verbose", "push");
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
@Override
public Void call() throws Exception {
System.out.println("#GitApp.call");
return null;
}
}
Выход
#PushCommand.call
#GitApp.main after. verbose: true
Я ожидаю, что GitApp.call
вызываться до вызова подкоманды. Но вызывается только подкоманда.
2 ответа
CommandLine.call
(а также CommandLine.run
) методы вызывают только последнюю подкоманду по своему замыслу, поэтому то, что вы видите в исходном посте, - это ожидаемое поведение.
call
а также run
методы на самом деле ярлык. Следующие две строки эквивалентны:
CommandLine.run(callable, args); // internally uses RunLast, equivalent to:
new CommandLine(callable).parseWithHandler(new RunLast(), args);
Также есть RunAll
обработчик, который запускает все команды, которые были сопоставлены. Следующие main
Метод дает желаемое поведение:
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
new CommandLine(app).parseWithHandler(new RunAll(), args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
Выход:
#GitApp.call
#PushCommand.call
#GitApp.main after. verbose: true
Вы также можете быть заинтересованы в @ParentCommand
аннотаций. Это говорит Picocli о необходимости вставки экземпляра родительской команды в подкоманду. Ваша подкоманда может затем вызвать методы родительской команды, например, чтобы проверить, verbose
правда. Например:
import picocli.CommandLine;
import picocli.CommandLine.*;
@Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Runnable {
@ParentCommand // picocli injects the parent instance
private GitApp parentCommand;
public void run() {
System.out.printf("#PushCommand.call: parent.verbose=%s%n",
parentCommand.verboseMode); // use parent instance
}
}
@Command(description = "Version control",
mixinStandardHelpOptions = true, // auto-include --help and --version
subcommands = {PushCommand.class,
HelpCommand.class}) // built-in help subcommand
public class GitApp implements Runnable {
@Option(names = {"-v", "--verbose"},
description = "Verbose mode. Helpful for troubleshooting.")
boolean verboseMode;
public void run() {
System.out.println("#GitApp.call");
}
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
new CommandLine(app).parseWithHandler(new RunAll(), args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
}
Другие мелкие правки: сделали аннотации немного более компактными, импортировав внутренние классы. Вам также может понравиться mixinStandardHelpOptions
атрибут и встроенный help
подкоманда, которая помогает уменьшить стандартный код.
Поскольку Picocli поддерживает наследование с помощью параметров, я извлек --help
а также --verbose
Вариант в абстрактный класс BaseCommand
и вызвать super.call
из подкоманд.
abstract class BaseCommand implements Callable<Void> {
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
@CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
@Override
public Void call() throws Exception {
if (verboseMode) {
setVerbose();
}
return null;
}
private void setVerbose() {
System.out.println("enter verbose mode");
}
}
@CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand extends BaseCommand {
@Override
public Void call() throws Exception {
super.call();
System.out.println("Execute push command");
return null;
}
}
@CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp extends BaseCommand {
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "push", "--verbose");
}
@Override
public Void call() throws Exception {
super.call();
System.out.println("GitApp.call called");
return null;
}
}