cmdr 03 - 用流式接口定义命令行参数处理选项

基于 v0.2.17

转眼已经来到了 cmdr v0.2.17 了,为了解决此前版本中关于子命令和选项定义语句的太多嵌套的问题,我们实现了流式调用接口(Fluent APIs)。

cmdr 是我发布的一个开源的 golang 命令行参数处理器。它是 golang flags 的替代品。之所以发布它,是因为已有的 command line UI 三方包无法满足我的日常要求,迫不得己自己造一个。如果尚未有了解 cmdr 怎么使用的,不妨抽空浏览我的早前文章,以求获得一些基本概念:

稍后我会继续针对 cmdr 的用法做介绍文章。

至于本文呢 ,只是简单讲述一下如何使用 cmdr 的流式接口(Fluent API)来完成定义。

定义 RootCommand

root := cmdr.Root("aa", "1.0.1").Header("aa - test for cmdr - no version")
rootCmd = root.RootCommand()

第二句是拿到一个 *cmdr.Command 指针,稍后可以做一下相关的其它操作。

此外,rootCmd 作为函数返回值,也便于被用到向 cmdr.Exec() 做传递参数。

func buildCmds() *cmdr.Command {
    root := ...
    rootCmdr = root.RootCommand()
    ...
    return rootCmdr
}

func main() {
    if err := cmdr.Exec(buildCmds()); err != nil {
        logrus.Fatal(err)
    }
}

定义命令 Command

顶级的命令其实就是 RootCommand 的 子命令,所以:

    co := root.NewSubCommand().
        Titles("ms", "micro-service").
        Description("", "").
        Group("")

在这里,你可以做的定义基本上和 cmdr.Command 结构定义是相匹配的,所以你可以使用 OptCmd接口所支持的方法来完成一条命令的定义:

// OptCmd to support fluent api of cmdr.
// see also: cmdr.Root().NewSubCommand()/.NewFlag()
OptCmd interface {
    Titles(short, long string, aliases ...string) (opt OptCmd)
    Short(short string) (opt OptCmd)
    Long(long string) (opt OptCmd)
    Aliases(ss ...string) (opt OptCmd)
    Description(oneLine, long string) (opt OptCmd)
    Examples(examples string) (opt OptCmd)
    Group(group string) (opt OptCmd)
    Hidden(hidden bool) (opt OptCmd)
    Deprecated(deprecation string) (opt OptCmd)
    Action(action func(cmd *Command, args []string) (err error)) (opt OptCmd)

    // FlagAdd(flg *Flag) (opt OptCmd)
    // SubCommand(cmd *Command) (opt OptCmd)
    PreAction(pre func(cmd *Command, args []string) (err error)) (opt OptCmd)
    PostAction(post func(cmd *Command, args []string)) (opt OptCmd)
    TailPlaceholder(placeholder string) (opt OptCmd)

    NewFlag(typ OptFlagType) (opt OptFlag)
    NewSubCommand() (opt OptCmd)

    OwnerCommand() (opt OptCmd)
    SetOwner(opt OptCmd)

    RootCommand() *RootCommand
}

定义选项 Flag

对于每条命令,你都可以为其附着一系列的选项,这是通过 NewFlag 来完成的:

    co.NewFlag(cmdr.OptFlagTypeUint).
        Titles("t", "retry").
        Description("", "").
        Group("").
        DefaultValue(3, "RETRY")

类似的,所有 OptFlag 接口支持的方法都可以用在这里:

// OptFlag to support fluent api of cmdr.
// see also: cmdr.Root().NewSubCommand()/.NewFlag()
OptFlag interface {
    Titles(short, long string, aliases ...string) (opt OptFlag)
    Short(short string) (opt OptFlag)
    Long(long string) (opt OptFlag)
    Aliases(ss ...string) (opt OptFlag)
    Description(oneLine, long string) (opt OptFlag)
    Examples(examples string) (opt OptFlag)
    Group(group string) (opt OptFlag)
    Hidden(hidden bool) (opt OptFlag)
    Deprecated(deprecation string) (opt OptFlag)
    Action(action func(cmd *Command, args []string) (err error)) (opt OptFlag)

    ToggleGroup(group string) (opt OptFlag)
    DefaultValue(val interface{}, placeholder string) (opt OptFlag)
    ExternalTool(envKeyName string) (opt OptFlag)

    OwnerCommand() (opt OptCmd)
    SetOwner(opt OptCmd)

    RootCommand() *RootCommand
}

重复以上步骤

按照递归的定义方案,反复重复,你就可以得到一套完整的命令行界面定义了。

我得承认,这个方式避免了传统方式的结构嵌套问题,可读性上是要好很多的了。但它的问题也很明显,你需要在程序启动时额外消耗一点点 CPU 来完成上述定义指令的执行,相比而言,这比传统方式略微费事了 a little bit。但我还要承认,这个消耗,人是感受不出来的。

小结

流式接口并未带来任何新鲜东西。它只是改善了定义 Command Line UI 的友善性。

cmdr 同时支持两种方式以支持你的命令行参数定义。

版本计划和规划

v0.2.17:在这个版本中,我们计划做一系列 gocov 自测工作,以便扫荡此前功能性推进过程中的潜在隐患。在某些临界条件满足的场景下,cmdr 也许会工作的不令人满意,因此是时候自检一下下了。

新的版本很快就会发布以覆盖 v0.2.17 的一系列子版本。

总的来说,如无意外,我们遵循古老的传统,奇数版本号代码着 stable 发布。如果有,偶数版本属于临时性的、又或是试验性的发布。

更多情况下,我们会在奇数版本号上加以后缀以完成线上测试,例如 v0.2.17-rel01。它们往往是为了配合开源 CI/CD 而产生的。

如果我们有实验性的想法,那么通常会在 devel 的基础上展开特殊分支来进行测试。

参考


hedzr
95 声望19 粉丝