笔者在《Golang:cobra包简介》一文中简要的介绍了cobra包及其基本的用法,本文我们从代码的角度来了解下cobra的核心逻辑。
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.goc.Run()方法即用户为命令(Command)设置的执行逻辑。
为了确保命令行上的子命令、位置参数和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子命令。
在执行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()//makepossiblehelpflagtobeshown 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()//makepossiblehelpflagtobeshown 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信息。除了在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命令还是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