1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
// Copyright 2020, 2022 Harald Albrecht.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package enumflag
import (
"fmt"
"github.com/spf13/cobra"
)
// Flag represents a CLI (enumeration) flag which can take on only a single
// enumeration value out of a fixed set of enumeration values. Applications
// using the enumflag package might want to “derive” their enumeration flags
// from Flag for documentation purposes; for instance:
//
// type MyFoo enumflag.Flag
//
// However, applications don't need to base their own enum types on Flag. The
// only requirement for user-defined enumeration flags is that they must be
// (“somewhat”) compatible with the Flag type, or more precise: user-defined
// enumerations must satisfy the predeclared type identifier [comparable].
//
// [comparable]: https://go.dev/blog/comparable
type Flag uint
// EnumCaseSensitivity specifies whether the textual representations of enum
// values are considered to be case sensitive, or not.
type EnumCaseSensitivity bool
// Controls whether the textual representations for enum values are case
// sensitive, or not.
const (
EnumCaseInsensitive EnumCaseSensitivity = false
EnumCaseSensitive EnumCaseSensitivity = true
)
// EnumFlagValue wraps a user-defined enum type value satisfying comparable or
// []comparable. It implements the [github.com/spf13/pflag.Value] interface, so
// the user-defined enum type value can directly be used with the fine pflag
// drop-in package for Golang CLI flags.
type EnumFlagValue[E comparable] struct {
value enumValue[E] // enum value of a user-defined enum scalar or slice type.
enumtype string // user-friendly name of the user-defined enum type.
names enumMapper[E] // enum value names.
}
// enumValue supports getting, setting, and stringifying an scalar or slice enum
// enumValue.
//
// Do I smell preemptive interfacing here...? Now watch the magic of “cleanest
// code”: by just moving the interface type from the source file with the struct
// types to the source file with the consumer we achieve immediate Go
// perfectness! Strike!
type enumValue[E comparable] interface {
Get() any
Set(val string, names enumMapper[E]) error
String(names enumMapper[E]) string
NewCompletor(enums EnumIdentifiers[E], help Help[E]) Completor
}
// New wraps a given enum variable (satisfying the predeclared type identifier
// comparable) so that it can be used as a flag Value with
// [github.com/spf13/pflag.Var] and [github.com/spf13/pflag.VarP]. In case no
// default enum value should be set and therefore no default shown in
// [spf13/cobra], use [NewWithoutDefault] instead.
//
// [spf13/cobra]: https://github.com/spf13/cobra
func New[E comparable](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
return new("New", flag, typename, mapping, sensitivity, false)
}
// NewWithoutDefault wraps a given enum variable (satisfying the predeclared
// type identifier comparable) so that it can be used as a flag Value with
// [github.com/spf13/pflag.Var] and [github.com/spf13/pflag.VarP]. Please note
// that the zero enum value must not be mapped and thus not be assigned to any
// enum value textual representation.
//
// [spf13/cobra] won't show any default value in its help for CLI enum flags
// created with NewWithoutDefault.
//
// [spf13/cobra]: https://github.com/spf13/cobra
func NewWithoutDefault[E comparable](flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
return new("NewWithoutDefault", flag, typename, mapping, sensitivity, true)
}
// new returns a new enum variable to be used with pflag.Var and pflag.VarP.
func new[E comparable](ctor string, flag *E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity, nodefault bool) *EnumFlagValue[E] {
if flag == nil {
panic(fmt.Sprintf("%s requires flag to be a non-nil pointer to an enum value satisfying comparable", ctor))
}
if mapping == nil {
panic(fmt.Sprintf("%s requires mapping not to be nil", ctor))
}
return &EnumFlagValue[E]{
value: &enumScalar[E]{v: flag, nodefault: nodefault},
enumtype: typename,
names: newEnumMapper(mapping, sensitivity),
}
}
// NewSlice wraps a given enum slice variable (satisfying [comparable])
// so that it can be used as a flag Value with [github.com/spf13/pflag.Var] and
// [github.com/spf13/pflag.VarP].
func NewSlice[E comparable](flag *[]E, typename string, mapping EnumIdentifiers[E], sensitivity EnumCaseSensitivity) *EnumFlagValue[E] {
if flag == nil {
panic("NewSlice requires flag to be a non-nil pointer to an enum value slice satisfying []any")
}
if mapping == nil {
panic("NewSlice requires mapping not to be nil")
}
return &EnumFlagValue[E]{
value: &enumSlice[E]{v: flag},
enumtype: typename,
names: newEnumMapper(mapping, sensitivity),
}
}
// Set sets the enum flag to the specified enum value. If the specified value
// isn't a valid enum value, then the enum flag won't be set and an error is
// returned instead.
func (e *EnumFlagValue[E]) Set(val string) error {
return e.value.Set(val, e.names)
}
// String returns the textual representation of an enumeration (flag) value. In
// case multiple textual representations (~identifiers) exist for the same
// enumeration value, then only the first textual representation is returned,
// which is considered to be the canonical one.
func (e *EnumFlagValue[E]) String() string { return e.value.String(e.names) }
// Type returns the name of the flag value type. The type name is used in error
// messages.
func (e *EnumFlagValue[E]) Type() string { return e.enumtype }
// Get returns the current enum value for convenience. Please note that the enum
// value is either scalar or slice, depending on how the enum flag was created.
func (e *EnumFlagValue[E]) Get() any { return e.value.Get() }
// RegisterCompletion registers completions for the specified (flag) name, with
// optional help texts.
func (e *EnumFlagValue[E]) RegisterCompletion(cmd *cobra.Command, name string, help Help[E]) error {
return cmd.RegisterFlagCompletionFunc(
name, e.value.NewCompletor(e.names.Mapping(), help))
}
// GetValue returns the (scalar) enum value of type E, otherwise it returns the
// zero value for type E.
func (e *EnumFlagValue[E]) GetValue() (v E) {
ev := e.Get() // returns E, not *E
v, _ = ev.(E)
return
}
// GetSliceValue returns the slice enum value of type []E, otherwise it returns
// the zero value for type []E.
func (e *EnumFlagValue[E]) GetSliceValue() (v []E) {
ev := e.Get() // returns []E, not *[]E
v, _ = ev.([]E)
return
}
|