[Golang] Talk about Gos libraries that handle command line parameters and configuration files

Preface

You should have been writing Go recently, so you won't have to work with some command line parameters and configuration files.Although the native flag libraries for Go are easier to use than other languages in dealing with command line parameters, there are many useful libraries in the Go community.This article mainly introduces you to the libraries you have used during this period of time, and provides some reference for friends who have the same needs.

flag

First, it's necessary to give a brief introduction to Go flag's native library, just to code it

Basic Usage

var id = flag.Int("id", 1, "user id")
var mail = flag.String("mail", "test@gmail.com", "mail")
var help = flag.Bool("h", false, "this help")

You can also use pointer variables to receive flag s

var name string
flag.StringVar(&name, "name", "leeif", "your name")

A variable can also be a structure that implements the flag.Value interface

type Address struct {
    s string
}

func (a *Address) String() string {
    return a.s
}

func (a *Address) Set(s string) error {
    if s == "" {
        return errors.New("address can't be empty")
    }
    a.s = s
    return nil
}

ad := Address{}
flag.Var(&ad, "address", "address of the server")

analysis

flag.Parse()

Complete Code
https://play.golang.org/p/mjgZ6SJMeAm

flagSet can be used to handle subcommand s

upload := flag.NewFlagSet("upload", flag.ContinueOnError)
localFile := upload.Bool("localFile", false, "")
download := flag.NewFlagSet("download", flag.ContinueOnError)
remoteFile := download.Bool("remoteFile", false, "")

switch os.Args[1] {
  case "upload":
    if err := upload.Parse(os.Args[2:]); err == nil {
      fmt.Println("upload", *localFile)
    }
  case "download":
    if err := download.Parse(os.Args[2:]); err == nil {
      fmt.Println("download", *remoteFile)
    }
}

The specified form of the command line.

-flag (Or it can be--flag)
-flag=x
-flag x  // non-boolean flags only

Native flags are enough for simple needs, but it's inconvenient to build some complex applications.However, the extensibility of flag has also spawned many unique third-party libraries.

kingpin

https://github.com/alecthomas...

Some main features:

  • The programming style of fluent-style
  • Not only can flag be parsed, but also non-flag parameters
  • Forms that support short parameters
  • sub command

General usage

debug   = kingpin.Flag("debug", "Enable debug mode.").Bool()
// flag that can be overridden by environment variables
// Short method can specify short parameters
timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration()
// Parameters of type IP
// Required parameter is a parameter that must be specified
ip      = kingpin.Arg("ip", "IP address to ping.").Required().IP()
count   = kingpin.Arg("count", "Number of packets to send").Int()

Receive flag with pointer type

var test string
kingpin.Flag("test", "test flag").StringVar(&test)

Parameter types that implement the kingpin.Value interface

type Address struct {
    s string
}

func (a *Address) String() string {
    return a.s
}

func (a *Address) Set(s string) error {
    if s == "" {
        return errors.New("address can't be empty")
    }
    a.s = s
    return nil
}

ad := Address{}
kingpin.Flag("address", "address of the server").SetValue

analysis

kingpin.Parse()

Use sub command

var (
  deleteCommand     = kingpin.Command("delete", "Delete an object.")
  deleteUserCommand = deleteCommand.Command("user", "Delete a user.")
  deleteUserUIDFlag = deleteUserCommand.Flag("uid", "Delete user by UID rather than username.")
  deleteUserUsername = deleteUserCommand.Arg("username", "Username to delete.")
  deletePostCommand = deleteCommand.Command("post", "Delete a post.")
)

func main() {
  switch kingpin.Parse() {
  case deleteUserCommand.FullCommand():
  case deletePostCommand.FullCommand():
  }
}

kingpin automatically generates help text.You don't need to set anything up--help lets you see it.-h requires manual configuration.

kingpin.HelpFlag.Short('h')

cobra

https://github.com/spf13/cobra
Cobra is a command line parameter library that go programmers must know.Many large projects are built with cobra.
cobra is a project for application-level command-line tools. It not only provides basic Command-line processing functions, but also provides a framework for building command-line tools.

