Skip to content

Commit

Permalink
Merge pull request #2009 from dearchap/add_map_value_source
Browse files Browse the repository at this point in the history
Add map value source
  • Loading branch information
dearchap authored Nov 22, 2024
2 parents ef45965 + 6c78afd commit 0a1c772
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 10 deletions.
2 changes: 1 addition & 1 deletion flag_bool_with_inverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (parent *BoolWithInverseFlag) initialize() {
sources := []ValueSource{}

for _, envVar := range child.GetEnvVars() {
sources = append(sources, &envVarValueSource{Key: strings.ToUpper(parent.InversePrefix) + envVar})
sources = append(sources, EnvVar(strings.ToUpper(parent.InversePrefix)+envVar))
}
parent.negativeFlag.Sources = NewValueSourceChain(sources...)
}
Expand Down
22 changes: 22 additions & 0 deletions godoc-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,12 @@ type DocGenerationMultiValueFlag interface {

type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]

type EnvValueSource interface {
IsFromEnv() bool
Key() string
}
EnvValueSource is to specifically detect env sources when printing help text

type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
Expand Down Expand Up @@ -848,6 +854,20 @@ func (i MapBase[T, C, VC]) ToString(t map[string]T) string
func (i *MapBase[T, C, VC]) Value() map[string]T
Value returns the mapping of values set by this flag

type MapSource interface {
fmt.Stringer
fmt.GoStringer

// Lookup returns the value from the source based on key
// and if it was found
// or returns an empty string and false
Lookup(string) (any, bool)
}
MapSource is a source which can be used to look up a value based on a key
typically for use with a cli.Flag

func NewMapSource(name string, m map[any]any) MapSource

