CLI 与 Picocli:在调用子命令之前调用主命令

Ver*_*tex 3 java command-line-interface 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是

#PushCommand.call
#GitApp.main after. verbose: true
Run Code Online (Sandbox Code Playgroud)

我希望GitApp.call在子命令被调用之前被调用。但只有子命令被调用。

Rem*_*pma 5

CommandLine.call(和)方法CommandLine.run仅根据设计调用最后一个子命令,因此您在原始帖子中看到的是预期的行为。

和方法实际上是一个快捷方式callrun以下两行是等效的:

CommandLine.run(callable, args); // internally uses RunLast, equivalent to: 
new CommandLine(callable).parseWithHandler(new RunLast(), args);
Run Code Online (Sandbox Code Playgroud)

更新:从 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));
}
Run Code Online (Sandbox Code Playgroud)

输出:

#GitApp.call
#PushCommand.call
#GitApp.main after. verbose: true
Run Code Online (Sandbox Code Playgroud)

您可能还对注释感兴趣@ParentCommand。这告诉 picocli 将父命令的实例注入子命令中。然后,您的子命令可以调用父命令上的方法,例如检查是否verbose为 true。例如:

更新:从 picocli 4.0 开始,使用setExecutionStrategy方法来指定RunAll. 以下示例已更新为使用新的 picocli 4.0+ API。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

其他小的编辑:通过导入内部类使注释更加紧凑。您可能还喜欢有助于减少样板代码的mixinStandardHelpOptions属性和内置子命令。help