The karyotype architecture of cobra.

▾ appName/
    ▾ cmd/
        root.go
        sub.go
      main.go

All command line configurations are distributed among files, such as root.go

package cmd

import (
    "fmt"
    "os"
    "github.com/spf13/cobra"
)

func init() {
  rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
}
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)
  }
}

sub.go

package cmd

import (
  "fmt"

  "github.com/spf13/cobra"
)

func init() {
  rootCmd.AddCommand(subCmd)
}

var subCmd = &cobra.Command{
  Use:   "sub command",
  Short: "short description",
  Long:  `long description`,
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("sub command")
  },
}

In the outermost main.go, just write one sentence.

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

Building command line tools with the cobra architecture will make the architecture clearer.

viper

https://github.com/spf13/viper
viper uses tools specifically for working with configuration files, since the author and Cobra author are the same person, they are often used with cobra.Even in Cobra's official description
The most basic way to use viper.

viper.SetConfigName("config") // name of config file (without extension)
viper.AddConfigPath("/etc/appname/")   // path to look for the config file in
viper.AddConfigPath("$HOME/.appname")  // call multiple times to add many search paths
viper.AddConfigPath(".")               // optionally look for config in the working directory
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
    panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

Gets the read parameter of type map[string]interface{}.

c := viper.AllSettings()

viper also provides flag handling, but personally does not feel as useful as the two libraries above and is not covered here.

kiper

Often we work with both command line parameters and configuration files, and we want to merge them.

Although it can be achieved with cobra+viper, individuals like kingpin because kingpin checks the correctness of parameters (by implementing the data type of the kingpin.Value interface).

So I wrote a wrapper tool for kingpin+viper, kiper.
https://github.com/leeif/kiper

Main features:

  • Configure flag settings via tag (kingpin)
  • Read configuration file through viper
  • Automatically merge flag and profile parameters

Specific usage

package main

import (
    "errors"
    "fmt"
    "os"
    "strconv"

    "github.com/leeif/kiper"
)

type Server struct {
    Address *Address `kiper_value:"name:address"`
    Port    *Port    `kiper_value:"name:port"`
}

type Address struct {
    s string
}

func (address *Address) Set(s string) error {
    if s == "" {
        return errors.New("address can't be empty")
    }
    address.s = s
    return nil
}

func (address *Address) String() string {
    return address.s
}

type Port struct {
    p string
}

func (port *Port) Set(p string) error {
    if _, err := strconv.Atoi(p); err != nil {
        return errors.New("not a valid port value")
    }
    port.p = p
    return nil
}

func (port *Port) String() string {
    return port.p
}

type Config struct {
    ID     *int   `kiper_value:"name:id;required;default:1"`
    Server Server `kiper_config:"name:server"`
}

func main() {
    // initialize config struct
    c := &Config{
        Server: Server{
            Address: &Address{},
            Port:    &Port{},
        },
    }

    // new kiper
    k := kiper.NewKiper("example", "example of kiper")
    k.SetConfigFileFlag("config", "config file", "./config.json")
    k.Kingpin.HelpFlag.Short('h')

    // parse command line and config file
    if err := k.Parse(c, os.Args[1:]); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println(c.Server.Port)
    fmt.Println(*c.ID)
}

The configuration file needs to be consistent with the Config structure.

config.json

{
    "server": {
        "address": "192.0.0.1",
        "port": "8080"
    },
    "id": 2
}

What needs to be improved

  • The sub command function is not yet available.
  • Configuration files always override command line parameters (merge priority)

summary

The Go community provides tools for developing command line parameters and configuration files.Each tool has its own features and scenarios.flag, for example, is native support and scalable.kingpin checks the correctness of the parameters.cobra is good for building complex command line tools.Developers can choose the tools they want to use based on their needs, and this degree of selectivity and freedom is also the greatest appeal of the Gocommunity.

Keywords: Go github JSON Programming

Added by estan on Thu, 12 Sep 2019 20:04:26 +0300