Skip to content

Commit 2d02868

Browse files
committed
Add map value source
1 parent ef45965 commit 2d02868

File tree

5 files changed

+248
-10
lines changed

5 files changed

+248
-10
lines changed

flag_bool_with_inverse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (parent *BoolWithInverseFlag) initialize() {
106106
sources := []ValueSource{}
107107

108108
for _, envVar := range child.GetEnvVars() {
109-
sources = append(sources, &envVarValueSource{Key: strings.ToUpper(parent.InversePrefix) + envVar})
109+
sources = append(sources, EnvVar(strings.ToUpper(parent.InversePrefix)+envVar))
110110
}
111111
parent.negativeFlag.Sources = NewValueSourceChain(sources...)
112112
}

godoc-current.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,11 @@ type DocGenerationMultiValueFlag interface {
591591

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

594+
type EnvValueSource interface {
595+
IsFromEnv() bool
596+
Key() string
597+
}
598+
594599
type ErrorFormatter interface {
595600
Format(s fmt.State, verb rune)
596601
}
@@ -848,6 +853,12 @@ func (i MapBase[T, C, VC]) ToString(t map[string]T) string
848853
func (i *MapBase[T, C, VC]) Value() map[string]T
849854
Value returns the mapping of values set by this flag
850855

856+
type MapSource struct {
857+
// Has unexported fields.
858+
}
859+
860+
func NewMapSource(name string, m map[any]any) *MapSource
861+
851862
type MultiError interface {
852863
error
853864
Errors() []error
@@ -1000,6 +1011,8 @@ func EnvVar(key string) ValueSource
10001011

10011012
func File(path string) ValueSource
10021013

1014+
func NewMapValueSource(key string, ms *MapSource) ValueSource
1015+
10031016
type ValueSourceChain struct {
10041017
Chain []ValueSource
10051018
}

testdata/godoc-v3.x.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,11 @@ type DocGenerationMultiValueFlag interface {
591591

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

594+
type EnvValueSource interface {
595+
IsFromEnv() bool
596+
Key() string
597+
}
598+
594599
type ErrorFormatter interface {
595600
Format(s fmt.State, verb rune)
596601
}
@@ -848,6 +853,12 @@ func (i MapBase[T, C, VC]) ToString(t map[string]T) string
848853
func (i *MapBase[T, C, VC]) Value() map[string]T
849854
Value returns the mapping of values set by this flag
850855

856+
type MapSource struct {
857+
// Has unexported fields.
858+
}
859+
860+
func NewMapSource(name string, m map[any]any) *MapSource
861+
851862
type MultiError interface {
852863
error
853864
Errors() []error
@@ -1000,6 +1011,8 @@ func EnvVar(key string) ValueSource
10001011

10011012
func File(path string) ValueSource
10021013

1014+
func NewMapValueSource(key string, ms *MapSource) ValueSource
1015+
10031016
type ValueSourceChain struct {
10041017
Chain []ValueSource
10051018
}

value_source.go

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ type ValueSource interface {
1717
Lookup() (string, bool)
1818
}
1919

20+
type EnvValueSource interface {
21+
IsFromEnv() bool
22+
Key() string
23+
}
24+
2025
// ValueSourceChain contains an ordered series of ValueSource that
2126
// allows for lookup where the first ValueSource to resolve is
2227
// returned
@@ -38,8 +43,8 @@ func (vsc *ValueSourceChain) EnvKeys() []string {
3843
vals := []string{}
3944

4045
for _, src := range vsc.Chain {
41-
if v, ok := src.(*envVarValueSource); ok {
42-
vals = append(vals, v.Key)
46+
if v, ok := src.(EnvValueSource); ok {
47+
vals = append(vals, v.Key())
4348
}
4449
}
4550

@@ -83,21 +88,29 @@ func (vsc *ValueSourceChain) LookupWithSource() (string, ValueSource, bool) {
8388

8489
// envVarValueSource encapsulates a ValueSource from an environment variable
8590
type envVarValueSource struct {
86-
Key string
91+
key string
8792
}
8893

8994
func (e *envVarValueSource) Lookup() (string, bool) {
90-
return os.LookupEnv(strings.TrimSpace(string(e.Key)))
95+
return os.LookupEnv(strings.TrimSpace(string(e.key)))
96+
}
97+
98+
func (e *envVarValueSource) IsFromEnv() bool {
99+
return true
91100
}
92101

93-
func (e *envVarValueSource) String() string { return fmt.Sprintf("environment variable %[1]q", e.Key) }
102+
func (e *envVarValueSource) Key() string {
103+
return e.key
104+
}
105+
106+
func (e *envVarValueSource) String() string { return fmt.Sprintf("environment variable %[1]q", e.key) }
94107
func (e *envVarValueSource) GoString() string {
95-
return fmt.Sprintf("&envVarValueSource{Key:%[1]q}", e.Key)
108+
return fmt.Sprintf("&envVarValueSource{Key:%[1]q}", e.key)
96109
}
97110

98111
func EnvVar(key string) ValueSource {
99112
return &envVarValueSource{
100-
Key: key,
113+
key: key,
101114
}
102115
}
103116

@@ -107,7 +120,7 @@ func EnvVars(keys ...string) ValueSourceChain {
107120
vsc := ValueSourceChain{Chain: []ValueSource{}}
108121

109122
for _, key := range keys {
110-
vsc.Chain = append(vsc.Chain, &envVarValueSource{Key: key})
123+
vsc.Chain = append(vsc.Chain, EnvVar(key))
111124
}
112125

113126
return vsc
@@ -138,8 +151,80 @@ func Files(paths ...string) ValueSourceChain {
138151
vsc := ValueSourceChain{Chain: []ValueSource{}}
139152

140153
for _, path := range paths {
141-
vsc.Chain = append(vsc.Chain, &fileValueSource{Path: path})
154+
vsc.Chain = append(vsc.Chain, File(path))
142155
}
143156

144157
return vsc
145158
}
159+
160+
type MapSource struct {
161+
name string
162+
m map[any]any
163+
}
164+
165+
func NewMapSource(name string, m map[any]any) *MapSource {
166+
return &MapSource{
167+
name: name,
168+
m: m,
169+
}
170+
}
171+
172+
func (ms *MapSource) lookup(name string) (any, bool) {
173+
// nestedVal checks if the name has '.' delimiters.
174+
// If so, it tries to traverse the tree by the '.' delimited sections to find
175+
// a nested value for the key.
176+
if sections := strings.Split(name, "."); len(sections) > 1 {
177+
node := ms.m
178+
for _, section := range sections[:len(sections)-1] {
179+
child, ok := node[section]
180+
if !ok {
181+
return nil, false
182+
}
183+
184+
switch child := child.(type) {
185+
case map[string]any:
186+
node = make(map[any]any, len(child))
187+
for k, v := range child {
188+
node[k] = v
189+
}
190+
case map[any]any:
191+
node = child
192+
default:
193+
return nil, false
194+
}
195+
}
196+
if val, ok := node[sections[len(sections)-1]]; ok {
197+
return val, true
198+
}
199+
}
200+
201+
return nil, false
202+
}
203+
204+
type mapValueSource struct {
205+
key string
206+
ms *MapSource
207+
}
208+
209+
func NewMapValueSource(key string, ms *MapSource) ValueSource {
210+
return &mapValueSource{
211+
key: key,
212+
ms: ms,
213+
}
214+
}
215+
216+
func (mvs *mapValueSource) String() string {
217+
return fmt.Sprintf("map source key %[1]q from %[2]q", mvs.key, mvs.ms.name)
218+
}
219+
220+
func (mvs *mapValueSource) GoString() string {
221+
return fmt.Sprintf("&mapValueSource{key:%[1]q, src:%[2]q}", mvs.key, mvs.ms.m)
222+
}
223+
224+
func (mvs *mapValueSource) Lookup() (string, bool) {
225+
if v, ok := mvs.ms.lookup(mvs.key); !ok {
226+
return "", false
227+
} else {
228+
return fmt.Sprintf("%+v", v), true
229+
}
230+
}

value_source_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,130 @@ func (svs *staticValueSource) GoString() string {
189189
}
190190
func (svs *staticValueSource) String() string { return svs.v }
191191
func (svs *staticValueSource) Lookup() (string, bool) { return svs.v, true }
192+
193+
func TestMapValueSource(t *testing.T) {
194+
tests := []struct {
195+
name string
196+
m map[any]any
197+
key string
198+
val string
199+
found bool
200+
}{
201+
{
202+
name: "No map no key",
203+
},
204+
{
205+
name: "No map with key",
206+
key: "foo",
207+
},
208+
{
209+
name: "Empty map no key",
210+
m: map[any]any{},
211+
},
212+
{
213+
name: "Empty map with key",
214+
key: "foo",
215+
m: map[any]any{},
216+
},
217+
{
218+
name: "Level 1 no key",
219+
key: ".foob",
220+
m: map[any]any{
221+
"foo": 10,
222+
},
223+
},
224+
{
225+
name: "Level 2",
226+
key: "foo.bar",
227+
m: map[any]any{
228+
"foo": map[any]any{
229+
"bar": 10,
230+
},
231+
},
232+
val: "10",
233+
found: true,
234+
},
235+
{
236+
name: "Level 2 invalid key",
237+
key: "foo.bar1",
238+
m: map[any]any{
239+
"foo": map[any]any{
240+
"bar": "10",
241+
},
242+
},
243+
},
244+
{
245+
name: "Level 3 no entry",
246+
key: "foo.bar.t",
247+
m: map[any]any{
248+
"foo": map[any]any{
249+
"bar": "sss",
250+
},
251+
},
252+
},
253+
{
254+
name: "Level 3",
255+
key: "foo.bar.t",
256+
m: map[any]any{
257+
"foo": map[any]any{
258+
"bar": map[any]any{
259+
"t": "sss",
260+
},
261+
},
262+
},
263+
val: "sss",
264+
found: true,
265+
},
266+
{
267+
name: "Level 3 invalid key",
268+
key: "foo.bar.t",
269+
m: map[any]any{
270+
"foo": map[any]any{
271+
"bar": map[any]any{
272+
"t1": 10,
273+
},
274+
},
275+
},
276+
},
277+
{
278+
name: "Level 4 no entry",
279+
key: "foo.bar.t.gh",
280+
m: map[any]any{
281+
"foo": map[any]any{
282+
"bar": map[any]any{
283+
"t1": 10,
284+
},
285+
},
286+
},
287+
},
288+
{
289+
name: "Level 4 slice entry",
290+
key: "foo.bar.t.gh",
291+
m: map[any]any{
292+
"foo": map[any]any{
293+
"bar": map[any]any{
294+
"t": map[any]any{
295+
"gh": []int{10},
296+
},
297+
},
298+
},
299+
},
300+
val: "[10]",
301+
found: true,
302+
},
303+
}
304+
305+
for _, test := range tests {
306+
t.Run(test.key, func(t *testing.T) {
307+
ms := NewMapSource("test", test.m)
308+
m := NewMapValueSource(test.key, ms)
309+
val, b := m.Lookup()
310+
if !test.found {
311+
assert.False(t, b)
312+
} else {
313+
assert.True(t, b)
314+
assert.Equal(t, val, test.val)
315+
}
316+
})
317+
}
318+
}

0 commit comments

Comments
 (0)