自学内容网 自学内容网

golang配置使用代理拉取依赖库,Swagger 在线解析API文档,Go参数解析工具 pflag、cobra、viper 在项目中的高级用法

golang配置使用代理拉取依赖库,Swagger 在线解析API文档,Go参数解析工具 pflag、cobra、viper 在项目中的高级用法。

在这里插入图片描述

1、查看go 的环境变量 在cmd中 输入

go env

设置GOPROXY代理:

go env -w GOPROXY=https://goproxy.cn,direct

设置GOPRIVATE来跳过私有库,比如常用的Gitlab或Gitee,中间使用逗号分隔:

go env -w GOPRIVATE=*.gitlab.com,*.gitee.com

如果在运行go mod vendor时,提示Get https://sum.golang.org/lookup/xxxxxx: dial tcp 216.58.200.49:443: i/o timeout,则是因为Go 1.13设置了默认的GOSUMDB=sum.golang.org,这个网站是被墙了的,用于验证包的有效性,可以通过如下命令关闭:

go env -w GOSUMDB=off

可以设置 GOSUMDB=“sum.golang.google.cn”, 这个是专门为国内提供的sum 验证服务。

go env -w GOSUMDB="sum.golang.google.cn"

通常报错:

http: server gave HTTP response to HTTPS client timeout

解决指令:(会话临时性),长久的可以在配置文件中配置

go env -w GOPROXY=https://goproxy.cn,direct

长久的,在~/.bashrc文件中添加:

export GOPROXY=https://goproxy.cn,direct

在1.3版本后,golang推出了go module 机制,同时发布了一个叫做 Module proxy protocol 的协议,通过这个机制,我们可以搭建go模块代理,最后通过GOPROXY变量以指引go命令在后续抓取模块的途径

但是GOPROXY拥有一个默认:https://proxy.golang.org,direct,而且很可惜 proxy.golang.org 在中国无法访问。这也是我们依赖下载时一直timeout的原因。

这里只需要我们将该变量设置为

GOPROXY=https://goproxy.cn,direct

在命令行输入

go env -w GOPROXY=https://goproxy.cn,direct

,即可正常拉取依赖

mac 电脑安装 brew 工具:

https://brew.sh/

https://openvpn.net/client/


test swagger json online
您可以使用Swagger Editor或Swagger UI在线测试Swagger JSON。

Swagger Editor 是一个在线编辑器,可以让您输入或上传Swagger JSON文件,并提供验证、编辑和预览功能。您可以在 editor.swagger.io/ 上访问Swagger Editor。

Swagger UI是一个交互式的API文档生成工具,它可以根据Swagger JSON文件动态地生成API文档,以便用户更好地理解API。您可以在 petstore.swagger.io/ 上访问Swagger UI,并使用提供的演示API测试Swagger JSON。

在使用这些工具之前,您需要先生成Swagger JSON文件。您可以使用Swagger框架来定义和生成Swagger JSON文件。您可以在 swagger.io/ 上找到Swagger的官方文档和教程,以便更好地理解Swagger的使用和工作原理。


Go参数解析工具–pflag

  1. 概述
    Go标准库提供了一个解析命令行的flag库,小型项目使用flag库其实已经足够用了,但在大型项目中应用较多的是另一个开源库:pflag,也是今天学习的对象。
    pflag是一个用来替代Go标准库flag包的,兼容flag库,几乎不用更改就可以替换,pflag在大型项目中应用的比较广泛,比较知名的有:K8S,Docker,ETCD等。

pflag包github地址为:https://github.com/spf13/pflag

作者spf13大牛,还有其他好几个强大的开源库,包括配置神器Viper,命令行框架cobra等.

Cobra是一个库,其提供简单的接口来创建强大现代的CLI接口,类似于git或者go工具。同时,它也是一个应用,用来生成个人应用框架,从而开发以Cobra为基础的应用。Docker源码中使用了Cobra。

概念
Cobra基于三个基本概念commands,arguments和flags。其中commands代表行为,arguments代表数值,flags代表对行为的改变。

基本模型如下:

APPNAME VERB NOUN --ADJECTIVE或者APPNAME COMMAND ARG --FLAG

例如:

# server是commands,port是flag
hugo server --port=1313

# clone是commands,URL是arguments,brae是flags
git clone URL --bare

Commands
Commands是应用的中心点,同样commands可以有子命令(children commands),其分别包含不同的行为。

Commands的结构体如下:

type Command struct {
    Use string // The one-line usage message.
    Short string // The short description shown in the 'help' output.
    Long string // The long message shown in the 'help <this-command>' output.
    Run func(cmd *Command, args []string) // Run runs the command.
}

Flags
Flags用来改变commands的行为。其完全支持POSIX命令行模式和Go的flag包。这里的flag使用的是spf13/pflag包,具体可以参考Golang之使用Flag和Pflag.

安装与导入
安装

go get -u github.com/spf13/cobra/cobra

导入

import "github.com/spf13/cobra"

Cobra文件结构
cjapp的基本结构

  ▾ cjapp/
    ▾ cmd/
        add.go
        your.go
        commands.go
        here.go
      main.go

main.go
其目的很简单,就是初始化Cobra。其内容基本如下:

package main

