最初于2002年发布的Apache Commons CLI可能是使用最广泛的Java命令行解析器,但是它的API显示了它的年龄。 寻找具有最少样板代码的现代方法的应用可能对picocli感兴趣。 为什么要花麻烦的钱进行迁移,以及如何将基于Commons CLI的应用程序迁移到picocli? Picocli提供了一种流畅的API,具有强大的输入功能,ANSI颜色的使用帮助,自动补全功能以及许多其他功能。 让我们以Checkstyle为例。
为什么要迁移?
从Commons CLI迁移到picocli是否值得解决? 从一个命令行解析器转到另一个命令行解析器有什么好处? 这不仅仅是重新装修我们应用程序的客厅吗?
最终用户体验
对最终用户有什么好处?
命令行完成 。 基于Picocli的应用程序可以在bash和zsh shell以及基于JLine的交互式shell应用程序中具有命令行完成功能 。
美丽,易读的用法帮助消息。 Commons CLI生成的使用帮助有点简单。 picocli开箱即用地生成帮助,该帮助使用ANSI样式和颜色进行对比以强调命令,选项和参数等重要信息。 使用注释可以轻松自定义帮助消息的布局。 此外,如果您需要其他帮助,还有一个帮助API。 有关一些示例屏幕截图,请参见picocli 自述文件 。
通过@ -files或“ argument files”支持非常大的命令行 。 有时用户需要指定比操作系统或外壳程序支持的命令行更长的命令行。 当picocli遇到以字符@
开头的参数时,它将文件的内容扩展到参数列表中。 这使应用程序可以处理任意长度的命令行。
开发人员经验
作为开发人员,对您有什么好处?
通常,picocli应用程序的代码要比Commons CLI的代码少得多。 picocli批注允许应用程序以声明性的方式定义选项和位置参数,所有信息都放在一个位置。 此外,picocli还提供了许多便利,例如类型转换和自动帮助,这些便利照顾了一些机制,因此应用程序可以将更多精力放在业务逻辑上。 本文的其余部分将更详细地说明这一点。
文档 :picocli具有广泛的用户手册和详细的javadoc 。
故障排除 。 Picocli具有内置的跟踪工具,以方便进行故障排除。 最终用户可以使用系统属性picocli.trace
来控制跟踪级别。 支持的级别为OFF
, WARN
, INFO
和DEBUG
。 默认跟踪级别为WARN
。
未来扩展
最后,除了立即获得回报之外,从Commons CLI迁移到picocli还可以获得任何未来的好处吗?
Picocli具有许多高级功能 。 您的应用程序可能尚未使用这些功能,但是如果您想将来扩展应用程序,picocli支持嵌套子命令 (以及子子命令到任何深度),可重复使用的mixins ,可轻松与 Dependency Injection容器集成以及一个不断发展的工具框架,可从picocli CommandSpec
模型生成源代码,文档和配置文件。
最终,picocli得到了积极维护 ,而Commons CLI似乎在16年内发布了6个版本,几乎处于停滞状态。
迁移示例:CheckStyle
命令行应用程序需要做三件事:
- 定义支持的选项
- 解析命令行参数
- 处理结果
让我们以CheckStyle的com.puppycrawl.tools.checkstyle.Main
命令行实用程序为例,比较在Commons CLI和picocli中如何完成此操作。
完整的源代码之前和之后的迁移是在GitHub上。
定义选项和位置参数
使用Commons CLI定义选项
Commons CLI有多种定义选项的方式: Options.addOption
,构造一个new Options(…)
并在此对象,不建议使用的OptionBuilder
类和推荐的Option.Builder
类上调用方法。
Checkstyle Main
类使用Options.addOption
方法。 首先为选项名称定义一些常量:
/** Name for the option 's'. */
private static final String OPTION_S_NAME = "s";/** Name for the option 't'. */
private static final String OPTION_T_NAME = "t";/** Name for the option '--tree'. */
private static final String OPTION_TREE_NAME = "tree";... // and more. Checkstyle Main has 26 options in total.
Main.buildOptions
方法使用以下常量来构造和返回定义了支持的选项的Commons CLI Options
对象:
private static Options buildOptions() {final Options options = new Options();options.addOption(OPTION_C_NAME, true, "Sets the check configuration file to use.");options.addOption(OPTION_O_NAME, true, "Sets the output file. Defaults to stdout");...options.addOption(OPTION_V_NAME, false, "Print product version and exit");options.addOption(OPTION_T_NAME, OPTION_TREE_NAME, false,"Print Abstract Syntax Tree(AST) of the file");...return options;
}
使用Picocli定义选项
在picocli中,您可以使用类似于Commons CLI方法的构建器以编程方式定义支持的选项,也可以使用注释以声明方式定义支持的选项。
对于并非事先知道所有选项的动态应用程序,Picocli的编程API可能会有用。 如果您对编程方法感兴趣,请查看CommandSpec
, OptionSpec
和PositionalParamSpec
类。 另请参阅Programmatic API 。
在本文中,我们将使用picocli批注。 对于CheckStyle示例,这看起来类似于以下内容:
@Option(names = "-c", description = "Sets the check configuration file to use.")
private File configurationFile;@Option(names = "-o", description = "Sets the output file. Defaults to stdout")
private File outputFile;@Option(names = "-v", versionHelp = true, description = "Print product version and exit")
private boolean versionHelpRequested;@Option(names = {"-t", "--tree"}, description = "Print Abstract Syntax Tree(AST) of the file")
private boolean printAST;
比较方式
陈述式
使用Commons CLI,您可以通过调用具有String值的方法来构建规范。 此类API的一个缺点是,良好的样式会迫使客户端代码定义常量以避免“不可思议的值”,就像Checkstyle Main
类尽职尽责。
使用picocli,所有信息都集中在一个地方。 注释仅接受String文字,因此定义和用法会自动放在一起,而无需声明常量。 这样可以使代码更简洁,更少。
强类型
Commons CLI使用布尔标志来表示该选项是否带有参数。
Picocli使您可以直接使用类型。 根据类型,picocli“知道”该选项需要多少个参数: boolean
字段没有参数, Collection
, Map
和array字段可以有零个到任意数量的参数,任何其他类型意味着这些选项只需要一个参数论点。 可以对其进行自定义(请参阅arity
),但是大多数情况下,默认值就足够了。
Picocli鼓励您将enum
类型用于带有有限有效值集的选项或位置参数。 picocli不仅会为您验证输入,还可以使用@Option(description = "Valid values: ${COMPLETION-CANDIDATES}")
在使用帮助消息中显示所有值 。 枚举还允许命令行补全功能为选项值建议补全候选值。
更少的代码
Picocli 将选项参数String值转换为字段类型。 它不仅可以节省应用程序的工作量,而且还可以对用户输入进行最小程度的验证。 如果转换失败,则会引发ParameterException
并显示用户友好的错误消息。
让我们看一个例子,看看这有多有用。 Checkstyle Main
类定义了-x
, --exclude-regexp
exclude --exclude-regexp
选项,该选项允许用于指定要排除的目录的多个正则表达式。
使用Commons CLI,您需要将在命令行上匹配的String值转换为应用程序java.util.regex.Pattern
对象:
/*** Gets the list of exclusions from the parse results.* @param commandLine object representing the result of parsing the command line* @return List of exclusion patterns.*/
private static List<Pattern> getExclusions(CommandLine commandLine) {final List<Pattern> result = new ArrayList<>();if (commandLine.hasOption(OPTION_X_NAME)) {for (String value : commandLine.getOptionValues(OPTION_X_NAME)) {result.add(Pattern.compile(value));}}return result;
}
根据合同,在picocli中,您只需在List<Pattern>
(或Pattern[]
数组)字段上声明该选项。 由于picocli具有java.util.regex.Pattern
的内置转换器,因此只需声明该选项即可。 转换代码完全消失了。 如果在命令行上指定了一个或多个-x
选项,Picocli将实例化并填充列表。
/** Option that allows users to specify a regex of paths to exclude. */
@Option(names = {"-x", "--exclude-regexp"},description = "Regular expression of directory to exclude from CheckStyle")
private List<Pattern> excludeRegex;
选项名称
Commons CLI支持“短”和“长”选项,例如-t
和--tree
。 这并不总是您想要的。
Picocli允许选项具有任意数量的名称和任意前缀。 例如,这在picocli中是完全可以的:
@Option(names = {"-cp", "-classpath", "--class-path"})
位置参数
在Commons CLI中,您无法预先定义位置参数。 相反,其CommandLine
解析结果类具有方法getArgs
,该方法以字符串数组形式返回位置参数。 Checkstyle Main
类使用它来创建要处理的File
对象的列表。
在picocli中, 位置参数是一等公民,例如命名选项。 不仅可以强类型化它们,而且位于不同位置的参数可以具有不同的类型,并且每个参数将在用法帮助消息中列出单独的条目和描述。
例如,Checkstyle Main
类需要处理的文件列表,因此我们声明一个字段并使用@Parameters
对其进行@Parameters
。 arity = "1..*"
属性意味着必须至少指定一个文件,否则picocli将显示有关缺少参数的错误消息。
@Parameters(paramLabel = "file", arity = "1..*", description = "The files to process")
private List<File> filesToProcess;
帮助选项
在Commons CLI中,用必需的选项创建一个具有--help
选项的应用程序是非常困难的。 Commons CLI对帮助选项没有特殊处理,当用户指定<command> --help
时,它将抱怨缺少必需的选项。
Picocli具有对常见(和自定义) 帮助选项的内置支持。
解析命令行参数
Commons CLI具有一个CommandLineParser
接口,该接口带有parse
方法,该方法返回代表解析结果的CommandLine
。 然后,应用程序调用CommandLine.hasOption(String)
来查看是否设置了标志,或者调用CommandLine.hasOption(String)
CommandLine.getOptionValue(String)
来获取选项值。
Picocli在分析命令行参数时填充带注释的字段。 Picocli的parse…
方法也返回ParseResult
可以上指定的选项并且什么的价值他们有,但大多数应用程序实际上并不需要使用查询ParseResult
类,因为它们可以简单地检查该注入的注释值解析期间的字段。
处理结果
解析器完成后,应用程序需要运行其业务逻辑,但首先要检查一些事情:
- 是否需要版本信息或使用帮助? 如果是这样,请打印出所需的信息并退出。
- 用户输入是否无效? 打印出一条包含详细信息的错误消息,打印使用帮助消息并退出。
- 最终运行业务逻辑–处理业务逻辑引发的错误。
使用Commons CLI,这看起来像这样:
int exitStatus;
try {CommandLine commandLine = new DefaultParser().parse(buildOptions(), args);if (commandLine.hasOption(OPTION_VERSION)) { // --versionSystem.out.println("Checkstyle version: " + version());exitStatus = 0;} else if (commandLine.hasOption(OPTION_HELP)) { // --helpprintUsage(System.out);exitStatus = 0;} else {exitStatus = runBusinessLogic(); // business logic}
} catch (ParseException pex) { // invalid inputexitStatus = EXIT_WITH_CLI_VIOLATION;System.err.println(pex.getMessage());printUsage(System.err);
} catch (CheckstyleException ex) { // business logic exceptionexitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE;ex.printStackTrace();
}
System.exit(exitStatus);
Picocli提供了一些方便的方法来解决以上大部分问题。 通过使您的命令实现Runnable
或Callable
,应用程序可以专注于业务逻辑。 最简单地说,它可能看起来像这样:
public class Main implements Callable<Integer> {public static void main(String[] args) {CommandLine.call(new Main(), args);}public Integer call() throws CheckstyleException {// business logic here}
}
Checkstyle Main
类需要控制退出代码,并且对错误处理有一些严格的内部要求,因此我们最终没有使用便捷方法,并且使解析结果处理与Commons CLI极为相似。 在picocli待办事项清单上可以改善这个领域。
使用帮助信息
Picocli在支持的平台上的使用帮助消息中使用ANSI颜色和样式。 这不仅看起来不错,而且还减轻了用户的认知负担 :对比度使重要的信息(如命令,选项和参数)从周围的文本中脱颖而出。
应用程序还可以在使用帮助消息的描述或其他部分中使用带有简单标记的ANSI颜色和样式,例如@|bg(red) text with red background|@
。 请参阅用户手册的相关部分 。
对于CheckStyle,我们将其保持在最低限度,而CheckStyle的结果输出如下所示:
总结:最后的提示
请注意,即使使用帮助消息仅显示带有双连字符的选项,Commons CLI的默认解析器也可以识别单连字符( -
)和双连字符( --
)长选项。 您需要确定是否继续支持此操作。
在picocli中@Option(names = "-xxx", hidden = true)
如果您想模仿与Commons CLI完全相同的行为,则可以使用@Option(names = "-xxx", hidden = true)
声明带有单个连字符的长选项:用法中未显示 picocli 中的隐藏选项帮助信息。
结论
从Commons CLI迁移到picocli可以为最终用户提供更好的用户体验,并且可以为开发人员带来显着的好处,即提高其可维护性和未来扩展的潜力。 迁移是手动过程,但相对简单。
更新:CheckStyle项目接受了本文更改中的拉取请求。 从CheckStyle 8.15开始,其命令行工具将使用picocli。 CheckStyle维护人员对结果感到满意:
Checkstyle从Apache CLI迁移到@picocli(将在8.15中发布),最后,CLI参数的文档现在已经以声明性的方式在代码中井井有条地组织起来,而Checkstyle的CLI遵循CLI最佳实践。
— CheckStyle维护者Roman Ivanov
翻译自: https://www.javacodegeeks.com/2018/11/migrating-commons-cli-picocli.html