Интерфейс командной строки с Picocli: вызовите основную команду перед вызовом подкоманды

Я перешел с интерфейса командной строки Apache Commons на 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 будет вызван до вызова подкоманды. Но вызывается только подкоманда.


person Vertex    schedule 08.05.2018    source источник


Ответы (2)


Методы CommandLine.callCommandLine.run) вызывают только подкоманду last, поэтому то, что вы видите в исходном посте, является ожидаемым поведением.

Методы call и run на самом деле являются ярлыками. Следующие две строки эквивалентны:

CommandLine.run(callable, args); // internally uses RunLast, equivalent to: 
new CommandLine(callable).parseWithHandler(new RunLast(), args);

Обновление: начиная с версии picocli 4.0 указанные выше методы устарели и заменены на new CommandLine(myapp).execute(args). «Обработчик» теперь называется «стратегией выполнения» (пример ниже).

Также существует RunAll обработчик, который запускает все совпавшие команды. Следующий main метод дает желаемое поведение:

public static void main(String[] args) {
    args = new String[] { "--verbose", "push" };
    GitApp app = new GitApp();
    // before picocli 4.0:
    new CommandLine(app).parseWithHandler(new RunAll(), args);
    // from picocli 4.0:
    //new CommandLine(app).setExecutionStrategy(new RunAll()).execute(args);
    System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}

Выход:

#GitApp.call
#PushCommand.call
#GitApp.main after. verbose: true

Вас также может заинтересовать аннотация @ParentCommand. Это указывает picocli внедрить экземпляр родительской команды в подкоманду. Затем ваша подкоманда может вызывать методы родительской команды, например, чтобы проверить, истинно ли verbose. Например:

Обновление: начиная с версии picocli 4.0 используйте метод setExecutionStrategy, чтобы указать RunAll. Приведенный ниже пример обновлен для использования нового API Picocli 4.0+.

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();
        int exitCode = new CommandLine(app)
            .setExecutionStrategy(new RunAll())
            .execute(args);

        System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
        System.exit(exitCode);
    }
}

Другие мелкие правки: сделали аннотации немного более компактными за счет импорта внутренних классов. Вам также могут понравиться атрибут mixinStandardHelpOptions и встроенный атрибут _ 17_ подкоманда, которая помогает сократить шаблонный код.

person Remko Popma    schedule 11.05.2018
comment
Отличные советы! Спасибо за Picocli! - person Vertex; 11.05.2018

Поскольку Picocli поддерживает наследование с помощью параметров, я извлек --help и --verbose Option в абстрактный класс 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;
    }
}
person Vertex    schedule 11.05.2018