Add flags library

This commit is contained in:
Chris Roberts 2022-01-28 14:46:55 -08:00 committed by Paul Hinze
parent 9ee70725fe
commit 05facc0035
No known key found for this signature in database
GPG Key ID: B69DEDF2D55501C0
6 changed files with 3737 additions and 0 deletions

821
internal/flags/flag.go Normal file
View File

@ -0,0 +1,821 @@
package flags
import (
"fmt"
"os"
"strconv"
"strings"
)
type Type uint
const (
UnsetType Type = iota // Unset
ArrayType // Array
BooleanType // Boolean
FloatType // Float
IncrementType // Increment
IntegerType // Integer
MapType // Map
StringType // String
)
type FlagModifier func(f *Flag)
type FlagProcessor func(f *Flag, v interface{}) (interface{}, error)
type FlagCallback func(f *Flag) error
// Add long name aliases for flag
func Alias(aliases ...string) FlagModifier {
return func(f *Flag) {
for _, a := range aliases {
f.aliases = append(f.aliases, a)
}
}
}
// Set description of flag
func Description(d string) FlagModifier {
return func(f *Flag) {
f.description = d
}
}
// Mark flag as being required
func Required() FlagModifier {
return func(f *Flag) {
f.required = true
}
}
// Mark flag as being optional (default state)
func Optional() FlagModifier {
return func(f *Flag) {
f.required = false
}
}
// Prevent flag from being displayed
func Hidden() FlagModifier {
return func(f *Flag) {
f.hidden = true
}
}
// Set default value for flag
func DefaultValue(v interface{}) FlagModifier {
return func(f *Flag) {
f.typeCheck(v)
f.defaultValue = v
}
}
// Set environment variable to read for flag value
func EnvVar(name string) FlagModifier {
return func(f *Flag) {
f.envVar = name
}
}
// Set short name for flag
func ShortName(n rune) FlagModifier {
return func(f *Flag) {
f.shortName = n
}
}
// Set the subtype of the flag (used for array and map types)
func SetSubtype(t Type) FlagModifier {
return func(f *Flag) {
f.subkind = t
}
}
// Add a processor to be executed before the flag value is
// set. The processor receives the current value to be set
// into the flag. The value returned by the process will be
// the value actually set into the flag.
func AddProcessor(fn FlagProcessor) FlagModifier {
return func(f *Flag) {
f.processors = append(f.processors, fn)
}
}
// Add a callback to be executed when flag is visited before
// the flag value is set. This can be used to modify the
// final value set.
func AddCallback(fn FlagCallback) FlagModifier {
return func(f *Flag) {
f.callbacks = append(f.callbacks, fn)
}
}
// Set value into custom pointer
func customVar(v interface{}) FlagModifier {
return func(f *Flag) {
f.ptrTypeCheck(v)
f.value = v
f.ptr = true
}
}
type Flag struct {
aliases []string // long name aliases
called bool // mark if called via cli
callbacks []FlagCallback // callback functions
defaultValue interface{} // default value when not updated
description string // description of flag
envVar string // environment variable to check for value
group *Group // group flag is attached to
hidden bool // flag is hidden from display
longName string // long name of flag
matchedName string // long or short name matched for flag (includes aliases)
processors []FlagProcessor // processor functions
ptr bool // mark if value is pointer
required bool // mark if flag requires value set
shortName rune // short name of flag
kind Type // data type of value
subkind Type // data type of values within array or map type
updated bool // value was updated (either by env var or cli)
value interface{} // value for flag
}
// Create a new flag
func newFlag(
name string, // long name of flag
kind Type, // data type of the flag
group *Group, // group flag is attached to
modifiers ...FlagModifier, // any modifier functions
) *Flag {
if group == nil {
panic("flag must be attached to group")
}
f := &Flag{
longName: name,
kind: kind,
group: group,
}
group.flags = append(group.flags, f)
for _, fn := range modifiers {
fn(f)
}
if kind == BooleanType {
Alias(fmt.Sprintf("no-%s", f.longName))(f)
}
return f
}
// List of aliases for this flag
func (f *Flag) Aliases() []string {
return f.aliases
}
// Flag value was set via CLI
func (f *Flag) Called() bool {
return f.called
}
// Flag name (long, short, aliases) matched on CLI
func (f *Flag) CalledAs() string {
return f.matchedName
}
// Default value assigned to flag
func (f *Flag) DefaultValue() interface{} {
return f.defaultValue
}
// Description of flag usage
func (f *Flag) Description() string {
return f.description
}
// Environment variable name used to populate flag value
func (f *Flag) EnvVar() string {
return f.envVar
}
// Group flag is attached to
func (f *Flag) Group() *Group {
return f.group
}
// Flag is hidden from display
func (f *Flag) Hidden() bool {
return f.hidden
}
// Long name of the flag
func (f *Flag) LongName() string {
return f.longName
}
// Flag is required to be set
func (f *Flag) Required() bool {
return f.required
}
// Short name of the flag
func (f *Flag) ShortName() rune {
return f.shortName
}
// Flag value was set via CLI or environment variable
func (f *Flag) Updated() bool {
return f.updated
}
// Value assigned to flag
func (f *Flag) Value() interface{} {
if f.ptr {
switch f.kind {
case ArrayType:
switch f.subkind {
case BooleanType:
return *(f.value.(*[]bool))
case FloatType:
return *(f.value.(*[]float64))
case IntegerType:
return *(f.value.(*[]int64))
case StringType:
return *(f.value.(*[]string))
default:
panic("unsupported array subtype: " + f.subkind.String())
}
case BooleanType:
return *(f.value.(*bool))
case FloatType:
return *(f.value.(*float64))
case IncrementType, IntegerType:
return *(f.value.(*int64))
case MapType:
switch f.subkind {
case BooleanType:
return *(f.value.(*map[string]bool))
case FloatType:
return *(f.value.(*map[string]float64))
case IntegerType:
return *(f.value.(*map[string]int64))
case StringType:
return *(f.value.(*map[string]string))
default:
panic("unsupported map subtype: " + f.subkind.String())
}
case StringType:
return *(f.value.(*string))
}
}
return f.value
}
// Initialize the flag to make ready for parsing. This is called
// by the Set before parsing.
func (f *Flag) init() error {
// if the value is already set, the flag has been previously
// used so we should consider it invalid
if f.updated || f.called {
return fmt.Errorf("flag has already been used")
}
// If the value is a custom variable then we don't need to initialize
if !f.ptr {
// Now we want to initialize our value so it can
// be modified as needed.
switch f.kind {
case ArrayType:
switch f.subkind {
case BooleanType:
f.value = []bool{}
case FloatType:
f.value = []float64{}
case IntegerType:
f.value = []int64{}
case StringType:
f.value = []string{}
default:
panic("invalid subtype for array: " + f.subkind.String())
}
case BooleanType:
var val bool
f.value = val
case FloatType:
var val float64
f.value = val
case IncrementType, IntegerType:
var val int64
f.value = val
case MapType:
switch f.subkind {
case BooleanType:
f.value = map[string]bool{}
case FloatType:
f.value = map[string]float64{}
case IntegerType:
f.value = map[string]int64{}
case StringType:
f.value = map[string]string{}
default:
panic("invalid subtype for map: " + f.subkind.String())
}
case StringType:
var val string
f.value = val
}
}
// Check if we have an environment variable configured, and if it
// is set, then set the value into the flag
if f.envVar != "" {
if eval := os.Getenv(f.envVar); eval != "" {
if err := f.setValue(eval); err != nil {
return err
}
f.updated = true
}
}
return nil
}
// Marks the flag as being called from the CLI and sets
// the flag name used.
func (f *Flag) markCalled(name string) {
defer func() {
f.matchedName = name
f.called = true
f.updated = true
}()
if f.longName == name || string(f.shortName) == name {
return
}
for _, n := range f.aliases {
if n == name {
return
}
}
panic(fmt.Sprintf(
"matched flag name (%s) is not a valid name for this flag (%s)",
name, f.longName))
}
// Set a value into the flag
func (f *Flag) setValue(v string) (err error) {
switch f.kind {
case ArrayType:
err = f.setValueArray(v)
case BooleanType:
err = f.setValueBoolean(v)
case FloatType:
err = f.setValueFloat(v)
case IncrementType:
err = f.setValueIncrement(v)
case MapType:
err = f.setValueMap(v)
case StringType:
err = f.setValueString(v)
default:
err = fmt.Errorf("invalid type, cannot set value")
}
if err != nil {
return
}
for _, fn := range f.callbacks {
if err = fn(f); err != nil {
return
}
}
return
}
// Set value into map type
func (f *Flag) setValueMap(v string) (err error) {
parts := strings.SplitN(v, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid value passed for map flag - %s", v)
}
key, value := parts[0], parts[1]
val, err := f.convertValue(value)
if err != nil {
return
}
for _, fn := range f.processors {
if val, err = fn(f, val); err != nil {
return
}
}
switch f.subkind {
case BooleanType:
var m map[string]bool
if f.ptr {
m = *(f.value.(*map[string]bool))
} else {
m = f.value.(map[string]bool)
}
m[key] = val.(bool)
case FloatType:
var m map[string]float64
if f.ptr {
m = *(f.value.(*map[string]float64))
} else {
m = f.value.(map[string]float64)
}
m[key] = val.(float64)
case IntegerType:
var m map[string]int64
if f.ptr {
m = *(f.value.(*map[string]int64))
} else {
m = f.value.(map[string]int64)
}
m[key] = val.(int64)
case StringType:
var m map[string]string
if f.ptr {
m = *(f.value.(*map[string]string))
} else {
m = f.value.(map[string]string)
}
m[key] = val.(string)
default:
return fmt.Errorf("invalid subtype configured for map flag - %s", f.subkind.String())
}
return
}
// Set value into array type
func (f *Flag) setValueArray(val string) (err error) {
v, err := f.convertValue(val)
if err != nil {
return
}
for _, fn := range f.processors {
if v, err = fn(f, v); err != nil {
return
}
}
switch f.subkind {
case BooleanType:
var array []bool
if f.ptr {
array = *(f.value.(*[]bool))
} else {
array = f.value.([]bool)
}
newArray := make([]bool, len(array)+1)
copy(newArray, array)
newArray[len(newArray)-1] = v.(bool)
if f.ptr {
*(f.value.(*[]bool)) = newArray
} else {
f.value = newArray
}
case FloatType:
var array []float64
if f.ptr {
array = *(f.value.(*[]float64))
} else {
array = f.value.([]float64)
}
newArray := make([]float64, len(array)+1)
copy(newArray, array)
newArray[len(newArray)-1] = v.(float64)
if f.ptr {
*(f.value.(*[]float64)) = newArray
} else {
f.value = newArray
}
case IntegerType:
var array []int64
if f.ptr {
array = *(f.value.(*[]int64))
} else {
array = f.value.([]int64)
}
newArray := make([]int64, len(array)+1)
copy(newArray, array)
newArray[len(newArray)-1] = v.(int64)
if f.ptr {
*(f.value.(*[]int64)) = newArray
} else {
f.value = newArray
}
case StringType:
var array []string
if f.ptr {
array = *(f.value.(*[]string))
} else {
array = f.value.([]string)
}
newArray := make([]string, len(array)+1)
copy(newArray, array)
newArray[len(newArray)-1] = v.(string)
if f.ptr {
*(f.value.(*[]string)) = newArray
} else {
f.value = newArray
}
default:
return fmt.Errorf("invalid subtype configured for array flag - %s", f.subkind.String())
}
return nil
}
// Set value into boolean type
func (f *Flag) setValueBoolean(val string) (err error) {
v, err := f.convertValue(val)
if err != nil {
return
}
for _, fn := range f.processors {
if v, err = fn(f, v); err != nil {
return
}
}
if f.ptr {
*(f.value.(*bool)) = v.(bool)
return
}
f.value = v
return
}
// Set value into float type
func (f *Flag) setValueFloat(val string) (err error) {
v, err := f.convertValue(val)
if err != nil {
return
}
for _, fn := range f.processors {
if v, err = fn(f, v); err != nil {
return
}
}
if f.ptr {
*(f.value.(*float64)) = v.(float64)
return
}
f.value = v
return
}
// Set value into integer type
func (f *Flag) setValueInteger(val string) (err error) {
v, err := f.convertValue(val)
if err != nil {
return
}
for _, fn := range f.processors {
if v, err = fn(f, v); err != nil {
return
}
}
if f.ptr {
*(f.value.(*int64)) = v.(int64)
return
}
f.value = v
return
}
// Set value into string type
func (f *Flag) setValueString(val string) (err error) {
v, err := f.convertValue(val)
if err != nil {
return
}
for _, fn := range f.processors {
if v, err = fn(f, v); err != nil {
return
}
}
if f.ptr {
*(f.value.(*string)) = v.(string)
return
}
f.value = v
return
}
// Set value on increment type
func (f *Flag) setValueIncrement(val string) (err error) {
if f.ptr {
*(f.value.(*int64)) = *(f.value.(*int64)) + 1
return
}
f.value = interface{}(f.value.(int64) + 1)
return
}
// Convert given value to expected type
func (f *Flag) convertValue(v string) (interface{}, error) {
switch f.kind {
case ArrayType, MapType:
return f.doConvert(v, f.subkind)
case IncrementType:
return 1, nil
default:
return f.doConvert(v, f.kind)
}
}
// Actually do the conversion based on type
func (f *Flag) doConvert(v string, t Type) (interface{}, error) {
switch t {
case BooleanType:
b, err := strconv.ParseBool(v)
if err != nil {
return nil, err
}
return b, nil
case FloatType:
pf, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, err
}
return pf, nil
case IntegerType:
pi, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, err
}
return pi, nil
case StringType:
// Original values are strings, so just return the value
return v, nil
default:
return nil, fmt.Errorf("invalid type for conversion - %s", f.kind.String())
}
}
// Check that the value given is the type expected by the flag. This
// will panic on unexpected type.
func (f *Flag) typeCheck(v interface{}) {
switch f.kind {
case ArrayType:
switch f.subkind {
case BooleanType:
if _, ok := v.([]bool); !ok {
panic(fmt.Sprintf("invalid variable type - expected: []bool received: %T", v))
}
case FloatType:
if _, ok := v.([]float64); !ok {
panic(fmt.Sprintf("invalid variable type - expected: []float64 received: %T", v))
}
case IntegerType:
if _, ok := v.([]int64); !ok {
panic(fmt.Sprintf("invalid variable type - expected: []int64 received: %T", v))
}
case StringType:
if _, ok := v.([]string); !ok {
panic(fmt.Sprintf("invalid variable type - expected: []string received: %T", v))
}
default:
panic("invalid subtype for array: " + f.subkind.String())
}
case BooleanType:
if _, ok := v.(bool); !ok {
panic(fmt.Sprintf("invalid variable type - expected: bool received: %T", v))
}
case FloatType:
if _, ok := v.(float64); !ok {
panic(fmt.Sprintf("invalid variable type - expected: float64 received: %T", v))
}
case IncrementType, IntegerType:
if _, ok := v.(int64); !ok {
panic(fmt.Sprintf("invalid variable type - expected: int64 received: %T", v))
}
case MapType:
switch f.subkind {
case BooleanType:
if _, ok := v.(map[string]bool); !ok {
panic(fmt.Sprintf("invalid variable type - expected: map[string]bool received: %T", v))
}
case FloatType:
if _, ok := v.(map[string]float64); !ok {
panic(fmt.Sprintf("invalid variable type - expected: map[string]float64 received: %T", v))
}
case IntegerType:
if _, ok := v.(map[string]int64); !ok {
panic(fmt.Sprintf("invalid variable type - expected: map[string]int64 received: %T", v))
}
case StringType:
if _, ok := v.(map[string]string); !ok {
panic(fmt.Sprintf("invalid variable type - expected: map[string]string received: %T", v))
}
default:
panic("invalid subtype for map: " + f.subkind.String())
}
case StringType:
if _, ok := v.(string); !ok {
panic(fmt.Sprintf("invalid variable type - expected: string received: %T", v))
}
}
}
// Check that the value given is a pointer to the type expected by the flag. This
// will panic on unexpected type.
func (f *Flag) ptrTypeCheck(v interface{}) {
switch f.kind {
case ArrayType:
switch f.subkind {
case BooleanType:
if c, ok := v.(*[]bool); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *[]bool received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("array pointer value cannot be nil - %#v", c))
}
}
case FloatType:
if c, ok := v.(*[]float64); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *[]float64 received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("array pointer value cannot be nil - %#v", c))
}
}
case IntegerType:
if c, ok := v.(*[]int64); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *[]int64 received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("array pointer value cannot be nil - %#v", c))
}
}
case StringType:
if c, ok := v.(*[]string); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *[]string received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("array pointer value cannot be nil - %#v", c))
}
}
default:
panic("invalid subtype for array: " + f.subkind.String())
}
case BooleanType:
if _, ok := v.(*bool); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected *bool received %T", v))
}
case FloatType:
if _, ok := v.(*float64); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected *float64 received %T", v))
}
case IncrementType, IntegerType:
if _, ok := v.(*int64); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected *int64 received %T", v))
}
case MapType:
switch f.subkind {
case BooleanType:
if c, ok := v.(*map[string]bool); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *map[string]bool received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("map pointer value cannot be nil - %#v", c))
}
}
case FloatType:
if c, ok := v.(*map[string]float64); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *map[string]float64 received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("map pointer value cannot be nil - %#v", c))
}
}
case IntegerType:
if c, ok := v.(*map[string]int64); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *map[string]int64 received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("map pointer value cannot be nil - %#v", c))
}
}
case StringType:
if c, ok := v.(*map[string]string); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected: *map[string]string received: %T", v))
} else {
if *c == nil {
panic(fmt.Sprintf("map pointer value cannot be nil - %#v", c))
}
}
default:
panic("invalid subtype for array: " + f.subkind.String())
}
case StringType:
if _, ok := v.(*string); !ok {
panic(fmt.Sprintf("invalid pointer variable - expected *string received %T", v))
}
}
}

