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;
    }
}
Другие вопросы по тегам