Golang : cobra 包解析
笔者在《Golang : cobra 包简介》一文中简要的介绍了 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) }}
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 包简介》。
输出 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
Golang之使用Cobra
MAKE YOUR OWN CLI WITH GOLANG AND COBRA
Cobra简介
golang命令行库cobra的使用
Golang : cobra 包解析的更多相关文章
- Golang : cobra 包简介
Cobra 是一个 Golang 包,它提供了简单的接口来创建命令行程序.同时,Cobra 也是一个应用程序,用来生成应用框架,从而开发以 Cobra 为基础的应用.本文的演示环境为 ubuntu 1 ...
- golang reflect包使用解析
golang reflect包使用解析 参考 Go反射编码 2个重要的类型 Type Value 其中Type是interface类型,Value是struct类型,意识到这一点很重要 Type和Va ...
- Golang : pflag 包简介
笔者在前文中介绍了 Golang 标准库中 flag 包的用法,事实上有一个第三方的命令行参数解析包 pflag 比 flag 包使用的更为广泛.pflag 包的设计目的就是替代标准库中的 flag ...
- java jar包解析:打包文件,引入文件
java jar包解析:打包文件,引入文件 cmd下: jar命令:package包打包 javac命令:普通类文件打包 Hello.java: package org.lxh.demo; publi ...
- Android做法说明(3)---Fragment使用app袋或v4包解析
Android做法说明(3)---Fragment使用app袋或v4包解析 1)问题简述 相信非常多的朋友在调用Fragment都会遇到以下的情况: watermark/2/text/aHR0cDov ...
- Spring (3.2.4) 常用jar 包解析
Spring (3.2.4) 常用jar 包解析 基本jar包 spring-aop-3.2.4.RELEASE.jar spring-aspects-3.2.4.RELEASE.jar spring ...
- Golang fmt包使用小技巧
h1 { margin-top: 0.6cm; margin-bottom: 0.58cm; direction: ltr; color: #000000; line-height: 200%; te ...
- Golang Vendor 包机制 及 注意事项
现在的 Go 版本是 1.8,早在 1.5 时期,就有了 Vendor 包机制,详情可查看博文:“理解 Go 1.5 vendor”. 遇到的问题 个人在使用 Glide 管理 Vendor 包时(附 ...
- Golang Vendor 包管理工具 glide 使用教程
Glide 是 Golang 的 Vendor 包管理器,方便你管理 vendor 和 verdor 包.类似 Java 的 Maven,PHP 的 Composer. Github:https:// ...
随机推荐
- 在元素标签中写简单的js函数
如: <img id="verifyImg" class="passcode" src="__URL__/verify_c" onCl ...
- python3操作mysql数据库增删改查
#!/usr/bin/python3 import pymysql import types db=pymysql.connect("localhost","root&q ...
- window操作常识
- 一个用 vue 写的树层级组件 vue-ztree
最近看了大神的关于vue-ztree的博客,感觉很赞,于是摘抄下来,方便自己学习,机智girl,哈哈哈O(∩_∩)O 最近由于后台管理项目的需要,页面需要制作一个无限树的需求,我第一感就想到了插件 z ...
- Oracle学习笔记_03_单行函数
1.SQL函数: 单行函数.多行函数 单行函数: 操作数据对象 接受参数返回一个结果 只对一行进行变换 每行返回一个结果 可以转换数据类型 ...
- Oracle学习笔记_02_基本SQL
1.select语句 (1)语法 SELECT *|{[DISTINCT] column|expression [alias],...} FROM table; (2)示例: 选择全部列 SELECT ...
- 51nod1428 活动安排问题 (贪心加暴力)
1428 活动安排问题 基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题 收藏 关注 有若干个活动,第i个开始时间和结束时间是[Si,fi),同一个教室安排的活动 ...
- 微信非全屏播放设置(仅Iphone)
由于微信X5内核强制视频全屏,用X5自带内核播放,一般内嵌视频打开播放就会被全屏. ihpone里面可以通过设置 x-webkit-airplay="true" webkit-pl ...
- BZOJ4676 Xor-Mul棋盘
传送门 题目大意懒得写了,题目说的挺明白的了 题解 主要的难点在于异或意义下的最大值和很玄学,但不难发现这道题中让你定义的$D_{i,j}$只参与异或运算,所以我们可以逐位进行讨论.所以我们每一位就只 ...
- ACM学习历程—HYSBZ 2818 Gcd(欧拉函数 || 莫比乌斯反演)
Description 给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对. Input 一个整数N Output 如题 Sample Input 4 Sam ...