1704
internal/flags/flag_test.go Normal file

File diff suppressed because it is too large Load Diff

260
internal/flags/group.go Normal file
View File

@ -0,0 +1,260 @@
package flags
import "fmt"
type GroupModifier func(g *Group)
func HideGroupName() GroupModifier {
return func(g *Group) {
g.showGroupName = false
}
}
func HideGroup() GroupModifier {
return func(g *Group) {
g.hidden = true
}
}
type Group struct {
flags []*Flag
hidden bool
name string
set *Set
showGroupName bool
}
func newGroup(s *Set, n string, modifiers ...GroupModifier) *Group {
if s == nil {
panic("group must be attached to set")
}
g := &Group{
set: s,
name: n,
flags: []*Flag{},
showGroupName: true,
}
for _, fn := range modifiers {
fn(g)
}
s.groups = append(s.groups, g)
return g
}
func (g *Group) Add(f *Flag) (err error) {
if f.group == g {
return nil
}
if f.group != nil {
idx := -1
for i, flg := range f.group.flags {
if flg.longName == f.longName {
idx = i
break
}
}
if idx >= 0 {
f.group.flags = append(f.group.flags[0:idx], f.group.flags[idx+1:]...)
}
}
f.group = g
g.flags = append(g.flags, f)
return err
}
func (g *Group) Name() string {
return g.name
}
func (g *Group) Flags() []*Flag {
f := make([]*Flag, len(g.flags))
copy(f, g.flags)
return f
}
func (g *Group) Bool(
name string,
modifiers ...FlagModifier,
) *Flag {
return newFlag(name, BooleanType, g, modifiers...)
}
func (g *Group) BoolVar(
name string,
ptr *bool,
modifiers ...FlagModifier,
) *Flag {
f := g.Bool(name, modifiers...)
f.ptr = true
f.value = ptr
return f
}
func (g *Group) String(
name string,
modifiers ...FlagModifier,
) *Flag {
return newFlag(name, StringType, g, modifiers...)
}
func (g *Group) StringVar(
name string,
ptr *string,
modifiers ...FlagModifier,
) *Flag {
f := g.String(name, modifiers...)
f.ptr = true
f.value = ptr
return f
}
func (g *Group) Array(
name string,
subtype Type,
modifiers ...FlagModifier,
) *Flag {
modifiers = append(modifiers, SetSubtype(subtype))
return newFlag(name, ArrayType, g, modifiers...)
}
func (g *Group) ArrayVar(
name string,
subtype Type,
ptr interface{},
modifiers ...FlagModifier,
) *Flag {
f := g.Array(name, subtype, modifiers...)
f.ptr = true
f.value = ptr
return f
}
func (g *Group) Float(
name string,
modifiers ...FlagModifier,
) *Flag {
return newFlag(name, StringType, g, modifiers...)
}
func (g *Group) FloatVar(
name string,
ptr *float64,
modifiers ...FlagModifier,
) *Flag {
f := g.Float(name, modifiers...)
f.ptr = true
f.value = ptr
return f
}
func (g *Group) Integer(
name string,
modifiers ...FlagModifier,
) *Flag {
return newFlag(name, IntegerType, g, modifiers...)
}
func (g *Group) IntegerVar(
name string,
ptr *int,
modifiers ...FlagModifier,
) *Flag {
f := g.Integer(name, modifiers...)
f.ptr = true
f.value = ptr
return f
}
func (g *Group) Increment(
name string,
modifiers ...FlagModifier,
) *Flag {
modifiers = append(modifiers, SetSubtype(IntegerType))
return newFlag(name, IncrementType, g, modifiers...)
}
func (g *Group) IncrementVar(
name string,
ptr *int,
modifiers ...FlagModifier,
) *Flag {
f := g.Increment(name, modifiers...)
f.ptr = true
f.value = ptr
return f
}
func (g *Group) Map(
name string,
subtype Type,
modifiers ...FlagModifier,
) *Flag {
modifiers = append(modifiers, SetSubtype(subtype))
return newFlag(name, MapType, g, modifiers...)
}
func (g *Group) MapVar(
name string,
subtype Type,
ptr *map[string]interface{},
modifiers ...FlagModifier,
) *Flag {
f := g.Map(name, subtype, modifiers...)
f.ptr = true
f.value = ptr
return f
}
// Generate printable output of flag group
func (g *Group) Display(
indent int, // Number of spaces to indent
) string {
var pad int
opts := []string{}
desc := []string{}
for i, f := range g.flags {
if f.hidden {
continue
}
if f.shortName != 0 {
opts = append(opts, fmt.Sprintf("-%c,", f.shortName))
} else {
opts = append(opts, " ")
}
if f.kind == BooleanType {
opts[i] = fmt.Sprintf("%s --[no-]%s", opts[i], f.longName)
} else {
opts[i] = fmt.Sprintf("%s --%s VALUE", opts[i], f.longName)
}
desc = append(desc, f.description)
if len(opts[i]) > pad {
pad = len(opts[i])
}
}
pad += indent
var d string
if g.showGroupName {
d = fmt.Sprintf("%s:\n", g.name)
}
for i := 0; i < len(opts); i++ {
d = fmt.Sprintf("%s%4s%-*s%s\n", d, "", pad, opts[i], desc[i])
}
return d
}

