美文网首页
Golang Library Cobra Commanline

Golang Library Cobra Commanline

作者: 大地缸 | 来源:发表于2021-01-13 21:20 被阅读0次

    title: "Golang Library Cobra Commanline Tool"
    date: 2021-01-12T20:10:31+08:00
    draft: true
    tags: ['golang','cobra']
    author: "dadigang"
    author_cn: "大地缸"
    personal: "http://www.real007.cn"


    关于作者

    http://www.real007.cn/about

    golang 命令行解析库cobra的使用

    关于go语言的命令行解析,标准库flag提供的功能比较少,不能满足我的使用需求,所以就需要寻找第三方解决方案了.我选择cobra是因为它支持
    sub command子命令,满足我的使用需求,而且被很多大公司使用(比如github cli),安全性应该没什么问题.

    Overview

    cobra提供简单的接口用于创建类似于git或者go官方工具的命令行工具

    cobra可以快速创建基于cobra的应用的脚手架.

    • 简单的基于子命令的命令行接口: app server, app fetch等等
    • 完全兼容POSIX的flag(包括短和长版本)
    • 嵌套子命令
    • 全局,本地和级联flag
    • 快速生成应用脚手架cobra init appname & cobra add cmdname
    • 智能提示功能 (app srver... did you mean app server?)
    • 自动基于command和flag生成帮助
    • 自动添加帮助flag -h, --help
    • 自动添加shell自动补全功能(bash, zsh, fish, powershell)
    • 自动生成帮助文档
    • command 别名功能
    • 灵活定制help,usage等等
    • 可选 集成viper用于构建12-factor app

    Concept

    cobra基于结构化的command,arguments和flag进行构建

    Commands 代表行动

    Args 表示事物

    Flags 是行动的修饰符

    最好的应用使用起来像读语句一样,用户会自然而然地知道如何使用这个应用

    遵循的原则是

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

    在接下来的例子里, server是一个command,port是一个flag

    hugo server --port=1313
    
    

    下一个命令我们在告诉git,从目标url 克隆一个裸仓库(只拷贝.git子目录)

    git clone URL --bare
    
    

    Commands

    command是应用的核心,每次应用的互动都会包含一个command,一个command可以存在可选的子command,在这个例子hugo server --port=1313里,server就是一个command

    More about cobra.Command

    Flags

    flag是修改command行为的一种方式,cobra提供完全POSIX兼容的flag就像gp官方命令行工具一样.

    cobra的command可以定义影响所有子命令的flag或者只影响一个命令的command

    .在这个例子里,hugo server --port=1313 port是一个flag

    flag功能由pflag库提供.

    Installing

    使用go get下载最新版

    go get -u github.com/spf13/cobra
    
    

    导入项目:

    import "github.com/spf13/cobra"
    
    

    Getting Started

    典型的项目结构

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

    通常main行数比较简单,起到初始化cobra的作用

    package main
    
    import (
    "{pathToYourApp}/cmd"
    )
    
    func main() {
    cmd.Execute()
    }
    
    

    Using the Cobra Generator

    cobra自身提供的程序可以帮助你创建app和添加command,这是最简单的添加cobra到你的应用程序的方式

    Here you can find more information about it.

    Using the Cobra Library

    手动实现cobra,你需要创建一个main.go和rootCmd文件,你需要视情况添加额外的command

    Create rootCmd

    cobra不需要构造函数,简单地创建你的command即可.

    通常把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.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    }
    
    

    可以在init函数中定义flag或者修改配置

    For example cmd/root.go:

    package cmd
    
    import (
        "fmt"
        "os"
    
        homedir "github.com/mitchellh/go-homedir"
        "github.com/spf13/cobra"
        "github.com/spf13/viper"
    )
    
    var (
        // Used for flags.
        cfgFile     string
        userLicense string
    
        rootCmd = &cobra.Command{
            Use:   "cobra",
            Short: "A generator for Cobra based Applications",
            Long: `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.`,
        }
    )
    
    // Execute executes the root command.
    func Execute() error {
        return rootCmd.Execute()
    }
    
    func init() {
        cobra.OnInitialize(initConfig)
    
        rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
        rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
        rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
        rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
        viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
        viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
        viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
        viper.SetDefault("license", "apache")
    
        rootCmd.AddCommand(addCmd)
        rootCmd.AddCommand(initCmd)
    }
    
    func er(msg interface{}) {
        fmt.Println("Error:", msg)
        os.Exit(1)
    }
    
    func initConfig() {
        if cfgFile != "" {
            // Use config file from the flag.
            viper.SetConfigFile(cfgFile)
        } else {
            // Find home directory.
            home, err := homedir.Dir()
            if err != nil {
                er(err)
            }
    
            // Search config in home directory with name ".cobra" (without extension).
            viper.AddConfigPath(home)
            viper.SetConfigName(".cobra")
        }
    
        viper.AutomaticEnv()
    
        if err := viper.ReadInConfig(); err == nil {
            fmt.Println("Using config file:", viper.ConfigFileUsed())
        }
    }
    
    

    Create your main.go

    定义好root command后,你需要在main函数里执行execute

    在cobra app中,通常main.go文件中的内容比较少,只用于初始化cobra

    In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra.

    package main
    
    import (
    "{pathToYourApp}/cmd"
    )
    
    func main() {
    cmd.Execute()
    }
    
    

    Create additional commands

    可以添加新的command,通常我们把每个command放到一个文件里,放在cmd/目录下

    如果你想添加一个version command,你可以创建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")
    },
    }
    
    

    Returning and handling errors

    If you wish to return an error to the caller of a command, RunE can be used.

    如果你想针对command的调用返回一个error,可以使用RunE

    package cmd
    
    import (
    "fmt"
    
    "github.com/spf13/cobra"
    )
    
    func init() {
    rootCmd.AddCommand(tryCmd)
    }
    
    var tryCmd = &cobra.Command{
    Use:   "try",
    Short: "Try and possibly fail at something",
    RunE: func(cmd *cobra.Command, args []string) error {
        if err := someFunc(); err != nil {
        return err
        }
        return nil
    },
    }
    
    

    这样在execute函数调用的时候,error就会被捕获

    Working with Flags

    通过flag传入参数可以控制comand的执行行为

    Assign flags to a command

    由于flag定义后,可以在很多不同的地方被使用,我们需要定义一个变量来接受flag

    var Verbose bool
    var Source string
    
    

    Persistent Flags

    persistent(持久性) flag,可以用于分配给他的命令以及所有子命令.

    下面例子里的verbose作用是让命令行输出的信息更详细,所有的子命令都会支持 verbos flag

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

    Local Flags

    local flag ,本地分配,只会分配给那个特定的command

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

    Local Flag on Parent Commands

    默认情况下 cobra只会在目标command的基础上解析local flag,它的parent command会忽略解析.开启Command.TraverseChildren选项后,cobra就会执行目标command之前,在每个command上解析local command

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

    Bind Flags 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"))
    }
    
    

    在这个persistent flag的例子中persistent flag author ,用viper进行了绑定, 注意,当用户没有使用 --author flag时,就不会从配置文件中读取值

    More in viper documentation.

    Required flags

    Flags are optional by default. If instead you wish your command to
    report an error when a flag has not been set, mark it as required:

    默认情况下flag时可选的,如果你希望缺失flag的时候报错,可以标记为Required flags,如下

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

    或者对于PersistentFlag来说

    rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
    rootCmd.MarkPersistentFlagRequired("region")
    
    

    Positional and Custom Arguments

    位置参数的验证,可以使用commnad的 args字段

    下列验证器是内置的:

    • NoArgs - 如果没有任何参数,command会报错
    • ArbitraryArgs - command接受任何参数
    • OnlyValidArgs - 如果有任何位置参数不在 validArgs的范围里面, command会报错
    • MinimumNArgs(int) - 当位置参数的个数少于n时,command 会报错
    • MaximumNArgs(int) - 当位置参数的个数大于n时,,command会报错
    • ExactArgs(int) - 当位置参数的个数不正好是n个时,command会报错
    • ExactValidArgs(int) - 当不是正好有n个位置参数或者有任何位置参数不属于ValidArgs,command会报错.
    • RangeArgs(min, max) - 如果参数的个数不是在min和max之间,command会报错

    关于自定义验证器的一个例子:

    var cmd = &cobra.Command{
    Short: "hello",
    Args: func(cmd *cobra.Command, args []string) error {
        if len(args) < 1 {
          return errors.New("requires a color argument")
        }
        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!")
    },
    }
    
    

    Example

    在下面的例子里,定义了3个command,其中有两个时最高级别的command,另一个(cmdTimes)时其中一个的子command.在这个例子里,root不能执行意味着需要subcommand,这是通过不给rootCmd提供Run参数来实现的.

    More documentation about flags is available at https://github.com/spf13/pflag

    执行go run main.go echo dasda dasdaa dadsa, 执行go run main.go print dasada dasdafesf fasfdasf

    times是echo的子命令所以执行命令是这样的go run main.go echo times -t 10 dsadas dasda dasda

    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("Echo: " + strings.Join(args, " "))
        },
    }
    
    var cmdTimes = &cobra.Command{
        Use:   "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()
    }
    
    

    For a more complete example of a larger application, please checkout Hugo.

    Help Command

    当你有一个新的subcommand,cobra会自动为你的应用添加help command.当用户执行app help时,会调用默认的help command.并且help支持所有其他输入的command.每个command都会自动添加--helpflag

    Example

    下面的输出是由cobra自动生成的. 除了command和flag你不需要其他东西就能生成help command

    $ 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就和其他command一样,没有特殊的逻辑或行为.如果你想的话也可以自定义help.

    Defining your own help

    你可以提供自己的和help command或者你自己的模板,只需要使用下面的函数

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

    The latter two will also apply to any children commands.

    后两个,还可以提供给子command

    Usage Message

    当用户提供了一个无效的flag或者command,cobra会回复一份usage

    Example

    默认的help,也嵌入了一份usage作为输出

    下面是遇到无效flag,输出usage文档的例子

    $ 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.
    
    

    Defining your own usage

    你可以提供自己的usage函数或者模板,这样就会覆盖公用模板了.

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

    Version Flag

    如果你的root command上有一个version字段,cobra会添加一个--versionflag. 使用--versionflag会使用version模板打印版本到输出.模板可以通过cmd.SetVersionTemplate(s string)函数来自定义

    PreRun and PostRun Hooks

    在你main函数里的run函数运行之前和之后,也是可以运行函数的.PersistentPreRunPreRun 函数会在Run之前执行,PersistentPostRunPostRun会在Run之后执行. 符合Persistent*Run格式的函数如果子命令没有定义自己的Run,就会继承.这些函数的执行顺序如下:

    • PersistentPreRun
    • PreRun
    • Run
    • PostRun
    • PersistentPostRun

    下面是一个两个comand运用了以上所有feature的例子.当子command执行的时候,会执行root command的PersistentPreRun函数而不是root command的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()
    }
    
    

    Output:

    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]
    
    

    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两个属性生成的,

    上面的例子生成的内容是:

    Copyright © 2020 Steve Francia <spf@spf13.com>
    
    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头部被使用.

    /*
    Copyright © 2020 Steve Francia <spf@spf13.com>
    This file is part of CLI application foo.
    */
    
    

    相关文章

      网友评论

          本文标题:Golang Library Cobra Commanline

          本文链接:https://www.haomeiwen.com/subject/mrweaktx.html