Golang : cobra 包解析
本文内容纲要:
-Command结构体
-执行命令的逻辑
-总是执行根命令的ExecuteC()方法
-解析命令行子命令
-为根命令添加help子命令
-为命令添加helpflag
-输出help信息
-总结
笔者在《Golang:cobra包简介》一文中简要的介绍了cobra包及其基本的用法,本文我们从代码的角度来了解下cobra的核心逻辑。
Command结构体
Command结构体是cobra抽象出来的核心概念,它的实例表示一个命令或者是一个命令的子命令。下面的代码仅展示Command结构体中一些比较重要的字段:
typeCommandstruct{
//用户通过指定Run函数来完成命令
//PreRun和PostRun则允许用户在Run运行的前后时机执行自定义代码
PersistentPreRunfunc(cmd*Command,args[]string)
PreRunfunc(cmd*Command,args[]string)
Runfunc(cmd*Command,args[]string)
PostRunfunc(cmd*Command,args[]string)
PersistentPostRunfunc(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()方法,其逻辑如下:
//ExecuteCexecutesthecommand.
func(c*Command)ExecuteC()(cmd*Command,errerror){
//Regardlessofwhatcommandexecuteiscalledon,runonRootonly
ifc.HasParent(){
returnc.Root().ExecuteC()
}
...
}
解析命令行子命令
ExecuteC()方法中,在执行execute()方法前,需要先通过Find()方法解析命令行上的子命令:
cmd,flags,err=c.Find(args)
比如我们执行下面的命令:
$./myAppimage
解析出的cmd就是image子命令,接下来就是执行image子命令的执行逻辑。
Find()方法的逻辑如下:
$./myApphelpimage
这里的myApp在代码中就是rootCmd,Find()方法中定义了一个名称为innerfind的函数,innerfind从参数中解析出下一个名称,这里是help,然后从rootCmd开始查找解析出的名称help是不是当前命令的子命令,如果help是rootCmd的子命令,继续查找。接下来查找名称image,发现image不是help的子命令,innerfind函数就返回help命令。execute()方法中就执行这个找到的help子命令。
为根命令添加help子命令
在执行ExecuteC()方法时,cobra会为根命令添加一个help子命令,这个子命令主要用来提供子命令的帮助信息。因为任何一个程序都需要提供输出帮助信息的方式,所以cobra就为它实现了一套默认的逻辑。help子命令是通过InitDefaultHelpCmd()方法添加的,其实现代码如下:
//InitDefaultHelpCmdaddsdefaulthelpcommandtoc.
//Itiscalledautomaticallybyexecutingthecorbycallinghelpandusage.
//Ifcalreadyhashelpcommandorchasnosubcommands,itwilldonothing.
func(c*Command)InitDefaultHelpCmd(){
if!c.HasSubCommands(){
return
}
ifc.helpCommand==nil{
c.helpCommand=&Command{
Use:"help[command]",
Short:"Helpaboutanycommand",
Long:`Helpprovideshelpforanycommandintheapplication.
Simplytype`+c.Name()+`help[pathtocommand]forfulldetails.`,
Run:func(c*Command,args[]string){
cmd,_,e:=c.Root().Find(args)
ifcmd==nil||e!=nil{
c.Printf("Unknownhelptopic%#q\n",args)
c.Root().Usage()
}else{
cmd.InitDefaultHelpFlag()//makepossible'help'flagtobeshown
cmd.Help()
}
},
}
}
c.RemoveCommand(c.helpCommand)
c.AddCommand(c.helpCommand)
}
没有找到用户指定的子命令
如果没有找到用户指定的子命令,就输出错误信息,并调用根命令的Usage()方法:
c.Printf("Unknownhelptopic%#q\n",args)
c.Root().Usage()
cobra默认提供的usage模板如下:
`Usage:{{if.Runnable}}
{{.UseLine}}{{end}}{{if.HasAvailableSubCommands}}
{{.CommandPath}}[command]{{end}}{{ifgt(len.Aliases)0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if.HasExample}}
Examples:
{{.Example}}{{end}}{{if.HasAvailableSubCommands}}
AvailableCommands:{{range.Commands}}{{if(or.IsAvailableCommand(eq.Name"help"))}}
{{rpad.Name.NamePadding}}{{.Short}}{{end}}{{end}}{{end}}{{if.HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages|trimTrailingWhitespaces}}{{end}}{{if.HasAvailableInheritedFlags}}
GlobalFlags:
{{.InheritedFlags.FlagUsages|trimTrailingWhitespaces}}{{end}}{{if.HasHelpSubCommands}}
Additionalhelptopics:{{range.Commands}}{{if.IsAdditionalHelpTopicCommand}}
{{rpad.CommandPath.CommandPathPadding}}{{.Short}}{{end}}{{end}}{{end}}{{if.HasAvailableSubCommands}}
Use"{{.CommandPath}}[command]--help"formoreinformationaboutacommand.{{end}}
`
找到了用户指定的子命令
如果找到用户指定的子命令,就为子命令添加默认的helpflag,并执行其Help()方法:
cmd.InitDefaultHelpFlag()//makepossible'help'flagtobeshown
cmd.Help()
为了解释help子命令的执行逻辑,我们举个例子。比如我们通过cobra实现了一个命令行程序myApp,它有一个子命令image,image也有一个子命令times。执行下面的命令:
$./myApphelpimage
在help命令的Run方法中,c为help命令,args为image。结果就是通过help查看image命令的帮助文档。如果image后面还有其他的子命令,比如:
$./myApphelpimagetimes
则c.Root().Find(args)逻辑会找出子命令times(此时args为imagetimes),最终由help查看times命令的帮助文档。
注意:help信息中包含usage信息。
为命令添加helpflag
除了在InitDefaultHelpCmd()方法中会调用InitDefaultHelpFlag()方法,在execute()方法中执行命令逻辑前也会调用InitDefaultHelpFlag()方法为命令添加默认的helpflag,
c.InitDefaultHelpFlag()
下面是InitDefaultHelpFlag()方法的实现:
//InitDefaultHelpFlagaddsdefaulthelpflagtoc.
//Itiscalledautomaticallybyexecutingthecorbycallinghelpandusage.
//Ifcalreadyhashelpflag,itwilldonothing.
func(c*Command)InitDefaultHelpFlag(){
c.mergePersistentFlags()
ifc.Flags().Lookup("help")==nil{
usage:="helpfor"
ifc.Name()==""{
usage+="thiscommand"
}else{
usage+=c.Name()
}
c.Flags().BoolP("help","h",false,usage)
}
}
这让我们不必为命令添加helpflag就可以直接使用!至于falg的解析,则是通过pflag包实现的,不了解pflag包的朋友可以参考《Golang:pflag包简介》。
输出help信息
不管是help命令还是helpfalg,最后都是通过HelpFunc()方法来获得输出help信息的逻辑:
//HelpFuncreturnseitherthefunctionsetbySetHelpFuncforthiscommand
//oraparent,oritreturnsafunctionwithdefaulthelpbehavior.
func(c*Command)HelpFunc()func(*Command,[]string){
ifc.helpFunc!=nil{
returnc.helpFunc
}
ifc.HasParent(){
returnc.Parent().HelpFunc()
}
returnfunc(c*Command,a[]string){
c.mergePersistentFlags()
err:=tmpl(c.OutOrStdout(),c.HelpTemplate(),c)
iferr!=nil{
c.Println(err)
}
}
}
如果我们没有指定自定义的逻辑,就找父命令的,再没有就用cobra的默认逻辑。cobra默认设置的帮助模板如下(包含usage):
`{{with(or.Long.Short)}}{{.|trimTrailingWhitespaces}}
{{end}}{{ifor.Runnable.HasSubCommands}}{{.UsageString}}{{end}}`
总结
本文简要介绍了cobra包的主要逻辑,虽然忽略了众多的实现细节,但梳理出了程序执行的主要过程,并对help子命令的实现以及helpflag的实现进行了介绍。希望对大家了解和使用cobra包有所帮助。
参考:
spf13/cobra
Golang之使用Cobra
MAKEYOUROWNCLIWITHGOLANGANDCOBRA
Cobra简介
golang命令行库cobra的使用
本文内容总结:Command结构体,执行命令的逻辑,总是执行根命令的ExecuteC()方法,解析命令行子命令,为根命令添加help子命令,为命令添加helpflag,输出help信息,总结,
原文链接:https://www.cnblogs.com/sparkdev/p/10868873.html