用Swift编写结构化CLI

Commander是一个Swift框架,用于通过与Swift标准库协议Decodable&Decoder集成来解码命令行参数。 Commander可以通过声明command的结构和该命令的options而无需编写任何代码来解析cli参数,从而帮助您编写结构化的cli程序。 使用Commander,您只需要专注于编写命令的options结构,其余工作将由Commander自动处理。

  • 结构化CLI,命令和选项均通过structclass声明进行struct
  • 通过实现可Decodable协议,选项类型是类型安全的。
  • 自动为commandercommand生成帮助消息。
  • 支持Shell补全。 支持Bash / zsh自动完成脚本。
  • Swift 4兼容性。
  • 零依赖和纯Swift。
  • 支持Linux和swift build
  • Mac OS X 10.10 + / Ubuntu 14.10
  • Xcode 10
  • 斯威夫特4.2
  // swift-tools-version:4.2 
依赖项:[
.package(URL:“ https://github.com/devedbox/Commander.git”,“ 0.5.6 .. <100.0.0”)
]

 指挥官命令--key值--key1 = value1 
指挥官命令--bool
指挥官命令-k值-K = value1
指挥官命令-z = value#{“ z”:“ value”}
指挥官命令-z#{“ z”:true}
指挥官命令-zop#{“ z”:true,“ o”:true,“ p”:true}
 指挥官命令--array val1,val2,val3 
指挥官命令-a val1,val2,val3
指挥官命令--dict key1 = val1,key2 = val2,key3 = val3
指挥官命令-d key1 = val1,key2 = val2,key3 = val3
指挥官命令--array val1 --array val2 --array val3
指挥官命令-a val1 -a val2 -a val3
指挥官命令--dict key1 = val1 --dict key2 = val2 --dict key3 = val3
指挥官命令-d key1 = val1 -d key2 = val2 -d key3 = val3

在Commander中,参数的位置不是固定的,可以在任意位置,但参数必须是连续的:

  commander command args ... --options#在选项之前 
Commander命令--options args ...
commander command --options args ... --options#选项之间
指挥官命令arg0 ... --options arg1 ... --options#错误

使用--标记选项的结尾和参数的开始,但是,这在Commander中通常是可选的:

commander command --options -- args...

众所周知, CommandLine.arguments所有参数都是String类型,在Commander中,可用的值类型为:

  • 布尔: commander command --verbose
  • Int(8,16,32,64 …): commander command --int 100
  • 字符串: commander command --string "this is a string value"
  • 数组: commander command --array val1,val2,val3
  • 词典: commander command --dict key1=val1,key2=val2,key3=val3

数组对象由字符分隔,而Dict对象由字符=,分隔。


Commander支持主命令程序以及该命令程序的命令,并且每个命令都有其自己的子命令和选项。

使用Commander很简单,您只需要声明commandsCommander().dispatch() usage ,然后调用Commander().dispatch() ,Commander会自动对命令行参数进行解码,并将解码后的选项分派给指定的特定命令命令行。

就像下面这样简单:

  import CommanderBuiltIn.Commander.commands = [ 
SampleCommand.self,
NoArgsCommand.self
]
BuiltIn.Commander.usage =“'Commander'的示例用法命令”
BuiltIn.Commander()。dispatch()

在Commander中,命令是符合协议CommandRepresentable的类型( classstruct )。 协议CommandRepresentable声明符合要求的命令的信息:

  • Options :命令Options的相关类型。
  • symbol :命令行外壳使用的命令符号。
  • usage :该命令的用法帮助消息。
  • children :该命令的子命令。
 公共结构您好:CommandRepresentable { 
公共结构选项:OptionsRepresentable {
公共枚举CodingKeys:字符串,CodingKeysRepresentable {
大小写
}

公共静态让描述:[SampleCommand.Options.CodingKeys:OptionDescription] = [
.verbose:.usage(“打印命令的日志”),
]

公共变量详细:Bool = false
}

公共静态让符号:字符串=“样本”
公共静态let用法:String =“显示指挥官的示例用法”

public static func main(_ options:Options)抛出{
如果options.verbose {
打印(options.argiments.first ??“”)
}
}
}

一旦创建了命令,就可以针对通常从CommandLine.arguments取而代之的参数列表,通过放下命令本身的符号来使它无动于衷。

  let arguments = [“ sample”,“ --verbose”,“ Hello world”] 
Command.dispatch(with:arguments.dropFirst())
// 你好,世界

作为真正的命令调度,您无需手动调度命令,调度程序将由Commander自动处理。

在Commander中添加子命令是通过声明[CommandDescribable.Type]类型的子命令:

 公共结构您好:CommandRepresentable { 
...
公共静态让子级:[CommandDescribable.Type] = [
子命令1.self,
子命令2.self
]
...
}

Options与命令相同,是一个符合协议OptionsRepresentable的类型( classstruct ),该协议从Decodable继承并可以视为简单数据模型,将由Commander中的内置代码类型OptionsDecoder进行解码。

如前面在创建命令中所述 ,声明选项类型非常容易,只是另一个数据模型代表了命令行参数中的原始字符串:

 公共结构选项:OptionsRepresentable { 
公共枚举CodingKeys:字符串,CodingKeysRepresentable {
大小写
} public static let描述:[SampleCommand.Options.CodingKeys:OptionDescription] = [
.verbose:.usage(“打印命令的日志”),
] public var verbose:Bool = false
}

正如声明为public var verbose: Bool ,我们可以在命令行中使用--verbose相应地使用符号,但是如何在命令行中使用另一个不同的符号来包裹--is-verbose这样--is-verbose呢? 在Commander中,我们可以这样做:

 公共枚举CodingKeys:字符串,CodingKeysRepresentable { 
case verbose =“ is-verbose”
}

有时在开发命令行工具中,使用-v类的模式是必要且有用的。 在Commander中,提供选项的短键很容易,我们只需要在Options.keys声明[CodingKeys: Character]类型的键-值对:

 公共结构选项:OptionsRepresentable { 
...
公共静态让密钥:[CodingKeys:Character] = [
.verbose:“ v”
]
...
}

当我们在命令中定义标志选项时,需要提供标志的默认值,因为如果我们错过了在命令行中键入标志的情况,则该标志的值表示false 。 通过在Options.descritions添加声明,可以在Commander中提供默认值,如下所示:

 公共结构选项:OptionsRepresentable { 
...
公共静态让描述:[SampleCommand.Options.CodingKeys:OptionDescription] = [
.verbose:.default(值:false,用法:“打印命令的日志”)
]
...
}

在Commander中,由CommandDescriber生成的帮助菜单会自动描述符合CommandDescribable类型,包括Commander本身和所有已声明的命令。

要提供帮助菜单用法,请在命令中:

 公共结构您好:CommandRepresentable { 
...
公共静态让符号:字符串=“样本”
公共静态let用法:String =“显示指挥官的示例用法”
...
}

在选项中:

 公共结构选项:OptionsRepresentable { 
...
公共静态让描述:[SampleCommand.Options.CodingKeys:OptionDescription] = [
.verbose:.default(值:false,用法:“打印命令的日志”)
]
...
}

通常,可以通过OptionsDescriotion类型提供帮助用法消息和默认值。

声明用法后,在终端中运行help

  commander-sample --help#或commander-sample帮助 
#用法:

#$ commander-sample [命令] [选项]

#'Commander'的示例用法命令

#命令:

#help打印命令的帮助消息。 用法:[帮助[命令]]
#sample显示指挥官的用法示例
#set-args使用给定参数设置命令的参数

#选项:

#-h,--help打印命令的帮助消息。 用法:[[---- help | -h] [COMMAND --help] [COMMAND -h]]

对于特定命令,请按以下方式运行:

  commander-sample help sample#或commander-sample sample --help 
#'sample'的用法:

#$ commander-sample sample [SUBCOMMAND] [OPTIONS] [ARGUMENTS]

#显示指挥官的用法示例

#子命令:

#set-args使用给定参数设置命令的参数

#选项:

#-s,--string-value将String的值传递给命令
#-h,--help打印命令的帮助消息。 用法:[[---- help | -h] [COMMAND --help] [COMMAND -h]]
#-v,--verbose打印命令的日志

#参数:

#[字符串] commander-sample示例[选项] arg1 arg2 ...

在Commander中,选项可以将命令行参数中的多个参数用作该选项的参数,并且可以通过调用options.arguments进行访问。 默认情况下,参数解码无法解析,如果要解析参数解码,则必须声明以下选项的ArgumentsResolver

 公共结构选项:OptionsRepresentable { 
...
公共类型别名ArgumentsResolver = AnyArgumentsResolver
...
}

类型AnyArgumentsResolver是通用类型,其中类型T表示参数元素的类型。 使用上面的声明,我们可以在命令行中执行此操作:

 指挥官你好--verbose-“ Hello world”“将被丢弃” 
#“ Hello world”“将被丢弃”都是Hello.Options的参数

Commander提供了在bash / zsh中编写自动完成功能的api,该要求在协议ShellCompletable声明。 默认情况下, CommandDescribableOptionsDescribable继承自ShellCompletable

要实现自动完成,您只需要编写:

 导入Commander.Utility 
//选项:
公共静态函数补全(对于commandLine:Utility.CommandLine)-> [String] {
切换键{
情况“ --string-value”:
返回[
“ a”,“ b”,“ c”
]
默认:
返回[]
}
}

在终端中,键入:

 指挥官样本--string-value  
#a b c

Commander可以为您生成自动完成脚本,您可以运行内置命令complete generate以根据Shell类型生成脚本。 当前可用的shell有:

  • 重击
  • sh

重击

  • 在终端中运行: commander complete generate --shell=bash > ./bash_completion
  • 然后: source ./bash_completion
  • 或者,将脚本安装到bash的登录脚本中,直到~/.profile

sh

  • 在终端中运行: commander complete generate --shell=zsh > ~/zsh_completions/_commander
  • 将内容添加到~/.zshrc

fpath =(〜/ zsh_completions $ fpath)自动加载-U + X compinit && compinit自动加载-U + X bashcompinit && bashcompinit

  • 重新启动终端

CommandDescribable已经提供了默认的完成实现,默认情况下,CommandDescribable提供了子命令以及选项作为Shell的完成,您可以覆盖默认实现以将自定义完成提供给Commander。

默认情况下, OptionsDescribable返回一个空的补全,在调用CommandDescribable的过程中会自动调用OptionsDescribable,您必须重写OptionsDescribable的实现以提供补全,否则将使用一个空的补全。

这是向shell提供git branchs完成的示例:

 导入Commander.Utilitypublic静态函数完成(对于commandLine:Utility.CommandLine)-> [String] { 
让当前= commandLine.arguments.last
让last = commandLine.arguments.dropLast()。last切换当前{
默认:
让输出= ShellIn(“ git branch -r”)。execute()。output.flatMap {
字符串(数据:$ 0,编码:.utf8)
} ?? “”返回outputs.split(whereSeparator:{
“ *-> \ n”。包含($ 0)
})。map {
如果$ 0.hasPrefix(“ origin /”){
return String(String($ 0)[“ origin /”。endIndex ...])
}其他{
返回字符串($ 0)
}
}
}
}

使用Commander,可以按以下方式定义命令及其相关选项:

  import Commanderpublic struct SampleCommand:CommandRepresentable { 
公共结构选项:OptionsRepresentable {
公共类型别名ArgumentsResolver = AnyArgumentsResolver
公共枚举CodingKeys:字符串,CodingKeysRepresentable {
case verbose =“详细”
case stringValue =“字符串值”
}公共静态让密钥:[Options.CodingKeys:Character] = [
.verbose:“ v”,
.stringValue:“ s”
] public static let描述:[Options.CodingKeys:OptionDescription] = [
.verbose:.usage(“打印命令的日志”),
.stringValue:.usage(“将String的值传递给命令”)
] public var verbose:Bool = false
public var stringValue:字符串=“”
} public static let符号:String =“ sample”
公共静态let用法:字符串=“显示指挥官的示例用法”公共静态函数main(_ options:Options)抛出{
打印(选项)
print(“ arguments:\(options.arguments)”)
打印(“ \ n \ n \(Options.CodingKeys.stringValue.stringValue)”)
}
}

然后,配置可用命令将如下所示:

  import CommanderCommander.commands = [ 
SampleCommand.self,
NoArgsCommand.self
]
Commander.usage =“'Commander'的示例用法命令”
Commander()。dispatch()//调用以调度并运行命令

之后,可以通过声明ArgumentsResolver来解析ArgumentsResolver

  public typealias ArgumentsResolver = AnyArgumentsResolver  // T必须是可解码的 

您可以通过以下方式获取参数:

  public static func main(_ options:Options)抛出{ 
print(“ arguments:\(options.arguments)”)//'arguments'在OptionsRepresentable中声明
}

从外壳:

  commander-sample sample --verbose --string-value字符串arg1 arg2 

#选项(详细:true,字符串值:“字符串”)
#个参数:[“ arg1”,“ arg2”]


# 字符串值

简单又有趣!!!

Commander是根据MIT许可发布的。