blob: 5f85250199791ecb270d6ba368b527406ce0f3f2 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001package flags
2
3import (
4 "bytes"
5 "fmt"
6 "os"
7 "reflect"
8 "strings"
9 "unicode/utf8"
10)
11
12// Option flag information. Contains a description of the option, short and
13// long name as well as a default value and whether an argument for this
14// flag is optional.
15type Option struct {
16 // The description of the option flag. This description is shown
17 // automatically in the built-in help.
18 Description string
19
20 // The short name of the option (a single character). If not 0, the
21 // option flag can be 'activated' using -<ShortName>. Either ShortName
22 // or LongName needs to be non-empty.
23 ShortName rune
24
25 // The long name of the option. If not "", the option flag can be
26 // activated using --<LongName>. Either ShortName or LongName needs
27 // to be non-empty.
28 LongName string
29
30 // The default value of the option.
31 Default []string
32
33 // The optional environment default value key name.
34 EnvDefaultKey string
35
36 // The optional delimiter string for EnvDefaultKey values.
37 EnvDefaultDelim string
38
39 // If true, specifies that the argument to an option flag is optional.
40 // When no argument to the flag is specified on the command line, the
41 // value of OptionalValue will be set in the field this option represents.
42 // This is only valid for non-boolean options.
43 OptionalArgument bool
44
45 // The optional value of the option. The optional value is used when
46 // the option flag is marked as having an OptionalArgument. This means
47 // that when the flag is specified, but no option argument is given,
48 // the value of the field this option represents will be set to
49 // OptionalValue. This is only valid for non-boolean options.
50 OptionalValue []string
51
52 // If true, the option _must_ be specified on the command line. If the
53 // option is not specified, the parser will generate an ErrRequired type
54 // error.
55 Required bool
56
57 // A name for the value of an option shown in the Help as --flag [ValueName]
58 ValueName string
59
60 // A mask value to show in the help instead of the default value. This
61 // is useful for hiding sensitive information in the help, such as
62 // passwords.
63 DefaultMask string
64
65 // If non empty, only a certain set of values is allowed for an option.
66 Choices []string
67
68 // If true, the option is not displayed in the help or man page
69 Hidden bool
70
71 // The group which the option belongs to
72 group *Group
73
74 // The struct field which the option represents.
75 field reflect.StructField
76
77 // The struct field value which the option represents.
78 value reflect.Value
79
80 // Determines if the option will be always quoted in the INI output
81 iniQuote bool
82
83 tag multiTag
84 isSet bool
85 isSetDefault bool
86 preventDefault bool
87
88 defaultLiteral string
89}
90
91// LongNameWithNamespace returns the option's long name with the group namespaces
92// prepended by walking up the option's group tree. Namespaces and the long name
93// itself are separated by the parser's namespace delimiter. If the long name is
94// empty an empty string is returned.
95func (option *Option) LongNameWithNamespace() string {
96 if len(option.LongName) == 0 {
97 return ""
98 }
99
100 // fetch the namespace delimiter from the parser which is always at the
101 // end of the group hierarchy
102 namespaceDelimiter := ""
103 g := option.group
104
105 for {
106 if p, ok := g.parent.(*Parser); ok {
107 namespaceDelimiter = p.NamespaceDelimiter
108
109 break
110 }
111
112 switch i := g.parent.(type) {
113 case *Command:
114 g = i.Group
115 case *Group:
116 g = i
117 }
118 }
119
120 // concatenate long name with namespace
121 longName := option.LongName
122 g = option.group
123
124 for g != nil {
125 if g.Namespace != "" {
126 longName = g.Namespace + namespaceDelimiter + longName
127 }
128
129 switch i := g.parent.(type) {
130 case *Command:
131 g = i.Group
132 case *Group:
133 g = i
134 case *Parser:
135 g = nil
136 }
137 }
138
139 return longName
140}
141
142// String converts an option to a human friendly readable string describing the
143// option.
144func (option *Option) String() string {
145 var s string
146 var short string
147
148 if option.ShortName != 0 {
149 data := make([]byte, utf8.RuneLen(option.ShortName))
150 utf8.EncodeRune(data, option.ShortName)
151 short = string(data)
152
153 if len(option.LongName) != 0 {
154 s = fmt.Sprintf("%s%s, %s%s",
155 string(defaultShortOptDelimiter), short,
156 defaultLongOptDelimiter, option.LongNameWithNamespace())
157 } else {
158 s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short)
159 }
160 } else if len(option.LongName) != 0 {
161 s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongNameWithNamespace())
162 }
163
164 return s
165}
166
167// Value returns the option value as an interface{}.
168func (option *Option) Value() interface{} {
169 return option.value.Interface()
170}
171
172// Field returns the reflect struct field of the option.
173func (option *Option) Field() reflect.StructField {
174 return option.field
175}
176
177// IsSet returns true if option has been set
178func (option *Option) IsSet() bool {
179 return option.isSet
180}
181
182// IsSetDefault returns true if option has been set via the default option tag
183func (option *Option) IsSetDefault() bool {
184 return option.isSetDefault
185}
186
187// Set the value of an option to the specified value. An error will be returned
188// if the specified value could not be converted to the corresponding option
189// value type.
190func (option *Option) set(value *string) error {
191 kind := option.value.Type().Kind()
192
193 if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet {
194 option.empty()
195 }
196
197 option.isSet = true
198 option.preventDefault = true
199
200 if len(option.Choices) != 0 {
201 found := false
202
203 for _, choice := range option.Choices {
204 if choice == *value {
205 found = true
206 break
207 }
208 }
209
210 if !found {
211 allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ")
212
213 if len(option.Choices) > 1 {
214 allowed += " or " + option.Choices[len(option.Choices)-1]
215 }
216
217 return newErrorf(ErrInvalidChoice,
218 "Invalid value `%s' for option `%s'. Allowed values are: %s",
219 *value, option, allowed)
220 }
221 }
222
223 if option.isFunc() {
224 return option.call(value)
225 } else if value != nil {
226 return convert(*value, option.value, option.tag)
227 }
228
229 return convert("", option.value, option.tag)
230}
231
232func (option *Option) canCli() bool {
233 return option.ShortName != 0 || len(option.LongName) != 0
234}
235
236func (option *Option) canArgument() bool {
237 if u := option.isUnmarshaler(); u != nil {
238 return true
239 }
240
241 return !option.isBool()
242}
243
244func (option *Option) emptyValue() reflect.Value {
245 tp := option.value.Type()
246
247 if tp.Kind() == reflect.Map {
248 return reflect.MakeMap(tp)
249 }
250
251 return reflect.Zero(tp)
252}
253
254func (option *Option) empty() {
255 if !option.isFunc() {
256 option.value.Set(option.emptyValue())
257 }
258}
259
260func (option *Option) clearDefault() {
261 usedDefault := option.Default
262
263 if envKey := option.EnvDefaultKey; envKey != "" {
264 if value, ok := os.LookupEnv(envKey); ok {
265 if option.EnvDefaultDelim != "" {
266 usedDefault = strings.Split(value,
267 option.EnvDefaultDelim)
268 } else {
269 usedDefault = []string{value}
270 }
271 }
272 }
273
274 option.isSetDefault = true
275
276 if len(usedDefault) > 0 {
277 option.empty()
278
279 for _, d := range usedDefault {
280 option.set(&d)
281 option.isSetDefault = true
282 }
283 } else {
284 tp := option.value.Type()
285
286 switch tp.Kind() {
287 case reflect.Map:
288 if option.value.IsNil() {
289 option.empty()
290 }
291 case reflect.Slice:
292 if option.value.IsNil() {
293 option.empty()
294 }
295 }
296 }
297}
298
299func (option *Option) valueIsDefault() bool {
300 // Check if the value of the option corresponds to its
301 // default value
302 emptyval := option.emptyValue()
303
304 checkvalptr := reflect.New(emptyval.Type())
305 checkval := reflect.Indirect(checkvalptr)
306
307 checkval.Set(emptyval)
308
309 if len(option.Default) != 0 {
310 for _, v := range option.Default {
311 convert(v, checkval, option.tag)
312 }
313 }
314
315 return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
316}
317
318func (option *Option) isUnmarshaler() Unmarshaler {
319 v := option.value
320
321 for {
322 if !v.CanInterface() {
323 break
324 }
325
326 i := v.Interface()
327
328 if u, ok := i.(Unmarshaler); ok {
329 return u
330 }
331
332 if !v.CanAddr() {
333 break
334 }
335
336 v = v.Addr()
337 }
338
339 return nil
340}
341
342func (option *Option) isBool() bool {
343 tp := option.value.Type()
344
345 for {
346 switch tp.Kind() {
347 case reflect.Slice, reflect.Ptr:
348 tp = tp.Elem()
349 case reflect.Bool:
350 return true
351 case reflect.Func:
352 return tp.NumIn() == 0
353 default:
354 return false
355 }
356 }
357}
358
359func (option *Option) isSignedNumber() bool {
360 tp := option.value.Type()
361
362 for {
363 switch tp.Kind() {
364 case reflect.Slice, reflect.Ptr:
365 tp = tp.Elem()
366 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
367 return true
368 default:
369 return false
370 }
371 }
372}
373
374func (option *Option) isFunc() bool {
375 return option.value.Type().Kind() == reflect.Func
376}
377
378func (option *Option) call(value *string) error {
379 var retval []reflect.Value
380
381 if value == nil {
382 retval = option.value.Call(nil)
383 } else {
384 tp := option.value.Type().In(0)
385
386 val := reflect.New(tp)
387 val = reflect.Indirect(val)
388
389 if err := convert(*value, val, option.tag); err != nil {
390 return err
391 }
392
393 retval = option.value.Call([]reflect.Value{val})
394 }
395
396 if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
397 if retval[0].Interface() == nil {
398 return nil
399 }
400
401 return retval[0].Interface().(error)
402 }
403
404 return nil
405}
406
407func (option *Option) updateDefaultLiteral() {
408 defs := option.Default
409 def := ""
410
411 if len(defs) == 0 && option.canArgument() {
412 var showdef bool
413
414 switch option.field.Type.Kind() {
415 case reflect.Func, reflect.Ptr:
416 showdef = !option.value.IsNil()
417 case reflect.Slice, reflect.String, reflect.Array:
418 showdef = option.value.Len() > 0
419 case reflect.Map:
420 showdef = !option.value.IsNil() && option.value.Len() > 0
421 default:
422 zeroval := reflect.Zero(option.field.Type)
423 showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
424 }
425
426 if showdef {
427 def, _ = convertToString(option.value, option.tag)
428 }
429 } else if len(defs) != 0 {
430 l := len(defs) - 1
431
432 for i := 0; i < l; i++ {
433 def += quoteIfNeeded(defs[i]) + ", "
434 }
435
436 def += quoteIfNeeded(defs[l])
437 }
438
439 option.defaultLiteral = def
440}
441
442func (option *Option) shortAndLongName() string {
443 ret := &bytes.Buffer{}
444
445 if option.ShortName != 0 {
446 ret.WriteRune(defaultShortOptDelimiter)
447 ret.WriteRune(option.ShortName)
448 }
449
450 if len(option.LongName) != 0 {
451 if option.ShortName != 0 {
452 ret.WriteRune('/')
453 }
454
455 ret.WriteString(option.LongName)
456 }
457
458 return ret.String()
459}