flagbind

package module
v0.0.0-...-4d2be03 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2020 License: MIT Imports: 10 Imported by: 1

README

package flagbind

GoDoc Build Status Coverage Status

Package flagbind parses the exported fields of a struct and binds them to flags in a flag.FlagSet or pflag.FlagSet.

Bind allows for creating flags declaratively right alongside the definition of their containing struct. For example, the following stuct could be passed to Bind to populate a flag.FlagSet or pflag.FlagSet.

flags := struct {
        StringFlag string `flag:"flag-name;default value;Usage for string-flag"`
        Int        int    `flag:"integer;5"`

        // Flag names default to `auto-kebab-case`
        AutoKebabCase int

        // If pflag is used, -s is be used as the shorthand flag name,
        // otherwise it is ignored for use with the standard flag package.
        ShortName bool `flag:"short,s"`

        // Optionally extende the usage tag with subsequent `use` tags
        // on _ fields.
        URL string `flag:"url,u;http://www.example.com/;Start usage here"
        _   struct{} `use:"continue longer usage string for --url below it",

        // Nested and Embedded structs can add a flag name prefix, or not.
        Nested     StructA
        NestedFlat StructB           `flag:";;;flatten"`
        StructA                      // Flat by default
        StructB    `flag:"embedded"` // Add prefix to nested field flag names.

        // Ignored
        ExplicitlyIgnored bool `flag:"-"`
        unexported        bool
}{
        // Default values may also be set directly to override the tag.
        StringFlag: "override default",
        _URL: "Include a longer usage string for --url here",
}

fs := pflag.NewFlagSet("", pflag.ContinueOnError)
flagbind.Bind(fs, &flags)
fs.Parse([]string{"--auto-kebab-case"})