import (
  "fmt"
  "os"

  "{pathToYourApp}/cmd"
)

func main() {
  if err := cmd.RootCmd.Execute(); err != nil {
    fmt.Println(err)
    os.Exit(1)
  }
}

使用cobra生成器
windows系统下使用:

go get github.com/spf13/cobra/cobra

或者在文件夹github.com/spf13/cobra/cobra下使用go install在$GOPATH/bin路径下生成cobra.exe可执行命令。

cobra init
命令cobra init [yourApp]将会创建初始化应用,同时提供正确的文件结构。同时,其非常智能,你只需给它一个绝对路径,或者一个简单的路径。

cobra.exe init cjapp

<<'COMMENT'
Your Cobra application is ready at
/home/chenjian/gofile/src/cjapp.

Give it a try by going there and running `go run main.go`.
Add commands to it by running `cobra add [cmdname]`.
COMMENT
ls -Ra /home/chenjian/gofile/src/cjapp

<<'COMMENT'
/home/chenjian/gofile/src/cjapp:
.  ..  cmd  LICENSE  main.go

/home/chenjian/gofile/src/cjapp/cmd:
.  ..  root.go
COMMENT

cobra add
在路径C:\Users\chenjian\GoglandProjects\src\cjapp下分别执行:

cobra add serve
<<'COMMENT'
serve created at /home/chenjian/gofile/src/cjapp/cmd/serve.go
COMMENT

cobra add config
<<'COMMENT'
config created at /home/chenjian/gofile/src/cjapp/cmd/config.go
COMMENT

cobra add create -p 'configCmd'
<<'COMMENT'
create created at /home/chenjian/gofile/src/cjapp/cmd/create.go
COMMENT

ls -Ra /home/chenjian/gofile/src/cjapp

<<'COMMENT'
/home/chenjian/gofile/src/cjapp:
.  ..  cmd  LICENSE  main.go

/home/chenjian/gofile/src/cjapp/cmd:
.  ..  config.go  create.go  root.go  serve.go
COMMENT

此时你可以使用:

go run main.go

<<'COMMENT'
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

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:
  cjapp [command]

Available Commands:
  config      A brief description of your command
  help        Help about any command
  serve       A brief description of your command

Flags:
      --config string   config file (default is $HOME/.cjapp.yaml)
  -h, --help            help for cjapp
  -t, --toggle          Help message for toggle

Use "cjapp [command] --help" for more information about a command.
COMMENT

go run main.go config
<<'COMMENT'
config called
COMMENT

go run main.go serve
<<'COMMENT'
serve called
COMMENT

go run main.go config create
<<'COMMENT'
create called
COMMENT

cobra生成器配置
Cobra生成器通过~/.cjapp.yaml(Linux下)或者$HOME/.cjapp.yaml(windows)来生成LICENSE。

一个.cjapp.yaml格式例子如下:

author: Chen Jian <chenjian158978@gmail.com>
license: MIT

或者可以自定义LICENSE:

license:
  header: This file is part of {{ .appName }}.
  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.

人工构建Cobra应用
人工构建需要自己创建main.go文件和RootCmd文件。例如创建一个Cobra应用cjappmanu

RootCmd文件
路径为cjappmanu/cmd/root.go

package cmd

import (
"fmt"
"os"

"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var RootCmd = &cobra.Command{
Use:     "chenjian",
Aliases: []string{"cj", "ccccjjjj"},
Short:   "call me jack",
Long: `A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at https://o-my-chenjian.com`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("OK")
},
}

var cfgFile, projectBase, userLicense string

