一个用于将类型编码为字符串数组,或称“参数”的库。
该库仍在开发中。在达到 1.0.0
版本之前,应将其视为实验性的,并且在功能发布之间可能会有重大更改。
通常,对 CLI 工具进行建模将从 TopLevelCommandRepresentable
开始。 这是入口点,必须通过 commandValue
函数显式声明其自身的名称。
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
}
每个命令或子命令可能对标志和选项有自己的格式要求。 因此,TopLevelCommandRepresentable
和 CommandRepresentable
都继承自 FormatterNode
。 这要求声明 FlagFormatter
和 OptionFormatter
。
在 MyCommand
中,我们需要能够对布尔值进行建模,以启用/禁用某些功能。 这就是 Flag
的用武之地。 作为某种符合 ArgumentGroup
类型的属性包装器,它最方便。
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
@Flag var myFlag: Bool = false
}
除了对启用/禁用功能的能力进行建模之外,我们还需要针对某个变量设置一个值。 为此,我们可以使用 Option
。 对于可以有多个值的选项,可以使用 OptionSet
。
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
@Flag var myFlag: Bool = false
@Option var myOption: Int = 0
@OptionSet var myOptions: [String] = ["value1", "value2"]
}
通过 Positional
类型支持仅作为值的,没有键的位置参数。
struct MyCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "my-command" }
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
@Flag var myFlag: Bool = false
@Option var myOption: Int = 0
@OptionSet var myOptions: [String] = ["value1", "value2"]
@Positional var myPositional: String = "positional"
}
当使用 Swift 运行可执行文件时,将结构化类型(struct、class、enum)编码为传递给可执行文件的参数数组可能会很有帮助。
public enum SwiftCommand {
case run(String)
case test(TestCommand)
public func asArgs() -> [String] {
let childArgs: [String]
switch self {
case let .run(executableProduct):
childArgs = [executableProduct]
case let .test(testCommand):
childArgs = testCommand.asArgs()
}
return ["swift"] + childArgs
}
}
public struct TestCommand {
public let parallel: Bool
public let numWorkers: Int
public let showCodecovPath: Bool
public let testProducts: [String]
public init(
parallel: Bool = true,
numWorkers: Int = 1,
showCodecovPath: Bool = false,
testProducts: [String]
) {
self.parallel = parallel
self.numWorkers = numWorkers
self.showCodecovPath = showCodecovPath
self.testProducts = testProducts
}
public func asArgs() -> [String] {
var args = [String]()
if parallel {
args.append("--parallel")
}
args.append(contentsOf: ["--num-workers", numWorkers.description])
if showCodecovPath {
args.append("--show-codecov-path")
}
args.append(contentsOf: testProducts)
return args
}
}
这种方法繁琐且容易出错。 有一些方法可以在仍然手动编写 asArgs
的同时进行改进,但它仍然远非理想。
ArgumentEncoding 允许编写可以轻松编码为参数数组的类型,而无需手动编码。
import ArgumentEncoding
enum SwiftCommand: TopLevelCommandRepresentable {
func commandValue() -> Command { "swift" }
var flagFormatter: FlagFormatter { FlagFormatter(prefix: .doubleDash, body: .kebabCase) }
var optionFormatter: OptionFormatter { OptionFormatter(prefix: .doubleDash, body: .kebabCase) }
case run(RunCommand)
case test(TestCommand)
}
struct RunCommand: CommandRepresentable {
@Positional var executable: String
}
extension RunCommand: ExpressibleByStringLiteral {
init(stringLiteral value: StringLiteralType) {
self.init(executable: Positional(wrapped: value))
}
}
struct TestCommand: CommandRepresentable {
@Flag var parallel: Bool = true
@Option var numWorkers: Int = 1
@Flag var showCodecovPath: Bool = false
@OptionSet var testProducts: [String] = []
}