golang Cobra库 创建CLI应用程序

github.com/spf13/cobra  库 用于创建CLI应用程序
Go项目用Cobra构建的有
Kubernetes
Hugo
rkt
etcd
Moby (former Docker)
Docker (distribution)
OpenShift
Delve
GopherJS
CockroachDB
Bleve
ProjectAtomic (enterprise)
GiantSwarm's swarm
Nanobox/Nanopack
rclone
nehm
Pouch
概述
Cobra 库 创建类似于git&go工具的现代CLI界面
Cobra 生成应用程序支架 开发基于Cobra的应用程序
Cobra提供简单易用的基于CLI:app server app fetch等
符合POSIX标准(包括including short & long versions)
嵌套子命令
全局 本地和级联标志
产生的应用程序和命令与cobra init appname&cobra add cmdname
智能建议
命令和标志的自动生成
自动帮助标志识别-h --help等
为应用程序自动生成bash自动完成
为应用程序自动生成的手册页
命令别名 以便在不破坏它们的情况下更改
灵活定义帮助 用法等
选择与viper紧密集成 适用于12-factor应用
Cobra建立在命令 参数和标志的结构上
Commands 动作
Args 参数
Flags 动作的修饰符
好的应用程序使用时像句子一样易读
用户知道如何用该应用程序 因为原生了解如何使用
遵循模式是 APPNAME VERB NOUN --ADJECTIVE
或 APPNAME COMMAND ARG --FLAG
示例 server 是命令
port 是标志
hugo server --port=1313
命令告诉Git克隆url
git clone URL --bare
Commands命令是应用程序中心点
应用程序支持的每个交互都包含在命令中
命令可有子命令并可选运行
例中 server 是命令
Flags标志是修改命令行为的方法
Cobra支持完全符合POSIX标准的标志及Go 标志包
Cobra命令可定义持久保存到子命令和标志的标志 这些命令和标志仅对该命令可用
例 port 是标志 功能由pflag库提供
pflag库是标准库的分支 在添加POSIX兼容性时保持相同的接口
Installing安装
先用go get安装最新版本的库 安装cobra生成器可执行文件以及库及其依赖项
go get -u github.com/spf13/cobra/cobra
在 应用程序中包含Cobra
import "github.com/spf13/cobra"

Getting Started入门
通常基于Cobra的应用程序将遵循以下组织结构
appName/
cmd/
    add.go
    your.go
    commands.go
    here.go
main.go
Cobra应用程序中main.go是暴露文件
目的初始化Cobra
package main
import (
"{AppPath}/cmd"
)
func main() {
  cmd.Execute()
}
使用Cobra生成器
Cobra的程序 可以创建应用程序并添加任何命令
是将Cobra整合到应用程序中的最简单方法
用Cobra Library 手动实现Cobra需要创建一个暴露的main.go文件和一个rootCmd文件
选择根据需要提供其他命令 创建rootCmd 只需创建命令即可
将其放在app / cmd / root.go中
var rootCmd = &cobra.Command{
Use:   "hugo"
Short: "Hugo is a very fast static site generator"
Long: `A Fast and Flexible Static Site Generator built with    love by spf13 and friends in Go. Complete documentation is available at http://hugo.spf13.com`
Run: func(cmd *cobra.Command  args []string) {// Do Stuff Here
}
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
在 init()函数中定义标志和句柄配置
如cmd / root.go:
import (
"fmt"
"os"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile  "config"  ""  "config file (default is $HOME/.cobra.yaml)")
rootCmd.PersistentFlags().StringVarP(&projectBase  "projectbase"  "b"  ""  "base project directory eg. github.com/spf13/")
rootCmd.PersistentFlags().StringP("author"  "a"  "YOUR NAME"  "Author name for copyright attribution")
rootCmd.PersistentFlags().StringVarP(&userLicense  "license"  "l"  ""  "Name of license for the project (can provide `licensetext` in config)")
rootCmd.PersistentFlags().Bool("viper"  true  "Use Viper for configuration")
viper.BindPFlag("author"  rootCmd.PersistentFlags().Lookup("author"))
viper.BindPFlag("projectbase"  rootCmd.PersistentFlags().Lookup("projectbase"))
viper.BindPFlag("useViper"  rootCmd.PersistentFlags().Lookup("viper"))
viper.SetDefault("author"  "NAME HERE <EMAIL ADDRESS>")
viper.SetDefault("license"  "apache")
}
func initConfig() {
// Don't forget to read config either from cfgFile or from home directory!
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home  err := homedir.Dir()
if err != nil {
  fmt.Println(err)
  os.Exit(1)
}
// Search config in home directory with name ".cobra" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".cobra")
}
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Can't read config:"  err)
os.Exit(1)
}
}
创建main.go 用root命令 要让main函数执行它  在root上运行Execute 尽管可在任何命令上调用它
Cobra应用程序中 在 main.go 暴露
目的是初始化Cobra
package main
import (
"fmt"
"os"
"{AppPath}/cmd"
)