func init() {
cobra.OnInitialize(initConfig)

// 在此可以定义自己的flag或者config设置,Cobra支持持久标签(persistent flag),它对于整个应用为全局
// 在StringVarP中需要填写`shorthand`,详细见pflag文档
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (defalut in $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")

// Cobra同样支持局部标签(local flag),并只在直接调用它时运行
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")

// 使用viper可以绑定flag
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 Execute()  {
RootCmd.Execute()
}

func initConfig() {
// 勿忘读取config文件,无论是从cfgFile还是从home文件
if cfgFile != "" {
viper.SetConfigName(cfgFile)
} else {
// 找到home文件
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}

// 在home文件夹中搜索以“.cobra”为名称的config
viper.AddConfigPath(home)
viper.SetConfigName(".cobra")
}
// 读取符合的环境变量
viper.AutomaticEnv()

if err := viper.ReadInConfig(); err != nil {
fmt.Println("Can not read config:", viper.ConfigFileUsed())
}
}

main.go
main.go的目的就是初始化Cobra

package main

import (
"fmt"
"os"

"cjappmanu/cmd"
)

func main() {
if err := cmd.RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}


附加命令
附加命令可以在/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 ChenJian",
Long:  `All software has versions. This is Hugo's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Chen Jian Version: v1.0 -- HEAD")
},
}

同时,可以将命令添加到父项中,这个例子中RootCmd便是父项。只需要添加:

RootCmd.AddCommand(versionCmd)

处理Flags
Persistent Flags
persistent意思是说这个flag能任何命令下均可使用,适合全局flag:

RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

Local Flags
Cobra同样支持局部标签(local flag),并只在直接调用它时运行

RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

Bind flag with Config
使用viper可以绑定flag

var author string

func init() {
  RootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
  viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author"))
}

Positional and Custom Arguments
Positional Arguments
Leagacy arg validation有以下几类:

NoArgs: 如果包含任何位置参数,命令报错
ArbitraryArgs: 命令接受任何参数
OnlyValidArgs: 如果有位置参数不在ValidArgs中,命令报错
MinimumArgs(init): 如果参数数目少于N个后,命令行报错
MaximumArgs(init): 如果参数数目多余N个后,命令行报错
ExactArgs(init): 如果参数数目不是N个话,命令行报错
RangeArgs(min, max): 如果参数数目不在范围(min, max)中,命令行报错

Custom Arguments

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!")
  },
}

实例
将root.go修改为以下:

package cmd

import (
"fmt"
"os"
"strings"

homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var cfgFile string
var echoTimes int

var RootCmd = &cobra.Command{
Use: "app",
}

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, " "))
}
},
}

func init() {
cobra.OnInitialize(initConfig)

cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")

// 两个顶层的命令,和一个cmdEcho命令下的子命令cmdTimes
RootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
}

func Execute() {
RootCmd.Execute()
}

func initConfig() {
// 勿忘读取config文件,无论是从cfgFile还是从home文件
if cfgFile != "" {
viper.SetConfigName(cfgFile)
} else {
// 找到home文件
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}

// 在home文件夹中搜索以“.cobra”为名称的config
viper.AddConfigPath(home)
viper.SetConfigName(".cobra")
}
// 读取符合的环境变量
viper.AutomaticEnv()

if err := viper.ReadInConfig(); err != nil {
fmt.Println("Can not read config:", viper.ConfigFileUsed())
}
}

操作如下:

go run main.go

<<'COMMENT'
Usage:
  app [command]

Available Commands:
  echo        Echo anything to the screen
  help        Help about any command
  print       Print anything to the screen
  version     Print the version number of ChenJian

Flags:
  -h, --help   help for app

Use "app [command] --help" for more information about a command.
COMMENT


go run main.go echo -h

<<'COMMENT'
echo is for echoing anything back.
Echo works a lot like print, except it has a child command.

Usage:
  app echo [string to echo] [flags]
  app echo [command]

Available Commands:
  times       Echo anything to the screen more times

Flags:
  -h, --help   help for echo

Use "app echo [command] --help" for more information about a command.
COMMENT

go run main.go echo times -h

<<'COMMENT'
echo things multiple times back to the user by providing
a count and a string.

Usage:
  app echo times [# times] [string to echo] [flags]

Flags:
  -h, --help        help for times
  -t, --times int   times to echo the input (default 1)
COMMENT

go run main.go print HERE I AM
<<'COMMENT'
Print: HERE I AM
COMMENT

go run main.go version
<<'COMMENT'
Chen Jian Version: v1.0 -- HEAD
COMMENT

go run main.go echo times WOW -t 3
<<'COMMENT'
Echo: WOW
Echo: WOW
Echo: WOW
COMMENT

自定义help和usage
help
默认的help命令如下:

func (c *Command) initHelp() {
  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: c.HelpFunc(),
    }
  }
  c.AddCommand(c.helpCommand)
}

可以通过以下来自定义help:

command.SetHelpCommand(cmd *Command)
command.SetHelpFunc(f func(*Command, []string))
command.SetHelpTemplate(s string)

usage
默认的help命令如下:

return func(c *Command) error {
  err := tmpl(c.Out(), c.UsageTemplate(), c)
  return err
}

可以通过以下来自定义help:

command.SetUsageFunc(f func(*Command) error)

command.SetUsageTemplate(s string)

先执行与后执行
Run功能的执行先后顺序如下:

PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun

错误处理函数
RunE功能的执行先后顺序如下:

PersistentPreRunE
PreRunE
RunE
PostRunE
PersistentPostRunE

对不明命令的建议
当遇到不明命令,会有提出一定的建,其采用最小编辑距离算法(Levenshtein distance)。例如:

hugo srever

<<'COMMENT'
Error: unknown command "srever" for "hugo"

Did you mean this?
        server

Run 'hugo --help' for usage.
COMMENT

想关闭智能提示,可以:

command.DisableSuggestions = true

// 或者

command.SuggestionsMinimumDistance = 1

或者使用SuggestFor属性来自定义一些建议,例如:

kubectl remove
<<'COMMENT'
Error: unknown command "remove" for "kubectl"

Did you mean this?
        delete

Run 'kubectl help' for usage.
COMMENT
  1. 快速使用
package main

import (
"fmt"

"github.com/spf13/pflag"
)

var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var stringpFlag = pflag.StringP("stringpflag", "s", "stringpflag", "stringp flag usage")
var intFlag int
var boolFlag bool

func init() {
pflag.IntVarP(&intFlag, "intflag", "i", 0, "int flag usage")
pflag.BoolVar(&boolFlag, "boolflag", false, "bool flag usage")
}

func main() {
pflag.Parse()

// flag保存在指针的
fmt.Println("stringflag = ", *stringFlag)

fmt.Println("stringpflag = ", *stringpFlag)

// flag绑定在变量
fmt.Println("intflag = ", intFlag)

fmt.Println("boolflag = ", boolFlag)

    // 也可以使用Getxxx方法获取
str, _ := pflag.CommandLine.GetString("stringflag")
fmt.Println("str = ", str)
}

可以先编译程序,然后再运行,这里直接用go run

go run pflag_fast.go
stringflag =  stringflag
stringpflag =  stringpflag
intflag =  0
boolflag =  false
str =  stringflag

在没有传递flag时,输出的都是默认值。

go run pflag_fast.go --stringflag hello -s world -i 1234 --boolflag true
stringflag =  hello
stringpflag =  world
intflag =  1234
boolflag =  true
str =  hello

指定flag的值后运行,相应的值都被设置了。
还可以使用-h打印帮助信息

go run pflag_fast.go -h
Usage of /tmp/go-build2029445686/b001/exe/pflag_fast:
      --boolflag             bool flag usage
  -i, --intflag int          int flag usage
      --stringflag string    string flag usage (default "stringflag")
  -s, --stringpflag string   stringp flag usage (default "stringpflag")
pflag: help requested
exit status 2

小结

使用pflag定义命令行参数有好几种方式,长选项/短选项/保存在指针/绑定到变量…
pflag.Parse()方法必须放在所有flag都定义后调用,否则flag就无法解析了。
3. PFlag包结构
从上面的快速使用例子里,使用Getxxx方法来获取flag的值:str, _ := pflag.CommandLine.GetString(“stringflag”)
这里调用了pflag包的CommandLine,查看源码CommandLine的定义如下:

// CommandLine is the default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

实质上CommandLine是由NewFlagSet()创建的一个全局的FlagSet,再看看FlagSet的定义:

// A FlagSet represents a set of defined flags.
type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler.
Usage func()

// SortFlags is used to indicate, if user wants to have sorted flags in
// help/usage messages.
SortFlags bool

// ParseErrorsWhitelist is used to configure a whitelist of errors
ParseErrorsWhitelist ParseErrorsWhitelist

name              string
parsed            bool
actual            map[NormalizedName]*Flag
orderedActual     []*Flag
sortedActual      []*Flag
formal            map[NormalizedName]*Flag
orderedFormal     []*Flag
sortedFormal      []*Flag
shorthands        map[byte]*Flag
args              []string // arguments after flags
argsLenAtDash     int      // len(args) when a '--' was located when parsing, or -1 if no --
errorHandling     ErrorHandling
output            io.Writer // nil means stderr; use out() accessor
interspersed      bool      // allow interspersed option/non-option args
normalizeNameFunc func(f *FlagSet, name string) NormalizedName

addedGoFlagSets []*goflag.FlagSet
}

FlagSet是一个Flag的集合,几乎所有的pflag操作都是借助FlagSet的方法来实现的,默认定义了一个全局的FlagSet:CommandLine,我们拿pflag.IntVarP()方法看看定义:

// IntVarP is like IntVar, but accepts a shorthand letter that can be used after a single dash.
func IntVarP(p *int, name, shorthand string, value int, usage string) {
CommandLine.VarP(newIntValue(value, p), name, shorthand, usage)
}

// VarPF is like VarP, but returns the flag created
func (f *FlagSet) VarPF(value Value, name, shorthand, usage string) *Flag {
// Remember the default value as a string; it won't change.
flag := &Flag{
Name:      name,
Shorthand: shorthand,
Usage:     usage,
Value:     value,
DefValue:  value.String(),
}
f.AddFlag(flag)
return flag
}

// VarP is like Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) VarP(value Value, name, shorthand, usage string) {
f.VarPF(value, name, shorthand, usage)
}

其内部调用的就是CommandLine.一般使用全局的FlagSet会更加方便。

我们也可以定义自己的FlagSet:调用NewFlagSet()方法:

var output string
flagset := pflag.NewFlagSet("myflag", pflag.ContinueOnError)

flagset.StringVar(&output, "output", "txt", "Output File Format")

flagset.Parse(os.Args[1:]) // 调用的是自己创建的FlagSet的parse方法!

fmt.Println("output = ", output)

接下来看看源码里的Flag相关定义:

在pflag包里,一个命令行参数会解析为一个Flag类型的变量,Flag定义如下:

// A Flag represents the state of a flag.
type Flag struct {
    Name                string              // name as it appears on command line (flag长选项的名称)
    Shorthand           string              // one-letter abbreviated flag (flag短选项的名称,一个字母)
    Usage               string              // help message (flag的使用帮助信息)
    Value               Value               // value as set (flag的值)
    DefValue            string              // default value (as text); for usage message (flag的默认值)
    Changed             bool                // If the user set the value (or if left to default) (记录flag的值是否被设置过)
    NoOptDefVal         string              // default value (as text); if the flag is on the command line without any options (当flag出现在命令行,但是没有指定选项值时的默认值)
    Deprecated          string              // If this flag is deprecated, this string is the new or now thing to use (如果该flag被废弃,显示的使用帮助信息)
    Hidden              bool                // used by cobra.Command to allow flags to be hidden from help/usage text (为true,则在help输出信息中隐藏这个flag)
    ShorthandDeprecated string              // If the shorthand of this flag is deprecated, this string is the new or now thing to use(如果该flag的短选项被废弃,显示的使用帮助信息)
    Annotations         map[string][]string // used by cobra.Command bash autocomple code (注解,用于cobra的bash自动补全)
}

Flag的值是一个Value类型的接口,定义如下:

// Value is the interface to the dynamic value stored in a flag.
// (The default value is represented as a string.)
type Value interface {
String() string
Set(string) error
Type() string
}

也就是说只要实现了Value接口的结构,就是一个新的类型值,我们拿源码里的String类型看看:

func (s *stringValue) Set(val string) error {
*s = stringValue(val)
return nil
}
func (s *stringValue) Type() string {
return "string"
}

func (s *stringValue) String() string { return string(*s) }

这里简单的自定义一个新类型:

type Person struct {
Name string
Age  int
}

func newPersonValue(val Person, p *Person) *Person {
*p = val
return (*Person)(p)
}

func (p *Person) Set(val string) error {
strs := strings.Split(val, "@")

p.Name = strs[0]
p.Age, _ = strconv.Atoi(strs[1])

return nil
}

func (p *Person) Type() string {
return "Person"
}

func (p *Person) String() string {
return fmt.Sprintf("%s@%d", p.Name, p.Age)
}

func PersonVarP(p *Person, name, shorthand string, value Person, usage string) {
pflag.CommandLine.VarP(newPersonValue(value, p), name, shorthand, usage)
}

var personFlag Person

func init() {
PersonVarP(&personFlag, "person", "p", Person{Name: "abc", Age: 12}, "input person msg")
}

func main() {
pflag.Parse()

fmt.Printf("person = %+v\n", personFlag)
}

测试如下:

go run pflag_fast.go
person = {Name:abc Age:12}
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go -p zhangsan@18
person = {Name:zhangsan Age:18}
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --person lisi@27
person = {Name:lisi Age:27}
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go -h
Usage of /tmp/go-build1927169625/b001/exe/pflag_fast:
  -p, --person Person   input person msg (default abc@12)
pflag: help requested
exit status 2

  1. 使用方法

多种命令行参数定义方式
定义方式按flag值存储位置分为两种:保存在指针或绑定到变量

flag值保存在指针
支持长选项,默认值和使用说明文本
比如:

var name = flag.String("name", "lucas", "Input your name")

func main() {
    flag.Parse()
    fmt.Println("name = ", *name)
}

支持长选项,短选项,默认值和使用说明文本
比如:

var name = flag.StringP("name", "n", "lucas", "Input your name")
func main() {
    flag.Parse()
    fmt.Println("name = ", *name)
}

flag值绑定到变量
支持长选项,默认值和使用说明文本
比如:

var name string
func main() {
    flag.StringVar(&name, "name", "lucas", "Input your name")

    flag.Parse()
    fmt.Println("name = ", name)
}

支持长选项,短选项,默认值和使用说明文本
比如:

var name string
func main() {
    flag.StringVarP(&name, "name", "n", "lucas", "Input your name")

    flag.Parse()
    fmt.Println("name = ", name)
}

从上面的函数命名中可以总结出以下规律:
函数名带P的说明是支持短选项的
函数名带Var的说明是将flag绑定到变量,否则则是存储在指针中
定义flag使用flag.String(),Bool(),Int()等,后缀可以带Var,P等
所有flag定义完成后,需要调用flag.Parse()去解析命令行参数
使用Get获取参数的值
可以使用Get的方法来获取flag的值,比如GetString(),GetInt(),其中表示pflag支持的所有类型。
需要注意的是,要获取的flag必须存在且类型必须和一致,比如获取上述的name,就可以使用GetString(“name”)来获取
注意:
这里调用这些Get方法都是FlagSet这个结构的方法集里的,以GetString为例,它的方法定义是这样的:
func (f *FlagSet) GetString(name string) (string, error)
而我们使用全局变量CommandLine调用时候需要这样使用:name, err := flag.CommandLine.GetString(“name”)

获取参数
参数在标签flag之后,在调用pflag.Parse()之后,可以调用pflag提供的几个方法用来获取非flag的参数:

// Arg returns the i'th command-line argument.  Arg(0) is the first remaining argument
// after flags have been processed.
func Arg(i int) string 
// Args returns the non-flag command-line arguments.
func Args() []string
// NArg is the number of arguments remaining after flags have been processed.
func NArg() int

Arg(i int): 返回第i个非flag参数
Args(): 返回所有非flag的参数
NArg(): 返回非flag的参数个数
使用示例如下:

var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var stringpFlag = pflag.StringP("stringpflag", "s", "stringpflag", "stringp flag usage")
var intFlag int
var boolFlag bool

func init() {
pflag.IntVarP(&intFlag, "intflag", "i", 0, "int flag usage")
pflag.BoolVar(&boolFlag, "boolflag", false, "bool flag usage")
}

func main() {

pflag.Parse()

fmt.Printf("arg个数是: %v\n", pflag.NArg())
fmt.Printf("arg: %v\n", pflag.Args())
fmt.Printf("第1个arg是:%v\n", pflag.Arg(0))
}

执行如下:

go run pflag_fast.go --stringflag Hello arg0 arg1 arg2
arg个数是: 3
arg: [arg0 arg1 arg2]
第1个arg是:arg0

没有给flag设置选项默认值(Setting no option default values for flags)
当你创建了一个flag后,可以给这个flag设置pflag.NoOptDefVal.
如果一个flag具有NoOptDefVal,并且该flag在命令行上没有传递这个flag的值,那么这个flag的值就被设置为NoOptDefVal指定的值。
示例:

var port = pflag.IntP("port", "p", 6379, "redis port")

func main() {

pflag.Lookup("port").NoOptDefVal = "8080"

pflag.Parse()

fmt.Printf("redis port = %v\n", *port)
}

执行如下:

go run pflag_fast.go
redis port = 6379

go run pflag_fast.go --port 8888
redis port = 8080

go run pflag_fast.go --port
redis port = 8080

go run pflag_fast.go --port=8888
redis port = 8888

总结结果如下表:

在这里插入图片描述

注意:
这里需要注意一点,就是指定了NoOptDefVal后,在命令行传递flag时,不能再用诸如–flag xxx,要用–flag=xxx,因为–flag xxx会被认为是没有设置值,从而使用了NoOptDefVal的值,xxx则会被认为是arg

命令行flag格式
pflag包的-与–是不同的,-表示短选项,–表示长选项(标准库flag包-和–作用是一样的)。
pflag包支持以下几种格式:

--flag    // 支持布尔类型flag,或者设置了NoOptDefVal的flag
--flag x  // 只支持没有设置NoOptDefVal的flag(这个上面的"注意"也有提到原因)
--flag=x

注意:pflag遇到–后会停止解析,这一点不同于flag包

改变或标准化flag名
pflag允许在构建代码或者使用命令行时标准化flag名,比如我们创建了flag为my-flag,但是用户传递时候写错成my.flag或者my_flag,也可以正常识别:

func wordSepNormailzeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
from := []string{"-", "_"}
to := "."

for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return pflag.NormalizedName(name)
}

var myFlag = pflag.String("my-flag", "myflag", "myflag test")

func main() {

pflag.CommandLine.SetNormalizeFunc(wordSepNormailzeFunc)

pflag.Parse()

fmt.Println("myFlag = ", *myFlag)
}

执行如下:

go run pflag_fast.go --my-flag=hello
myFlag =  hello

go run pflag_fast.go --my_flag hello
myFlag =  hello

go run pflag_fast.go --my.flag hello
myFlag =  hello

go run pflag_fast.go --my+flag hello
unknown flag: --my+flag
Usage of /tmp/go-build951017333/b001/exe/pflag_fast:
      --my.flag string      myflag test (default "myflag")
  -p, --port int            redis port (default 6379)
      --stringflag string   string flag usage (default "stringflag")
unknown flag: --my+flag
exit status 2

弃用flag或flag缩写
pflag支持设置flag或其简写为弃用的,弃用的flag在帮助文本中会被隐藏,并且在使用弃用的flag时会打印提示信息。
示例如下:

var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var port = pflag.IntP("port", "p", 6379, "redis port")
var intFlag = pflag.IntP("intflag", "i", 0, "int flag usage")
func main() {

pflag.CommandLine.MarkDeprecated("port", "please use --new-port instead")
    // 只弃用缩写,保留长选项
    pflag.CommandLine.MarkShorthandDeprecated("intflag", "please use --intflag only")
pflag.Parse()
}

执行如下:

go run pflag_fast.go --port 1234
Flag --port has been deprecated, please use --new-port instead

go run pflag_fast.go --help
Usage of /tmp/go-build2446988640/b001/exe/pflag_fast:
      --stringflag string   string flag usage (default "stringflag")
pflag: help requested
exit status 2

go run pflag_fast.go -i 12
Flag shorthand -i has been deprecated, please use --intflag only

go run pflag_fast.go --intflag 12

注意:usage文本是必需的,并且不应该为空

隐藏flag
pflag包支持将flag标记为隐藏的,这样子在程序内部这个flag仍是正常运行的,但是不会显示在帮助文本中。

var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var port = pflag.IntP("port", "p", 6379, "redis port")
var intFlag = pflag.IntP("intflag", "i", 0, "int flag usage")

func main() {
pflag.CommandLine.MarkHidden("port")
pflag.Parse()
}

执行help结果:

go run pflag_fast.go --help
Usage of /tmp/go-build739458557/b001/exe/pflag_fast:
  -i, --intflag int         int flag usage
      --stringflag string   string flag usage (default "stringflag")
pflag: help requested
exit status 2

禁用flag排序
pflag支持在显示帮助文本时禁用flag排序,比如:

pflag.BoolP("verbose", "v", false, "verbose output")
pflag.String("coolflag", "yeaah", "it's really cool flag")
pflag.Int("usefulflag", 777, "sometimes it's very useful")
pflag.CommandLine.SortFlags = false
pflag.PrintDefaults()

输出如下(按定义的顺序输出):

-v, --verbose           verbose output
    --coolflag string   it's really cool flag (default "yeaah")
    --usefulflag int    sometimes it's very useful (default 777)

如果设置pflag.CommandLine.SortFlags = true,输出如下:

    --coolflag string   it's really cool flag (default "yeaah")
    --usefulflag int    sometimes it's very useful (default 777)
-v, --verbose           verbose output 

支持Go的flag包

import (
goflag "flag"
flag "github.com/spf13/pflag"
)

var ip *int = flag.Int("flagname", 1234, "help message for flagname")

func main() {
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
flag.Parse()
} 

GoLang Cobra 命令行解析组件

Cobra 命令行
特性
简单的基于子命令的 CLIs:app server、app fetch 等;
完全兼容 POSIX(可移植操作系统接口) 的标志(包括短版和长版)
嵌套子命令
全局、局部和级联的标志
使用 cobra init appname 和 cobra add cmdname 轻松生成应用程序和命令
智能提示(app srver …did you mean app server)
自动生成命令和标志的帮助
自动识别 -h、–help 等帮助标识
自动为你的应用程序生成的 bash 自动完成
自动为你的应用程序生成 man 手册
命令别名,以便你可以更改内容而不会破坏它们
定义自己的帮助,用法等的灵活性。
可选与 viper 紧密集成,可用于 12factor 应用程序
安装
Cobra 非常易用,首先使用 go get 命令安装最新版本。此命令将安装 cobra generator 的可执行文件及其依赖项:

$ go get -u github.com/spf13/cobra/cobra

概念
Cobra 构建在命令(commands)、参数(arguments)和 标志(flags)上。

Commands 代表动作,Args 是事物,Flags 是这些动作的修饰符。

最好的应用程序在使用时会像句子一样读起来。用户将知道如何使用该应用程序,因为他们将自然地了解如何使用它。

遵循的模式是 APPNAME VERB NOUN --ADJECTIVE。 或 APPNAME COMMAND ARG --FLAG

一些真实的例子可以更好地说明这一点。

在以下示例中,server 是命令,port 是标志:

hugo server --port=1313

在此命令中,我们告诉 Git 克隆 url 的内容:

git clone URL --bare

命令(Command)
命令是应用程序的核心。应用程序提供的每一个交互都包含在 Command 中。一个命令可以有子命令和可选的运行一个动作。

在上面的示例中,server 是命令。

cobra.Command API

标志(Flags)
一个标志是一种修饰命令行为的方式。Cobra 支持完全符合 POSIX(可移植操作系统接口) 的标志和 Go flag 包。

Cobra 命令可以定义一直保留到子命令的标志和仅可用于该命令的标志。

在上面的例子中,port 是标志。

标志的功能是 pflag 库提供的,该库是一个标准库的 fork,在维护相同接口的基础上兼容了 POSIX(可移植操作系统接口)。

入门
欢迎大家提供自己的项目组织结构,但是通常基于 Cobra 的应用程序将遵循以下组织结构

​cmd 放置命令的文件夹
 add.go
 your.go
 commands.go
 here.go
main.go 应用程序入口

在 Cobra 应用程序中,通常 main.go 文件非常。它有一个目的:初始化 Cobra。

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

使用 Cobra 生成器
Cobra 提供了 CLI 来创建您的应用程序和添加任意你想要的命令。这是将 Cobra 集成到您的应用程序中的最简单方法。

这里 你可以查看更多关于生成器的资料。

使用 Cobra 库
要手动接入 Cobra,您需要创建一个 main.go 文件和 rootCmd 文件。您可以选择提供合适的其他命令。

创建 rootCmd
Cobra 不需要任何特殊的构造函数。只需创建您的命令。

理想情况下,将其放置在 /cmd/root.go 中:

// rootCmd 代表没有调用子命令时的基础命令
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`,
  // 如果有相关的 action 要执行,请取消下面这行代码的注释
  // Run: func(cmd *cobra.Command, args []string) { },
}

