-
Notifications
You must be signed in to change notification settings - Fork 0
/
loader.go
128 lines (117 loc) · 3.22 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
package cli
import (
"fmt"
"strings"
)
// Provider is implemented by types which provide option values,
// such as from environment variables, config files, CLI flags, etc.
type Provider interface {
Provide(*Loader) error
}
// NewLoader returns a Loader instance which is configured
// to load option values from the given providers.
func NewLoader(opts []*Opt, providers ...Provider) *Loader {
var keys [][]string
for _, opt := range opts {
keys = append(keys, opt.Key)
}
return &Loader{
keys: keys,
opts: opts,
providers: providers,
Coerce: Coerce,
}
}
// Loader is used to load, coerce, and set option values
// at command run time. Loader.Load runs the providers
// in order. Once an option has been set, it is not overridden
// by later values.
type Loader struct {
opts []*Opt
keys [][]string
providers []Provider
errors []error
// Coerce can be used to override the type coercion
// needed when setting an option value. A coerce function
// must set the value. "dst" is always a pointer to the
// value which needs to be set, e.g. *int for an option
// value of type int. See coerce.go for an example.
Coerce func(dst, src interface{}) error
}
// Load runs the providers, loading and setting option values.
// Load is meant to be called only once.
// TODO this is awkward. The code should make it clear that
// it only gets called once, and/or make it safe to load
// multiple times.
func (l *Loader) Load() {
for _, src := range l.providers {
err := src.Provide(l)
if err != nil {
l.errors = append(l.errors, err)
}
}
}
// Errors returns a list of errors encountered during loading.
func (l *Loader) Errors() []error {
return l.errors
}
// Keys returns a list of keys for all options.
func (l *Loader) Keys() [][]string {
return l.keys
}
// Get gets the current option value for the given key.
func (l *Loader) Get(key []string) interface{} {
for _, opt := range l.opts {
if l.eq(key, opt.Key) {
return opt.Value
}
}
return nil
}
// GetString returns the option value as a string,
// or else an empty string.
func (l *Loader) GetString(key []string) string {
val := l.Get(key)
s, ok := val.(string)
if !ok {
return ""
}
return s
}
// Set sets an option value for the option at the given key.
// Once an option is set, it will not be overridden by future
// calls to Set. Set uses Loader.Coerce to set the value.
//
// TODO this is ugly. This checks IsSet, but doesn't set it to true,
// that happens somewhere else. The whole set/coerce interface
// needs cleanup
func (l *Loader) Set(key []string, val interface{}) {
for _, opt := range l.opts {
if !l.eq(key, opt.Key) {
continue
}
if opt.IsSet {
continue
}
err := l.Coerce(opt.Value, val)
if err != nil {
l.errors = append(l.errors, err)
}
return
}
// TODO these errors are missing context, e.g. "in file config.yaml"
l.errors = append(l.errors, fmt.Errorf("unknown opt key %v", key))
}
// eq returns true if two option keys are equal.
// eq is case insensitive.
func (l *Loader) eq(key1, key2 []string) bool {
if len(key1) != len(key2) {
return false
}
for i := 0; i < len(key1); i++ {
if strings.ToLower(key1[i]) != strings.ToLower(key2[i]) {
return false
}
}
return true
}