开发者

go语言Pflag Viper Cobra 核心功能使用介绍

目录
  • 1.如何构建应用框架
  • 2.命令行参数解析工具:Pflag
    • 2.1 Pflag 包 Flag 定义
    • 2.2 Pflag 包 FlagSet 定义
    • 2.3 Pflag 使用方法
  • 3.配置解析神器:Viper
    • 3.1读入配置
    • 3.2 读取配置
  • 4.现代化的命令行框架:Cobra
    • 4.1 使用 Cobra 库创建命令
    • 4.2使用标志
  • 5.总结

    1.如何构建应用框架

    一般来说构建应用框架包含3个部分:

    • 命令行参数解析
    • 配置文件解析
    • 应用的命令行框架:需要具备 Help 功能、需要能够解析命令行参数和配置文件、命令需要能够初始化业务代码,并最终启动业务进程 上3个需求便涉及Pflag、Viper、Cobra的使用,并且这三个包也是相互联系滴

    2.命令行参数解析工具:Pflag

    虽然 Go 源码中提供了一个标准库编程客栈 Flag 包,用来对命令行参数进行解析,但在大型项目中应用更广泛的是另外一个包:Pflag

    2.1 Pflag 包 Flag 定义

    Pflag 可以对命令行参数进行处理,一个命令行参数在 Pflag 包中会解析为一个 Flag 类型的变量

    type Flag struct {
        Name                string // flag长选项的名称
        Shorthand           string // flag短选项的名称,一个缩写的字符
        Usage               string // flag的使用文本
        Value               Value  // flag的值
        DefValue            string // flag的默认值
        Changed             bool // 记录flag的值是否有被设置过
        NoOptDefVal         string // 当flag出现在命令行,但是没有指定选项值时的默认值
        Deprecated          string // 记录该flag是否被放弃
        Hidden              bool // 如果值为true,则从help/usage输出信息中隐藏该flag
        ShorthandDeprecated string // 如果flag的短选项被废弃,当使用flag的短选项时打印该信息
        Annotations         map[string][]string // 给flag设置注解
    }
    

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

    type Value interface {
        String() string // 将flag类型的值转换为string类型的值,并返回string的内容
        Set(string) error // 将string类型的值转换为flag类型的值,转换失败报错
        Type() string // 返回flag的类型,例如:string、int、ip等
    }
    

    2.2 Pflag 包 FlagSet 定义

    Pflag 除了支持单个的 Flag 之外,还支持 FlagSet。FlagSet 是一些预先定义好的 Flag 的集合

    • 调用 NewFlagSet 创建一个 FlagSet
    • 使用 Pflag 包定义的全局 FlagSet:CommandLine
    var version bool
    flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
    flagSet.BoolVar(&version, "version", true, "Print version information and quit.")
    =================================
    import (
        "github.com/spf13/pflag"
    )
    pflag.BoolVarP(&version, "version", "v", true, "Print version information and quit.")
    func BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
        flag := CommandLine.VarPF(newBoolValue(value, p), name, shorthand, usage)
        flag.NoOptDefVal = "true"
    }
    

    2.3 Pflag 使用方法

    • 支持多种命令行参数定义方式
    // 支持长选项、默认值和使用文本,并将标志的值存储在指针中。
    var name = pflag.String("name", "colin", "Input Your Name")
    // 支持长选项、短选项、默认值和使用文本,并将标志的值存储在指针中
    var name = pflag.StringP("name", "n", "colin", "Input www.devze.comYour Name")
    // 支持长选项、默认值和使用文本,并将标志的值绑定到变量。
    var name string
    pflag.StringVar(&name, "name", "colin", "Input Your Name")
    // 支持长选项、短选项、默认值和使用文本,并将标志的值绑定到变量。
    var name string
    pflag.StringVarP(&name, "name", "n","colin", "Input Your Name")
    // 函数名带Var说明是将标志的值绑定到变量,否则是将标志的值存储在指针中
    // 函数名带P说明支持短选项,否则不支持短选项。
    
    • 使用Get获取参数的值。
    i, err := flagset.GetInt("flagname")
    
    • 获取非选项参数。
    package main
    import (
        "fmt"
        "github.com/spf13/pflag"
    )
    var (
        flagvar = pflag.Int("flagname", 1234, "help message for flagname")
    )
    // 在定义完标志之后,可以调用pflag.Parse()来解析定义的标志。解析后,可通过pflag.Args()返回所有的非选项参数,通过pflag.Arg(i)返回第 i 个非选项参数。参数下标 0 到 pflag.NArg() - 1。
    func main() {
        pflag.Parse()
        fmt.Printf("argument number is: %v\n", pflag.NArg())
        fmt.Printf("argument list is: %v\n", pflag.Args())
        fmt.Printf("the first argument is: %v\n", pflag.Arg(0))
    }
    
    • 指定了选项但是没有指定选项值时的默认值。
    var ip = pflag.IntP("flagname", "f", 1234, "help message")
    pflag.Lookup("flagname").NoOptDefVal = "4321"
    --flagname=135javascript7 ==> 1357
    --flagname ==> 4321
    [nothing] ==> 1234
    
    • 弃用标志或者标志的简写
    // deprecate a flag by specifying its name and a usage message
    pflag.CommandLine.MarkDeprecated("logmode", "please use --log-mode instead")
    
    • 保留名为 port 的标志,但是弃用它的简写形式
    pflag.IntVarP(&port, "port", "P", 3306, "mysql service host port.")
    // deprecate a flag shorthand by specifying its flag name and a usage message
    pflag.CommandLine.MarkShorthandDep开发者_JAVArecated("port", "please use --port only")
    
    • 隐藏标志。
    // 可以将 Flag 标记为隐藏的,这意味着它仍将正常运行,但不会显示在 usage/help 文本中。
    // hide a flag by specifying its name
    pflag.CommandLine.MarkHidden("secretFlag")
    

    3.配置解析神器:Viper

    几乎所有的后端服务,都需要一些配置项来配置我们的服务;Viper 是 Go 应用程序现代化的、完整的解决方案,能够处理不同格式的配置文件 Viper 可以从不同的位置读取配置,不同位置的配置具有不同的优先级:

    • 通过 viper.Set 函数显示设置的配置
    • 命令行参数
    • 环境变量
    • 配置文件
    • Key/Value 存储
    • 默认值

    3.1读入配置

    读入配置,就是将配置读入到 Viper 中,有如下读入方式:

    • 设置默认值。
    // 当没有通过配置文件、环境变量、远程配置或命令行标志设置 key 时,设置默认值通常是很有用的
    viper.SetDefault("ContentDir", "content")
    viper.SetDefault("LayoutDir", "layouts")
    viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
    
    • 读取配置文件
    package main
    import (
      "fmt"
      "github.com/spf13/pflag"
      "github.com/spf13/viper"
    )
    var (
      cfg  = pflag.StringP("config", "c", "", "Configuration file.")
      help = pflag.BoolP("help", "h", false, "Show this help message.")
    )
    func main() {
      pflag.Parse()
      if *help {
        pflag.Usage()
        return
      }
      // 从配置文件中读取配置
      if *cfg != "" {
        viper.SetConfigFile(*cfg)   // 指定配置文件名
        viper.SetConfigType("yaml") // 如果配置文件名中没有文件扩展名,则需要指定配置文件的格式,告诉viper以何种aWmHp格式解析文件
      } else {
        viper.AddConfigPath(".")          // 把当前目录加入到配置文件的搜索路径中
        viper.AddConfigPath("$HOME/.iam") // 配置文件搜索路径,可以设置多个配置文件搜索路径
        viper.SetConfigName("config")     // 配置文件名称(没有文件扩展名)
      }
      if err := viper.ReadInConfig(); err != nil { // 读取配置文件。如果指定了配置文件名,则使用指定的配置文件,否则在注册的搜索路径中搜索
        panic(fmt.Errorf("Fatal error config file: %s \n", err))
      }
      fmt.Printf("Used configuration file is: %s\n", viper.ConfigFileUsed())
    }
    
    • 监听和重新读取配置文件。 Viper 支持在运行时让应用程序实时读取配置文件,也就是热加载配置
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
       // 配置文件发生变更之后会调用的回调函数
      fmt.Println("Config file changed:", e.Name)
    })
    //不建议在实际开发中使用热加载功能,因为即使配置热加载了,程序中的代码也不一定会热加载。例如:修改了服务监听端口,但是服务没有重启,这时候服务还是监听在老的端口上,会造成不一致
    
    • 设置配置值
    // 可以通过 viper.Set() 函数来显式设置配置:
    viper.Set("user.username", "colin")
    
    • 使用环境变量
    // 使用环境变量
    os.Setenv("VIPER_USER_SECRET_ID", "QLdywI2MrmDVjsSv6e95weNRvmteRjfKAuNV")
    os.Setenv("VIPER_USER_SECRET_KEY", "bVix2WBv0VPfrDrvlLWrhEdzjLpPCNYb")
    viper.AutomaticEnv()                                             // 读取环境变量
    viper.SetEnvPrefix("VIPER")                                      // 设置环境变量前缀:VIPER_,如果是viper,将自动转变为大写。
    viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) // 将viper.Get(key) key字符串中'.'和'-'替换为'_android'
    viper.BindEnv("user.secret-key")
    viper.BindEnv("user.secret-id", "USER_SECRET_ID") // 绑定环境变量名到key
    
    • 使用标志 Viper 支持 Pflag 包,能够绑定 key 到 Flag。与 BindEnv 类似,在调用绑定方法时,不会设置该值,但在访问它时会设置。
    viper.BindPFlag("token", pflag.Lookup("token")) // 绑定单个标志
    viper.BindPFlags(pflag.CommandLine)             //绑定标志集
    

    3.2 读取配置

    • 访问嵌套的键。
    {
        "host": {
            "address": "localhost",
            "port": 5799
        },
        "datastore": {
            "metric": {
                "host": "127.0.0.1",
                "port": 3099
            },
            "warehouse": {
                "host": "198.0.0.1",
                "port": 2112
            }
        }
    }
    viper.GetString("datastore.metric.host") // (返回 "127.0.0.1")
    
    • 反序列化 Viper 可以支持将所有或特定的值解析到结构体、map 等。可以通过两个函数来实现:
    type config struct {
      Port int
      Name string
      PathMap string `mapstructure:"path_map"`
    }
    var C config
    err := viper.Unmarshal(&C)
    if err != nil {
      t.Fatalf("unable to decode into struct, %v", err)
    }
    

    Viper 在后台使用github.com/mitchellh/mapstructure来解析值,其默认情况下使用mapstructure tags。当我们需要将 Viper 读取的配置反序列到我们定义的结构体变量中时,一定要使用 mapstructure tags

    • 序列化成字符串
    import (
        yaml "gopkg.in/yaml.v2"
        // ...
    )
    func yamlStringSettings() string {
        c := viper.AllSettings()
        bs, err := yaml.Marshal(c)
        if err != nil {
            log.Fatalf("unable to marshal config to YAML: %v", err)
        }
        return string(bs)
    }
    

    4.现代化的命令行框架:Cobra

    Cobra 既是一个可以创建强大的现代 CLI 应用程序的库,也是一个可以生成应用和命令文件的程序 应用程序通常遵循如下模式:APPNAME VERB NOUN --ADJECTIVE或者APPNAME COMMAND ARG --FLAG VERB 代表动词,NOUN 代表名词,ADJECTIVE 代表形容词

    4.1 使用 Cobra 库创建命令

    • 创建 rootCmd
    $ mkdir -p newApp2 && cd newApp2
    // 通常情况下,我们会将 rootCmd 放在文件 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() 函数中定义标志和处理配置
    import (
      "fmt"
      "os"
      homedir "github.com/mitchellh/go-homedir"
      "github.com/spf13/cobra"
      "github.com/spf13/viper"
    )
    var (
        cfgFile     string
        projectBase string
        userLicense string
    )
    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 函数来调用 rootCmd,通常我们会创建一个 main.go 文件,在 main.go 中调用 rootCmd.Execute() 来执行命令
    package main
    import (
      "{pathToYourApp}/cmd"
    )
    func main() {
      cmd.Execute()
    }
    
    • 添加命令 通常情况下,我们会把其他命令的源码文件放在 cmd/ 目录下,例如,我们添加一个 version 命令,可以创建 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")
      },
    }
    
    • 编译并运行
    $ go mod init github.com/marmotedu/gopractise-demo/cobra/newApp2
    $ go build -v .
    $ ./newApp2 -h
    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
    Usage:
    hugo [flags]
    hugo [command]
    Available Commands:
    help Help about any command
    version Print the version number of Hugo
    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 hugo
    -l, --license licensetext Name of license for the project (can provide licensetext in config)
    -b, --projectbase string base project directory eg. github.com/spf13/
    --viper Use Viper for configuration (default true)
     Use "hugo [command] --help" for more information about a command.
    

    4.2使用标志

    Cobra 可以跟 Pflag 结合使用,实现强大的标志功能

    // 使用持久化的标志
    rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
    // 分配一个本地标志,本地标志只能在它所绑定的命令上使用
    rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
    // 将标志绑定到 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"))
    }
    // 设置标志为必选
    rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
    rootCmd.MarkFlagRequired("region")
    

    5.总结

    在开发 Go 项目时,我们可以通过 Pflag 来解析命令行参数,通过 Viper 来解析配置文件,用 Cobra 来实现命令行框架。

    你可以通过 pflag.String()、 pflag.StringP()、pflag.StringVar()、pflag.StringVarP() 方法来设置命令行参数,并使用 Get 来获取参数的值

    以上就是go语言Pflag Viper Cobra 核心功能使用介绍的详细内容,更多关于Go语言Pflag Viper Cobra功能的资料请关注我们其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