2

由于子命令支持(和基于注释的声明),我从 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在调用子命令之前调用它。但只有 sub 命令被调用。

4

2 回答 2

4

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

callandrun方法实际上是一个捷径。以下两行是等价的:

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. 以下示例已更新为使用新的 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);
    }
}

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

于 2018-05-11T09:40:05.107 回答
0

由于 Picocli 支持使用 Options 进行继承,因此我将--helpand --verboseOption 提取到一个抽象类中并从子命令中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;
    }
}
于 2018-05-11T08:53:04.030 回答