type MultiError interface {
error
Errors() []error
Expand Down Expand Up @@ -1000,6 +1020,8 @@ func EnvVar(key string) ValueSource

func File(path string) ValueSource

func NewMapValueSource(key string, ms MapSource) ValueSource

type ValueSourceChain struct {
Chain []ValueSource
}
Expand Down
22 changes: 22 additions & 0 deletions testdata/godoc-v3.x.txt
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,12 @@ type DocGenerationMultiValueFlag interface {

type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]

type EnvValueSource interface {
IsFromEnv() bool
Key() string
}
EnvValueSource is to specifically detect env sources when printing help text

type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
Expand Down Expand Up @@ -848,6 +854,20 @@ func (i MapBase[T, C, VC]) ToString(t map[string]T) string
func (i *MapBase[T, C, VC]) Value() map[string]T
Value returns the mapping of values set by this flag

type MapSource interface {
fmt.Stringer
fmt.GoStringer

// Lookup returns the value from the source based on key
// and if it was found
// or returns an empty string and false
Lookup(string) (any, bool)
}
MapSource is a source which can be used to look up a value based on a key
typically for use with a cli.Flag

func NewMapSource(name string, m map[any]any) MapSource

type MultiError interface {
error
Errors() []error
Expand Down Expand Up @@ -1000,6 +1020,8 @@ func EnvVar(key string) ValueSource

func File(path string) ValueSource

func NewMapValueSource(key string, ms MapSource) ValueSource

type ValueSourceChain struct {
Chain []ValueSource
}
Expand Down
123 changes: 114 additions & 9 deletions value_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ type ValueSource interface {
Lookup() (string, bool)
}

// EnvValueSource is to specifically detect env sources when
// printing help text
type EnvValueSource interface {
IsFromEnv() bool
Key() string
}

// MapSource is a source which can be used to look up a value
// based on a key
// typically for use with a cli.Flag
type MapSource interface {
fmt.Stringer
fmt.GoStringer

// Lookup returns the value from the source based on key
// and if it was found
// or returns an empty string and false
Lookup(string) (any, bool)
}

// ValueSourceChain contains an ordered series of ValueSource that
// allows for lookup where the first ValueSource to resolve is
// returned
Expand All @@ -38,8 +58,8 @@ func (vsc *ValueSourceChain) EnvKeys() []string {
vals := []string{}

for _, src := range vsc.Chain {
if v, ok := src.(*envVarValueSource); ok {
vals = append(vals, v.Key)
if v, ok := src.(EnvValueSource); ok && v.IsFromEnv() {
vals = append(vals, v.Key())
}
}

Expand Down Expand Up @@ -83,21 +103,29 @@ func (vsc *ValueSourceChain) LookupWithSource() (string, ValueSource, bool) {

// envVarValueSource encapsulates a ValueSource from an environment variable
type envVarValueSource struct {
Key string
key string
}

func (e *envVarValueSource) Lookup() (string, bool) {
return os.LookupEnv(strings.TrimSpace(string(e.Key)))
return os.LookupEnv(strings.TrimSpace(string(e.key)))
}

func (e *envVarValueSource) String() string { return fmt.Sprintf("environment variable %[1]q", e.Key) }
func (e *envVarValueSource) IsFromEnv() bool {
return true
}

func (e *envVarValueSource) Key() string {
return e.key
}

func (e *envVarValueSource) String() string { return fmt.Sprintf("environment variable %[1]q", e.key) }
func (e *envVarValueSource) GoString() string {
return fmt.Sprintf("&envVarValueSource{Key:%[1]q}", e.Key)
return fmt.Sprintf("&envVarValueSource{Key:%[1]q}", e.key)
}

func EnvVar(key string) ValueSource {
return &envVarValueSource{
Key: key,
key: key,
}
}

Expand All @@ -107,7 +135,7 @@ func EnvVars(keys ...string) ValueSourceChain {
vsc := ValueSourceChain{Chain: []ValueSource{}}

for _, key := range keys {
vsc.Chain = append(vsc.Chain, &envVarValueSource{Key: key})
vsc.Chain = append(vsc.Chain, EnvVar(key))
}

return vsc
Expand Down Expand Up @@ -138,8 +166,85 @@ func Files(paths ...string) ValueSourceChain {
vsc := ValueSourceChain{Chain: []ValueSource{}}

for _, path := range paths {
vsc.Chain = append(vsc.Chain, &fileValueSource{Path: path})
vsc.Chain = append(vsc.Chain, File(path))
}

return vsc
}

type mapSource struct {
name string
m map[any]any
}

func NewMapSource(name string, m map[any]any) MapSource {
return &mapSource{
name: name,
m: m,
}
}

func (ms *mapSource) String() string { return fmt.Sprintf("map source %[1]q", ms.name) }
func (ms *mapSource) GoString() string {
return fmt.Sprintf("&mapSource{name:%[1]q}", ms.name)
}

func (ms *mapSource) Lookup(name string) (any, bool) {
// nestedVal checks if the name has '.' delimiters.
// If so, it tries to traverse the tree by the '.' delimited sections to find
// a nested value for the key.
if sections := strings.Split(name, "."); len(sections) > 1 {
node := ms.m
for _, section := range sections[:len(sections)-1] {
child, ok := node[section]
if !ok {
return nil, false
}

switch child := child.(type) {
case map[string]any:
node = make(map[any]any, len(child))
for k, v := range child {
node[k] = v
}
case map[any]any:
node = child
default:
return nil, false
}
}
if val, ok := node[sections[len(sections)-1]]; ok {
return val, true
}
}

return nil, false
}

type mapValueSource struct {
key string
ms MapSource
}

func NewMapValueSource(key string, ms MapSource) ValueSource {
return &mapValueSource{
key: key,
ms: ms,
}
}

func (mvs *mapValueSource) String() string {
return fmt.Sprintf("key %[1]q from %[2]s", mvs.key, mvs.ms.String())
}

func (mvs *mapValueSource) GoString() string {
return fmt.Sprintf("&mapValueSource{key:%[1]q, src:%[2]s}", mvs.key, mvs.ms.GoString())
}

func (mvs *mapValueSource) Lookup() (string, bool) {
if v, ok := mvs.ms.Lookup(mvs.key); !ok {
return "", false
} else {
return fmt.Sprintf("%+v", v), true
}
}
Loading

0 comments on commit 0a1c772

Please sign in to comment.