381
internal/flags/set.go Normal file
View File

@ -0,0 +1,381 @@
package flags
import (
"fmt"
"strings"
)
type ErrorHandling uint
const INTERNAL_GROUP_NAME = "__internal__"
const (
ReturnOnError ErrorHandling = iota
PanicOnError
)
type UnknownHandling uint
const (
PassOnUnknown = iota
ErrorOnUnknown
)
type Set struct {
errorHandling ErrorHandling
flagMap map[string]*Flag
groups []*Group
name string
parsed bool
remaining []string
unknownFlags []string
unknownHandling UnknownHandling
}
type SetModifier func(s *Set)
type Visitor func(f *Flag)
func SetErrorMode(m ErrorHandling) SetModifier {
return func(s *Set) {
s.errorHandling = m
}
}
func SetUnknownMode(m UnknownHandling) SetModifier {
return func(s *Set) {
s.unknownHandling = m
}
}
func NewSet(name string, modifiers ...SetModifier) *Set {
s := &Set{
name: name,
groups: []*Group{},
errorHandling: ReturnOnError,
flagMap: map[string]*Flag{},
remaining: []string{},
unknownHandling: ErrorOnUnknown,
unknownFlags: []string{},
}
s.NewGroup(INTERNAL_GROUP_NAME, HideGroupName())
for _, m := range modifiers {
m(s)
}
return s
}
// Name of this flag set
func (s *Set) Name() string {
return s.name
}
// All defined groups within the set
func (s *Set) Groups() []*Group {
g := make([]*Group, len(s.groups))
copy(g, s.groups)
return g
}
// Visit flags that were updated either by CLI or
// environment variable
func (s *Set) Visit(fn Visitor) {
for _, f := range s.Flags() {
if f.Updated() {
fn(f)
}
}
}
// Visit flags that were set by the CLI only
func (s *Set) VisitCalled(fn Visitor) {
for _, f := range s.Flags() {
if f.Called() {
fn(f)
}
}
}
// Visit all flags
func (s *Set) VisitAll(fn Visitor) {
for _, f := range s.Flags() {
fn(f)
}
}
func (s *Set) AddGroup(g *Group) error {
// Check that group hasn't already been added
for _, cg := range s.groups {
if g == cg {
return fmt.Errorf("group already exists in set")
}
}
// Remove the group from its current set
idx := -1
for i, cg := range g.set.groups {
if cg == g {
idx = i
break
}
}
if idx >= 0 {
g.set.groups = append(g.set.groups[0:idx], g.set.groups[idx+1:]...)
}
// Update the groups Set and add the group to this Set's groups
g.set = s
s.groups = append(s.groups, g)
return nil
}
// Add a new group
func (s *Set) NewGroup(name string, modifiers ...GroupModifier) error {
for _, g := range s.groups {
if g.name == name {
return fmt.Errorf("flag group already exists with name %s", name)
}
}
newGroup(s, name, modifiers...)
return nil
}
// Default group for flags. The default group does
// not include a title section when displayed
func (s *Set) DefaultGroup() *Group {
if len(s.groups) < 1 {
panic("default group does not exist")
}
return s.groups[0]
}
// All defined flags within the set
func (s *Set) Flags() []*Flag {
f := []*Flag{}
for _, g := range s.groups {
f = append(f, g.flags...)
}
return f
}
func (s *Set) Flag(n string) (f *Flag, err error) {
if s.parsed {
f = s.flagMap[n]
} else {
for _, flg := range s.Flags() {
if flg.longName == n {
f = flg
}
}
}
if f == nil {
err = fmt.Errorf("failed to locate flag named: %s", n)
}
return
}
// Generate flag usage output
func (s *Set) Display() (o string) {
for _, g := range s.groups {
o += g.Display(11)
}
return
}
// Parse the command line options. The remaining arguments will
// be non-flag arguments, or if the unkown handling allows for
// passing, it will include unused flag/values and arguments.
func (s *Set) Parse(args []string) (remaining []string, err error) {
defer func() {
if err != nil && s.errorHandling == PanicOnError {
panic(err)
}
}()
// We only allow a set to parse once
if s.parsed {
return nil, fmt.Errorf("Set has already parsed arguments")
}
// Initialize all the flags. Errors returned from here
// are either flag initialization issues or flag name
// collisions
if err = s.initFlags(); err != nil {
return
}
// Now we start parsing
for i := 0; i < len(args); i++ {
w := args[i]
// If the argument is the `--` separator, we stop parsing
// and add the unprocessed arguments to the remaining list
// to be returned
if w == "--" {
if i+1 < len(args) {
// The remaining arguments may already be populated with
// previously encountered unknown flags if the set is
// configured for pass through. In that situation, we
// want to retain the `--` separater
if len(s.remaining) > 0 {
s.remaining = append(s.remaining, args[i:]...)
} else {
s.remaining = append(s.remaining, args[i+1:]...)
}
}
break
}
// Handle long name flag
if strings.HasPrefix(w, "--") {
var valueNext bool
var name, value string
flag := strings.Replace(w, "--", "", 1)
if strings.Contains(flag, "=") {
parts := strings.SplitN(flag, "=", 2)
name, value = parts[0], parts[1]
} else {
name = flag
valueNext = true
}
f, ok := s.flagMap[name]
// If the flag is not found check if we should error
if !ok {
if err = s.flagNotFound(name); err != nil {
return
}
// Since we haven't errored, add flag to remaining
s.remaining = append(s.remaining, w)
s.unknownFlags = append(s.unknownFlags, name)
continue
}
switch f.kind {
case BooleanType:
// Since boolean types can be negated, check the flag
// name to set the correct value
if strings.HasPrefix(name, "no-") {
value = "false"
} else {
value = "true"
}
case IncrementType:
// Increment values don't matter, so just set as 1
value = "1"
default:
// If the value was not included in the argument (argument form was not --flag=VAL)
// then we need to get the value from the next argument
if valueNext {
if i+1 >= len(args) {
return nil, fmt.Errorf("missing argument for flag `--%s`", f.longName)
}
i += 1
value = args[i]
}
}
// Mark the flag as being called on the CLI and the name used
f.markCalled(name)
// And finally, set the value
if err = f.setValue(value); err != nil {
return
}
} else if strings.HasPrefix(w, "-") {
// For short flags, multiple patterns can be used. Valid examples:
//
// Boolean/Increment types can be chained: -vvvbx (-v -v -v -b -x)
// Other types can include value in argument: -aVAL
// Chaining can be used for both: -vvvbxaVAL
wordLoop:
for j := 1; j < len(w); j++ {
c := string(w[j])
f, ok := s.flagMap[c]
// If the flag was not found check if we should error
if !ok {
if err = s.flagNotFound(c); err != nil {
return
}
// Add the unprocessed to remaining
s.remaining = append(s.remaining, "-"+w[j:])
// Only add the flag we encountered that was unknown
s.unknownFlags = append(s.unknownFlags, c)
continue
}
// Mark the flag as being called on the CLI and the name used
f.markCalled(c)
switch f.kind {
case BooleanType:
err = f.setValue("true")
case IncrementType:
err = f.setValue("1")
default:
// Check if we have anything left in this argument. If we do, it is the value.
// Otherwise, get the value from the next argument.
if len(w)-1 == j {
if i+1 >= len(args) {
return nil, fmt.Errorf("missing argument for flag `-%s", string(f.shortName))
}
i += 1
err = f.setValue(args[i])
} else {
err = f.setValue(w[j+1:])
}
if err != nil {
return
}
break wordLoop
}
// If an error was encountered, bail out
if err != nil {
return
}
}
} else {
s.remaining = append(s.remaining, w)
}
}
// TODO: need to validate for required flags
s.parsed = true
return s.remaining, nil
}
func (s *Set) flagNotFound(name string) error {
if s.unknownHandling == ErrorOnUnknown {
return fmt.Errorf("unknown flag encountered `%s`", name)
}
return nil
}
func (s *Set) initFlags() error {
for _, f := range s.Flags() {
if err := f.init(); err != nil {
return err
}
names := make([]string, len(f.aliases))
copy(names, f.aliases)
names = append(names, f.longName)
if f.shortName != 0 {
names = append(names, string(f.shortName))
}
for _, n := range names {
if cf, ok := s.flagMap[n]; ok {
var colFlag string
if len(n) == 1 {
colFlag = "-" + n
} else {
colFlag = "--" + n
}
return fmt.Errorf("flags --%s and --%s share a common flag (collision on %s)",
f.longName, cf.longName, colFlag)
}
s.flagMap[n] = f
}
}
return nil
}