// Execute 将所有子命令添加到root命令并适当设置标志。
// 这由 main.main() 调用。它只需要对 rootCmd 调用一次。
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

在 init() 函数中定义标志并处理配置。例子如下:

// cmd/root.go
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"

homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)

var cfgFile string
var projectBase string
var userLicense string

// rootCmd 代表没有调用子命令时的基础命令
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`,
  // 如果有相关的 action 要执行,请取消下面这行代码的注释
  // Run: func(cmd *cobra.Command, args []string) { },
}

// Execute 将所有子命令添加到root命令并适当设置标志。会被 main.main() 调用一次。
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

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
有了根命令,你需要一个 main 函数去执行它。为了清晰起见,Execute 应该在根目录上运行,尽管可以在任何命令上调用它。

在 Cobra 应用中,main.go 是非常简单的。它只有一个作用——初始化 Cobra。

// main.go
package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

创建额外的命令
可以定义其他命令,并且通常在 cmd/ 目录中为每个命令提供自己的文件。

如果要创建 version 命令,则可以创建 cmd/version.go 并使用以下命令进行填充:

// 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")
  },
}

使用标志
标志提供修饰符以控制命令的操作方式。

由于标志是在不同位置定义和使用的,我们需要在外部定义一个具有正确作用域的变量,以分配要使用的标志。

var verbose bool
var source string

这里有两种不同分配标志的方法。

持久标志
标志可以是 “persistent” 的,这意味着该标志将可用于分配给它的命令以及该命令下的每个命令。对于全局标志,将标志分配为根上的持久标志。

rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")


本地标志
也可以在本地分配一个标志,该标志仅适用于该特定命令。

rootCmd.Flags().StringVarP(&source, "source", "s", "", "Source directory to read from")


父命令上的本地标志
默认情况下,Cobra 仅解析目标命令上的本地标志,而忽略父命令上的任何本地标志。通过启用 Command.TraverseChildren,Cobra 将在执行目标命令之前解析每个命令上的本地标志

command := cobra.Command{
  Use: "print [OPTIONS] [COMMANDS]",
  TraverseChildren: true,
}

用配置绑定标志
您还可以将标志与 viper 绑定:

var author string

func init() {
  rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
  viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}

在此示例中,持久标记 author 与 viper 绑定。请注意,当用户未提供 --author 标志时,变量 author 不会设置为 config 中的值。

更多信息请查看 viper。

必需标志
标志默认是可选的。如果你想在缺少标志时命令报错,请设置该标志为必需:

var region string

rootCmd.Flags().StringVarP(&region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")

位置和自定义参数
可以使用 Command 的 Args 字段指定位置参数的验证。

下面的验证符是内置的:

NoArgs - 如果有任何位置参数,该命令将报告错误。
ArbitraryArgs - 命令将接受任意参数
OnlyValidArgs - 如果 Command 的 ValidArgs 字段中不存在该位置参数,则该命令将报告错误。
MinimumNArgs(int) - 如果不存在至少 N 个位置参数,则该命令将报告错误。
MaximumNArgs(int) - 如果存在超过 N 个位置参数,则该命令将报告错误。
ExactArgs(int) - 如果不存在 N 个位置参数,则该命令将报告错误。
ExactValidArgs(int) - 如果没有确切的 N 个位置参数,或者如果 Command 的 ValidArgs 字段中不存在该位置参数,则该命令将报告并出错。
RangeArgs(min, max) - 如果 args 的数目不在期望的 args 的最小和最大数目之间,则该命令将报告并出错。

内置验证符使用实例:

var cmd = &cobra.Command{
Use:   "hello",
Short: "hello",
Args:  cobra.MinimumNArgs(2),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello, World!")
},
}

如果只传递一个位置参数会报 Error: requires at least 2 arg(s), only received 1 的警告。

设置自定义验证器的示例:

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)是子命令。在这种情况下,根目录不可执行,这意味着需要一个子命令。通过不为 rootCmd 提供 Run 来实现。

我们只为一个命令定义了一个标志。

关于标志的文档在 [pflag]github.com/spf13/pflag…%E3%80%82)

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 anyting to the screen more times",
Long: `echo things multiple times back to the user y 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 命令时会显示帮助信息。另外,help 还支持其他命令作为输入参数。举例来说,你有一个没有额外配置的 create 命令,app help create 是有效的。每一个命令还会自动获取一个 --help 标志。

