-
Notifications
You must be signed in to change notification settings - Fork 1
/
loader.go
296 lines (236 loc) · 6.85 KB
/
loader.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
package gofig
import (
"reflect"
"strings"
"sync"
)
// Gofig default configuration.
const (
DefaultStructTag = "gofig"
)
// Loader parses configuration from one or more sources.
type Loader struct {
// parsers priority mapping
parsers Parsers
// notifiers we are currently watching
notifiers []NotifyParser
wg sync.WaitGroup
// flattened map of field keys to struct reflect values
fields Fields
// Configurable options
keyFormatter Formatter // case sensitive
structTag string // gofig
enforcePriority bool // true
delimiter string // "."
// Logging configuration
logger Logger
debug bool
}
// New constructs a new Loader
func New(dst interface{}, opts ...Option) (*Loader, error) {
t := reflect.TypeOf(dst)
v := reflect.ValueOf(dst)
if t.Kind() != reflect.Ptr || v.IsNil() {
return nil, ErrInvalidValue{reflect.TypeOf(v)}
}
if v.Elem().Kind() != reflect.Struct {
return nil, ErrInvalidValue{reflect.TypeOf(v)}
}
l := &Loader{
parsers: make(Parsers),
notifiers: make([]NotifyParser, 0),
fields: make(Fields),
// Defaults
keyFormatter: CaseSensitiveKeys(),
structTag: DefaultStructTag,
enforcePriority: true,
delimiter: ".",
// Logger
logger: DefaultLogger(),
}
for _, opt := range opts {
opt.apply(l)
}
l.flatten(v.Elem(), t.Elem(), "")
return l, nil
}
// Parse parses the given parsers in order. If any one parser fails an error will be returned.
func (l *Loader) Parse(parsers ...Parser) error {
for _, p := range parsers {
if err := l.parse(l.parsers.Add(p)); err != nil {
return err
}
}
return nil
}
// log returns a logger if debug is true
func (l *Loader) log() Logger {
if l.debug {
return l.logger
}
return NopLogger()
}
// parse parses an single parser.
func (l *Loader) parse(p PrioritisedParser) error {
// Set the delimiter
p.SetDelimeter(l.delimiter)
// Send keys to the parser
if err := l.sendKeys(p); err != nil {
return nil
}
// Get the values
ch, err := p.Values()
if err != nil {
return err
}
// Range over the channel until it's closed processing the returned key / values
for fn := range ch {
// Call the function passed on the channel returning key value pair
key, val := fn()
key = l.keyFormatter.Format(key, l.delimiter)
// Lookup the field
field, ok := l.lookup(key)
if !ok {
l.log().Printf("%s key not found", key)
continue
}
// Check we can set the fields value if we are enforcing priority.
if l.enforcePriority && !field.CanSet(p) {
continue
}
// Set the value on the field.
if err := field.Set(val); err != nil {
return err
}
// If enforcing we the priority on the field.
if l.enforcePriority {
field.SetPriority(p)
}
}
return nil
}
// sends keys to the parser.
func (l *Loader) sendKeys(p Parser) error {
// Send the keys
errCh := make(chan error, 1)
keyCh := make(chan string, len(l.fields))
go func() {
close(errCh)
if err := p.Keys(keyCh); err != nil {
errCh <- err
}
}()
for _, f := range l.fields {
keyCh <- f.Key()
}
close(keyCh)
return <-errCh
}
// lookup finds a field by it's key. If it finds a field that is a map that map is initialised
// returning a field values can be set on.
func (l *Loader) lookup(key string) (Field, bool) {
// Look up the field
field, ok := l.find(key)
if !ok {
return nil, false
}
// Return the field if it is not a map
if field.Value().Kind() != reflect.Map {
return field, true
}
// The field is a map, this could be a leaf node, init the map
// Generate the map key path by removing the root key from the field key
// e.g foo.bar.baz becomes baz where bar is a map ahd baz the map key
mk := strings.Trim(strings.Replace(key, field.Key(), "", -1), l.delimiter)
// Returns the leaf map that the value should be set into
mv, err := l.initMap(field.Value(), mk)
if err != nil {
return nil, false
}
// Make a field we can set map index values on
kp := strings.Split(mk, l.delimiter)
field = newMapField(key, kp[len(kp)-1], mv)
// Insert the field into the field map so we don't have to initMap again for this value
l.fields.Set(key, field)
return field, true
}
// find recursively finds a field based on the key path until a field is found or the key is empty.
func (l *Loader) find(key string) (Field, bool) {
// Look up the field
field, ok := l.fields[key]
if ok {
return field, true
}
elms := strings.Split(key, l.delimiter)
if key := strings.Join(elms[:len(elms)-1], l.delimiter); key != "" {
return l.find(key)
}
return nil, false
}
// initMap initialises maps with zero values for the given keep. Also handles deeply nested maps.
// Returns the map to set values into.
func (l *Loader) initMap(elem reflect.Value, key string) (reflect.Value, error) {
// If the elem value is nil, initialise a new map of the correct types and set it as the fields value
if elem.IsNil() {
if elem.Type().Key().Kind() != reflect.String {
return reflect.Value{}, ErrInvalidValue{
Type: elem.Type().Key(),
}
}
elem.Set(reflect.MakeMap(reflect.MapOf(
elem.Type().Key(),
elem.Type().Elem())))
}
// If the maps value is of another map this is a nested map
// We need too initialise a new map of the correct types and set the keys index to be that new
// nested map.
if elem.Type().Elem().Kind() == reflect.Map {
// Split the key at the delimiter extracting the parent and children key elements
elms := strings.Split(key, l.delimiter)
parent, children := elms[0], elms[1:]
// Remove the parent from the key, e.g foo.bar.baz becomes bar.baz
key = strings.Join(children, l.delimiter)
if key == "" {
return reflect.Value{}, nil // error
}
// Check if we have this map index in the map, if not create a new value for the index of
// the correct type - this will be another map.
m := elem.MapIndex(reflect.ValueOf(parent))
if !m.IsValid() {
m = reflect.New(elem.Type().Elem()).Elem()
}
// As this is a map we now init that map
field, err := l.initMap(m, key)
if err != nil {
return reflect.Value{}, err
}
// Set the map index of key parent to the value of the new map.
elem.SetMapIndex(reflect.ValueOf(elms[0]), m)
return field, nil
}
// Return the map for index value setting.
return elem, nil
}
// flatten recursively flattens a struct.
func (l *Loader) flatten(rv reflect.Value, rt reflect.Type, key string) {
for i := 0; i < rv.NumField(); i++ {
fv := rv.Field(i)
ft := rt.Field(i)
// TODO: embedded support
if fv.CanSet() {
tag := TagFromStructField(ft, l.structTag)
fk := l.keyFormatter.Format(
strings.Trim(
strings.Join(
append(strings.Split(key, l.delimiter), tag.Name), l.delimiter),
l.delimiter), l.delimiter)
l.log().Printf("<Field %s kind:%s key:%s tag:%s>", ft.Name, fv.Kind(), fk, tag)
switch fv.Kind() {
case reflect.Struct:
l.flatten(fv, ft.Type, fk)
default:
l.fields.Set(fk, newField(fk, fv))
}
}
}
}