-
Notifications
You must be signed in to change notification settings - Fork 64
/
Copy pathflag.go
207 lines (170 loc) · 5.26 KB
/
flag.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
package multiconfig
import (
"flag"
"fmt"
"os"
"reflect"
"strings"
"github.com/fatih/camelcase"
"github.com/fatih/structs"
)
// FlagLoader satisfies the loader interface. It creates on the fly flags based
// on the field names and parses them to load into the given pointer of struct
// s.
type FlagLoader struct {
// Prefix prepends the prefix to each flag name i.e:
// --foo is converted to --prefix-foo.
// --foo-bar is converted to --prefix-foo-bar.
Prefix string
// Flatten doesn't add prefixes for nested structs. So previously if we had
// a nested struct `type T struct{Name struct{ ...}}`, this would generate
// --name-foo, --name-bar, etc. When Flatten is enabled, the flags will be
// flattend to the form: --foo, --bar, etc.. Panics if the nested structs
// has a duplicate field name in the root level of the struct (outer
// struct). Use this option only if you know what you do.
Flatten bool
// CamelCase adds a separator for field names in camelcase form. A
// fieldname of "AccessKey" would generate a flag name "--accesskey". If
// CamelCase is enabled, the flag name will be generated in the form of
// "--access-key"
CamelCase bool
// EnvPrefix is just a placeholder to print the correct usages when an
// EnvLoader is used
EnvPrefix string
// ErrorHandling is used to configure error handling used by
// *flag.FlagSet.
//
// By default it's flag.ContinueOnError.
ErrorHandling flag.ErrorHandling
// Args defines a custom argument list. If nil, os.Args[1:] is used.
Args []string
// FlagUsageFunc an optional function that is called to set a flag.Usage value
// The input is the raw flag name, and the output should be a string
// that will used in passed into the flag for Usage.
FlagUsageFunc func(name string) string
// only exists for testing. This is the raw flagset that is to parse
flagSet *flag.FlagSet
}
// Load loads the source into the config defined by struct s
func (f *FlagLoader) Load(s interface{}) error {
strct := structs.New(s)
structName := strct.Name()
flagSet := flag.NewFlagSet(structName, f.ErrorHandling)
f.flagSet = flagSet
for _, field := range strct.Fields() {
f.processField(field.Name(), field)
}
flagSet.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
flagSet.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nGenerated environment variables:\n")
e := &EnvironmentLoader{
Prefix: f.EnvPrefix,
CamelCase: f.CamelCase,
}
e.PrintEnvs(s)
fmt.Println("")
}
args := filterArgs(os.Args[1:])
if f.Args != nil {
args = f.Args
}
return flagSet.Parse(args)
}
func filterArgs(args []string) []string {
r := []string{}
for i := 0; i < len(args); i++ {
if strings.Index(args[i], "test.") >= 0 {
if i + 1 < len(args) && strings.Index(args[i + 1], "-") == -1 {
i++
}
i++
} else {
r = append(r, args[i])
}
}
return r
}
// processField generates a flag based on the given field and fieldName. If a
// nested struct is detected, a flag for each field of that nested struct is
// generated too.
func (f *FlagLoader) processField(fieldName string, field *structs.Field) error {
if f.CamelCase {
fieldName = strings.Join(camelcase.Split(fieldName), "-")
fieldName = strings.Replace(fieldName, "---", "-", -1)
}
switch field.Kind() {
case reflect.Struct:
for _, ff := range field.Fields() {
flagName := field.Name() + "-" + ff.Name()
if f.Flatten {
// first check if it's set or not, because if we have duplicate
// we don't want to break the flag. Panic by giving a readable
// output
f.flagSet.VisitAll(func(fl *flag.Flag) {
if strings.ToLower(ff.Name()) == fl.Name {
// already defined
panic(fmt.Sprintf("flag '%s' is already defined in outer struct", fl.Name))
}
})
flagName = ff.Name()
}
if err := f.processField(flagName, ff); err != nil {
return err
}
}
default:
// Add custom prefix to the flag if it's set
if f.Prefix != "" {
fieldName = f.Prefix + "-" + fieldName
}
// we only can get the value from expored fields, unexported fields panics
if field.IsExported() {
f.flagSet.Var(newFieldValue(field), flagName(fieldName), f.flagUsage(fieldName, field))
}
}
return nil
}
func (f *FlagLoader) flagUsage(fieldName string, field *structs.Field) string {
if f.FlagUsageFunc != nil {
return f.FlagUsageFunc(fieldName)
}
usage := field.Tag("flagUsage")
if usage != "" {
return usage
}
return fmt.Sprintf("Change value of %s.", fieldName)
}
// fieldValue satisfies the flag.Value and flag.Getter interfaces
type fieldValue struct {
field *structs.Field
}
func newFieldValue(f *structs.Field) *fieldValue {
return &fieldValue{
field: f,
}
}
func (f *fieldValue) Set(val string) error {
return fieldSet(f.field, val)
}
func (f *fieldValue) String() string {
if f.IsZero() {
return ""
}
return fmt.Sprintf("%v", f.field.Value())
}
func (f *fieldValue) Get() interface{} {
if f.IsZero() {
return nil
}
return f.field.Value()
}
func (f *fieldValue) IsZero() bool {
return f.field == nil
}
// This is an unexported interface, be careful about it.
// https://code.google.com/p/go/source/browse/src/pkg/flag/flag.go?name=release#101
func (f *fieldValue) IsBoolFlag() bool {
return f.field.Kind() == reflect.Bool
}
func flagName(name string) string { return strings.ToLower(name) }