flagsfiller

package module
v1.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2023 License: MIT Imports: 11 Imported by: 23

README

go-flagsfiller

Bring your own struct and make Go's flag package pleasant to use.

Install

go get github.com/itzg/go-flagsfiller

Import

import "github.com/itzg/go-flagsfiller"

Features

  • 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 a key=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 tag layout 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

Quick example

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)

Real world example

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
}

Using with google/subcommands

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))
}

More information

Refer to the GoDocs for more information about this module.

Documentation

Overview

Package flagsfiller makes Go's flag package pleasant to use by mapping the fields of a given struct into flags in a FlagSet.

Quick Start

A FlagSetFiller is created with the New constructor, passing it any desired FillerOptions. With that, call Fill, passing it a flag.FlatSet, such as flag.CommandLine, and your struct to be mapped.

Even a simple struct with no special changes can be used, such as:

type Config struct {
	Host string
	Enabled bool
}
var config Config

// create a FlagSetFiller
filler := flagsfiller.New()
// fill and map struct fields to flags
filler.Fill(flag.CommandLine, &config)
// parse command-line like usual
flag.Parse()

After calling Parse on the flag.FlagSet, the corresponding fields of the mapped struct will be populated with values passed from the command-line.

For an even quicker start, flagsfiller provides a convenience Parse function that does the same as the snippet above in one call:

type Config struct {
	Host string
	Enabled bool
}
var config Config

flagsfiller.Parse(&config)

Flag Naming

By default, the flags are named by taking the field name and performing a word-wise conversion to kebab-case. For example the field named "MyMultiWordField" becomes the flag named "my-multi-word-field".

The naming strategy can be changed by passing a custom Renamer using the WithFieldRenamer option in the constructor.

Additional aliases, such as short names, can be declared with the `aliases` tag as a comma-separated list:

type Config struct {
	Timeout time.Duration `aliases:"t"`
	Limit   int `aliases:"l,lim"`
}

Nested Structs

FlagSetFiller supports nested structs and computes the flag names by prefixing the field name of the struct to the names of the fields it contains. For example, the following maps to the flags named remote-host, remote-auth-username, and remote-auth-password:

type Config struct {
	Remote struct {
		Host string
		Auth struct {
			Username string
			Password string
		}
	}
}

Flag Usage

To declare a flag's usage add a `usage:""` tag to the field, such as:

type Config struct {
	Host string `usage:"the name of the host to access"`
}

Since flag.UnquoteUsage normally uses back quotes to locate the argument placeholder name but struct tags also use back quotes, flagsfiller will instead use [square brackets] to define the placeholder name, such as:

SomeUrl      string `usage:"a [URL] to configure"`

results in the rendered output:

-some-url URL
	a URL to configure

Defaults

To declare the default value of a flag, you can either set a field's value before passing the struct to process, such as:

type Config struct {
	Host string
}
var config = Config{Host:"localhost"}

or add a `default:""` tag to the field. Be sure to provide a valid string that can be converted into the field's type. For example,

type Config struct {
	Host 	string `default:"localhost"`
	Timeout time.Duration `default:"1m"`
}

String Slices

FlagSetFiller also includes support for []string fields. Repetition of the argument appends to the slice and/or an argument value can contain a comma or newline separated list of values.

For example:

--arg one --arg two,three

results in a three element slice.

The default tag's value is provided as a comma-separated list, such as

MultiValues []string `default:"one,two,three"`

Maps of String to String

FlagSetFiller also includes support for map[string]string fields. Each argument entry is a key=value and/or repetition of the arguments adds to the map or multiple entries can be comma or newline separated in a single argument value.

For example:

--arg k1=v1 --arg k2=v2,k3=v3

results in a map with three entries.

The default tag's value is provided a comma-separate list of key=value entries, such as

Mappings map[string]string `default:"k1=v1,k2=v2,k3=v3"`

Other supported types

FlagSetFiller also supports following field types:

- net.IP: format used by net.ParseIP() - net.IPNet: format used by net.ParseCIDR() - net.HardwareAddr (MAC addr): format used by net.ParseMAC() - time.Time: format is the layout string used by time.Parse(), default layout is time.DateTime, could be overriden by field tag "layout"

Environment variable mapping

To activate the setting of flag values from environment variables, pass the WithEnv option to flagsfiller.New or flagsfiller.Parse. That option takes a prefix that will be prepended to the resolved field name and then the whole thing is converted to SCREAMING_SNAKE_CASE.

The environment variable name will be automatically included in the flag usage along with the standard inclusion of the default value. For example, using the option WithEnv("App") along with the following field declaration

Host string `default:"localhost" usage:"the host to use"`

would render the following usage:

-host string
  	the host to use (env APP_HOST) (default "localhost")

Per-field overrides

To override the naming of a flag, the field can be declared with the tag `flag:"name"` where the given name will be used exactly as the flag name. An empty string for the name indicates the field should be ignored and no flag is declared. For example,

