-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
434 lines (380 loc) · 11.9 KB
/
config.go
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
package config
import (
"fmt"
"io"
"os"
"reflect"
"strings"
)
/*
Config represents a configuration for an application.
It can be used in lieu of the package-level functions or DefaultConfig if
an application needs to manage multiple, separate configurations.
The zero value is ready to use.
*/
type Config struct {
settings settings
root NodePath
ptrs [][2]reflect.Value
loaders Loaders
reg SetterRegistry
}
/*
GetSetterRegistry returns the SetterRegistry used by the Config.
It returns the last value passed to c.SetSetterRegistry, or
DefaultSetterRegistry if none is set.
*/
func (c *Config) GetSetterRegistry() SetterRegistry {
if c.reg == nil {
return DefaultSetterRegistry
}
return c.reg
}
/*
SetSetterRegistry sets a new SetterRegistry to be used by the Config.
This can be useful for providing custom SetterCreator implementations for new or
existing types without changing the global DefaultSetterRegistry.
Passing nil will cause the Config to use the DefaultSetterRegistry.
*/
func (c *Config) SetSetterRegistry(reg SetterRegistry) {
c.reg = reg
}
/*
GetLoaders returns the current Loaders used by the Config.
It returns the last loaders set by SetLoaders, or a set of default loaders if
none was set.
*/
func (c *Config) GetLoaders() Loaders {
if c.loaders == nil {
c.loaders = GetDefaultLoaders()
}
return c.loaders
}
/*
SetLoaders sets loaders to be used by the Config.
It makes a copy of the parameter, so modifying the parameter after the function
returns is safe. Similarly, subsequently calling c.AddLoader will not modify the
parameter passed to this method.
Ordering is important, as subsequent loaders can overwrite configuration
values from earlier ones.
Passing a nil value will cause Config to use a default set of loaders.
*/
func (c *Config) SetLoaders(loaders Loaders) {
c.loaders = loaders.Copy()
}
/*
AddLoader appends a loader to the set of loaders used by c.
*/
func (c *Config) AddLoader(loader Loader) {
if c.loaders == nil {
c.loaders = append(c.GetLoaders(), loader)
} else {
c.loaders.Add(loader)
}
}
/*
Args returns the command-line arguments left after parsing.
Returns nil if Load has not been called, or if no flag loader was included in
the config.
*/
func (c *Config) Args() []string {
// We only return something useful after Load() has been called, which means
// c.loaders should be populated.
for i := range c.loaders {
if l, ok := c.loaders[i].(interface{ Args() []string }); ok {
return l.Args()
}
}
return nil
}
/*
Scan uses reflection to populate configuration settings from a struct.
The strct parameter must be a non-nil pointer to a struct type. Scan will panic
if any other value is passed.
This method is implicitly called by calling c.Configure. This method can be used
if multiple struct types are to be scanned; if additional settings are to be
added with c.Var before calling c.Load; or if unsupported type errors are to be
ignored. Note that any names scanned from the struct must be unique. If any
name is duplicated, Scan panics. This can only happen when calling Scan multiple
times or mixing calls to Var and Scan.
If the return value is non-nil, it will be of type Errors. Each element will in
turn be of type *UnknownTypeError, one for each scanned field for which a
Setter could not be created.
Only exported fields can be set. Unexported fields will be ignored. See the
package-level documentation for information about which field types are
supported by default and the effects of various struct tags.
*/
func (c *Config) Scan(strct interface{}) error {
v := reflect.ValueOf(strct)
if t := v.Type(); t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
panic(fmt.Sprintf("parameter must be a pointer to a struct, not %s", t))
}
if v.IsNil() {
panic(fmt.Sprintf("parameter can not be nil"))
}
return c.scan(v.Elem(), &c.root)
}
/*
Var adds a single value to be set by Config.
The value parameter must be a non-nil pointer. Passing any other value will
panic. The tag parameter can be used to set most tags as documented in the
package-level documentation, with the exception of 'config' and 'prefix' tags.
If value points to a struct, the function is equivalent to calling Scan with
value nested at the level indicated by name. For example:
opts := struct {
Level1: struct {
Level2: struct {
X int
}
}
}
Scan(&opts)
is equivalent to
Var(&opts.Level1.Level2, "", "Level1", "Level2")
The remaining parameters are variadic, but at least one must be provided. If
only one value is provided, it is a non-prefixed name. If multiple values are
provided, leading values are prefixes. For example,
DefaultConfig.Var(&version, "", "version")
will parse command-line flag -version and environment variable VERSION, while
DefaultConfig.Var(&version, "", "api", "version")
parses command-line flag -api-version and environment variable API_VERSION.
Any name, with or without prefix, must be unique to the configuration. If it
duplicates a name already set with Var or parsed by Scan, Var panics.
*/
func (c *Config) Var(value interface{}, tag reflect.StructTag, name ...string) error {
v := reflect.ValueOf(value)
if v.Kind() != reflect.Ptr {
panic(fmt.Sprintf("parameter 'value' must be a pointer, not %s", v.Type()))
}
if v.IsNil() {
panic(fmt.Sprintf("parameter 'value' can not be nil"))
}
v = v.Elem()
if len(name) == 0 {
panic("parameter 'path' can not be empty")
}
lastPath := &c.root
for i := 0; i < len(name)-1; i++ {
if np := lastPath.FindNodePath(name[i]); np == nil {
lastPath = lastPath.AddNodePath(lastPath.NewNodePath(name[i]))
} else {
lastPath = np
}
}
setter := c.findSetter(v, tag)
if setter == nil && v.Kind() == reflect.Struct {
if np := lastPath.FindNodePath(name[len(name)-1]); np == nil {
lastPath = lastPath.AddNodePath(lastPath.NewNodePath(name[len(name)-1]))
} else {
lastPath = np
}
return c.scan(v, lastPath)
}
p := lastPath.NewPath(name[len(name)-1])
if setter == nil {
return &UnknownTypeError{Type: v.Type(), Path: p}
}
c.settings.add(Setting{
Path: lastPath.AddPath(p),
Tag: tag,
Setter: setter,
})
return nil
}
/*
Load calls loaders to parse and validate configuration.
This method is implicitly called by c.Configure. It is only necessary to call
it if not calling c.Configure. This method will only do something useful if
c.Scan or c.Var has been called to populate a list of values to set.
Any validation or load errors will result in a non-nil return status.
If one of the loaders is a *FlagLoader and the -h flag has not been overridden,
calling Load with "-h" in the application's command line arguments will cause
a list of settings and their descriptions to be printed to stderr.
*/
func (c *Config) Load() error {
loaders := c.GetLoaders()
settingsMap := c.settingsByLoader(loaders)
var errs Errors
for i := range loaders {
if settings := settingsMap[loaders[i].Name()]; len(settings) != 0 {
loaders[i].Init(settings)
if loader, ok := loaders[i].(interface{ SetUsageFn(func()) }); ok {
loader.SetUsageFn(func() { c.Usage(nil) })
}
}
}
for i := range loaders {
if err := loaders[i].Load(); err != nil {
errs.Append(err)
}
}
c.setPtrs()
return errs.AsError()
}
/*
Usage dumps usage information to an io.Writer.
Usage writes a list of setting names and their descriptions to w. If w is nil,
the list is dumped to os.Stderr.
*/
func (c *Config) Usage(w io.Writer) {
if w == nil {
w = os.Stderr
}
loaders := c.GetLoaders()
for i := range loaders {
io.WriteString(w, loaders[i].Usage())
io.WriteString(w, "\n")
}
}
/*
Configure scans the strct argument to generate a list of parameters and then
loads them.
The strct parameter must be a non-nil pointer to a struct type. Any other value
panics.
It is equivalent to calling c.Scan followed by c.Load. The return value, if not
nil, will be of type Errors, and will contain the concatenated errors from both
Scan and Load.
*/
func (c *Config) Configure(strct interface{}) error {
var errs Errors
if err := c.Scan(strct); err != nil {
errs.Append(err)
}
if err := c.Load(); err != nil {
errs.Append(err)
}
return errs.AsError()
}
func (c *Config) scan(structVal reflect.Value, lastPath *NodePath) error {
var errs Errors
structType := structVal.Type()
for i := 0; i < structType.NumField(); i++ {
structField, fieldVal := structType.Field(i), structVal.Field(i)
if !fieldVal.CanInterface() {
continue
}
name := structField.Tag.Get("config")
if name == "" {
name = structField.Name
} else if name == "-" {
continue
}
p := lastPath.NewPath(name)
if setter := c.findSetter(fieldVal, structField.Tag); setter != nil {
c.settings.add(Setting{
Path: lastPath.AddPath(p),
Tag: structField.Tag,
Setter: setter,
})
continue
}
// Find the last value that's a pointer and that isn't nil.
for fieldVal.Kind() == reflect.Ptr && !fieldVal.IsNil() {
fieldVal = fieldVal.Elem()
}
if fieldVal.Kind() == reflect.Ptr {
// We have a nil pointer. Iterate the type until we find a
// non-pointer type. If it's a struct, create a temporary value
// for fieldVal, and save the original (ptr) and temporary value
// to c.ptrs.
t := fieldVal.Type().Elem()
for ; t.Kind() == reflect.Ptr; t = t.Elem() {
}
if t.Kind() == reflect.Struct {
// we have to create the value with new, otherwise it won't be
// addressable.
ptrs := [2]reflect.Value{fieldVal, reflect.New(t).Elem()}
fieldVal = ptrs[1]
c.ptrs = append(c.ptrs, ptrs)
}
}
if fieldVal.Kind() == reflect.Struct {
prefix := structField.Tag.Get("prefix")
if prefix == "" {
prefix = name
}
var np *NodePath
if prefix == "-" {
np = lastPath
} else {
np = lastPath.FindNodePath(prefix)
if np == nil {
np = lastPath.AddNodePath(lastPath.NewNodePath(prefix))
}
}
if err := c.scan(fieldVal, np); err != nil {
errs.Append(err)
}
} else {
errs.Append(&UnknownTypeError{Type: structField.Type, Path: p})
}
}
return errs.AsError()
}
func (c *Config) setPtrs() {
// Iterate in reverse of the order that this list was created. This causes
// us to process more deeply nested structs before outer ones. Otherwise,
// we will evaluate the shallower structs for the zero value before we've
// had a chance to set pointers for their members.
for i := len(c.ptrs) - 1; i >= 0; i-- {
ptr, val := c.ptrs[i][0], c.ptrs[i][1]
if reflect.Zero(val.Type()).Interface() != val.Interface() {
for t := ptr.Type().Elem(); t.Kind() == reflect.Ptr; t = t.Elem() {
ptr.Set(reflect.New(t))
ptr = ptr.Elem()
}
ptr.Set(val.Addr())
}
}
}
func (c *Config) findSetter(val reflect.Value, tag reflect.StructTag) Setter {
reg := c.reg
if reg == nil {
reg = DefaultSetterRegistry
}
return reg.GetSetter(val, tag)
}
func (c *Config) settingsByLoader(loaders []Loader) map[string]settings {
m := make(map[string]settings, len(loaders))
loaderNames := make([]string, len(loaders))
for i := range loaders {
loaderNames[i] = loaders[i].Name()
m[loaderNames[i]] = make(settings, 0, len(c.settings))
}
for i := range c.settings {
var keys []string
if tag := c.settings[i].Tag.Get("from"); tag == "" || tag == "*" {
keys = loaderNames
} else {
keys = strings.Split(tag, ",")
}
for _, k := range keys {
if settings, ok := m[k]; ok {
m[k] = append(settings, c.settings[i])
}
}
}
return m
}
/*
DefaultConfig is a default, global Config.
Is is used by the package-level Scan, Var and Configure functions, or can be
used directly by an application that doesn't require multiple configurations.
*/
var DefaultConfig = new(Config)
// Scan calls DefaultConfig.Scan.
func Scan(strct interface{}) error {
return DefaultConfig.Scan(strct)
}
//Var calls DefaultConfig.Var
func Var(value interface{}, tag reflect.StructTag, name ...string) error {
return DefaultConfig.Var(value, tag, name...)
}
//Load calls DefaultConfig.Load
func Load() error {
return DefaultConfig.Load()
}
// Configure calls DefaultConfig.Configure
func Configure(strct interface{}) error {
return DefaultConfig.Configure(strct)
}