Bring your own struct and make Go's flag package pleasant to use.
go get github.com/itzg/go-flagsfiller
import "github.com/itzg/go-flagsfiller"
- Populates Go's flag.FlagSet from a struct of your choosing
- By default, field names are converted to flag names using kebab-case, but can be configured.
- Use nested structs where flag name is prefixed by the nesting struct field names
- Allows defaults to be given via struct tag
default
- Falls back to using instance field values as declared default
- Declare flag usage via struct tag
usage
- Can be combined with other modules, such as google/subcommands for sub-command processing. Can also be integrated with spf13/cobra by using pflag's AddGoFlagSet
- Beyond the standard types supported by flag.FlagSet also includes support for:
[]string
where repetition of the argument appends to the slice and/or an argument value can contain a comma-separated list of values. For example:--arg one --arg two,three
map[string]string
where each entry is akey=value
and/or repetition of the arguments adds to the map or multiple entries can be comma-separated in a single argument value. For example:--arg k1=v1 --arg k2=v2,k3=v3
time.Time
parse via time.Parse(), with taglayout
specify the layout string, default is "2006-01-02 15:04:05"net.IP
parse via net.ParseIP()net.IPNet
parse via net.ParseCIDR()net.HardwareAddr
parse via net.ParseMAC()- and all types that implement encoding.TextUnmarshaler interface
- Optionally set flag values from environment variables. Similar to flag names, environment variable names are derived automatically from the field names
- New types could be supported via user code, via
RegisterSimpleType(ConvertFunc)
, check time.go and net.go to see how it works- note: in case of a registered type also implements encoding.TextUnmarshaler, then registered type's ConvertFunc is preferred
package main
import (
"flag"
"fmt"
"github.com/itzg/go-flagsfiller"
"log"
"time"
)
type Config struct {
Host string `default:"localhost" usage:"The remote host"`
DebugEnabled bool `default:"true" usage:"Show debugs"`
MaxTimeout time.Duration `default:"5s" usage:"How long to wait"`
Feature struct {
Faster bool `usage:"Go faster"`
LudicrousSpeed bool `usage:"Go even faster"`
}
}
func main() {
var config Config
// create a FlagSetFiller
filler := flagsfiller.New()
// fill and map struct fields to flags
err := filler.Fill(flag.CommandLine, &config)
if err != nil {
log.Fatal(err)
}
// parse command-line like usual
flag.Parse()
fmt.Printf("Loaded: %+v\n", config)
}
The following shows an example of the usage provided when passing --help
:
-debug-enabled
Show debugs (default true)
-feature-faster
Go faster
-feature-ludicrous-speed
Go even faster
-host string
The remote host (default "localhost")
-max-timeout duration
How long to wait (default 5s)
saml-auth-proxy shows an end-to-end usage of flagsfiller where the main function fills the flags, maps those to environment variables with envy, and parses the command line:
func main() {
var serverConfig server.Config
filler := flagsfiller.New()
err := filler.Fill(flag.CommandLine, &serverConfig)
if err != nil {
log.Fatal(err)
}
envy.Parse("SAML_PROXY")
flag.Parse()
where server.Config
is declared as
type Config struct {
Version bool `usage:"show version and exit"`
Bind string `default:":8080" usage:"host:port to bind for serving HTTP"`
BaseUrl string `usage:"External URL of this proxy"`
BackendUrl string `usage:"URL of the backend being proxied"`
IdpMetadataUrl string `usage:"URL of the IdP's metadata XML"`
IdpCaPath string `usage:"Optional path to a CA certificate PEM file for the IdP"`
// ...see https://github.com/itzg/saml-auth-proxy/blob/master/server/server.go for full set
}
Flagsfiller can be used in combination with google/subcommands to fill both global command-line flags and subcommand flags.
For the global flags, it is best to declare a struct type, such as
type GlobalConfig struct {
Debug bool `usage:"enable debug logging"`
}
Prior to calling Execute
on the subcommands' Commander
, fill and parse the global flags like normal:
func main() {
//... register subcommands here
var globalConfig GlobalConfig
err := flagsfiller.Parse(&globalConfig)
if err != nil {
log.Fatal(err)
}
//... execute subcommands but pass global config
os.Exit(int(subcommands.Execute(context.Background(), &globalConfig)))
}
Each of your subcommand struct types should contain the flag fields to fill and parse, such as:
type connectCmd struct {
Host string `usage:"the hostname of the server" env:"GITHUB_TOKEN"`
Port int `usage:"the port of the server" default:"8080"`
}
Your implementation of SetFlags
will use flagsfiller to fill the definition of the subcommand's flagset, such as:
func (c *connectCmd) SetFlags(f *flag.FlagSet) {
filler := flagsfiller.New()
err := filler.Fill(f, c)
if err != nil {
log.Fatal(err)
}
}
Finally, your subcommand's Execute
function can accept the global config passed from the main Execute
call and access its own fields populated from the subcommand flags:
func (c *loadFromGitCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
globalConfig := args[0].(*GlobalConfig)
if globalConfig.Debug {
//... enable debug logs
}
// ...operate on subcommand flags, such as
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port))
}
Refer to the GoDocs for more information about this module.