Brief introduction
In the previous article , we introduced the flag
library. flag
Library is used to parse command line options. But flag
there are several drawbacks:
- Do not show support for short options. Of course, the article also mentioned two options can be shared by the same circuitous achieve a variable, but to write more complicated;
- More complicated variables defined options, each option corresponding to call according to the type
Type
orTypeVar
function; - Default supports only limited data types, currently only basic types
bool/int/uint/string
andtime.Duration
;
To solve these problems, there have been many third-party libraries to parse the command line options, today's hero go-flags
is one of them. The first time I saw go-flags
the library in the reading pgweb source of time.
go-flags
Than the standard library provides flag
more options. It uses the structure of the label (struct tag) and provides a reflected convenient, simple interface. In addition to its basic functions, but also provides a wealth of features:
- Support short options (-v) and long options (--verbose);
- Support co-wrote the short options, such as
-aux
; - With an option to set up multiple values;
- It supports all basic types and map types, or even function;
- Support namespaces and options group;
- and many more.
The above is only a rough description of the go-flags
features, let's turn to introduce.
Quick Start
Learn from the beginning to use! Let's take a look at go-flags
the basic use.
Because it is third-party libraries need to be installed before use, execute the following command to install:
$ go get github.com/jessevdk/go-flags
Used in the code import
introduced into the library:
import "github.com/jessevdk/go-flags"
Complete sample code is as follows:
package main
import (
"fmt"
"github.com/jessevdk/go-flags"
)
type Option struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug message"`
}
func main() {
var opt Option
flags.Parse(&opt)
fmt.Println(opt.Verbose)
}
Use go-flags
general steps:
- Define the options structure, information structure setting options tab. By
short
andlong
setting short and long option name,description
set up help information. Command line parameters passed when, before adding short options-
, plus the option before long--
; - Option variable declaration;
- Call
go-flags
analytic method of resolution.
Compile and run the code (My environment is Win10 + Git Bash):
$ go build -o main.exe main.go
Short options:
$ ./main.exe -v
[true]
Long options:
$ ./main.exe --verbose
[true]
Since the Verbose
field is a slice types, each encounter -v
or --verbose
will append a true
to slice.
Multiple short options:
$ ./main.exe -v -v
[true true]
More long options:
$ ./main.exe --verbose --verbose
[true true]
Short Options + long options:
$ ./main.exe -v --verbose -v
[true true true]
Co-wrote the short option:
$ ./main.exe -vvv
[true true true]
Basic characteristics
Support for rich data types
go-flags
Compared to the standard library flag
supports a richer set of data types:
- All basic types (including a signed integer
int/int8/int16/int32/int64
, unsigned integeruint/uint8/uint16/uint32/uint64
, floating pointfloat32/float64
, booleanbool
, and stringstring
) and their sections ; - map types. Only support for the key
string
, based on the value of the type of Map; - Function types.
If the field is of type slice base, base type and substantially corresponding to the resolution process is the same. The difference is that the slice type options, encountered the same options, the value will be added to the slices. Rather than the slice type of option value overrides the value that appears first appear.
A look at the following example:
package main
import (
"fmt"
"github.com/jessevdk/go-flags"
)
type Option struct {
IntFlag int `short:"i" long:"int" description:"int flag value"`
IntSlice []int `long:"intslice" description:"int slice flag value"`
BoolFlag bool `long:"bool" description:"bool flag value"`
BoolSlice []bool `long:"boolslice" description:"bool slice flag value"`
FloatFlag float64 `long:"float", description:"float64 flag value"`
FloatSlice []float64 `long:"floatslice" description:"float64 slice flag value"`
StringFlag string `short:"s" long:"string" description:"string flag value"`
StringSlice []string `long:"strslice" description:"string slice flag value"`
PtrStringSlice []*string `long:"pstrslice" description:"slice of pointer of string flag value"`
Call func(string) `long:"call" description:"callback"`
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
}
func main() {
var opt Option
opt.Call = func (value string) {
fmt.Println("in callback: ", value)
}
err := flags.Parse(&opt, os.Args[1:])
if err != nil {
fmt.Println("Parse error:", err)
return
}
fmt.Printf("int flag: %v\n", opt.IntFlag)
fmt.Printf("int slice flag: %v\n", opt.IntSlice)
fmt.Printf("bool flag: %v\n", opt.BoolFlag)
fmt.Printf("bool slice flag: %v\n", opt.BoolSlice)
fmt.Printf("float flag: %v\n", opt.FloatFlag)
fmt.Printf("float slice flag: %v\n", opt.FloatSlice)
fmt.Printf("string flag: %v\n", opt.StringFlag)
fmt.Printf("string slice flag: %v\n", opt.StringSlice)
fmt.Println("slice of pointer of string flag: ")
for i := 0; i < len(opt.PtrStringSlice); i++ {
fmt.Printf("\t%d: %v\n", i, *opt.PtrStringSlice[i])
}
fmt.Printf("int map: %v\n", opt.IntMap)
}
Basic types and their slice is relatively simple, do not introduce too much. It is noteworthy that the slice base type pointer, i.e. above the PtrStringSlice
field type []*string
.
Since the structure is stored in a string pointers, go-flags
encountered during parsing this option automatically creates a string, the pointer is added to the sections.
Run the program, passing --pstrslice
options:
$ ./main.exe --pstrslice test1 --pstrslice test2
slice of pointer of string flag:
0: test1
1: test2
In addition, we can define the function type options. The only requirement is a function of the parameters of type string. Parsing each encounter will be the option to option value parameters to call this function.
The above code, the Call
function simply print incoming option value. Run the code, passing --call
options:
$ ./main.exe --call test1 --call test2
in callback: test1
in callback: test2
Finally, go-flags
also supported map types. Although the key must be to limit string
the type, value must be a primitive type, but also to achieve a more flexible configuration.
map
Type option key values - values by :
the partition, such as key:value
, a plurality may be provided. Run the code, passing --intmap
options:
$ ./main.exe --intmap key1:12 --intmap key2:58
int map: map[key1:12 key2:58]
Common settings
go-flags
Provides a lot of setup options, see specific documentation . Here we focus on two required
and default
.
required
It is not empty, it means that the value of the corresponding option must be set, otherwise parsing ErrRequired
error.
default
The option of setting default values. If you have set a default value, required
whether the setting is not affected, that is the command-line argument This option can not.
See the following example:
package main
import (
"fmt"
"log"
"github.com/jessevdk/go-flags"
)
type Option struct {
Required string `short:"r" long:"required" required:"true"`
Default string `short:"d" long:"default" default:"default"`
}
func main() {
var opt Option
_, err := flags.Parse(&opt)
if err != nil {
log.Fatal("Parse error:", err)
}
fmt.Println("required: ", opt.Required)
fmt.Println("default: ", opt.Default)
}
Run the program, not passing default
options, Default
field default values, do not pass required
option, execution error:
$ ./main.exe -r required-data
required: required-data
default: default
$ ./main.exe -d default-data -r required-data
required: required-data
default: default-data
$ ./main.exe
the required flag `/r, /required' was not specified
2020/01/09 18:07:39 Parse error:the required flag `/r, /required' was not specified
Advanced Features
Options are grouped
package main
import (
"fmt"
"log"
"os"
"github.com/jessevdk/go-flags"
)
type Option struct {
Basic GroupBasicOption `description:"basic type" group:"basic"`
Slice GroupSliceOption `description:"slice of basic type" group:"slice"`
}
type GroupBasicOption struct {
IntFlag int `short:"i" long:"intflag" description:"int flag"`
BoolFlag bool `short:"b" long:"boolflag" description:"bool flag"`
FloatFlag float64 `short:"f" long:"floatflag" description:"float flag"`
StringFlag string `short:"s" long:"stringflag" description:"string flag"`
}
type GroupSliceOption struct {
IntSlice int `long:"intslice" description:"int slice"`
BoolSlice bool `long:"boolslice" description:"bool slice"`
FloatSlice float64 `long:"floatslice" description:"float slice"`
StringSlice string `long:"stringslice" description:"string slice"`
}
func main() {
var opt Option
p := flags.NewParser(&opt, flags.Default)
_, err := p.ParseArgs(os.Args[1:])
if err != nil {
log.Fatal("Parse error:", err)
}
basicGroup := p.Command.Group.Find("basic")
for _, option := range basicGroup.Options() {
fmt.Printf("name:%s value:%v\n", option.LongNameWithNamespace(), option.Value())
}
sliceGroup := p.Command.Group.Find("slice")
for _, option := range sliceGroup.Options() {
fmt.Printf("name:%s value:%v\n", option.LongNameWithNamespace(), option.Value())
}
}
The above basic code we slice types and their type option to split the two structures, this can make the code appear sharper and more natural, especially in the case where a large amount of code.
This also has the advantage, we try to use --help
to run the program:
$ ./main.exe --help
Usage:
D:\code\golang\src\github.com\darjun\go-daily-lib\go-flags\group\main.exe [OPTIONS]
basic:
/i, /intflag: int flag
/b, /boolflag bool flag
/f, /floatflag: float flag
/s, /stringflag: string flag
slice:
/intslice: int slice
/boolslice bool slice
/floatslice: float slice
/stringslice: string slice
Help Options:
/? Show this help message
/h, /help Show this help message
Help output, the packet is in accordance with our set of shows for easy viewing.
Subcommand
go-flags
Child support orders. We often use the command-line program Git Go and have a large number of sub-commands. For example go version
, go build
, go run
, git status
, git commit
these commands version/build/run/status/commit
are the sons command.
Use go-flags
the definition of sub-command is simple:
package main
import (
"errors"
"fmt"
"log"
"strconv"
"strings"
"github.com/jessevdk/go-flags"
)
type MathCommand struct {
Op string `long:"op" description:"operation to execute"`
Args []string
Result int64
}
func (this *MathCommand) Execute(args []string) error {
if this.Op != "+" && this.Op != "-" && this.Op != "x" && this.Op != "/" {
return errors.New("invalid op")
}
for _, arg := range args {
num, err := strconv.ParseInt(arg, 10, 64)
if err != nil {
return err
}
this.Result += num
}
this.Args = args
return nil
}
type Option struct {
Math MathCommand `command:"math"`
}
func main() {
var opt Option
_, err := flags.Parse(&opt)
if err != nil {
log.Fatal(err)
}
fmt.Printf("The result of %s is %d", strings.Join(opt.Math.Args, opt.Math.Op), opt.Math.Result)
}
Sub-command must implement go-flags
defined Commander
interfaces:
type Commander interface {
Execute(args []string) error
}
Parsing the command line, if they are not in -
or --
parameters beginning, go-flags
it will try to interpret it as a sub command name. The name of the sub-commands by using the structure of the label command
specified.
Subcommand parameters will be back as a parameter sub-command, subcommands can also have options.
In the above code, we can achieve a calculated arbitrary integer add, subtract, multiply, divide subcommand math
.
Then take a look at how you can use:
$ ./main.exe math --op + 1 2 3 4 5
The result of 1+2+3+4+5 is 15
$ ./main.exe math --op - 1 2 3 4 5
The result of 1-2-3-4-5 is -13
$ ./main.exe math --op x 1 2 3 4 5
The result of 1x2x3x4x5 is 120
$ ./main.exe math --op ÷ 120 2 3 4 5
The result of 120÷2÷3÷4÷5 is 1
Note that, not using a multiplication sign *
and division symbols /
, they are unrecognizable.
other
go-flags
Library there are many interesting features, such as support for Windows format options ( /v
and /verbose
) read the default values from environment variables, read the default settings from ini file, and so on. We are interested can go to their own research -
reference
I
I welcome the attention of the public micro-channel number] [GoUpUp, learn together and progress together ~
This article from the blog article multiple platforms OpenWrite release!