@@ -17,6 +17,26 @@ type ValueSource interface {
1717 Lookup () (string , bool )
1818}
1919
20+ // EnvValueSource is to specifically detect env sources when
21+ // printing help text
22+ type EnvValueSource interface {
23+ IsFromEnv () bool
24+ Key () string
25+ }
26+
27+ // MapSource is a source which can be used to look up a value
28+ // based on a key
29+ // typically for use with a cli.Flag
30+ type MapSource interface {
31+ fmt.Stringer
32+ fmt.GoStringer
33+
34+ // Lookup returns the value from the source based on key
35+ // and if it was found
36+ // or returns an empty string and false
37+ Lookup (string ) (any , bool )
38+ }
39+
2040// ValueSourceChain contains an ordered series of ValueSource that
2141// allows for lookup where the first ValueSource to resolve is
2242// returned
@@ -38,8 +58,8 @@ func (vsc *ValueSourceChain) EnvKeys() []string {
3858 vals := []string {}
3959
4060 for _ , src := range vsc .Chain {
41- if v , ok := src .(* envVarValueSource ); ok {
42- vals = append (vals , v .Key )
61+ if v , ok := src .(EnvValueSource ); ok && v . IsFromEnv () {
62+ vals = append (vals , v .Key () )
4363 }
4464 }
4565
@@ -83,21 +103,29 @@ func (vsc *ValueSourceChain) LookupWithSource() (string, ValueSource, bool) {
83103
84104// envVarValueSource encapsulates a ValueSource from an environment variable
85105type envVarValueSource struct {
86- Key string
106+ key string
87107}
88108
89109func (e * envVarValueSource ) Lookup () (string , bool ) {
90- return os .LookupEnv (strings .TrimSpace (string (e .Key )))
110+ return os .LookupEnv (strings .TrimSpace (string (e .key )))
91111}
92112
93- func (e * envVarValueSource ) String () string { return fmt .Sprintf ("environment variable %[1]q" , e .Key ) }
113+ func (e * envVarValueSource ) IsFromEnv () bool {
114+ return true
115+ }
116+
117+ func (e * envVarValueSource ) Key () string {
118+ return e .key
119+ }
120+
121+ func (e * envVarValueSource ) String () string { return fmt .Sprintf ("environment variable %[1]q" , e .key ) }
94122func (e * envVarValueSource ) GoString () string {
95- return fmt .Sprintf ("&envVarValueSource{Key:%[1]q}" , e .Key )
123+ return fmt .Sprintf ("&envVarValueSource{Key:%[1]q}" , e .key )
96124}
97125
98126func EnvVar (key string ) ValueSource {
99127 return & envVarValueSource {
100- Key : key ,
128+ key : key ,
101129 }
102130}
103131
@@ -107,7 +135,7 @@ func EnvVars(keys ...string) ValueSourceChain {
107135 vsc := ValueSourceChain {Chain : []ValueSource {}}
108136
109137 for _ , key := range keys {
110- vsc .Chain = append (vsc .Chain , & envVarValueSource { Key : key } )
138+ vsc .Chain = append (vsc .Chain , EnvVar ( key ) )
111139 }
112140
113141 return vsc
@@ -138,8 +166,85 @@ func Files(paths ...string) ValueSourceChain {
138166 vsc := ValueSourceChain {Chain : []ValueSource {}}
139167
140168 for _ , path := range paths {
141- vsc .Chain = append (vsc .Chain , & fileValueSource { Path : path } )
169+ vsc .Chain = append (vsc .Chain , File ( path ) )
142170 }
143171
144172 return vsc
145173}
174+
175+ type mapSource struct {
176+ name string
177+ m map [any ]any
178+ }
179+
180+ func NewMapSource (name string , m map [any ]any ) MapSource {
181+ return & mapSource {
182+ name : name ,
183+ m : m ,
184+ }
185+ }
186+
187+ func (ms * mapSource ) String () string { return fmt .Sprintf ("map source %[1]q" , ms .name ) }
188+ func (ms * mapSource ) GoString () string {
189+ return fmt .Sprintf ("&mapSource{name:%[1]q}" , ms .name )
190+ }
191+
192+ func (ms * mapSource ) Lookup (name string ) (any , bool ) {
193+ // nestedVal checks if the name has '.' delimiters.
194+ // If so, it tries to traverse the tree by the '.' delimited sections to find
195+ // a nested value for the key.
196+ if sections := strings .Split (name , "." ); len (sections ) > 1 {
197+ node := ms .m
198+ for _ , section := range sections [:len (sections )- 1 ] {
199+ child , ok := node [section ]
200+ if ! ok {
201+ return nil , false
202+ }
203+
204+ switch child := child .(type ) {
205+ case map [string ]any :
206+ node = make (map [any ]any , len (child ))
207+ for k , v := range child {
208+ node [k ] = v
209+ }
210+ case map [any ]any :
211+ node = child
212+ default :
213+ return nil , false
214+ }
215+ }
216+ if val , ok := node [sections [len (sections )- 1 ]]; ok {
217+ return val , true
218+ }
219+ }
220+
221+ return nil , false
222+ }
223+
224+ type mapValueSource struct {
225+ key string
226+ ms MapSource
227+ }
228+
229+ func NewMapValueSource (key string , ms MapSource ) ValueSource {
230+ return & mapValueSource {
231+ key : key ,
232+ ms : ms ,
233+ }
234+ }
235+
236+ func (mvs * mapValueSource ) String () string {
237+ return fmt .Sprintf ("key %[1]q from %[2]s" , mvs .key , mvs .ms .String ())
238+ }
239+
240+ func (mvs * mapValueSource ) GoString () string {
241+ return fmt .Sprintf ("&mapValueSource{key:%[1]q, src:%[2]s}" , mvs .key , mvs .ms .GoString ())
242+ }
243+
244+ func (mvs * mapValueSource ) Lookup () (string , bool ) {
245+ if v , ok := mvs .ms .Lookup (mvs .key ); ! ok {
246+ return "" , false
247+ } else {
248+ return fmt .Sprintf ("%+v" , v ), true
249+ }
250+ }
0 commit comments