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()

就这样!

自定义验证和帮助

一个好的命令行工具会记录并验证其输入。让我们添加一些描述性文本并实现 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 采用情况

接下来是什么?

在短期内,还需要添加一些额外的功能,以便 SwiftPM 可以在不降低功能的情况下采用 ArgumentParser — 您可以在存储库的问题跟踪中找到这些功能。一旦 SwiftPM 完成采用,我们也希望在 Swift 编译器驱动程序的 Swift 重写中采用该库。

除了这些集成之外,我们还希望与社区合作,共同定义 1.0 版本的需求。哪些其他功能对于在各种环境(例如服务器、Windows 和其他平台)中广泛采用至关重要?哪些其他自定义点最重要?使用 ArgumentParser 的人越多,我们就越能一起回答这些问题。

参与其中

非常鼓励您的经验、反馈和贡献!

问题?

请随时在 Swift 论坛上的相关主题帖中发布关于这篇文章的问题。