Golang实践录:命令行cobra库实例优化

717 阅读2分钟

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

本文在上一文章《Golang实践录:命令行cobra库实例》 的基础上继续进行优化,主要优化的部分是子命令的业务实现。

起因

旧版本中,每个子命令的入口函数,均需一一判断传入参数,并调用对应的业务实现函数,编码扩展稍有繁琐,而且也不美观。 思考再三,决定使用结构体数组的形式来优化。

思路

此思路来源于 busybox 。

首先定义结构体:

// 命令列表,包括名称,帮助信息
type UserCmdFunc struct {
    Name string
    ShortHelp string
    // LongHelp string
    Func func(args []string)
}

再实现遍历命令列表函数:

func PrintHelpInfo(theCmd []conf.UserCmdFunc) {
    fmt.Println("valid command: ");
    for _, item:=range theCmd {
        fmt.Println(item.Name, "\t:", item.ShortHelp)
    }
}

在使用时,只需要定义结构体数组,并填写对应的命令名称,帮助信息,及对应的函数指针即可。示例:

var theCmd = []conf.UserCmdFunc{
    conf.UserCmdFunc {
        Name: "foo",
        ShortHelp: "just a foo help info",
        Func: foo,
    },
    conf.UserCmdFunc {"watch", "watch config file", testWatch,},
}

当命令不合法——亦即无法在结构体数组中找到时,提示合法的命令,提高体验。 由于各子命令位于不同的包中,实际上 theCmd 及子命令入口函数绝大部分代码是相同的,容易扩展。

实现

以子命令 test 为例,旧版本入口源码如下:

func NewCmdTest() *cobra.Command{
    
    var cmd = &cobra.Command{
        Use:     name,
        Short:   shortDescription,
        Long:    longDescription,
        Example: example,
        RunE: func(cmd *cobra.Command, args []string) error {
            if (len(args) == 0) {
                klog.Warning("no args found")
                return nil
            }
            // !! 以下要一一判断并调用
            if (args[0] == "foo"){
                foo(args)
            } else if (args[0] == "watch"){
                testWatch(args)
            } else {
                klog.Printf("cmd '%v' not support", args[0])
                return nil
            } 
            return nil
        },
    }
​
    return cmd
}

新版本变化如下:

var theCmd = []conf.UserCmdFunc{
    conf.UserCmdFunc {
        Name: "foo",
        ShortHelp: "just a foo help info",
        Func: foo,
    },
    conf.UserCmdFunc {"watch", "watch config file", testWatch,},
}
​
func NewCmdTest() *cobra.Command{
​
    var cmd = &cobra.Command{
        Use:     name,
        Short:   shortDescription,
        Long:    longDescription,
        Example: example,
        RunE: func(cmd *cobra.Command, args []string) error {
            //klog.Println(common.DBName)
            if (len(args) == 0) {
                klog.Warning("no args found")
                common.PrintHelpInfo(theCmd)
                return nil
            }
            // !! 遍历并调用即可
            for _, item:=range theCmd {
                if (args[0] == item.Name) {
                    item.Func(args)
                    return nil
                }
            }
            klog.Printf("cmd '%v' not support", args[0])
            common.PrintHelpInfo(theCmd)
            return nil
        },
    }
    // note:使用子命令形式,下列可在help中展开
    // 命令参数,保存的值,参数名,默认参数,说明
    //cmd.Flags().StringVar(&mode, "db", "-", "set the database name")
​
    return cmd
}

测试

默认输出帮助信息:

$ ./cmdtool.exe
  cmd test tool.
  命令终端测试示例工具。
​
Usage:
  cmdtool.exe [command]
​
Examples:
  comming soon...
​
​
Available Commands:
  db          db command
  help        Help about any command
  misc        misc command
  test        test command
​
Flags:
      --config string   config file (config.yaml)
  -h, --help            help for cmdtool.exe
      --print           will print sth
      --version         version for cmdtool.exe
​
Use "cmdtool.exe [command] --help" for more information about a command.

执行子命令,默认将合法的命令输出:

​
$ ./cmdtool.exe test
[2020-12-02 17:43:40.771 rootCmd.go:112] helloooooo 100s firstblood
[2020-12-02 17:43:40.772 cmd.go:43] no args found
valid command:
foo     : just a foo help info
watch   : watch config file
​
$ ./cmdtool.exe test nocmd
[2020-12-02 17:43:47.953 rootCmd.go:112] helloooooo 100s firstblood
[2020-12-02 17:43:47.954 cmd.go:53] cmd 'nocmd' not support
valid command:
foo     : just a foo help info
watch   : watch config file

源码

源码在此。 本次也修改了 cobra 帮助信息不对齐的小问题。