Bind works seemlessly with both the standard library flag package and the popular [github.com/spf13/pflag](https://github.com/spf13/pflag) package.

If pflag is used, for types that implement flag.Value but not pflag.Value, Bind wraps them in an adapter so that they can still be used as a pflag.Value. The return value of the additional function Type() string is the type name of the struct field.

Additional options may be set for each flag. See Bind for the full documentation details.

Documentation

Overview

Package flagbind makes defining flags as simple as defining a struct type.

flagbind.Bind parses the exported fields of a struct and binds them to a FlagSet, which can be the standard flag package or the popular pflag package github.com/spf13/pflag.

By coupling the flag definitions with a type definition, the use of globals is discouraged, and flag types may be coupled with related behavior allowing for better organized and documented code.

Flag names, usage, defaults and other options can be set using struct tags on exported fields. Using struct nesting, flags can be composed and assigned a flag name prefix. Exposing the settings of another package as flags is as simple as embedding the relevant types.

Alternatively, any type may implement the Binder interface, which allows for more direct control over the FlagSet, much like json.Unmarshal passes control to types that implement json.Unmarshaler. Also similar to json.Unmarshal, Bind will initialize any fields that are nil, and leave any fields that are already populated, as defaults.

See Bind documentation for the full details on controling flags.

Bind works seamlessly with both the standard library flag package and the popular github.com/spf13/pflag package.

If pflag is used, Bind adapts a flag.Value to a pflag.Value. The underlying type name of the flag.Value is used as the return value of the additional `Type() string` function required by the pflag.Value interface.

Getting Started

Start by declaring a struct type for your flags.

var flags := struct {
        StringFlag string `flag:"flag-name;default value;Usage for string-flag"`
        Int        int    `flag:"integer;5"`

        // Flag names default to `auto-kebab-case`
        AutoKebabCase int

        // If pflag is used, -s is be used as the shorthand flag name,
        // otherwise it is ignored for use with the standard flag package.
        ShortName bool `flag:"short,s"`

        // Optionally extende the usage tag with subsequent `use` tags
        // on _ fields.
        URL string `flag:"url,u;http://www.example.com/;Start usage here"
        _   struct{} `use:"continue longer usage string for --url below it",

        // Nested and Embedded structs can add a flag name prefix, or not.
        Nested     StructA
        NestedFlat StructB           `flag:";;;flatten"`
        StructA                      // Flat by default
        StructB    `flag:"embedded"` // Add prefix to nested field flag names.

        // Ignored
        ExplicitlyIgnored bool `flag:"-"`
        unexported        bool
}{
        // Default values may also be set directly to override the tag.
        StringFlag: "override tag default",
}

fs := pflag.NewFlagSet("", pflag.ContinueOnError)
flagbind.Bind(fs, &flags)
fs.Parse([]string{"--auto-kebab-case"})

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrorInvalidFlagSet = fmt.Errorf("flg must implement STDFlagSet or PFlagSet")

ErrorInvalidFlagSet is returned from Bind if flg doesn't implement STDFlagSet or PFlagSet.

View Source
var Separator = "-"

Separator is used to separate a prefix from a flag name and as the separator passed to FromCamelCase.

Functions

func Bind

func Bind(fs FlagSet, v interface{}, opts ...Option) error

Bind the exported fields of struct `v` to new flags in the FlagSet `fs`.

Bind returns ErrorInvalidFlagSet if `fs` does not implement STDFlagSet or PFlagSet.

Bind returns ErrorInvalidType if `v` is not a pointer to a struct.

Bind recovers from FlagSet panics and instead returns the panic as an error if a duplicate flag name occurs.

For each exported field of `v` Bind attempts to define one or more corresponding flags in `fs` according to the following rules.

If the field is a nil pointer, it is initialized.

If the field implements Binder, then only FlagBind is called on the field.

If the field implements flag.Value and not Binder, then it is bound as a Value on the FlagSet.

Otherwise, if the field is a struct, or struct pointer, then Bind is recursively called on a pointer to the struct field.

If the field is any supported type, a new flag is defined in `fs` with the settings defined in the field's `flag:"..."` tag. If the field is non-zero, its value is used as the default for that flag instead of whatever is defined in the `flag:";<default>"` tag. See FlagTag Settings below.

For a complete list of supported types see STDFlagSet and PFlagSet. Additionally, a json.RawMessage is also natively supported and is bound as a JSONRawMessage flag.

Ignoring a Field

Use the tag `flag:"-"` to prevent an exported field from being bound to any flag. If the field is a nested or embedded struct then its fields are also ignored.

Flag Tag Settings

The flag settings for a particular field can be customized using a struct field tag of the form:

`flag:"[<long>][,<short>][;<default>[;<usage>[;<options>]]]"`

The tag and all of its settings are [optional]. Semi-colons are used to distinguish subsequent settings.

<long>[,<short>] - Explicitly set the long and short names of the flag. All leading dashes are trimmed from both names. The two names are sorted for length, and the short name must be a single character, else it is ignored.

If `fs` does not implement PFlagSet, then the short name is ignored if a long name is defined, otherwise the short name is used as the long name.

If `fs` does implement PFlagSet, and only a short flag is defined, the long name defaults to the field name in kebab-case.

If no name is set, the long name defaults to the field name in "kebab-case". For example, "ThisFieldName" becomes "this-field-name". See FromCamelCase and Separator.

If the field is a nested or embedded struct and the "flatten" option is not set (see below), then the name is used as a prefix for all nested field flag names.

<default> - Bind attempts to parse <default> as the field's default, just like it would be parsed as a flag. Non-zero field values override this as the default.

<usage> - The usage string for the flag. See Extended Usage below for a way to break longer usage strings across multiple lines.

<options> - A comma separated list of additional options for the flag.

hide-default - Do not print the default value of this flag in the usage
output.

hidden - (PFlagSet only) Do not show this flag in the usage output.

flatten - (Nested/embedded structs only) Do not prefix the name of the
struct to the names of its fields. This overrides any explicit name on
an embedded struct which would otherwise unflatten it.

Extended Usage

Usage lines can frequently be longer than what comfortably fits in a flag tag on a single line. To keep line lengths shorter, use any number of blank identifier fields of any type with a `use` field tag to extend the usage of a flag. Each `use` tag is joined with a single space.

type Flags struct {
        URL string   `flag:"url;;Usage starts here"`
        _   struct{} `use:"and continues here"`
        _   struct{} `use:"and ends here."`
}

Auto-Adapt flag.Value To pflag.Value

The pflag.Value interface is the flag.Value interface, but with an additional Type() string function. This means that flag.Value cannot be used directly as a pflag.Value.

In order to work around this when fs implements PFlagSet, Bind wraps any fields that implement flag.Value but not pflag.Value in a shim adapter that uses the underlying type name as the Type() string. This allows you to only need to implement flag.Value. If the field does implement pflag.Value, it is used directly.

Nested/Embedded Structs Flag Prefix

If the field is a nested or embedded struct, its fields are also recursively parsed.

In order to help avoid flag name collisions, child flag names may be prepended with a prefix. The prefix defaults to the parent's field name passed through FromCamelCase, with a trailing Separator.

The prefix may be set explicitly using the <name> on the parent's Flag Tag.

To allow for a distinct separator symbol to be used just for a prefix, an explicitly set prefix that ends in "-", "_", or "." will not have Separator appended.

By default, flags in nested structs always have a prefix, but this can be omitted with Flag Tag `flatten` <option>.

By default, flags in embedded structs do not given a prefix, but one can be added by setting an explicit Flag Tag <name>.

Overriding Flag Settings

It is not always possible to set a Flag Tag on the fields of a nested struct type, such as when the type is from an external package. To allow for setting the default value, usage, or other options, an Overriding Flag Tag may be specified on a blank identifier field (`_`) that occurs anywhere after the field that defined the overridden flag.

The name specified in the Overriding Flag Tag must exactly match the flag name of the overridden flag, including any prefixes that were prepended due to nesting. Bind returns ErrorFlagOverrideUndefined if the flag name cannot be found.

Extended Usage may also defined immediately after an Overriding Flag Tag field.

For example, this sets the default value and usage on the flag for Timeout on an embedded http.Client.

type Flags struct {
        http.Client // Defines the -timeout flag
        _ struct{} `flag:"timeout;5s;HTTP request timeout"`
        _ struct{} `use:"... continued usage"`
}
Example
package main

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/AdamSLevy/flagbind"
	"github.com/spf13/pflag"
)

// Flags is a struct that exercises all ways to declare flag tags.
type Flags struct {
	// Unexported fields are ignored.
	skip bool

	// Explicitly ignored field.
	Ignored bool `flag:"-"`

	// Explicitly set <default>.
	Default bool `flag:";true"`

	// Explicitly set <usage>, but not <name> or <default>
	Usage bool `flag:";;Unique usage goes here"`

	// Custom <name>
	CustomName bool `flag:"different-flag-name"`

	// Dashes are trimmed and have no effect.
	WithDash    bool `flag:"-with-dash"`
	WithTwoDash bool `flag:"--with-two-dash"`

	// The default name for this flag is `auto-kebab`.
	AutoKebab bool

	// When using pflag this is named --short and -s.
	// When using flag, this is just be named -s.
	Short bool `flag:"s"`

	// Order doesn't matter for short flags, only length.
	// Short flags are ignored when using flag, and not pflag.
	LongShort bool `flag:"long,l"`
	ShortLong bool `flag:"r,-rlong"`

	// When using pflag, this flag is hidden from the usage.
	Hidden bool `flag:";;Hidden usage;hidden"`

	// The default value of this flag is hidden from the usage.
	HideDefault string `flag:";default value;Hide default;hide-default"`

	// These pointers are allocated by Bind if they are nil.
	Ptr *bool
	// This is allocated, if not nil, and set to true, if not zero, by
	// Bind.
	PtrDefault *bool `flag:";true"`

	// Set <name> and <usage> but not <default>.
	Bool    bool    `flag:"bool;;Set the Bool true."`
	Int     int     `flag:";0"`
	Int64   int64   `flag:";0"`
	Uint    uint    `flag:";0"`
	Uint64  uint64  `flag:";0"`
	Float64 float64 `flag:";0"`

	// The <default> is 1*time.Hour.
	Duration     time.Duration `flag:";1h"`
	String       string
	Value        TestValue
	ValueDefault TestValue `flag:";true"`

	// Nested and embedded structs are also parsed
	Nested     StructA
	NestedFlat StructB `flag:";;;flatten"`

	StructA // embedded
	StructB `flag:"embedded"`
}

type StructA struct {
	StructABool bool
}
type StructB struct {
	StructBBool bool
}

type TestValue bool

func (v *TestValue) Set(text string) error {
	switch strings.ToLower(text) {
	case "true":
		*v = true
	case "false":
		*v = false
	default:
		return fmt.Errorf("could not parse %q as TestValue", text)
	}
	return nil
}
func (v TestValue) String() string {
	return fmt.Sprint(bool(v))
}

func main() {
	// Set some defaults
	f := Flags{String: "inherit this default"}
	fs := pflag.NewFlagSet("", pflag.ContinueOnError)
	if err := flagbind.Bind(fs, &f); err != nil {
		log.Fatal(err)
	}

	if err := fs.Parse([]string{
		"--bool",
		"--hidden",
		"-s",
		"--duration", "1m",
		"--auto-kebab",
		"--nested-struct-a-bool",
		"--struct-b-bool",
		"--struct-a-bool",
		"--embedded-struct-b-bool",
	}); err != nil {
		log.Fatal(err)
	}

	fmt.Println("--bool", f.Bool)
	fmt.Println("--hidden", f.Hidden)
	fmt.Println("--short", f.Short)
	fmt.Println("--duration", f.Duration)
}
Output:

--bool true
--hidden true
--short true
--duration 1m0s

func FromCamelCase

func FromCamelCase(name, sep string) string

FromCamelCase converts CamelCase to kebab-case, or snake_case, or lowercase, depending on `sep`.

It makes a best effort at respecting capitalized acronyms. For example:

camel -> camel
CamelCamel -> camel-camel
CamelID -> camel-id
IDCamel -> id-camel
APICamel -> api-camel
APIURL -> apiurl
ApiUrl -> api-url
APIUrlID -> api-url-id

Types

type Binder

type Binder interface {
	FlagBind(fs FlagSet, prefix string, opt Option) error
}

Binder binds itself to a FlagSet.

FlagBind should prepend the `prefix` to any flag names when adding flags to `fs` to avoid potential flag name conflicts and allow more portable implementations.

Additionally the opt should be passed down to Bind to preserve original opts if called again by the implementation.

The underlying type of `fs` is the same as the original FlagSet passed to Bind.

type ErrorDefaultValue

type ErrorDefaultValue struct {
	FieldName string
	Value     string
	Err       error
}

ErrorDefaultValue is returned from Bind if the <default> value given in the tag cannot be parsed and assigned to the field.

func (ErrorDefaultValue) Error

func (err ErrorDefaultValue) Error() string

Error implements error.

func (ErrorDefaultValue) Unwrap

func (err ErrorDefaultValue) Unwrap() error

Unwrap implements Unwrap.

type ErrorFlagOverrideUndefined

type ErrorFlagOverrideUndefined struct {
	FlagName string
}

ErrorFlagOverrideUndefined is returned by Bind if a flag override tag is defined for a FlagName that has yet to be defined in the flag set.

func (ErrorFlagOverrideUndefined) Error

func (err ErrorFlagOverrideUndefined) Error() string

type ErrorInvalidType

type ErrorInvalidType struct {
	Type interface{}
	Nil  bool
}

ErrorInvalidType is returned from Bind if v is not a pointer to a struct."

func (ErrorInvalidType) Error

func (err ErrorInvalidType) Error() string

Error implements error.

type ErrorNestedStruct

type ErrorNestedStruct struct {
	FieldName string
	Err       error
}

ErrorNestedStruct is returned from Bind if a recursive call to bind on a nested struct returns an error.

func (ErrorNestedStruct) Error

func (err ErrorNestedStruct) Error() string

Error implements error.

func (ErrorNestedStruct) Unwrap

func (err ErrorNestedStruct) Unwrap() error

Unwrap implements unwrap.

type FlagSet

type FlagSet interface {
	Arg(i int) string
	Args() []string
	NArg() int
	NFlag() int
	Set(name, value string) error

	Parse([]string) error

	BoolVar(p *bool, name string, value bool, usage string)
	DurationVar(p *time.Duration, name string, value time.Duration, usage string)
	Float64Var(p *float64, name string, value float64, usage string)
	Int64Var(p *int64, name string, value int64, usage string)
	IntVar(p *int, name string, value int, usage string)
	StringVar(p *string, name string, value string, usage string)
	Uint64Var(p *uint64, name string, value uint64, usage string)
	UintVar(p *uint, name string, value uint, usage string)
}

FlagSet is an interface satisfied by both *flag.FlagSet and *pflag.FlagSet.

type JSONRawMessage

type JSONRawMessage json.RawMessage

func (*JSONRawMessage) Set

func (data *JSONRawMessage) Set(text string) error

func (JSONRawMessage) String

func (data JSONRawMessage) String() string

func (JSONRawMessage) Type

func (data JSONRawMessage) Type() string

type Option

type Option func(*bind)

Option is an option that may be passed to Bind.

func NoAutoFlatten

func NoAutoFlatten() Option

By default the flags in embedded struct fields are not given a prefix unless they explicitly have a `flag:"name"` in their tag.

This overrides this behavior so the flags in embedded struct fields are prefixed with their type name unless explicitly flattened with the tag `flag:";;;flatten"`.

func Prefix

func Prefix(prefix string) Option

Prefix all flag names with prefix, which should include any final separator (e.g. 'http-' or 'http.')

type PFlagSet

type PFlagSet interface {
	Lookup(name string) *pflag.Flag

	BoolVarP(p *bool, name, short string, value bool, usage string)
	BoolSliceVarP(p *[]bool, name, shorthand string, value []bool, usage string)

	DurationVarP(p *time.Duration, name, short string, value time.Duration, usage string)
	DurationSliceVarP(p *[]time.Duration, name, short string, value []time.Duration, usage string)

	Float32VarP(p *float32, name, short string, value float32, usage string)
	Float32SliceVarP(p *[]float32, name, short string, value []float32, usage string)

	Float64VarP(p *float64, name, short string, value float64, usage string)
	Float64SliceVarP(p *[]float64, name, short string, value []float64, usage string)

	Int64VarP(p *int64, name, short string, value int64, usage string)
	Int64SliceVarP(p *[]int64, name, short string, value []int64, usage string)

	IntVarP(p *int, name, short string, value int, usage string)
	IntSliceVarP(p *[]int, name, short string, value []int, usage string)

	StringVarP(p *string, name, short string, value string, usage string)
	StringSliceVarP(p *[]string, name, short string, value []string, usage string)

	Uint64VarP(p *uint64, name, short string, value uint64, usage string)

	UintVarP(p *uint, name, short string, value uint, usage string)
	UintSliceVarP(p *[]uint, name, short string, value []uint, usage string)

	IPVarP(p *net.IP, name, shorthand string, value net.IP, usage string)
	IPSliceVarP(p *[]net.IP, name, shorthand string, value []net.IP, usage string)

	VarPF(value pflag.Value, name, short string, usage string) *pflag.Flag

	Visit(func(*pflag.Flag))
	VisitAll(func(*pflag.Flag))
}

PFlagSet is an interface satisfied by *pflag.FlagSet.

type STDFlagSet

type STDFlagSet interface {
	FlagSet
	Lookup(name string) *flag.Flag
	Var(value flag.Value, name string, usage string)
	Visit(func(*flag.Flag))
	VisitAll(func(*flag.Flag))
}

STDFlagSet is an interface satisfied by *flag.FlagSet.

type URL

type URL url.URL

func (*URL) Set

func (u *URL) Set(text string) error

func (URL) String

func (u URL) String() string

func (URL) Type

func (u URL) Type() string

Jump to

Keyboard shortcuts

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