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.