541
internal/flags/set_test.go Normal file
View File

@ -0,0 +1,541 @@
package flags
import (
"fmt"
"testing"
)
func Test_NewSet(t *testing.T) {
s := testSet()
if s.name != "testing-set" {
t.Errorf("invalid name - testing-set != %s", s.name)
}
if len(s.groups) < 1 {
t.Fatalf("default group does not exist")
}
if s.groups[0].name != INTERNAL_GROUP_NAME {
t.Errorf("invalid default group - %s != %s", INTERNAL_GROUP_NAME, s.groups[0].name)
}
}
func Test_SetErrorMode(t *testing.T) {
s := testSet()
if s.errorHandling != ReturnOnError {
t.Errorf("invalid error handling - %d != %d", ReturnOnError, s.errorHandling)
}
SetErrorMode(PanicOnError)(s)
if s.errorHandling != PanicOnError {
t.Errorf("invalid error handling - %d != %d", PanicOnError, s.errorHandling)
}
}
func Test_SetUnknownMode(t *testing.T) {
s := testSet()
if s.unknownHandling != ErrorOnUnknown {
t.Errorf("invalid unknown handling - %d != %d", ErrorOnUnknown, s.unknownHandling)
}
SetUnknownMode(PassOnUnknown)(s)
if s.unknownHandling != PassOnUnknown {
t.Errorf("invalid unknown handling - %d != %d", PassOnUnknown, s.unknownHandling)
}
}
func Test_Set_Name(t *testing.T) {
s := testSet()
s.name = "my-set"
if s.Name() != "my-set" {
t.Errorf("invalid name - my-set != %s", s.Name())
}
}
func Test_Set_Groups(t *testing.T) {
s := testSet()
if len(s.Groups()) != 1 {
t.Fatalf("invalid groups length - 1 != %d", len(s.Groups()))
}
}
func Test_Set_Visit(t *testing.T) {
s := testSet()
g := s.groups[0]
for i := 0; i < 5; i++ {
f := testFlag(g)
f.longName = fmt.Sprintf("test-flag-%d", i)
if i > 2 {
f.updated = true
}
if i > 3 {
f.called = true
}
}
seen := []string{}
s.Visit(func(f *Flag) {
seen = append(seen, f.longName)
})
if len(seen) != 2 {
t.Errorf("invalid number of flags seen - 2 != %d", len(seen))
}
}
func Test_Set_VisitCalled(t *testing.T) {
s := testSet()
g := s.groups[0]
for i := 0; i < 5; i++ {
f := testFlag(g)
f.longName = fmt.Sprintf("test-flag-%d", i)
if i > 2 {
f.updated = true
}
if i > 3 {
f.called = true
}
}
seen := []string{}
s.VisitCalled(func(f *Flag) {
seen = append(seen, f.longName)
})
if len(seen) != 1 {
t.Errorf("invalid number of flags seen - 1 != %d", len(seen))
}
}
func Test_Set_VisitAll(t *testing.T) {
s := testSet()
g := s.groups[0]
for i := 0; i < 5; i++ {
f := testFlag(g)
f.longName = fmt.Sprintf("test-flag-%d", i)
if i > 2 {
f.updated = true
}
if i > 3 {
f.called = true
}
}
seen := []string{}
s.VisitAll(func(f *Flag) {
seen = append(seen, f.longName)
})
if len(seen) != 5 {
t.Errorf("invalid number of flags seen - 5 != %d", len(seen))
}
}
func Test_Set_CreateGroup(t *testing.T) {
s := testSet()
if err := s.NewGroup("test-group"); err != nil {
t.Fatalf("failed to create new group: %s", err)
}
if len(s.groups) != 2 {
t.Fatalf("invalid groups length - 2 != %d", len(s.groups))
}
if s.groups[1].name != "test-group" {
t.Errorf("invalid group name - test-group != %s", s.groups[1].name)
}
}
func Test_Set_CreateGroup_duplicate(t *testing.T) {
n := "test-group-name"
s := testSet()
if err := s.NewGroup(n); err != nil {
t.Fatalf("failed to create new group: %s", err)
}
if err := s.NewGroup(n); err == nil {
t.Fatalf("expected error but no error returned")
}
}
func Test_Set_DefaultGroup(t *testing.T) {
s := testSet()
if s.DefaultGroup().name != INTERNAL_GROUP_NAME {
t.Errorf("invalid default group name - %s != %s", INTERNAL_GROUP_NAME, s.name)
}
}
func Test_Set_Flags(t *testing.T) {
s := testSet()
if err := s.NewGroup("test-group"); err != nil {
t.Fatalf("failed to create new group: %s", err)
}
for i := 0; i < 5; i++ {
testFlag(s.groups[0])
testFlag(s.groups[1])
}
if len(s.Flags()) != 10 {
t.Errorf("invalid flags length - 10 != %d", len(s.Flags()))
}
}
func Test_Set_Parse(t *testing.T) {
s := testSet()
r, err := s.Parse([]string{})
if len(r) != 0 {
t.Errorf("invalid remaining args - 0 != %d", len(r))
}
if err != nil {
t.Errorf("unexpected parse error: %s", err)
}
}
func Test_Set_Parse_no_flags(t *testing.T) {
s := testSet()
r, err := s.Parse([]string{"some-arg"})
if err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
if len(r) != 1 {
t.Fatalf("invalid remaining args - 1 != %d", len(r))
}
if r[0] != "some-arg" {
t.Errorf("invalid remaining value - some-arg != %s", r[0])
}
}
func Test_Set_Parse_multi_error(t *testing.T) {
s := testSet()
if _, err := s.Parse([]string{}); err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
if _, err := s.Parse([]string{}); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_Parse_multi_panic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic but no panic recovered")
}
}()
s := testSet()
s.errorHandling = PanicOnError
if _, err := s.Parse([]string{}); err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
if _, err := s.Parse([]string{}); err != nil {
t.Errorf("expected panic from parse but received error: %s", err)
}
}
func Test_Set_Parse_single_bool(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark")
r, err := s.Parse([]string{"--mark"})
if err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
if len(r) > 0 {
t.Errorf("invalid remaining args - 0 != %d", len(r))
}
if !s.Flags()[0].Value().(bool) {
t.Errorf("invalid flag value - true != false")
}
}
func Test_Set_Parse_single_bool_extra_arg(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark")
r, err := s.Parse([]string{"--mark", "extra-arg"})
if err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
if len(r) != 1 {
t.Fatalf("invalid remaining args length - 1 != %d", len(r))
}
if r[0] != "extra-arg" {
t.Errorf("invalid remaining arg - extra-arg != %s", r[0])
}
}
func Test_Set_flagNotFound(t *testing.T) {
s := testSet()
s.unknownHandling = ErrorOnUnknown
if err := s.flagNotFound("mark"); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_flagNotFound_pass(t *testing.T) {
s := testSet()
s.unknownHandling = PassOnUnknown
if err := s.flagNotFound("mark"); err != nil {
t.Errorf("expected no error but error was returned: %s", err)
}
}
func Test_Set_initFlags(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark")
s.DefaultGroup().String("entry")
for _, f := range s.Flags() {
if f.value != nil {
t.Fatalf("expected value to be nil before init (%#v)", f.value)
}
}
if err := s.initFlags(); err != nil {
t.Fatalf("unexpected init error: %s", err)
}
for _, f := range s.Flags() {
if f.value == nil {
t.Errorf("flag value should not be nil - flag: %s", f.longName)
}
}
}
func Test_Set_initFlags_bool_negated(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark")
if err := s.initFlags(); err != nil {
t.Fatalf("unexpected init error: %s", err)
}
if _, ok := s.flagMap["no-mark"]; !ok {
t.Errorf("negated boolean flag not found")
}
}
func Test_Set_initFlags_collision_long(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark")
s.DefaultGroup().String("mark")
if err := s.initFlags(); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_initFlags_collision_short(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark", ShortName('m'))
s.DefaultGroup().String("entry", ShortName('m'))
if err := s.initFlags(); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_initFlags_collision_long_alias(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark", Alias("thing"))
s.DefaultGroup().String("entry", Alias("thing"))
if err := s.initFlags(); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_initFlags_collision_short_alias(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark", ShortName('m'))
s.DefaultGroup().String("entry", Alias("m"))
if err := s.initFlags(); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_initFlags_collision_bool_negate_long(t *testing.T) {
s := testSet()
s.DefaultGroup().Bool("mark")
s.DefaultGroup().String("no-mark")
if err := s.initFlags(); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_Parse_unknown_error(t *testing.T) {
s := testSet()
s.unknownHandling = ErrorOnUnknown
s.errorHandling = ReturnOnError
s.DefaultGroup().Bool("mark")
if _, err := s.Parse([]string{"--entry"}); err == nil {
t.Errorf("expected error but no error returned")
}
}
func Test_Set_Parse_unknown_panic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected panic but no panic recovered")
}
}()
s := testSet()
s.unknownHandling = ErrorOnUnknown
s.errorHandling = PanicOnError
s.DefaultGroup().Bool("mark")
s.Parse([]string{"--entry"})
}
func Test_Set_Parse_unknown_pass(t *testing.T) {
s := testSet()
s.unknownHandling = PassOnUnknown
s.DefaultGroup().Bool("mark")
r, err := s.Parse([]string{"--entry", "VALUE"})
if err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
if len(r) != 2 {
t.Fatalf("invalid remaining length - 2 != %d", len(r))
}
if r[0] != "--entry" {
t.Errorf("invalid arg value - --entry != %s", r[0])
}
if r[1] != "VALUE" {
t.Errorf("invalid arg value - VALUE != %s", r[1])
}
if len(s.unknownFlags) != 1 {
t.Fatalf("invalid unknown flags length - 1 != %d", len(s.unknownFlags))
}
if s.unknownFlags[0] != "entry" {
t.Errorf("invalid unknown flags value - entry != %s", s.unknownFlags[0])
}
}
// -vvv --entry eVALUE --mark -x xVALUE -y
func Test_Set_Parse_1(t *testing.T) {
s := testSet()
s.DefaultGroup().Increment("verbosity", ShortName('v'))
s.DefaultGroup().String("entry")
s.DefaultGroup().Bool("mark")
s.DefaultGroup().String("xylophone", ShortName('x'))
s.DefaultGroup().Bool("yesterday", ShortName('y'))
if _, err := s.Parse([]string{"-vvv", "--entry", "eVALUE", "--mark", "-x", "xVALUE", "-y"}); err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
for _, f := range s.Flags() {
switch f.longName {
case "verbosity":
if f.Value().(int64) != 3 {
t.Errorf("invalid verbosity value - 3 != %#v", f.Value())
}
case "entry":
if f.Value().(string) != "eVALUE" {
t.Errorf("invalid entry value - eVALUE != %#v", f.Value())
}
case "mark":
if f.Value().(bool) != true {
t.Errorf("invalid mark value - true != %#v", f.Value())
}
case "xylophone":
if f.Value().(string) != "xVALUE" {
t.Errorf("invalid xylophone value - xVALUE != %#v", f.Value())
}
case "yesterday":
if f.Value().(bool) != true {
t.Errorf("invalid yesterday value - true != %#v", f.Value())
}
}
}
}
// -vvyvvxxVALUE --mark --entry=EVALUE
func Test_Set_Parse_2(t *testing.T) {
s := testSet()
s.DefaultGroup().Increment("verbosity", ShortName('v'))
s.DefaultGroup().String("entry")
s.DefaultGroup().Bool("mark")
s.DefaultGroup().String("xylophone", ShortName('x'))
s.DefaultGroup().Bool("yesterday", ShortName('y'))
if _, err := s.Parse([]string{"-vvyvvxxVALUE", "--mark", "--entry=EVALUE"}); err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
for _, f := range s.Flags() {
switch f.longName {
case "verbosity":
if f.Value().(int64) != 4 {
t.Errorf("invalid verbosity value - 4 != %#v", f.Value())
}
case "entry":
if f.Value().(string) != "EVALUE" {
t.Errorf("invalid entry value - EVALUE != %#v", f.Value())
}
case "mark":
if f.Value().(bool) != true {
t.Errorf("invalid mark value - true != %#v", f.Value())
}
case "xylophone":
if f.Value().(string) != "xVALUE" {
t.Errorf("invalid xylophone value - xVALUE != %#v", f.Value())
}
case "yesterday":
if f.Value().(bool) != true {
t.Errorf("invalid yesterday value - true != %#v", f.Value())
}
}
}
}
// -vvyv --xylophone xVALUE --entry=EVALUE -mv
func Test_Set_Parse_3(t *testing.T) {
s := testSet()
s.DefaultGroup().Increment("verbosity", ShortName('v'))
s.DefaultGroup().String("entry")
s.DefaultGroup().Bool("mark", ShortName('m'))
s.DefaultGroup().String("xylophone", ShortName('x'))
s.DefaultGroup().Bool("yesterday", ShortName('y'))
if _, err := s.Parse([]string{"-vvyv", "--xylophone", "xVALUE", "--entry=EVALUE", "-mv"}); err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
for _, f := range s.Flags() {
switch f.longName {
case "verbosity":
if f.Value().(int64) != 4 {
t.Errorf("invalid verbosity value - 4 != %#v", f.Value())
}
case "entry":
if f.Value().(string) != "EVALUE" {
t.Errorf("invalid entry value - EVALUE != %#v", f.Value())
}
case "mark":
if f.Value().(bool) != true {
t.Errorf("invalid mark value - true != %#v", f.Value())
}
case "xylophone":
if f.Value().(string) != "xVALUE" {
t.Errorf("invalid xylophone value - xVALUE != %#v", f.Value())
}
case "yesterday":
if f.Value().(bool) != true {
t.Errorf("invalid yesterday value - true != %#v", f.Value())
}
}
}
}
// --entry 3.14 --hash ping=pong --hash=fee=fi --entry=99.9
func Test_Set_Parse_4(t *testing.T) {
s := testSet()
s.DefaultGroup().Array("entry", FloatType)
s.DefaultGroup().Map("hash", StringType)
if _, err := s.Parse([]string{"--entry", "3.14", "--hash", "ping=pong", "--hash=fee=fi", "--entry=99.9"}); err != nil {
t.Fatalf("unexpected parse error: %s", err)
}
for _, f := range s.Flags() {
switch f.longName {
case "entry":
v := f.Value().([]float64)
if len(v) != 2 {
t.Fatalf("invalid entry length - 2 != %d", len(v))
}
if v[0] != 3.14 {
t.Errorf("invalid entry value - 3.14 != %#v", v)
}
if v[1] != 99.9 {
t.Errorf("invalid entry value - 99.9 != %#v", v)
}
case "hash":
h := f.Value().(map[string]string)
if v, ok := h["ping"]; !ok {
t.Errorf("invalid hash value - missing ping key")
} else {
if v != "pong" {
t.Errorf("invalid hash value - pong != %#v", v)
}
}
if v, ok := h["fee"]; !ok {
t.Errorf("invalid hash value - missing fee key")
} else {
if v != "fi" {
t.Errorf("invalid hash value - fi != %#v", v)
}
}
}
}
}
// TODO: add some complex usage tests which include flag modifiers

View File

@ -0,0 +1,30 @@
// Code generated by "stringer -type=Type -linecomment ./internal/flags"; DO NOT EDIT.
package flags
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[UnsetType-0]
_ = x[ArrayType-1]
_ = x[BooleanType-2]
_ = x[FloatType-3]
_ = x[IncrementType-4]
_ = x[IntegerType-5]
_ = x[MapType-6]
_ = x[StringType-7]
}
const _Type_name = "UnsetArrayBooleanFloatIncrementIntegerMapString"
var _Type_index = [...]uint8{0, 5, 10, 17, 22, 31, 38, 41, 47}
func (i Type) String() string {
if i >= Type(len(_Type_index)-1) {
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Type_name[_Type_index[i]:_Type_index[i+1]]
}