笔者在《Golang : cobra 包简介 <https://www.cnblogs.com/sparkdev/p/10856077.html>
》一文中简要的介绍了 cobra 包及其基本的用法,本文我们从代码的角度来了解下 cobra 的核心逻辑。

Command 结构体

Command 结构体是 cobra 抽象出来的核心概念,它的实例表示一个命令或者是一个命令的子命令。下面的代码仅展示 Command
结构体中一些比较重要的字段:
type Command struct { // 用户通过指定 Run 函数来完成命令 // PreRun 和 PostRun 则允许用户在 Run
运行的前后时机执行自定义代码 PersistentPreRun func(cmd *Command, args []string) PreRun
func(cmd*Command, args []string) Run func(cmd *Command, args []string) PostRun
func(cmd*Command, args []string) PersistentPostRun func(cmd *Command, args []
string) // commands 字段包含了该命令的所有子命令 commands []*Command // parent 字段记录了该命令的父命令
parent *Command // 该命令的 help 子命令 helpCommand *Command ... }
执行命令的逻辑

cobra 包启动程序执行的代码一般为:
cmd.Execute()
Execute() 函数会调用我们定义的 rootCmd(Command 的一个实例)的 Execute() 方法。
在 Command 的 Execute() 方法中又调用了 Command 的 ExecuteC()
方法,我们可以通过下面的调用堆栈看到执行命令逻辑的调用过程:
cmd.Execute() -> // main.go rootCmd.Execute() -> // root.go c.ExecuteC() -> //
command.go cmd.execute(flags) -> // command.go c.Run() // command.go
c.Run() 方法即用户为命令(Command) 设置的执行逻辑。

总是执行根命令的 ExecuteC() 方法

为了确保命令行上的子命令、位置参数和 Flags 能够被准确的解析,cobra 总是执行根命令的 ExecuteC() 方法,其实现为在
ExecuteC() 方法中找到根命令,然后执行根命令的 ExecuteC() 方法,其逻辑如下:
// ExecuteC executes the command. func (c *Command) ExecuteC() (cmd *Command,
err error) {// Regardless of what command execute is called on, run on Root only
if c.HasParent() { return c.Root().ExecuteC() } ... }
解析命令行子命令

ExecuteC() 方法中,在执行 execute() 方法前,需要先通过 Find() 方法解析命令行上的子命令:
cmd, flags, err = c.Find(args)
比如我们执行下面的命令:
$ ./myApp image
解析出的 cmd 就是 image 子命令,接下来就是执行 image 子命令的执行逻辑。

Find() 方法的逻辑如下:
$ ./myApp help image
这里的 myApp 在代码中就是 rootCmd,Find() 方法中定义了一个名称为 innerfind 的函数,innerfind
从参数中解析出下一个名称,这里是 help,然后从 rootCmd 开始查找解析出的名称 help 是不是当前命令的子命令,如果 help 是 rootCmd
的子命令,继续查找。接下来查找名称 image,发现 image 不是 help 的子命令,innerfind 函数就返回 help 命令。execute()
方法中就执行这个找到的 help 子命令。

为根命令添加 help 子命令

