这是一个支持在 macOS 上的 Swift 编程语言中开发命令行工具的库。它也可以在 Linux 下编译。该库提供以下功能:
CommandLineKit 使用以下协议处理命令行参数:
Flags
对象中注册。CommandLineKit 定义了不同类型的 Flag 子类,用于处理选项(即没有参数的标志)和参数(即带有参数的标志)。参数可以是单例参数(即它们只有一个值)或重复参数(即它们有多个值)。参数使用类型进行参数化,该类型定义了如何解析值。该框架原生支持 int、double、string 和 enum 类型,这意味着在实践中,仅仅使用内置的标志类几乎总是足够的。然而,该框架是可扩展的,并支持任意参数类型。
标志由短名称字符和长名称字符串标识。两者中至少需要定义一个。例如,“help”选项可以通过短名称“h”和长名称“help”来定义。在命令行中,用户可以使用 -h
或 --help
来引用此选项;即,短名称以单破折号为前缀,长名称以双破折号为前缀。
参数是参数化的标志。参数直接跟在标志标识符之后(通常用空格分隔)。例如,带有长名称“size”的整数参数可以定义为:--size 64
。如果参数是重复的,则多个参数可以跟在标志标识符之后,如本例所示:--size 2 4 8 16
。序列以命令行参数的结尾、另一个标志或终止符“---”终止。终止符之后的所有命令行参数都不会被解析,并返回在 Flags
对象的 parameters
字段中。
这是一个来自 LispKit 项目的 示例。它使用 Flags 类提供的工厂方法(如 flags.string
、flags.int
、flags.option
、flags.strings
等)来创建和注册单个标志。
// Create a new flags object for the system-provided command-line arguments
var flags = Flags()
// Define the various flags
let filePaths = flags.strings("f", "filepath",
description: "Adds file path in which programs are searched for.")
let libPaths = flags.strings("l", "libpath",
description: "Adds file path in which libraries are searched for.")
let heapSize = flags.int("x", "heapsize",
description: "Initial capacity of the heap", value: 1000)
let importLibs = flags.strings("i", "import",
description: "Imports library automatically after startup.")
let prelude = flags.string("p", "prelude",
description: "Path to prelude file which gets executed after " +
"loading all provided libraries.")
let prompt = flags.string("r", "prompt",
description: "String used as prompt in REPL.", value: "> ")
let quiet = flags.option("q", "quiet",
description: "In quiet mode, optional messages are not printed.")
let help = flags.option("h", "help",
description: "Show description of usage and options of this tools.")
// Parse the command-line arguments and return error message if parsing fails
if let failure = flags.parsingFailure() {
print(failure)
exit(1)
}
该框架支持通过 Flags.usageDescription
函数打印支持的选项。对于上面定义的命令行标志,此函数返回以下用法描述:
usage: LispKitRepl [<option> ...] [---] [<program> <arg> ...]
options:
-f, --filepath <value> ...
Adds file path in which programs are searched for.
-l, --libpath <value> ...
Adds file path in which libraries are searched for.
-h, --heapsize <value>
Initial capacity of the heap
-i, --import <value> ...
Imports library automatically after startup.
-p, --prelude <value>
Path to prelude file which gets executed after loading all provided libraries.
-r, --prompt <value>
String used as prompt in REPL.
-q, --quiet
In quiet mode, optional messages are not printed.
-h, --help
Show description of usage and options of this tools.
命令行工具可以检查是否通过 Flag.wasSet
字段设置了标志。对于带有参数的标志,参数存储在 Flag.value
字段中。此字段的类型取决于标志类型。对于重复标志,使用数组。
这是一个关于如何使用上面代码片段定义的标志的示例:
// If help flag was provided, print usage description and exit tool
if help.wasSet {
print(flags.usageDescription(usageName: TextStyle.bold.properties.apply(to: "usage:"),
synopsis: "[<option> ...] [---] [<program> <arg> ...]",
usageStyle: TextProperties.none,
optionsName: TextStyle.bold.properties.apply(to: "options:"),
flagStyle: TextStyle.italic.properties),
terminator: "")
exit(0)
}
...
// Define how optional messages and errors are printed
func printOpt(_ message: String) {
if !quiet.wasSet {
print(message)
}
}
...
// Set heap size (assuming 1234 is the default if the flag is not set)
virtualMachine.setHeapSize(heapSize.value ?? 1234)
...
// Register all file paths
for path in filePaths.value {
virtualMachine.fileHandler.register(path)
}
...
// Load prelude file if it was provided via flag `prelude`
if let file = prelude.value {
virtualMachine.load(file)
}
下面的代码说明了如何将 Command
协议与声明各种命令行标志的属性包装器结合使用。像这样声明的命令行工具的整个生命周期将自动管理。在标志被解析后,将调用 run()
或 fail(with:)
方法(取决于标志解析是成功还是失败)。
@main struct LispKitRepl: Command {
@CommandArguments(short: "f", description: "Adds file path in which programs are searched for.")
var filePath: [String]
@CommandArguments(short: "l", description: "Adds file path in which libraries are searched for.")
var libPaths: [String]
@CommandArgument(short: "x", description: "Initial capacity of the heap")
var heapSize: Int = 1234
...
@CommandOption(short: "h", description: "Show description of usage and options of this tools.")
var help: Bool
@CommandParameters // Inject the unparsed parameters
var params: [String]
@CommandFlags // Inject the flags object
var flags: Flags
mutating func fail(with reason: String) throws {
print(reason)
exit(1)
}
mutating func run() throws {
// If help flag was provided, print usage description and exit tool
if help {
print(flags.usageDescription(usageName: TextStyle.bold.properties.apply(to: "usage:"),
synopsis: "[<option> ...] [---] [<program> <arg> ...]",
usageStyle: TextProperties.none,
optionsName: TextStyle.bold.properties.apply(to: "options:"),
flagStyle: TextStyle.italic.properties),
terminator: "")
exit(0)
}
...
// Define how optional messages and errors are printed
func printOpt(_ message: String) {
if !quiet {
print(message)
}
}
...
// Set heap size
virtualMachine.setHeapSize(heapSize)
...
// Register all file paths
for path in filePaths {
virtualMachine.fileHandler.register(path)
}
...
// Load prelude file if it was provided via flag `prelude`
if let file = prelude {
virtualMachine.load(file)
}
}
}
CommandLineKit 提供了一个 TextProperties 结构,用于将文本颜色、背景颜色和文本样式捆绑到一个对象中。文本属性可以使用 with(:)
函数合并,并使用 apply(to:)
函数应用于字符串。
用于 TextColor、BackgroundColor 和 TextStyle 的单独枚举定义了各个属性。
CommandLineKit 包含库 Linenoise-Swift 最初定义的“readline”API 的显着改进版本。它支持 Unicode 文本、多行文本输入和样式化文本。它支持所有现有功能,例如高级键盘支持、历史记录、文本补全和提示。
以下代码说明了 LineReader API 的用法:
if let ln = LineReader() {
ln.setCompletionCallback { currentBuffer in
let completions = [
"Hello!",
"Hello Google",
"Scheme is awesome!"
]
return completions.filter { $0.hasPrefix(currentBuffer) }
}
ln.setHintsCallback { currentBuffer in
let hints = [
"Foo",
"Lorem Ipsum",
"Scheme is awesome!"
]
let filtered = hints.filter { $0.hasPrefix(currentBuffer) }
if let hint = filtered.first {
let hintText = String(hint.dropFirst(currentBuffer.count))
return (hintText, TextColor.grey.properties)
} else {
return nil
}
}
print("Type 'exit' to quit")
var done = false
while !done {
do {
let output = try ln.readLine(prompt: "> ",
maxCount: 200,
strippingNewline: true,
promptProperties: TextProperties(.green, nil, .bold),
readProperties: TextProperties(.blue, nil),
parenProperties: TextProperties(.red, nil, .bold))
print("Entered: \(output)")
ln.addHistory(output)
if output == "exit" {
break
}
} catch LineReaderError.CTRLC {
print("\nCaptured CTRL+C. Quitting.")
done = true
} catch {
print(error)
}
}
}
作者:Matthias Zenger (matthias@objecthub.com)
版权所有 © 2018-2023 Google LLC。
请注意:这不是 Google 的官方产品。