ArgumentParser 发布公告
我们很高兴地宣布 ArgumentParser
,这是一个新的开源库,它可以让在 Swift 中解析命令行参数变得简单 — 甚至令人愉快!
构建命令行工具
为了向您展示 ArgumentParser
库的使用方式,我们将创建一个生成随机数的实用工具。与许多命令行工具一样,这个工具将逐步积累功能,我们将看到 ArgumentParser
如何帮助作者和用户保持一切井井有条。
这是我们 random
实用工具的预期界面
> random 20
17
> random 100
89
> random
Error: Missing expected argument '<high-value>'
Usage: random <high-value>
让我们定义一个 Random
类型,它接受一个整数参数 — highValue
— 然后打印一个介于 1 和 highValue
之间的随机数
import ArgumentParser
struct Random: ParsableCommand {
@Argument() var highValue: Int
func run() {
print(Int.random(in: 1...highValue))
}
}
Random.main()
就这样!
@Argument
属性包装器表明该属性应从命令行参数中获取。- 在我们的
ParsableCommand
类型上调用main()
会启动解析,如果解析成功,则运行命令行工具。 - 该库综合了帮助和错误消息,以引导用户成功使用,使用了我们提供的所有信息:
highValue
属性的名称和类型,以及我们的命令类型的名称。 -
由于
highValue
被定义为Int
类型,因此只有有效输入才会被识别,您无需手动解析或转换> random ZZZ Error: The value 'ZZZ' is invalid for '<high-value>' Usage: random <high-value>
自定义验证和帮助
一个好的命令行工具会记录并验证其输入。让我们添加一些描述性文本并实现 validate()
方法,以便我们可以捕获用户为 highValue
给出的值过低的情况
struct Random: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Chooses a random number between 1 and your input.")
@Argument(help: "The highest value to pick.")
var highValue: Int
func validate() throws {
guard highValue >= 1 else {
throw ValidationError("'<high-value>' must be at least 1.")
}
}
func run() {
print(Int.random(in: 1...highValue))
}
}
我们的工具现在可以更智能地处理它可以接受的值,并在自动生成的帮助屏幕中包含丰富的文档
> random 0
Error: '<high-value>' must be at least 1.
Usage: random <high-value>
> random --help
OVERVIEW: Chooses a random number between 1 and your input.
USAGE: random <high-value>
ARGUMENTS:
<high-value> The highest value to pick.
OPTIONS:
-h, --help Show help information.
使用子命令
现代命令行工具,例如 Git 和 Swift 包管理器,使用子命令在命令树中对相关工具进行分组。使用 ArgumentParser
,您可以通过将每个子命令声明为单独的类型来构建这样的界面。
让我们通过将现有逻辑移至嵌套的 Number
类型来实现一个子命令
extension Random {
struct Number: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Chooses a random number between 1 and your input.")
@Argument(help: "The highest value to pick.")
var highValue: Int
func validate() throws {
guard highValue >= 1 else {
throw ValidationError("'<high-value>' must be at least 1.")
}
}
func run() {
print(Int.random(in: 1...highValue))
}
}
}
…并在根命令的配置中列出子命令
struct Random: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Randomness utilities.",
subcommands: [Number.self])
}
ArgumentParser
会处理剩下的事情!
> random number 100
79
添加子命令
为了完善我们的工具,让我们添加第二个子命令,用于从您作为参数提供的列表中 pick
一个元素
> random pick Fuji Gala Cameo Honeycrisp McIntosh Braeburn
McIntosh
> random pick --count 3 Fuji Gala Cameo Honeycrisp McIntosh Braeburn
Honeycrisp
Cameo
Braeburn
Pick
命令接受一个 count
选项,并期望一个要从中选择的 elements
数组
struct Random: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Randomness utilities.",
subcommands: [Number.self, Pick.self])
// ...
struct Pick: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Picks random elements from your input.")
@Option(default: 1, help: "The number of elements to choose.")
var count: Int
@Argument(help: "The elements to choose from.")
var elements: [String]
func validate() throws {
guard !elements.isEmpty else {
throw ValidationError("Must provide at least one element.")
}
}
func run() {
let picks = elements.shuffled().prefix(count)
print(picks.joined(separator: "\n"))
}
}
}
@Option
属性包装器表明属性应从命令行参数中读取,使用属性的名称来创建键值对。
我们的 random
实用工具的最终版本不到 50 行代码!它会自动检测用户给出的子命令,解析该子命令的参数,并调用其 run()
方法。如果您省略子命令,该库将调用 Random
命令的 run()
默认实现,该实现仅打印命令的帮助屏幕
> random
OVERVIEW: Randomness utilities.
USAGE: random <subcommand>
OPTIONS:
-h, --help Show help information.
SUBCOMMANDS:
number Chooses a random number between 1 and your input.
pick Picks random elements from your input.
设计目标
在设计 ArgumentParser
时,我们考虑了以下目标
- 鼓励最佳命令行界面实践,并支持对库的渐进式理解。
- 支持从简单的一次性脚本到复杂工具的各种项目,包括嵌套子命令和丰富的帮助信息。
- 消除通常与解析命令行参数相关的样板代码,减少重复和出错的机会。
这些设计目标引导我们设计了一种使用 Swift 的类型系统以及属性包装器和反射等特性,从您的自定义类型声明中隐式构建接口的设计。ArgumentParser
就是成果。
为什么现在?
Swift 项目包含几个用 Swift 编写的命令行工具 — 一些作为 Swift 工具链的一部分发布,一些用于构建和测试。特别是,SwiftPM 在其 TSCUtility
库中包含一个参数解析器,该解析器已发展为支持 SwiftPM 的需求,但从未打算更广泛地采用。
我们将努力在整个 Swift 项目中采用 ArgumentParser
,并邀请您试用该库、提供反馈并参与其持续开发!
了解更多
除了我们目前所看到的,ArgumentParser
还支持用于布尔或可枚举属性的 --flag
参数、选项和标志的多个名称、封装参数组等等。您可以通过访问存储库的 README、浏览文档文件夹中的指南以及阅读源代码符号文档来了解更多信息。
您还可以探索 Swift 项目正在进行的 ArgumentParser
采用情况
indexstore-db
是一个简单的实用工具,包含两个命令。swift-format
使用了一些高级功能,例如自定义选项值和隐藏标志。
接下来是什么?
在短期内,还需要添加一些额外的功能,以便 SwiftPM 可以在不降低功能的情况下采用 ArgumentParser
— 您可以在存储库的问题跟踪中找到这些功能。一旦 SwiftPM 完成采用,我们也希望在 Swift 编译器驱动程序的 Swift 重写中采用该库。
除了这些集成之外,我们还希望与社区合作,共同定义 1.0 版本的需求。哪些其他功能对于在各种环境(例如服务器、Windows 和其他平台)中广泛采用至关重要?哪些其他自定义点最重要?使用 ArgumentParser
的人越多,我们就越能一起回答这些问题。
参与其中
非常鼓励您的经验、反馈和贡献!
- 首先在 GitHub 上试用
ArgumentParser
库, - 在 ArgumentParser 论坛中讨论该库并获得帮助,
- 针对您发现的问题或您提出的改进想法,打开一个 issue,
- 和往常一样,欢迎提交 pull request!