argp:Go语言的GNU风格命令行参数解析器
argp: GNU-style command line argument parser for Go

原始链接: https://github.com/tdewolff/argp

Go语言的`argp`包简化了遵循GNU标准的命令行参数解析。它提供了一些特性,例如自动生成帮助信息,使用标签将参数扫描到结构体字段中,以及支持复合字段类型(数组、切片、结构体)。它处理短选项(-v)、长选项(--verbose)、选项值以及交错的选项/非选项。该包支持计数标志出现次数,将值附加到列表中,以及从TOML配置文件加载参数。它还引入了列表和字典数据源,允许从外部资源(如MySQL数据库)读取值。 选项和参数可以使用描述、默认值和自定义名称进行定义。`argp`包还支持子命令,用于更复杂的应用程序。它允许创建结构化、易于维护且用户友好的命令行界面。

Hacker News 上的讨论总结如下: 一个新的 Go 包 `argp` 旨在提供一个 GNU 风格的命令行参数解析器。 评论者 `acmj` 总体上赞扬了其对 GNU 风格的遵守,但指出允许使用 `-a 1 2 3` 而不是标准的 `-a 1 -a 2 -a 3` 来传递多个值可能会造成混淆。他们还注意到 `-a=1` 并非严格意义上的 GNU 风格,`--a=1` 才是。 另一位评论者 `arccy` 表达了对 GNU 风格标志的不一致性的不满,强调了诸如组合选项(`-abc`)、不同的值分隔符(`-a1`,`-a=1`,`-a 1`)以及多个值等特性,认为这些特性使参数解析对人类来说变得困难。
相关文章
  • (评论) 2024-02-08
  • (评论) 2024-08-14
  • 在Go 1.22中,更好的HTTP服务器路由 2023-10-17
  • (评论) 2023-12-11
  • (评论) 2024-01-06

  • 原文

    Command line argument parser following the GNU standard.

    ./test -vo out.png --size 256 input.txt
    

    with the following features:

    • build-in help (-h and --help) message
    • scan arguments into struct fields with configuration in tags
    • scan into composite field types (arrays, slices, structs)
    • allow for nested sub commands

    GNU command line argument rules:

    • arguments are options when they begin with a hyphen -
    • multiple options can be combined: -abc is the same as -a -b -c
    • long options start with two hyphens: --abc is one option
    • option names are alphanumeric characters
    • options can have a value: -a 1 means that a has value 1
    • option values can be separated by a space, equal sign, or nothing: -a1 -a=1 -a 1 are all equal
    • options and non-options can be interleaved
    • the argument -- terminates all options so that all following arguments are treated as non-options
    • a single - argument is a non-option usually used to mean standard in or out streams
    • options may be specified multiple times, only the last one determines its value
    • options can have multiple values: -a 1 2 3 means that a is an array/slice/struct of three numbers of value [1,2,3]

    See also github.com/tdewolff/prompt for a command line prompter.

    Make sure you have Git and Go (1.22 or higher) installed, run

    mkdir Project
    cd Project
    go mod init
    go get -u github.com/tdewolff/argp
    

    Then add the following import

    import (
        "github.com/tdewolff/argp"
    )

    A regular command with short and long options. See cmd/test/main.go.

    package main
    
    import "github.com/tdewolff/argp"
    
    func main() {
        var verbose int
        var input string
        var output string
        var files []string
        size := 512 // default value
    
        cmd := argp.New("CLI tool description")
        cmd.AddOpt(argp.Count{&verbose}, "v", "verbose", "Increase verbosity, eg. -vvv")
        cmd.AddOpt(&output, "o", "output", "Output file name")
        cmd.AddOpt(&size, "", "size", "Image size")
        cmd.AddArg(&input, "input", "Input file name")
        cmd.AddRest(&files, "files", "Additional files")
        cmd.Parse()
    
        // ...
    }

    with help output

    Usage: test [options] input files...
    
    Options:
      -h, --help          Help
      -o, --output string Output file name
          --size=512 int  Image size
      -v, --verbose int   Increase verbosity, eg. -vvv
    
    Arguments:
      input     Input file name
      files     Additional files
    

    Example with sub commands using a main command for when no sub command is used, and a sub command named "cmd". For the main command we can also use New and AddOpt instead and process the command after argp.Parse().

    package main
    
    import "github.com/tdewolff/argp"
    
    func main() {
        cmd := argp.NewCmd(&Main{}, "CLI tool description")
        cmd.AddCmd(&Command{}, "cmd", "Sub command")
        cmd.Parse()
    }
    
    type Main struct {
        Version bool `short:"v"`
    }
    
    func (cmd *Main) Run() error {
        // ...
    }
    
    type Command struct {
        Verbose bool `short:"v" name:""`
        Output string `short:"o" desc:"Output file name"`
        Size int `default:"512" desc:"Image size"`
    }
    
    func (cmd *Command) Run() error {
        // ...
    }
    var input string
    cmd.AddArg(&input, "input", "Input file name")
    
    var files []string
    cmd.AddRest(&files, "files", "Additional input files")

    Basic types

    var v string = "default"
    cmd.AddOpt(&v, "v", "var", "description")
    
    var v bool = true
    cmd.AddOpt(&v, "v", "var", "description")
    
    var v int = 42 // also: int8, int16, int32, int64
    cmd.AddOpt(&v, "v", "var", "description")
    
    var v uint = 42 // also: uint8, uint16, uint32, uint64
    cmd.AddOpt(&v, "v", "var", "description")
    
    var v float64 = 4.2 // also: float32
    cmd.AddOpt(&v, "v", "var", "description")

    Composite types

    v := [2]int{4, 2} // element can be any valid basic or composite type
    cmd.AddOpt(&v, "v", "var", "description")
    // --var [4 2]  =>  [2]int{4, 2}
    // or: --var 4,2  =>  [2]int{4, 2}
    
    v := []int{4, 2, 1} // element can be any valid basic or composite type
    cmd.AddOpt(&v, "v", "var", "description")
    // --var [4 2 1]  =>  []int{4, 2, 1}
    // or: --var 4,2,1  =>  []int{4, 2, 1}
    
    v := map[int]string{1:"one", 2:"two"} // key and value can be any valid basic or composite type
    cmd.AddOpt(&v, "v", "var", "description")
    // --var {1:one 2:two}  =>  map[int]string{1:"one", 2:"two"}
    
    v := struct { // fields can be any valid basic or composite type
        S string
        I int
        B [2]bool
    }{"string", 42, [2]bool{0, 1}}
    cmd.AddOpt(&v, "v", "var", "description")
    // --var {string 42 [0 1]}  =>  struct{S string, I int, B [2]bool}{"string", 42, false, true}

    Count the number of time a flag has been passed.

    var c int
    cmd.AddOpt(argp.Count{&c}, "c", "count", "Count")
    // Count the number of times flag is present
    // -c -c / -cc / --count --count  =>  2
    // or: -c 5  =>  5

    Append each flag to a list.

    var v []int
    cmd.AddOpt(argp.Append{&v}, "v", "value", "Values")
    // Append values for each flag
    // -v 1 -v 2  =>  [1 2]

    Load all arguments from a configuration file. Currently only TOML is supported.

    cmd.AddOpt(&argp.Config{cmd, "config.toml"}, "", "config", "Configuration file")

    Use a list source specified as type:list. Default supported types are: inline.

    • Inline takes a []string, e.g. inline:[foo bar]
    list := argp.NewList(il)
    defer list.Close()
    
    cmd.AddOpt(&list, "", "list", "List")

    You can add a MySQL source:

    type mysqlList struct {
    	Hosts    string
    	User     string
    	Password string
    	Dbname   string
    	Query    string
    }
    
    func newMySQLList(s []string) (argp.ListSource, error) {
    	if len(s) != 1 {
    		return nil, fmt.Errorf("invalid path")
    	}
    
    	t := mysqlList{}
    	if err := argp.LoadConfigFile(&t, s[0]); err != nil {
    		return nil, err
    	}
    
    	uri := fmt.Sprintf("%s:%s@%s/%s", t.User, t.Password, t.Hosts, t.Dbname)
    	db, err := sqlx.Open("mysql", uri)
    	if err != nil {
    		return nil, err
    	}
    	db.SetConnMaxLifetime(time.Minute)
    	db.SetConnMaxIdleTime(time.Minute)
    	db.SetMaxOpenConns(10)
    	db.SetMaxIdleConns(10)
    	return argp.NewSQLList(db, t.Query, "")
    }
    
    // ...
    list.AddSource("mysql", newMySQLList)
    // ...
    

    Use as ./bin -list mysql:list-config.toml.

    Use a dict source specified as type:dict. Default supported types are: static and inline.

    • Static takes a string and will return that as a value for all keys, e.g. static:foobar
    • Inline takes a map[string]string, e.g. inline:{foo:1 bar:2}
    dict := argp.NewDict([]string{"static:value"})
    defer dict.Close()
    
    cmd.AddOpt(&dict, "", "dict", "Dict")

    You can add custom sources must like the mysqlList example above.

    The following struct will accept the following options and arguments:

    • -v or --var with a default value of 42
    • The first argument called first with a default value of 4.2
    • The other arguments called rest
    type Command struct {
        Var1 int `short:"v" name:"var" default:"42" desc:"Description"`
        Var2 float64 `name:"first" index:"0" default:"4.2"`
        Var3 []string `name:"rest" index:"*"`
    }
    
    func (cmd *Command) Run() error {
        // run command
        return nil
    }

    Released under the MIT license.

    联系我们 contact @ memedata.com