996.icu

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

目录


特性 - 示例

特性

示例

使用 Commander,可以如下定义命令及其关联的选项

import Commander

public struct SampleCommand: CommandRepresentable {
  public struct Options: OptionsRepresentable {
    public typealias ArgumentsResolver = AnyArgumentsResolver<String>
    public enum CodingKeys: String, CodingKeysRepresentable {
      case verbose = "verbose"
      case stringValue = "string-value"
    }

    public static let keys: [Options.CodingKeys : Character] = [
      .verbose: "v",
      .stringValue: "s"
    ]

    public static let descriptions: [Options.CodingKeys : OptionDescription] = [
      .verbose: .usage("Prints the logs of the command"),
      .stringValue: .usage("Pass a value of String to the command")
    ]

    public var verbose: Bool = false
    public var stringValue: String = ""
  }

  public static let symbol: String = "sample"
  public static let usage: String = "Show sample usage of commander"

  public static func main(_ options: Options) throws {
    print(options)
    print("arguments: \(options.arguments)")
    print("\n\n\(Options.CodingKeys.stringValue.stringValue)")
  }
}

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

import Commander

Commander.commands = [
  SampleCommand.self,
  NoArgsCommand.self
]
Commander.usage = "The sample usage command of 'Commander'"
Commander().dispatch() // Call this to dispatch and run the command

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

public typealias ArgumentsResolver = AnyArgumentsResolver<T> // T must be Decodable

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

public static func main(_ options: Options) throws {
  print("arguments: \(options.arguments)") // 'arguments' is being declared in OptionsRepresentable 
}

最后,从 shell 运行

commander-sample sample --verbose --string-value String arg1 arg2
# 
# Options(verbose: true, stringValue: "String")
# arguments: ["arg1", "arg2"]
#
#
# string-value

它简单又有趣!!!


要求 - 测试覆盖率图 - 安装

要求

测试覆盖率图

安装

使用 SPM

// swift-tools-version:4.2
dependencies: [
  .package(url: "https://github.com/devedbox/Commander.git", "0.5.6..<100.0.0")
]

模式 - 值类型

模式

键值对

单值

commander command --key value --key1=value1
commander command --bool
commander command -k value -K=value1
commander command -z=value # {"z": "value"}
commander command -z # {"z": true}
commander command -zop # {"z": true, "o": true, "p": true}

多值

commander command --array val1,val2,val3
commander command -a val1,val2,val3
commander command --dict key1=val1,key2=val2,key3=val3
commander command -d key1=val1,key2=val2,key3=val3
commander command --array val1 --array val2 --array val3
commander command -a val1 -a val2 -a val3
commander command --dict key1=val1 --dict key2=val2 --dict key3=val3
commander command -d key1=val1 -d key2=val2 -d key3=val3

参数

在 Commander 中,参数的位置是不固定的,它们可以在任何地方,但参数必须是连续的

commander command args... --options                   # before options
commander command --options args...                   # after options
commander command --options args... --options         # between options
commander command arg0... --options arg1... --options # Error

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

commander 命令 --选项 -- args...

值类型

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

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


高级用法 - 许可

用法

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

使用 Commander 很简单,您只需要声明 commander 的 commandsusage,然后调用 Commander().dispatch(),Commander 将自动解码命令行参数并将解码后的选项分发到命令行给定的特定命令。

就像以下一样简单

import Commander

Commander.commands = [
  SampleCommand.self,
  NoArgsCommand.self
]
Commander.usage = "The sample usage command of 'Commander'"
Commander().dispatch()

命令

在 Commander 中,命令是一种类型(classstruct),它符合协议 CommandRepresentableCommandRepresentable 协议声明了符合命令的信息

创建命令

public struct Hello: CommandRepresentable {
  public struct Options: OptionsRepresentable {
    public enum CodingKeys: String, CodingKeysRepresentable {
      case verbose
    }
    
    public static let descriptions: [SampleCommand.Options.CodingKeys : OptionDescription] = [
      .verbose: .usage("Prints the logs of the command"),
    ]
    
    public var verbose: Bool = false
  }
  
  public static let symbol: String = "sample"
  public static let usage: String = "Show sample usage of commander"
  
  public static func main(_ options: Options) throws {
    if options.verbose {
      print(options.argiments.first ?? "")
    }
  }
}

分发命令

一旦创建了命令,就可以针对参数列表进行分发,通常是从 CommandLine.arguments 中获取,并删除命令本身的符号。

let arguments = ["sample", "--verbose", "Hello world"]
Command.dispatch(with: arguments.dropFirst())
// Hello world

作为命令的实际分发,您不需要手动分发命令,分发将由 Commander 自动处理。

