Bariloche 是一个参数解析库,旨在用 Swift 构建声明式、多彩且类型安全的命令行界面。支持高级 zsh 自动补全。
将 Bariloche 添加为项目的依赖项
dependencies: [
.package(url: "https://github.com/Subito-it/Bariloche", from: "1.0.0")
]
在 Xcode 中运行 Bariloche 演示
git clone https://github.com/Subito-it/Bariloche;
cd Bariloche
swift package update; swift package generate-xcodeproj
xed .
选择 BarilocheExample scheme 并运行。
Bariloche 允许为扁平型(如 ls
、cp
、cat
)和嵌套型(如 pod
、docker
)命令行工具声明接口。
用法很简单,只需定义一个遵守 Command
协议的类,并将 Argument
和 Flag
属性添加到您自定义的命令中。通过内省,Bariloche 将确定如何解析命令行参数,提取定义的标志和参数值。
一旦定义了自定义命令,您需要将它的一个实例传递给 Bariloche
的初始化器并调用 parse()
来执行命令行解析。
let parser = Bariloche(command: CatCommand())
let result = parser.parse()
parse()
返回一个 [Command]
,其中包含所有成功解析的命令。 对于扁平型工具,此数组将始终包含 1 个项目;对于嵌套型工具,它可能包含 1 个或多个项目。
该库的基本组件是 Command
协议,它定义了命令的接口。
protocol Command: AnyObject {
var name: String? { get }
var usage: String? { get }
var help: String? { get }
func shouldShowUsage() -> Bool
func run() -> Bool
}
name
: 子命令的名称,用于嵌套命令行界面,显示在帮助横幅中。对于扁平型命令行界面,您可以利用默认实现,它返回 nil
usage
: 命令的描述,显示在帮助横幅中。请注意,如果您使用反引号引用参数和标志,它们将自动突出显示help
: 对于嵌套命令行界面,在父命令的帮助横幅旁边显示的帮助信息func shouldShowUsage() -> Bool
(可选):调用此方法以确定是否应显示帮助横幅。实现是可选的,因为您可以依赖默认实现,它将在适当的时候显示帮助func run() -> Bool
:当命令成功解析时,将调用此方法。 这应该是您的业务逻辑实现的入口点。 返回 false 将停止进一步的解析。 如果没有命令返回 true,则将显示帮助横幅Argument
类允许您将参数(命名、位置和可变参数)添加到命令。
class Argument<Value> : Equatable, CustomStringConvertible {
var value: Value?
init(name: String,
kind: Kind,
optional: Bool,
help: String?,
autocomplete: Autocomplete)
}
name
: 参数的名称,将显示在帮助横幅中optional
: 一个布尔值,用于指定该参数是否为可选参数help
(可选): 参数的描述,将显示在帮助横幅中kind
: 指定参数的类型,有效值为: .positional
, .named(let short: String, let long: String)
和 .variadic
value
: 解析的参数值autocomplete
(可选): 一个枚举,用于确定如何在 shell 中自动补全参数,请参阅 自动补全要初始化一个实例,您需要指定一个泛型类型,该类型将用于确定 value 属性的类型。 当前支持的类型包括: Int
, UInt
, Float
, TimeInterval
, Bool
, URL
。 当指定 URL
时,它将自动转换网络和文件系统 URL。
可变参数应指定为上述类型的数组(例如 Argument<[Int]>()
)。
当添加 Argument
属性到您自定义的 Command
时,添加它们的顺序将反映在解析命令行参数时。
采用以下两个命令
class MyCustomCommand1: Command {
let arg1 = Argument<String>(name: "arg1", kind: .positional, optional: false)
let arg2 = Argument<String>(name: "arg2", kind: .positional, optional: false)
}
class MyCustomCommand2: Command {
let arg2 = Argument<String>(name: "arg2", kind: .positional, optional: false)
let arg1 = Argument<String>(name: "arg1", kind: .positional, optional: false)
}
Flag
类允许您将标志添加到命令。
class Flag: Equatable, CustomStringConvertible {
let short: String?
let long: String?
let help: String?
var value: Bool = false
init(short: String? = nil, long: String? = nil, help: String?)
}
short
: 标志的短版本。 不应包含短划线long
: 标志的长版本。 不应包含短划线help
: 参数的描述,将显示在帮助横幅中value
: 一个布尔值,当在命令行参数中找到该标志时,该值为 trueBariloche 开箱即用地支持 zsh 自动补全。 每次执行命令时,当前用户 $fpath 中的自动补全文件都会更新,因此在第一次调用可执行文件后,它应该可以正常工作。 如果仍然不行,您可能需要关闭并重新打开终端一次。
您填充的 help
属性越多(在 Flag
、Argument
和 Autocomplete.Item
中),自动补全的结果就越好。
一个枚举,用于确定如何在 shell 中自动补全参数
enum Autocomplete {
case none
case items(_ items: [Item])
case files(extension: String)
case directories
case paths(pattern: String?)
}
none
: 不会执行进一步的自动补全items
: 使用一组自定义定义的 Autocomplete.Item
files
: 匹配特定扩展名的文件(例如 json)。 传递 nil 将匹配所有文件,但不匹配目录directories
: 仅目录paths
: 使用文件系统路径自动补全,可以通过指定匹配模式进一步过滤掉。 有关更多信息,请参阅 zsh 文档一个结构体,用于建模在 shell 中自动补全参数时所需的预期值
extension Autocomplete {
struct Item {
let value: String
let help: String?
}
}
value
: 预期的值help
(可选): 对预期值的描述一个使用预期值的 Argument 示例
class MyCustomCommand: Command {
let arg = Argument<String>(
name: "arg",
kind: .named(short: "t", long: "type"),
optional: true,
help: "Some help for the whole argument",
autocomplete: .items([.init(value: "type1", help: "Type 1 help"),
.init(value: "type2", help: "Type 2 help")]))
}
为了便于演示,我们重新创建了 cat
和 cocoapods 命令的部分实现。
下面显示了一个采用单个字符串参数的扁平命令
import Bariloche
class FlatCommand: Command {
let usage: String? = "The cat utility reads files..."
let verboseFlag = Flag(short: "v", long: "verbose", help: "Display non-printing characters...")
let fileArgument = Argument<String>(name: "file", kind: .positional, optional: false, help: nil)
func run() -> Bool {
guard let value = fileArgument.value else {
return false
}
print("Running with argument \(value)")
return true
}
}
let parser = Bariloche(command: CatCommand())
let result = parser.parse()
帮助横幅输出将如下所示
下面显示了一个采用单个字符串参数的嵌套命令
class CocoaPodsCommand: Command {
let usage: String? = "CocoaPods, the Cocoa library package manager."
let subcommands: [Command] = [SearchCommand(), CacheCommand()]
func run() -> Bool {
return true
}
}
class SearchCommand: Command {
let name: String? = "search"
let usage: String? = "Searches for pods, ignoring case, whose..."
let help: String? = "Search for pods"
let simple = Flag(short: nil, long: "simple", help: "Search only by name")
let query = Argument<String>(name: "QUERY", kind: .positional, optional: false, help: nil)
func run() -> Bool {
guard let query = query.value else {
return false
}
print("Running with query \(query)")
return true
}
}
class CacheCommand: Command {
let name: String? = "cache"
let usage: String? = "Manipulate the download cache for pods, like printing the cache content or cleaning the pods cache."
let help: String? = "Manipulate the CocoaPods cache"
func run() -> Bool {
print("Running cache")
return true
}
}
let parser = Bariloche(command: CocoaPodsCommand())
let result = parser.parse()
根命令的帮助横幅输出将如下所示
而 search
子命令的帮助将如下所示
请注意 QUERY
和 --simple
是如何在用法描述中自动着色的。
Bariloche 允许使用 static func ask<Value>(_ question: String, endOfMarker: String = "", validate: ((_ answer: Value) throws -> Value)? = nil) -> Value
方法从 stdin 提示输入。
参数
question
: 将打印到 stdout 的字符串endOfMarker
: 该值指定哪个标记将从 stdin 返回,默认值在第一个行尾符 ("\n") 上返回。validate
: 一个可选的验证块,允许通过抛出异常或返回一个修改后的值作为 ask()
方法的返回值来拒绝提供的答案并再次提示问题。 如果未提供验证块,则方法 () 的返回值将是从 stdin 转换的值。假设您想请求用户输入一个 Int
,范围在 1-5 之间,然后将其转换为 0-4 范围。
let targetIndex: Int = Bariloche.ask("Select an index:") { answer in
guard 1...5 ~= answer else { throw Error("Invalid index") }
return answer - 1
}
欢迎贡献! 如果您有错误要报告,请随时通过打开新 issue 或发送 pull request 来提供帮助。
Barloche 在 Apache License, Version 2.0 许可下可用。 有关更多信息,请参见 LICENSE 文件。