桥接 CLI 参数解析与应用程序设置

Bridging CLI argument parsing with application setup

我基于 PicoCLI 的应用程序有多个命令和子命令,其中包含适用于所有命令的常规选项,以及适用于特定命令的一些选项。通用选项用于所有命令。

我的PicoCLI (sub-)commands与这个例子类似:

@Command(name = "country", description = "Resolve ISO country code (ISO-3166-1, Alpha-2 code)")
static class Subcommand1 implements Runnable {

    @Parameters(arity = "1..*", paramLabel = "<country code>", description = "country code(s) to be resolved")
    private String[] countryCodes;

    @Override
    public void run() {
        for (String code : countryCodes) {
            System.out.println(String.format("%s: %s", code.toUpperCase(), new Locale("", code).getDisplayCountry()));
        }
    }
}

但是每个(子)命令需要先运行一些通用设置代码,类似于:

@Override
public void run() {
    try (Channel channel = _establishChannel(generalConfiguration)) {
        // do sub-command work
    }
}

其中 generalConfiguration 是用于所有(子)命令的一般参数和选项的示例。因此,这个通用设置代码块将在每个命令中重复:

try (Channel channel = _establishChannel(generalConfiguration)) {
     // do sub-command work
}

但我希望它在一个地方表达,而不是。今天,我基本上复制了(子)参数和选项并调用了一个通用的助手:

void runCommand(String command, String c1Param, bool c1AllOption, String c2Filename, String c3Param /*...*/) {
    try (Channel channel = _establishChannel(generalConfiguration)) {
        switch(command) {
        case "COMMAND_1":
            doCommand1(c1Param,c1AllOption);
            break;
        case "COMMAND_2":
            doCommand2(c2Filename);
            break;
        case "COMMAND_3":
            doCommand3(c3Param);
            break;
        // ...
        }
    }
}

这很丑陋,而且很脆弱。有cleaner/better方法吗?

一个想法是使用自定义 Execution Strategy

picocli 用户手册的Initialization Before Execution 部分有一个示例。 让我们尝试针对您的用例修改该示例。我得出这样的结论:

@Command(subcommands = {Sub1.class, Sub2.class, Sub3.class})
class MyApp implements Runnable {

    Channel channel; // initialized in executionStrategy method

    // A reference to this method can be used as a custom execution strategy
    // that first calls the init() method,
    // and then delegates to the default execution strategy.
    private int executionStrategy(ParseResult parseResult) {

        // custom initialization to be done before executing any command or subcommand
        try (this.channel = _establishChannel(generalConfiguration)) {

            // default execution strategy
            return new CommandLine.RunLast().execute(parseResult); 
        }
    }

    public static void main(String[] args) {
        MyApp app = new MyApp();
        new CommandLine(app)
                // wire in the custom execution strategy
                .setExecutionStrategy(app::executionStrategy) // Java 8 method reference syntax
                .execute(args);
    }

    // ...
}

此自定义执行策略可确保 top-level 命令的 channel 字段在执行任何命令之前初始化。

下一块是,子命令如何访问这个 channel 字段(因为这个字段在 top-level 命令中)? 这就是 @ParentCommand 注释的用途。

当子命令有一个@ParentCommand注释的字段时,picocli会将父命令的用户对象注入该字段,以便子命令可以引用父命令中的状态。 例如:

@Command(name = "country", description = "Resolve ISO country code (ISO-3166-1, Alpha-2 code)")
static class Subcommand1 implements Runnable {

    @ParentCommand
    private MyApp parent; // picocli injects reference to parent command

    @Parameters(arity = "1..*", paramLabel = "<country code>", description = "country code(s) to be resolved")
    private String[] countryCodes;

    @Override
    public void run() {
        Channel channel = parent.channel;
        doSomethingWith(channel);
        // ...
    }
}