添加子命令

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

public struct Hello: CommandRepresentable {
  ...
  public static let children: [CommandDescribable.Type] = [
    Subcommand1.self,
    Subcommand2.self
  ]
  ...
}

选项

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

声明选项

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

public struct Options: OptionsRepresentable {
  public enum CodingKeys: String, CodingKeysRepresentable {
    case verbose
  }

  public static let descriptions: [SampleCommand.Options.CodingKeys : OptionDescription] = [
    .verbose: .usage("Prints the logs of the command"),
  ]

  public var verbose: Bool = false
}

更改选项符号

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

public enum CodingKeys: String, CodingKeysRepresentable {
  case verbose = "is-verbose"
}

提供短键

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

public struct Options: OptionsRepresentable {
  ...
  public static let keys: [CodingKeys: Character] = [
    .verbose: "v"
  ]
  ...
}

提供默认值

当我们在命令中定义一个标志选项时,需要为该标志提供一个默认值,因为如果我们忘记在命令行中输入该标志,则该标志的值意味着 false。 在 Commander 中提供默认值是通过在 Options.descritions 中添加声明来实现的,如下所示

public struct Options: OptionsRepresentable {
  ...
  public static let descriptions: [SampleCommand.Options.CodingKeys : OptionDescription] = [
    .verbose: .default(value: false, usage:"Prints the logs of the command")
  ]
  ...
}

帮助菜单

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

为了提供帮助菜单的用法,在命令中

public struct Hello: CommandRepresentable {
  ...
  public static let symbol: String = "sample"
  public static let usage: String = "Show sample usage of commander"
  ...
}

在选项中

public struct Options: OptionsRepresentable {
  ...
  public static let descriptions: [SampleCommand.Options.CodingKeys : OptionDescription] = [
    .verbose: .default(value: false, usage:"Prints the logs of the command")
  ]
  ...
}

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

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

commander-sample --help # or, commander-sample help
# Usage:
#
#   $ commander-sample [COMMAND] [OPTIONS]
#
#     The sample usage command of 'Commander'
#
#   Commands:
#
#     help        Prints the help message of the command. Usage: [help [COMMANDS]]
#     sample      Show sample usage of commander
#     set-args    Set arguments of the command with given arguments
#
#   Options:
#
#     -h, --help  Prints the help message of the command. Usage: [[--help|-h][COMMAND --help][COMMAND -h]]

对于特定命令,运行如下

commander-sample help sample # or, commander-sample sample --help
# Usage of 'sample':
#
#   $ commander-sample sample [SUBCOMMAND] [OPTIONS] [ARGUMENTS]
#
#     Show sample usage of commander
#
#   Subcommands:
#
#     set-args            Set arguments of the command with given arguments
#
#   Options:
#
#     -s, --string-value  Pass a value of String to the command
#     -h, --help          Prints the help message of the command. Usage: [[--help|-h][COMMAND --help][COMMAND -h]]
#     -v, --verbose       Prints the logs of the command
#
#   Arguments:
#
#     [String]            commander-sample sample [options] arg1 arg2 ...

参数

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

public struct Options: OptionsRepresentable {
  ...
  public typealias ArgumentsResolver = AnyArgumentsResolver<String>
  ...
}

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

commander hello --verbose -- "Hello world" "Will be dropped"
# "Hello world" "Will be dropped" are both the arguments of Hello.Options

补全

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

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

import Commander.Utility
// Options:
public static func completions(for commandLine: Utility.CommandLine) -> [String] {
  switch key {
  case "--string-value":
    return [
      "a", "b", "c"
    ]
  default:
    return [ ]
  }
}

在终端中,输入这个

commander sample --string-value <Tab>
# a	b	c

安装补全脚本

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

Bash

Zsh

编写您自己的补全

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

默认情况下,OptionsDescribable 返回一个空完成,在调用 CommandDescribable 期间会自动调用 OptionsDescribable,您必须覆盖 OptionsDescribable 的实现以提供您的完成,否则将使用空完成。

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

import Commander.Utility

public static func completions(for commandLine: Utility.CommandLine) -> [String] {
  let current = commandLine.arguments.last
  let previous = commandLine.arguments.dropLast().last

  switch current {
  default:
    let outputs = ShellIn("git branch -r").execute().output.flatMap {
      String(data: $0, encoding: .utf8)
    } ?? ""

    return outputs.split(whereSeparator: {
      " *->\n".contains($0)
    }).map {
      if $0.hasPrefix("origin/") {
        return String(String($0)["origin/".endIndex...])
      } else {
        return String($0)
      }
    }
  }
}

许可

Commander 在 MIT 许可下发布。