示例
以下输出由 Cobra 自动生成。 除了命令和标志定义外,什么都不需要。

help 就像其他命令一样。并没有特殊的逻辑或行为。实际上,你可以根据需要提供自己的服务。

定义你自己的 help
你可以使用下面的方法提供你自己的 Help 命令或模板。

cmd.SetHelpCommand(cmd *Command)
cmd.setHelpCommand(f func(*Command, []string))
cmd.setHelpTemplate(s string)


后两者也适用于所有子命令。

使用信息
当用户提供无效的标志或无效的命令时,Cobra 会通过向用户显示 usage 进行响应。

定义你自己的使用信息
你可以提供你自己的 usage 函数或模板。像 help 一样,函数和模板可通过公共方法重写:

cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)

版本标志
如果给根命令设置了 Version 字段,Cobra 会添加一个顶级的 --version 标志。运行带有 –version 标志的应用程序,将使用版本模板将版本打印到 stdout。模板可以使用 cmd.SetVersionTemplate(s string) 函数自定义。

SetVersionTemplate 的使用可以参考 GitHub CLI

PreRun 和 PostRun Hooks
可以在执行命令之前和之后运行一个函数。PersistentPreRun 和 PreRun 函数将在 Run 之前执行。PersistentPostRun 和 PostRun 会在 Run 之后运行。如果子级未声明自己的 Persistent * Run 函数,则子级将继承父级的。这些函数的执行顺续如下:

PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun
下面这个包含了两个命令的例子使用了这些特性。当子命令执行时,它会运行根命令的 PersistentPreRun,但是不会运行根命令的 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)
},
}

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]

“unknown command” 时的提示
当 “unknown command” 错误发生时,Cobra 会自动打印提示。这和 git 命令的行为一致。比如

$ hugo srever
Error: unknown command "srever" for "hugo"

Did you mean this?
        server

Run 'hugo --help' for usage.

系统会根据注册的每个子命令自动生成建议,并使用萊文斯坦距離的实现。每个匹配最小距离 2(忽略大小写)的注册命令都将显示为建议。

如果需要禁用建议或在命令中调整字符串距离,请使用:

cmd.DisableSuggestions = true

cmd.SuggestionsMinimumDistance = 1

还可以使用 SuggestFor 属性显式为给定命令设置建议的名称。这样就可以针对不是距离很近的字符串提出建议,但是对于您的命令集和不希望使用别名的命令来说,它们都是有意义的。比如:

$ kubectl remove
Error: unknown command "remove" for "kubectl"

Did you mean this?
        delete

Run 'kubectl help' for usage.

原文地址:https://blog.csdn.net/u014374009/article/details/137261323

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!