本文在上一文章《Golang实践录:命令行cobra库实例》 的基础上继续进行优化,主要优化的部分是子命令的业务实现。
起因 旧版本中,每个子命令的入口函数,均需一一判断传入参数,并调用对应的业务实现函数,编码扩展稍有繁琐,而且也不美观。 思考再三,决定使用结构体数组的形式来优化。
思路 此思路来源于 busybox 。
首先定义结构体:
1 2 3 4 5 6 7 // 命令列表,包括名称,帮助信息 type UserCmdFunc struct { Name string ShortHelp string // LongHelp string Func func(args []string) }
再实现遍历命令列表函数:
1 2 3 4 5 6 func PrintHelpInfo(theCmd []conf.UserCmdFunc) { truefmt.Println("valid command: "); truefor _, item:=range theCmd { fmt.Println(item.Name, "\t:", item.ShortHelp) } }
在使用时,只需要定义结构体数组,并填写对应的命令名称,帮助信息,及对应的函数指针即可。示例:
1 2 3 4 5 6 7 8 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 为例,旧版本入口源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func NewCmdTest() *cobra.Command{ true var cmd = &cobra.Command{ Use: name, Short: shortDescription, Long: longDescription, Example: example, RunE: func(cmd *cobra.Command, args []string) error { truetruetrueif (len(args) == 0) { truetruetruetrueklog.Warning("no args found") truetruetruetruereturn nil truetruetrue} // !! 以下要一一判断并调用 if (args[0] == "foo"){ foo(args) } else if (args[0] == "watch"){ testWatch(args) } else { truetruetruetrueklog.Printf("cmd '%v' not support", args[0]) truetruetruetruereturn nil truetruetrue} return nil }, } return cmd }
新版本变化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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 { truetruetrue//klog.Println(common.DBName) truetruetrueif (len(args) == 0) { truetruetruetrueklog.Warning("no args found") truetruetruetruecommon.PrintHelpInfo(theCmd) truetruetruetruereturn nil truetruetrue} // !! 遍历并调用即可 truetruetruefor _, item:=range theCmd { truetruetruetrueif (args[0] == item.Name) { truetruetruetruetrueitem.Func(args) truetruetruetruetruereturn nil truetruetruetrue} truetruetrue} truetruetrueklog.Printf("cmd '%v' not support", args[0]) truetruetruecommon.PrintHelpInfo(theCmd) return nil }, } // note:使用子命令形式,下列可在help中展开 // 命令参数,保存的值,参数名,默认参数,说明 //cmd.Flags().StringVar(&mode, "db", "-", "set the database name") return cmd }
测试 默认输出帮助信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ ./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.
执行子命令,默认将合法的命令输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ ./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 帮助信息不对齐的小问题。