Go daily a library of go-flags

Brief introduction

In the previous article , we introduced the flaglibrary. flagLibrary is used to parse command line options. But flagthere 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 Typeor TypeVarfunction;
  • Default supports only limited data types, currently only basic types bool/int/uint/stringand time.Duration;

To solve these problems, there have been many third-party libraries to parse the command line options, today's hero go-flagsis one of them. The first time I saw go-flagsthe library in the reading pgweb source of time.

go-flagsThan the standard library provides flagmore 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-flagsfeatures, let's turn to introduce.

Quick Start

Learn from the beginning to use! Let's take a look at go-flagsthe 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 importintroduced 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-flagsgeneral steps:

  • Define the options structure, information structure setting options tab. By shortand longsetting short and long option name, descriptionset up help information. Command line parameters passed when, before adding short options -, plus the option before long --;
  • Option variable declaration;
  • Call go-flagsanalytic 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 Verbosefield is a slice types, each encounter -vor --verbosewill append a trueto 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-flagsCompared to the standard library flagsupports a richer set of data types:

  • All basic types (including a signed integer int/int8/int16/int32/int64, unsigned integer uint/uint8/uint16/uint32/uint64, floating point float32/float64, boolean bool, and string string) 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 PtrStringSlicefield type []*string.
Since the structure is stored in a string pointers, go-flagsencountered during parsing this option automatically creates a string, the pointer is added to the sections.

Run the program, passing --pstrsliceoptions:

$ ./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 Callfunction simply print incoming option value. Run the code, passing --calloptions:

$ ./main.exe --call test1 --call test2
in callback:  test1
in callback:  test2

Finally, go-flagsalso supported map types. Although the key must be to limit stringthe type, value must be a primitive type, but also to achieve a more flexible configuration.
mapType option key values - values by :the partition, such as key:value, a plurality may be provided. Run the code, passing --intmapoptions:

$ ./main.exe --intmap key1:12 --intmap key2:58
int map: map[key1:12 key2:58]

Common settings

go-flagsProvides a lot of setup options, see specific documentation . Here we focus on two requiredand default.

requiredIt is not empty, it means that the value of the corresponding option must be set, otherwise parsing ErrRequirederror.

defaultThe option of setting default values. If you have set a default value, requiredwhether 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 defaultoptions, Defaultfield default values, do not pass requiredoption, 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 --helpto 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-flagsChild 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 committhese commands version/build/run/status/commitare the sons command.
Use go-flagsthe 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-flagsdefined Commanderinterfaces:

type Commander interface {
    Execute(args []string) error
}

Parsing the command line, if they are not in -or --parameters beginning, go-flagsit will try to interpret it as a sub command name. The name of the sub-commands by using the structure of the label commandspecified.
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-flagsLibrary there are many interesting features, such as support for Windows format options ( /vand /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

  1. flags-Go Github repository
  2. flags-Go godoc document

I

my blog

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!

Guess you like

Origin www.cnblogs.com/darjun/p/12189459.html