func main() {
cmd.Execute()
}
可定义其他命令 且常在cmd /目录中为每个命令提供自己的文件
如要创建版本命令 可创建cmd / version.go 并用以下内容填充
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use:   "version"
Short: "Print the version number of Hugo"
Long:  `All software has versions. This is Hugo's`
Run: func(cmd *cobra.Command  args []string) {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
}
}
用Flags提供修饰符控制动作命令的操作方式
为命令分配Flags 在不同的位置定义和使用 需要在外部定义一个具有正确范围的变量来分配要使用的标志
var Verbose bool
var Source string
分配标志有两种不同的方法
持久的Flags 持久意味着该标志可用于它所分配的命令及该命令下的每个命令 对于全局标志 根上分配Flag作为持久Flag
rootCmd.PersistentFlags().BoolVarP(&Verbose  "verbose"  "v"  false  "verbose output")
本地Flags仅适用于该特定命令
rootCmd.Flags().StringVarP(&Source  "source"  "s"  ""  "Source directory to read from")
父命令上的本地Flags
默认Cobra仅解析目标命令上的本地Flag 忽略父命令上的任何本地Flag 通过启用Command.TraverseChildrenCobra将在执行目标命令之前解析每个命令上的本地Flag
command := cobra.Command{
Use: "print [OPTIONS] [COMMANDS]"
TraverseChildren: true
}
配置绑定Flags 用viper绑定你的Flags
var author string
func init() {
rootCmd.PersistentFlags().StringVar(&author  "author"  "YOUR NAME"  "Author name for copyright attribution")
viper.BindPFlag("author"  rootCmd.PersistentFlags().Lookup("author"))
}
持久Flags author与之绑定viper  
author当--author用户未提供该标志时 该变量将不会设置为config中的值
必需的Flags
Flags默认可选 如希望命令在未设置Flags时报告错误 请将其标记为必需:
rootCmd.Flags().StringVarP(&Region  "region"  "r"  ""  "AWS region (required)")
rootCmd.MarkFlagRequired("region")
位置和自定义参数
Args字段指定位置参数的验证Command
NoArgs - 如果存在任何位置参数 该命令将报告错误
ArbitraryArgs - 该命令将接受任何args
OnlyValidArgs- 如果存在任何不在ValidArgs字段中的位置参数 该命令将报告错误Command
MinimumNArgs(int) - 如果没有至少N个位置参数 该命令将报告错误
MaximumNArgs(int) - 如果有多于N个位置参数 该命令将报告错误
ExactArgs(int) - 如果没有确切的N位置参数 该命令将报告错误
RangeArgs(min  max) - 如果args的数量不在预期args的最小和最大数量之间 则该命令将报告错误
设置自定义验证器的
var cmd = &cobra.Command{
Short: "hello"
Args: func(cmd *cobra.Command  args []string) error {
if len(args) < 1 {
  return errors.New("requires at least one arg")
}
if myapp.IsValidColor(args[0]) {
  return nil
}
return fmt.Errorf("invalid color specified: %s"  args[0])
}
Run: func(cmd *cobra.Command  args []string) {
fmt.Println("Hello  World!")
}
}
定义三个命令 两个位于顶层 一个cmdTimes是顶级命令之一的子级 在这种情况下 root不可执行
意味着需要子命令 这是通过不为 rootCmd 提供'Run'来实现的
一个命令定义了一个标志 标志的更多文档 //github.com/spf13/pflag
package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)