Host        string `flag:"server_address"
GetsIgnored string `flag:""`

Environment variable naming and processing can be overridden with the `env:"name"` tag, where the given name will be used exactly as the mapped environment variable name. If the WithEnv or WithEnvRenamer options were enabled, a field can be excluded from environment variable mapping by giving an empty string. Conversely, environment variable mapping can be enabled per field with `env:"name"` even when the flagsfiller-wide option was not included. For example,

Host 			string `env:"SERVER_ADDRESS"`
NotEnvMapped 	string `env:""`

This file implements support for all types that support interface encoding.TextUnmarshaler

Example
type Config struct {
	Host      string        `default:"localhost" usage:"The remote host"`
	Enabled   bool          `default:"true" usage:"Turn it on"`
	Automatic bool          `default:"false" usage:"Make it automatic" aliases:"a"`
	Retries   int           `default:"1" usage:"Retry" aliases:"r,t"`
	Timeout   time.Duration `default:"5s" usage:"How long to wait"`
}

var config Config

flagset := flag.NewFlagSet("ExampleBasic", flag.ExitOnError)

filler := flagsfiller.New()
err := filler.Fill(flagset, &config)
if err != nil {
	log.Fatal(err)
}

err = flagset.Parse([]string{"--host", "external.svc", "--timeout", "10m", "-a", "-t", "2"})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("%+v\n", config)
Output:

{Host:external.svc Enabled:true Automatic:true Retries:2 Timeout:10m0s}

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultFieldRenamer = KebabRenamer()

DefaultFieldRenamer is used when no WithFieldRenamer option is passed to the FlagSetFiller constructor.

View Source
var DefaultTimeLayout = "2006-01-02 15:04:05"

DefaultTimeLayout is the default layout string to parse time, following golang time.Parse() format, can be overridden per field by field tag "layout". Default value is "2006-01-02 15:04:05", which is the same as time.DateTime in Go 1.20

Functions

func Parse added in v1.3.0

func Parse(from interface{}, options ...FillerOption) error

Parse is a convenience function that creates a FlagSetFiller with the given options, fills and maps the flags from the given struct reference into flag.CommandLine, and uses flag.Parse to parse the os.Args. Returns an error if the given struct could not be used for filling flags.

func RegisterSimpleType added in v1.11.0

func RegisterSimpleType[T any](c ConvertFunc[T])

RegisterSimpleType register a new type, should be called in init(), see time.go and net.go for implementation examples

func RegisterTextUnmarshaler added in v1.12.0

func RegisterTextUnmarshaler(in any)

RegisterTextUnmarshaler use is optional, since flagsfiller will automatically register the types implement encoding.TextUnmarshaler it encounters

Types

type ConvertFunc added in v1.11.0

type ConvertFunc[T any] func(s string, tag reflect.StructTag) (T, error)

ConvertFunc is a function convert string s into a specific type T, the tag is the struct field tag, as addtional input. see time.go and net.go for implementation examples

type FillerOption

type FillerOption func(opt *fillerOptions)

FillerOption instances are passed to the FlagSetFiller constructor.

func NoSetFromEnv added in v1.9.0

func NoSetFromEnv() FillerOption

NoSetFromEnv ignores setting values from the environment. All environment variable renamers are run but values are not set from the environment. This is good to use if you need to build a flag set with default values that don't consider the current environment.

func WithEnv added in v1.4.0

func WithEnv(prefix string) FillerOption

WithEnv activates pre-setting the flag values from environment variables. Fields are mapped to environment variables names by prepending the given prefix and converting word-wise to SCREAMING_SNAKE_CASE. The given prefix can be empty.

Example
type Config struct {
	MultiWordName string
}

// simulate env variables from program invocation
_ = os.Setenv("MY_APP_MULTI_WORD_NAME", "from env")

var config Config

// enable environment variable processing with given prefix
filler := flagsfiller.New(flagsfiller.WithEnv("MyApp"))
var flagset flag.FlagSet
_ = filler.Fill(&flagset, &config)

// simulate no args passed in
_ = flagset.Parse([]string{})

fmt.Println(config.MultiWordName)
Output:

from env

func WithEnvRenamer added in v1.4.0

func WithEnvRenamer(renamer Renamer) FillerOption

WithEnvRenamer activates pre-setting the flag values from environment variables where fields are mapped to environment variable names by applying the given Renamer

func WithFieldRenamer

func WithFieldRenamer(renamer Renamer) FillerOption

WithFieldRenamer declares an option to customize the Renamer used to convert field names to flag names.

func WithValueSplitPattern added in v1.14.0

func WithValueSplitPattern(pattern string) FillerOption

WithValueSplitPattern allows for changing the default value splitting regex pattern from newlines and commas. Any empty string can be provided for pattern to disable value splitting.

type FlagSetFiller

type FlagSetFiller struct {
	// contains filtered or unexported fields
}

FlagSetFiller is used to map the fields of a struct into flags of a flag.FlagSet

func New

func New(options ...FillerOption) *FlagSetFiller

New creates a new FlagSetFiller with zero or more of the given FillerOption's

func (*FlagSetFiller) Fill

func (f *FlagSetFiller) Fill(flagSet *flag.FlagSet, from interface{}) error

Fill populates the flagSet with a flag for each field in given struct passed in the 'from' argument which must be a struct reference. Fill returns an error when a non-struct reference is passed as 'from' or a field has a default tag which could not converted to the field's type.

type Renamer

type Renamer func(name string) string

Renamer takes a field's name and returns the flag name to be used

func CompositeRenamer added in v1.4.0

func CompositeRenamer(renamers ...Renamer) Renamer

CompositeRenamer applies all of the given Renamers to a name

Example
renamer := flagsfiller.CompositeRenamer(
	flagsfiller.PrefixRenamer("App"),
	flagsfiller.ScreamingSnakeRenamer())

renamed := renamer("SomeFieldName")
fmt.Println(renamed)
Output:

APP_SOME_FIELD_NAME

func KebabRenamer added in v1.4.0

func KebabRenamer() Renamer

KebabRenamer converts a given name into kebab-case

func PrefixRenamer added in v1.4.0

func PrefixRenamer(prefix string) Renamer

PrefixRenamer prepends the given prefix to a name

func ScreamingSnakeRenamer added in v1.4.0

func ScreamingSnakeRenamer() Renamer

ScreamingSnakeRenamer converts a given name into SCREAMING_SNAKE_CASE

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL