gflag

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2023 License: Apache-2.0 Imports: 16 Imported by: 1

README

Lint CI Coverage Status Vulnerability Check Go Report Card

GitHub tag (latest by date) Go Reference license

gflag

pflags with generic types.

OK so this is yet another CLI flags library...

We do not reinvent the wheel: this module reuses the great package github.com/spf13/pflag, with an interface built on go generics.

The main idea is to simplify the pflag interface, with less things to remember about flag types.

So this is not a fork or drop-in replacement, but rather an extension of the pflag functionality.

Usage

This package is designed to be used together with github.com/spf13/pflag. All types created by gflag implement the pflag.Value interface.

Use-case: build idiomatic cobra CLIs with a simpler declaration of flags. See how go-cli achieves this.

import (
	"fmt"

	"github.com/fredbi/gflag"
	"github.com/spf13/pflag"
)

var flagVal int

fs := pflag.NewFlagSet("", pflag.ContinueOnError)
intFlag := gflag.NewFlagValue(&flagVal, 1)      // infer flag from underlying type int, with a default value

fs.Var(intFlag, "integer",  "integer value")    // register the flag in pflag flagset

_ = fs.Parse([]string{"--integer", 10})         // parse command line arguments

fmt.Println(intFlag.GetValue())                 // the flag knows the type of the value

With pflag this piece of code would look very much similar.

import (
	"fmt"

	"github.com/spf13/pflag"
)

var flagVal int

fs := pflag.NewFlagSet("", pflag.ContinueOnError)

fs.IntVar(&flagVal, "integer",  "integer value") // register the flag in pflag flagset

_ = fs.Parse([]string{"--integer", 10})         // parse command line arguments

fmt.Println(fs.GetInt("integer"))               // has to know an integer type is expected

You may take a look at more examples, with slices and maps.

What does this bring to the table?

This package provides a unified approach to strongly typed flags, using go generics.

This way, we no longer have to register CLI flags using dozen of type-specific methods, just three:

  • NewFlagValue(): for scalar values
  • NewFlagSliceValue(): for flags as lists
  • NewFlagMapValue(): for flags as map (e.g. key=value pairs)

The flag building logic is consistent for single values, slices and maps of all types.

  • All primitive types (yes, complex too!) are supported.
  • All common types handled by pflag (time.Duration, net.IP, etc..) are also supported.
  • I have also added time.Time
  • Support any extension built for pflag (arbitrary types with the pflag.Value interface)

Variations in the semantics for a flag with a given underlying type may be fine-tuned with options.

There is an extensions sub-package to contribute some custom extra flag types. It is now populated with a byte-size flag. See the example.

About CLI flags

Documentation

Overview

Package gflag exposes generic types to deal with flags.

Supported types are: all primitive golang types (e.g. int, uint, float64, complex128 ...) as well as a few commonly used types, which supported by pflag: time.Duration, net.IP, net.IPNet, net.IPMask, plus time.Time.

The new types can be readily used as extensions to the github.com/spf13/flag package.

All types currently provided by pflag can be obtained from gflag using generic types.

gflag provides a more consistent interface across types, and can build single-valued flags, slice of flags as well as maps of flags for all underlying types.

Notice that the []byte type is considered single-valued and that we don't support []uint8 as a slice of unsigned integer values (because []byte and []uint8 are aliases).

There are a few attention points, though. Take a look at the examples to deal with these.

  • []byte semantics as base64-encoded string require the option BytesIsBase64(true) to be passed to the flag constructor NewFlagValue(). The default behavior is to use hex-encoded strings.
  • int semantics an incremental count require the option IntIsCount(true) to be passed to the flag constructor.
  • []string semantics as "slice" (values passed as a list of CSV value) is the default. Adopting "array" semantics (each string value passed with another instance of the flag) requires the StringSliceValueIsArray(true) option.

Note to users of the github.com/spf13/viper package:

Consuming flags from viper remain still possible.

All pflag types currently supported by viper will work seamlessly with their gflag counterpart. Other types will be converted to a their string representation by viper.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FlaggablePrimitives

type FlaggablePrimitives interface {
	constraints.Integer |
		constraints.Float |
		constraints.Complex |
		~string |
		~bool |
		~[]byte // aka: ~[]uint8
}

FlaggablePrimitives is a type constraint that holds all primitive types supported by pflag, and then some.

type FlaggableTypes

type FlaggableTypes interface {
	time.Duration |
		time.Time |
		net.IP |
		net.IPNet |
		net.IPMask |

		~struct{}
}

FlaggableTypes is a type constraint that holds all types supported by pflag, besides primitive types.

type MapValuable

type MapValuable interface {
	GetMap() map[string]string
}

type MapValue

type MapValue[T FlaggablePrimitives | FlaggableTypes] struct {
	Value *map[string]T
	// contains filtered or unexported fields
}

MapValue is a generic type that implements github.com/spf13/pflag.Value and MapValue.

It implements flags as maps of type map[string]T.

The underlying value, as map[string]T, may be retrieved using GetMapValue().

Example

Joint usage with pflag, for a key=value map of integers

package main

import (
	"fmt"
	"log"

	"github.com/fredbi/gflag"
	"github.com/spf13/pflag"
)

func main() {
	var flagVal map[string]int

	name := "map"
	short := name[:1]
	fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)

	mapFlag := gflag.NewFlagMapValue(&flagVal, map[string]int{"a": 5, "b": 2})
	fs.VarP(mapFlag, name, short, "map of integers")

	if err := fs.Parse([]string{"--map", "f=1,g=4", "--map", "e=3"}); err != nil {
		log.Fatalln("parsing error:", err)
	}

	// retrieve parsed values
	flag := fs.Lookup(name)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	flag = fs.ShorthandLookup(short)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	// using underlying value
	fmt.Println(flagVal)

	// using GetValue[bool]()
	fmt.Println(mapFlag.GetValue())

}
Output:

map
[e=3,f=1,g=4]
map
[e=3,f=1,g=4]
map[e:3 f:1 g:4]
map[e:3 f:1 g:4]

func AddMapValueFlag added in v0.1.0

func AddMapValueFlag[T FlaggablePrimitives | FlaggableTypes](fs *pflag.FlagSet, flag *MapValue[T], name, shorthand, usage string) (*MapValue[T], *pflag.Flag)

func NewFlagMapValue

func NewFlagMapValue[T FlaggablePrimitives | FlaggableTypes](addr *map[string]T, defaultValue map[string]T, opts ...Option) *MapValue[T]

NewFlagMapValue constructs a generic flag compatible with github.com/spf13/pflag.Value.

It replaces pflag.StringToInt(), pflag.StringToInt64(), pflag.StringToString(). Being generic, it can build a map of any type, e.g. map[string]net.IP, map[string]time.Duration, map[string]float64...

func (MapValue[T]) Get added in v0.1.0

func (m MapValue[T]) Get() any

Get() implements the flag.Getter interface for programs using this Value from the standard "flag" package.

func (*MapValue[T]) GetMap

func (m *MapValue[T]) GetMap() map[string]string

GetMap return a map[string]string representation of the slice values.

func (MapValue[T]) GetValue added in v0.1.0

func (m MapValue[T]) GetValue() map[string]T

GetValue returns the underlying value of the flag.

func (*MapValue[T]) MarshalFlag

func (m *MapValue[T]) MarshalFlag() (string, error)

MarshalFlag implements github.com/jessevdk/go-flags.Marshaler interface

func (*MapValue[T]) MarshalText

func (m *MapValue[T]) MarshalText() ([]byte, error)

func (*MapValue[T]) Set

func (m *MapValue[T]) Set(strValue string) error

Set knows how to config a string representation of the Value into a type T.

func (MapValue[T]) String

func (m MapValue[T]) String() string

func (*MapValue[T]) Type

func (m *MapValue[T]) Type() string

func (*MapValue[T]) UnmarshalFlag

func (m *MapValue[T]) UnmarshalFlag(value string) error

UnmarshalFlag implements github.com/jessevdk/go-flags.Unmarshaler interface

func (*MapValue[T]) UnmarshalText

func (m *MapValue[T]) UnmarshalText(text []byte) error

type Option

type Option func(*options)

Option to tune the behavior of a generic flag.

Example

Example of using gflag options.

package main

import (
	"fmt"
	"log"

	"github.com/fredbi/gflag"
	"github.com/spf13/pflag"
)

func main() {
	// example with int used with count semantics
	var flagVal int

	name := "count"
	short := name[:1]
	fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)

	countFlag := gflag.NewFlagValue(&flagVal, 0, gflag.IntIsCount(true))
	fl := fs.VarPF(countFlag, name, short, "increment counter")
	fl.NoOptDefVal = countFlag.GetNoOptDefVal() // allow no argument passed to the flag

	if err := fs.Parse([]string{"--count", "--count", "--count"}); err != nil {
		log.Fatalln("parsing error:", err)
	}

	// retrieve parsed values
	flag := fs.Lookup(name)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	flag = fs.ShorthandLookup(short)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	// using underlying value
	fmt.Println(flagVal)

	// using GetValue[bool]()
	fmt.Println(countFlag.GetValue())

}
Output:

count
3
count
3
3
3

func BytesIsBase64

func BytesIsBase64(enabled bool) Option

BytesIsBase64 adopts base64 encoding for flags of type []byte. The default is to encode []byte as an hex-encoded string.

Applies to: Value[[]byte]

func IntIsCount

func IntIsCount(enabled bool) Option

IntIsCount adopts count semantics for flags of type int.

Applies to: Value[int]

Count semantics mean that multiple flags without a given value increment a counter. The default is to use plain integer.

func StringSliceIsArray

func StringSliceIsArray(enabled bool) Option

StringSliceIsArray adopts "array" semantics for flags of type []string, that is, we accumulate multiple instances of the flag, each with a single value rather than parsing a CSV list of values in a single flag.

Applies to: SliceValue[string]

The default is to use a CSV list of values ("slice" semantics).

func WithTimeFormats

func WithTimeFormats(formats ...string) Option

WithTimeFormats define the formats supported to parse time.

Applies to: Value[time.Time], SliceValue[time.time], MapValue[time.Time]

The first format specified will be used to render time values as strings.

Defaults are: time.RFC3339Nano, time.RFC1123Z.

type SliceValue

type SliceValue[T FlaggablePrimitives | FlaggableTypes] struct {
	Value *[]T
	// contains filtered or unexported fields
}

SliceValue is a generic type that implements github.com/spf13/pflag.Value and SliceValue.

Example

Joint usage with pflag, for a string array flag

package main

import (
	"fmt"
	"log"

	"github.com/fredbi/gflag"
	"github.com/spf13/pflag"
)

func main() {
	var flagVal []string

	name := "strings"
	short := name[:1]
	fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)

	stringsFlag := gflag.NewFlagSliceValue(&flagVal, []string{"a", "b"})
	fs.VarP(stringsFlag, name, short, "csv input strings")

	if err := fs.Parse([]string{"--strings", "d,e,f", "--strings", "g,h"}); err != nil {
		log.Fatalln("parsing error:", err)
	}

	// retrieve parsed values
	flag := fs.Lookup(name)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	flag = fs.ShorthandLookup(short)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	// using underlying value
	fmt.Println(flagVal)

	// using GetValue[bool]()
	fmt.Println(stringsFlag.GetValue())

}
Output:

strings
[d,e,f,g,h]
strings
[d,e,f,g,h]
[d e f g h]
[d e f g h]

func AddSliceValueFlag added in v0.1.0

func AddSliceValueFlag[T FlaggablePrimitives | FlaggableTypes](fs *pflag.FlagSet, flag *SliceValue[T], name, shorthand, usage string) (*SliceValue[T], *pflag.Flag)

func NewFlagSliceValue

func NewFlagSliceValue[T FlaggablePrimitives | FlaggableTypes](addr *[]T, defaultValue []T, opts ...Option) *SliceValue[T]