在执行 ExecuteC() 方法时,cobra 会为根命令添加一个 help
子命令,这个子命令主要用来提供子命令的帮助信息。因为任何一个程序都需要提供输出帮助信息的方式,所以 cobra 就为它实现了一套默认的逻辑。help
子命令是通过 InitDefaultHelpCmd() 方法添加的,其实现代码如下:
// InitDefaultHelpCmd adds default help command to c. // It is called
automatically by executing the c or by calling help and usage.// If c already
has help command or c has no subcommands, it will do nothing. func (c *Command)
InitDefaultHelpCmd() {if !c.HasSubCommands() { return } if c.helpCommand == nil
{ c.helpCommand= &Command{ Use: "help [command]", Short: "Help about any command
", Long: `Help provides help for any command in the application. Simply type `
+ c.Name() + ` help [path to command]for full details.`, Run: func(c *Command,
args []string) { cmd, _, e := c.Root().Find(args) if cmd == nil || e != nil {
c.Printf("Unknown help topic %#q\n", args) c.Root().Usage() } else {
cmd.InitDefaultHelpFlag()// make possible 'help' flag to be shown cmd.Help() }
}, } } c.RemoveCommand(c.helpCommand) c.AddCommand(c.helpCommand) }
没有找到用户指定的子命令
如果没有找到用户指定的子命令,就输出错误信息,并调用根命令的 Usage() 方法:
c.Printf("Unknown help topic %#q\n", args) c.Root().Usage()
cobra 默认提供的 usage 模板如下:
`Usage:{{if .Runnable}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}} Examples: {{.Example}}{{end}}{{if
.HasAvailableSubCommands}} Available Commands:{{range .Commands}}{{if (or
.IsAvailableCommand (eq .Name"help"))}} {{rpad .Name .NamePadding }}
{{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags:
{{.LocalFlags.FlagUsages| trimTrailingWhitespaces}}{{end}}{{if
.HasAvailableInheritedFlags}} Global Flags: {{.InheritedFlags.FlagUsages|
trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} Additional help
topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} {{rpad
.CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if
.HasAvailableSubCommands}} Use"{{.CommandPath}} [command] --help" for more
information about a command.{{end}} `
找到了用户指定的子命令
如果找到用户指定的子命令,就为子命令添加默认的 help flag,并执行其 Help() 方法:
cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown cmd.Help()
为了解释 help 子命令的执行逻辑,我们举个例子。比如我们通过 cobra 实现了一个命令行程序 myApp,它有一个子命令 image,image
也有一个子命令 times。执行下面的命令:
$ ./myApp help image
在 help 命令的 Run 方法中,c 为 help 命令, args 为 image。结果就是通过 help 查看 image 命令的帮助文档。如果
image 后面还有其他的子命令,比如:
$ ./myApp help image times
则 c.Root().Find(args) 逻辑会找出子命令 times(此时 args 为 image times),最终由 help 查看 times
命令的帮助文档。
注意:help 信息中包含 usage 信息。

为命令添加 help flag

除了在 InitDefaultHelpCmd() 方法中会调用 InitDefaultHelpFlag() 方法,在 execute()
方法中执行命令逻辑前也会调用  InitDefaultHelpFlag() 方法为命令添加默认的 help flag,
c.InitDefaultHelpFlag()
下面是 InitDefaultHelpFlag() 方法的实现:
// InitDefaultHelpFlag adds default help flag to c. // It is called
automatically by executing the c or by calling help and usage.// If c already
has help flag, it will do nothing. func (c *Command) InitDefaultHelpFlag() {
c.mergePersistentFlags()if c.Flags().Lookup("help") == nil { usage := "help for
" if c.Name() == "" { usage += "this command" } else { usage += c.Name() }
c.Flags().BoolP("help", "h", false, usage) } }
这让我们不必为命令添加 help flag 就可以直接使用!至于 falg 的解析,则是通过 pflag 包实现的,不了解 pflag 包的朋友可以参考《
Golang : pflag 包简介 <https://www.cnblogs.com/sparkdev/p/10833186.html>》。

输出 help 信息

不管是 help 命令还是 help falg,最后都是通过 HelpFunc() 方法来获得输出 help 信息的逻辑:
// HelpFunc returns either the function set by SetHelpFunc for this command //
or a parent, or it returns a function with default help behavior. func (c
*Command) HelpFunc() func(*Command, []string) { if c.helpFunc != nil { return
c.helpFunc }if c.HasParent() { return c.Parent().HelpFunc() } return func(c
*Command, a []string) { c.mergePersistentFlags() err := tmpl(c.OutOrStdout(),
c.HelpTemplate(), c)if err != nil { c.Println(err) } } }
如果我们没有指定自定义的逻辑,就找父命令的,再没有就用 cobra 的默认逻辑。cobra 默认设置的帮助模板如下(包含 usage):
`{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}} {{end}}{{if or
.Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
总结

本文简要介绍了 cobra 包的主要逻辑,虽然忽略了众多的实现细节,但梳理出了程序执行的主要过程,并对 help 子命令的实现以及 help flag
的实现进行了介绍。希望对大家了解和使用 cobra 包有所帮助。

参考:
spf13/cobra <https://github.com/spf13/cobra>
Golang之使用Cobra <https://o-my-chenjian.com/2017/09/20/Using-Cobra-With-Golang/>
MAKE YOUR OWN CLI WITH GOLANG AND COBRA
<https://ordina-jworks.github.io/development/2018/10/20/make-your-own-cli-with-golang-and-cobra.html>
Cobra简介 <https://niyanchun.com/cobra-brief-introduction.html>
golang命令行库cobra的使用
<http://www.cnblogs.com/borey/p/5715641.html?hmsr=studygolang.com&utm_medium=studygolang.com&utm_source=studygolang.com>

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信