func main() {
var echoTimes int
var cmdPrint = &cobra.Command{
Use:   "print [string to print]"
Short: "Print anything to the screen"
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`
Args: cobra.MinimumNArgs(1)
Run: func(cmd *cobra.Command  args []string) {
  fmt.Println("Print: " + strings.Join(args  " "))
}
}
var cmdEcho = &cobra.Command{
Use:   "echo [string to echo]"
Short: "Echo anything to the screen"
Long: `echo is for echoing anything back.
Echo works a lot like print  except it has a child command.`
Args: cobra.MinimumNArgs(1)
Run: func(cmd *cobra.Command  args []string) {
  fmt.Println("Print: " + strings.Join(args  " "))
}
}
var cmdTimes = &cobra.Command{
Use:   "times [# times] [string to echo]"
Short: "Echo anything to the screen more times"
Long: `echo things multiple times back to the user by providing
a count and a string.`
Args: cobra.MinimumNArgs(1)
Run: func(cmd *cobra.Command  args []string) {
  for i := 0; i < echoTimes; i++ {
    fmt.Println("Echo: " + strings.Join(args  " "))
  }
}
}
cmdTimes.Flags().IntVarP(&echoTimes  "times"  "t"  1  "times to echo the input")
var rootCmd = &cobra.Command{Use: "app"}
rootCmd.AddCommand(cmdPrint  cmdEcho)
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
有子命令时 Cobra 自动为 应用程序添加一个帮助命令
当用户运行 app help 时调用此方法
帮助还支持所有其他命令作为输入
如有个名为 create 的命令 没有任何额外的配置
当 app help create 被调用时 Cobra会工作 每个命令都会自动添加  - help 标志
由Cobra自动生成 除了命令和标志定义之外 不需要任何其他内容
$ cobra help
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

Usage:
cobra [command]
Available Commands:
add         Add a command to a Cobra Application
help        Help about any command
init        Initialize a Cobra Application
Flags:
-a  --author string    author name for copyright attribution (default "YOUR NAME")
  --config string    config file (default is $HOME/.cobra.yaml)
-h  --help             help for cobra
-l  --license string   name of license for the project
  --viper            use Viper for configuration (default true)
Use "cobra [command] --help" for more information about a command.
帮助就像任何其他命令一样
周围没有特殊的逻辑或行为 事实上 如果你愿意 你可以提供自己的
定义自己的help
可以提供自己的help命令或自己的模板 以使用以下函数使用的默认命令:
cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command  []string))
cmd.SetHelpTemplate(s string)
后两者也适用于任何子命令
用法
当用户提供无效标志或无效命令时 Cobra会通过向用户显示“使用情况”来做出响应
可从上面的帮助中认识到这一点 那是因为默认帮助将用法嵌入其输出中
$ cobra --invalid
Error: unknown flag: --invalid
Usage:
cobra [command]
Available Commands:
add         Add a command to a Cobra Application
help        Help about any command
init        Initialize a Cobra Application
Flags:
-a  --author string    author name for copyright attribution (default "YOUR NAME")
  --config string    config file (default is $HOME/.cobra.yaml)
-h  --help             help for cobra
-l  --license string   name of license for the project
  --viper            use Viper for configuration (default true)

Use "cobra [command] --help" for more information about a command.
定义自己的用法
可提供自己的使用功能或模板供Cobra使用 与help一样 函数和模板可以通过公共方法覆盖:
cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)
版本flags
如在root命令上设置了Version字段 Cobra会添加顶级'--version'标志 使用'--version'标志运行应用程序将使用版本模板将版本打印到stdout 可以使用该cmd.SetVersionTemplate(s string)功能自定义模板  
PreRun和PostRun Hooks
Run命令在主函数之前或之后运行函数 PersistentPreRun和PreRun功能执行之前Run PersistentPostRun和PostRun将在后执行Run Persistent*Run如果子程序没有声明他们自己的功能 他们将继承这些功能 这些功能按以下顺序运行:
PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun
用所有这些功能的两个命令的示例 执行子命令时 它将运行root命令 PersistentPreRun但不运行root命令PersistentPostRun:
package main
import (
"fmt"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{
Use:   "root [sub]"
Short: "My root command"
PersistentPreRun: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n"  args)
}
PreRun: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside rootCmd PreRun with args: %v\n"  args)
}
Run: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside rootCmd Run with args: %v\n"  args)
}
PostRun: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside rootCmd PostRun with args: %v\n"  args)
}
PersistentPostRun: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n"  args)
}
}
var subCmd = &cobra.Command{
Use:   "sub [no options!]"
Short: "My subcommand"
PreRun: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside subCmd PreRun with args: %v\n"  args)
}
Run: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside subCmd Run with args: %v\n"  args)
}
PostRun: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside subCmd PostRun with args: %v\n"  args)
}
PersistentPostRun: func(cmd *cobra.Command  args []string) {
  fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n"  args)
}
}
rootCmd.AddCommand(subCmd)
rootCmd.SetArgs([]string{""})
rootCmd.Execute()
fmt.Println()
rootCmd.SetArgs([]string{"sub"  "arg1"  "arg2"})
rootCmd.Execute()
}
打印
Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Inside rootCmd Run with args: []
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []
Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
Inside subCmd Run with args: [arg1 arg2]
Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2]

未知命令发生时的建议
未知命令错误发生时 Cobra将打印自动建议 这使得Cobra git在发生拼写错误时的行为与命令类似
$ hugo srever
Error: unknown command "srever" for "hugo"
Did you mean this?
    server
Run 'hugo --help' for usage.
据注册的每个子命令自动提出建议 并用Levenshtein距离的实现 每个匹配最小距离为2(忽略大小写)的注册命令将显示为建议

如要在命令中禁用建议或调整字符串距离 请用
command.DisableSuggestions = true
#或
command.SuggestionsMinimumDistance = 1

还可用该SuggestFor属性显式设置要为其指定命令的名称 这允许建议字符串距离不接近的字符串 但命令集和一些不想要别名的字符串中有意义

$ kubectl remove
Error: unknown command "remove" for "kubectl"
Did you mean this?
    delete
Run 'kubectl help' for usage.
Suggestions when "unknown command" happens
当unknow command错误发生时,cobra自动打印建议
cobra拥有和git命令行类似的效果
$ hugo srever
Error: unknown command "srever" for "hugo"
Did you mean this?
        server
Run 'hugo --help' for usage.
建议会基于每个子command的注册自动生成
实现基于 Levenshtein distance
每个注册的command匹配到最小两个字母的时候,就会显示建议

如果你想要取消建议功能,或者调整字符的距离:
command.DisableSuggestions = true
or
command.SuggestionsMinimumDistance = 1
可设置可能会被建议的command的名字,使用SuggestFor属性.这对那些字符串距离不太近的单词可以起到效果,但是注意不要使用那些你会用来作为别名的名字
$ kubectl remove
Error: unknown command "remove" for "kubectl"
Did you mean this?
        delete
Run 'kubectl help' for usage.
Generating documentation for your command
cobra会基于你的 subcommand和flag生成文档.Read more about it in the docs generation documentation.
Generating shell completions
cobra能生成shell自动补全
Bash, Zsh, Fish, Powershell.Read more about it in Shell Completions.
cobra generator的使用
cobra用于创建命令行应用的脚手架工具
执行命令安装
go get github.com/spf13/cobra/cobra
cobra init
创建应用初始代码
提供正确的项目结构
自动应用给定的license
在当前应用运行,或指定相对路径,如果目录不存在,会创建一个
执行需要提供--pkg-name
在非空的目录中也能成功执行
mkdir -p newApp && cd newApp
cobra init --pkg-name github.com/spf13/newApp
or
cobra init --pkg-name github.com/spf13/newApp path/to/newApp
cobra add
用于添加新的command
例子
app serve
app config
app config create
在项目目录上执行以下命令即可
cobra add serve
cobra add config
cobra add create -p 'configCmd'
command驼峰式命名
不然会遇到错误
For example, cobra add add-user is incorrect, but cobra add addUser is valid.
Once you have run these three commands you would have an app structure similar to the following:
执行以上命令后的项目结构
  ▾ app/
    ▾ cmd/
        serve.go
        config.go
        create.go
      main.go

配置文件
提供配置文件可以避免每次使用提供一堆信息.
举个例子 ~/.cobra.yaml :
author: Steve Francia <spf@spf13.com>
license: MIT
用其内置license
比如GPLv2, GPLv3, LGPL, AGPL, MIT, 2-Clause BSD or 3-Clause BSD.
可不用证书,把license设置为none即可,或自定义license
author: Steve Francia <spf@spf13.com>
year: 2020
license:
  header: This file is part of CLI application foo.
  text: |
    {{ .copyright }}
    This is my license. There are many like it, but this one is mine.
    My license is my best friend. It is my life. I must master it as I must
    master my life.
上面的copyright部分是用author和year两个属性生成的,
This is my license. There are many like it, but this one is mine.
My license is my best friend. It is my life. I must master it as I must
master my life.
header也会在license头部被使用.