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

本文在上一文章《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 帮助信息不对齐的小问题。