在 picocli 中,如何在命令行上设置选项以覆盖 @-file 中的相同选项

In picocli how do you make options on the command line to override the same option in an @-file

我目前在 Java 11 应用程序中使用 picocli 4.7.0-SNAPSHOT 效果很好,该应用程序具有一组复杂的选项,因此我正在使用 @-file 功能。

我试图开始工作的是直接在命令行上指定的选项,以覆盖@-文件中存在的相同选项。因此在命令行上指定的选项优先于@-文件。可以吗

当我尝试 运行 我的测试应用程序时,主要基于 picocli 示例,同时使用命令行选项和 @-file,我从 picocli 得到以下错误以及预期的用法:

myapp --sourceDatabaseType=MySQL @.\myapp.options

option '--sourceDatabaseType' (<sourceDatabaseType>) should be specified only once

然后是预期的使用信息。

让我解释一下这个问题,看看我是否理解正确:

如果最终用户直接在命令行上指定一个选项,则应使用命令行值,而如果未在命令行上指定该选项,则应使用文件中的值。

本质上,您使用的是一个@文件,目的是为一个或多个选项定义default values。 但是,这不是 @-files 的设计目的:picocli 无法区分来自命令行的参数和来自 @-file 的参数。

我建议改用 picocli 的 default provider 机制。

一个想法是使用 built-in PropertiesDefaultProvider:

import picocli.CommandLine.PropertiesDefaultProvider;

@Command(name = "myapp", defaultValueProvider = PropertiesDefaultProvider.class)
class MyApp { }

PropertiesDefaultProvider 也使用一个文件,该文件中的值仅用于命令行中指定的选项。

棘手的一点是这个文件的位置。 PropertiesDefaultProvider 在以下位置查找文件:

  • 系统指定的路径属性picocli.defaults.${COMMAND-NAME}.path
  • 最终用户的用户主目录中名为 .${COMMAND-NAME}.properties 的文件

(将${COMMAND-NAME}替换为命令名称,所以对于名为myapp的命令,系统属性是picocli.defaults.myapp.path)

为了让最终用户能够指定文件的位置,我们需要在 picocli 完成解析命令行参数之前设置系统 属性。

我们可以使用 @Option 注释的 setter 方法来做到这一点。例如:

class MyApp { 
    @Option(names = "-D")
    void setSystemProperty(Map<String, String> properties) {
        System.getProperties().putAll(properties);
    }
}

这将允许最终用户使用如下方式调用命令:

myapp --sourceDatabaseType=MySQL -Dpicocli.defaults.myapp.path=.\myapp.options

如果这太冗长,您可以更进一步,创建一个特殊的 -@ 选项,以允许用户使用类似这样的命令调用命令:

myapp --sourceDatabaseType=MySQL -@.\myapp.options

此实现将是带注释的 setter 方法,类似于上面的方法:

class MyApp { 
    @Spec CommandSpec spec; // injected by picocli

    @Option(names = "-@")
    void setDefaultProviderPath(File path) {
        // you could do some validation here:
        if (!path.canRead()) {
            String msg = String.format("ERROR: file not found: %s", path);
            throw new ParameterException(spec.commandLine(), msg);
        }
        // only set the system property if the file exists
        System.setProperty("picocli.defaults.myapp.path", path.toString());
    }
}