NewFlagSliceValue constructs a generic flag compatible with github.com/spf13/pflag.SliceValue.

Since the flag type is inferred from the underlying data type, some flexibility allowed by pflag is not always possible at this point.

For example, when T = []string, NewFlagSliceValue adopts the semantics of the pflag.StringSlice (with comma-separated values), whereas pflag also supports a StringArray flag.

The underlying value, as []T, may be retrieved using GetSliceValue().

In order to cover the full range of semantics offered by the pflag package, some options are available.

func (*SliceValue[T]) Append

func (m *SliceValue[T]) Append(strValue string) error

Append a single element to the SliceValue, from its string representation.

func (SliceValue[T]) Get added in v0.1.0

func (m SliceValue[T]) Get() any

Get() implements the flag.Getter interface for programs using this Value from the standard "flag" package.

func (*SliceValue[T]) GetSlice

func (m *SliceValue[T]) GetSlice() []string

GetSlice return a []string representation of the slice values.

func (SliceValue[T]) GetValue added in v0.1.0

func (m SliceValue[T]) GetValue() []T

GetValue returns the underlying value of the flag.

func (*SliceValue[T]) MarshalFlag

func (m *SliceValue[T]) MarshalFlag() (string, error)

MarshalFlag implements github.com/jessevdk/go-flags.Marshaler interface

func (*SliceValue[T]) MarshalText

func (m *SliceValue[T]) MarshalText() ([]byte, error)

func (*SliceValue[T]) Replace

func (m *SliceValue[T]) Replace(strValues []string) error

func (*SliceValue[T]) Set

func (m *SliceValue[T]) Set(strValue string) error

Set knows how to config a string representation of the Value into a type T.

func (SliceValue[T]) String

func (m SliceValue[T]) String() string

func (*SliceValue[T]) Type

func (m *SliceValue[T]) Type() string

func (*SliceValue[T]) UnmarshalFlag

func (m *SliceValue[T]) UnmarshalFlag(value string) error

UnmarshalFlag implements github.com/jessevdk/go-flags.Unmarshaler interface

func (*SliceValue[T]) UnmarshalText

func (m *SliceValue[T]) UnmarshalText(text []byte) error

type Value

type Value[T FlaggablePrimitives | FlaggableTypes] struct {
	Value       *T
	NoOptDefVal string
	// contains filtered or unexported fields
}

Value is a generic type that implements github.com/spf13/pflag.Value.

The underlying value, as T, may be retrieved using GetValue().

Example

Joint usage with pflag, for a simple bool flag

package main

import (
	"fmt"
	"log"

	"github.com/fredbi/gflag"
	"github.com/spf13/pflag"
)

func main() {
	// variable to store the value of the flag
	var flagVal bool

	// Custom pflag.FlagSet.
	// Simple CLIs may just use the default pre-baked package-level FlagSet for the command line.
	name := "verbose"
	short := name[:1]
	fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)

	// declare a new generic flag: type is inferred from the provided default value
	verboseFlag := gflag.NewFlagValue(&flagVal, false)

	// register this flag into the FlagSet
	fl := fs.VarPF(verboseFlag, name, short, "verbose output")
	fl.NoOptDefVal = verboseFlag.GetNoOptDefVal() // allow no argument passed to the flag

	// parse args from the command line.
	// Simple CLIs may just use the default, with pflag.Parse() from the command line arguments.
	if err := fs.Parse([]string{"--verbose"}); err != nil {
		log.Fatalln("parsing error:", err)
	}

	// retrieve parsed values
	flag := fs.Lookup(name)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	flag = fs.ShorthandLookup(short)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	// various ways to retrieve the parsed value

	// using the variable used for storing the value
	fmt.Println(flagVal)

	// using GetValue[bool]()
	fmt.Println(verboseFlag.GetValue())

}
Output:

verbose
true
verbose
true
true
true

func AddValueFlag added in v0.1.0

func AddValueFlag[T FlaggablePrimitives | FlaggableTypes](fs *pflag.FlagSet, flag *Value[T], name, shorthand, usage string) (*Value[T], *pflag.Flag)

AddValueFlag registers a new gflag.Value to a pflag.FlagSet and return the passed flag as well as its pflag.Flag version.

Example

Simple int flag

package main

import (
	"fmt"
	"log"

	"github.com/fredbi/gflag"
	"github.com/spf13/pflag"
)

func main() {
	const name = "integer"
	short := name[:1]
	const usage = "an integer flag"

	fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)

	// add flag without a preallocated variable: all interaction is performed via the flag
	gfl, fl := gflag.AddValueFlag(
		fs,
		gflag.NewFlagValue(nil, 5), // create a generic flag of type integer
		name, short, usage,         // the usual specification for the flag
	)

	if err := fs.Parse([]string{"--integer", "12"}); err != nil {
		log.Fatalln("parsing error:", err)
	}

	// retrieve parsed values from name
	flag := fs.Lookup(name)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	// retrieve parsed value from short name
	flag = fs.ShorthandLookup(short)
	fmt.Println(flag.Name)
	fmt.Println(flag.Value)

	// various ways to retrieve the parsed value

	// using underlying value
	fmt.Println(fl.Value)

	// using FlagSet.GetInt() (old way)
	val, err := fs.GetInt(name)
	if err != nil {
		log.Fatalln("flag type error:", err)
	}
	fmt.Printf("%v (%T)\n", val, val)

	// using GetValue[int]() (new way)
	val2 := gfl.GetValue()
	fmt.Printf("%v (%T)\n", val2, val2)

}
Output:

integer
12
integer
12
12
12 (int)
12 (int)

func NewFlagValue

func NewFlagValue[T FlaggablePrimitives | FlaggableTypes](addr *T, defaultValue T, opts ...Option) *Value[T]

NewFlagValue constructs a generic flag compatible with github.com/spf13/pflag.Value.

Since the flag type is inferred from the underlying data type, some flexibility allowed by pflag is not always possible at this point.

For example, when T = []byte, NewFlagValue adopts by default the semantics of the pflag.BytesHex flag.

Similarly, when T = int, we adopt by default the semantics of pflag.Int and not pflag.Count.

In order to cover the full range of semantics offered by the pflag package, some options are available.

func (Value[T]) Get added in v0.1.0

func (m Value[T]) Get() any

Get implements the flag.Getter interface for programs using this Value from the standard "flag" package.

func (Value[T]) GetNoOptDefVal added in v0.1.0

func (m Value[T]) GetNoOptDefVal() string

GetNoOptDefVal returns the default value to consider whenever there is no argument added to the flag.

Example: for a Value[bool], NoOptDefVal defaults to "true".

func (Value[T]) GetValue

func (m Value[T]) GetValue() T

GetValue returns the underlying value of the flag.

func (Value[T]) IsBoolFlag added in v0.1.0

func (m Value[T]) IsBoolFlag() bool

IsBoolFlag indicates to the "flag" standard lib package that this is a boolean flag.

func (*Value[T]) MarshalFlag

func (m *Value[T]) MarshalFlag() (string, error)

MarshalFlag implements github.com/jessevdk/go-flags.Marshaler interface

func (*Value[T]) MarshalText

func (m *Value[T]) MarshalText() ([]byte, error)

func (*Value[T]) Set

func (m *Value[T]) Set(strValue string) error

Set knows how to config a string representation of the Value into a type T.

func (Value[T]) String

func (m Value[T]) String() string

String knows how to yield a string representation of type T.

func (*Value[T]) Type

func (m *Value[T]) Type() string

func (*Value[T]) UnmarshalFlag

func (m *Value[T]) UnmarshalFlag(value string) error

UnmarshalFlag implements github.com/jessevdk/go-flags.Unmarshaler interface

func (*Value[T]) UnmarshalText

func (m *Value[T]) UnmarshalText(text []byte) error

Directories

Path Synopsis
Package extensions provides extensions to the github.com/spf13/pflag package.
Package extensions provides extensions to the github.com/spf13/pflag package.

Jump to

Keyboard shortcuts

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