blob: f6d6941813be679081135259129b55db504e61d2 [file] [log] [blame]
Naveen Sampath04696f72022-06-13 15:19:14 +05301package 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 clearReferenceBeforeSet bool
88
89 defaultLiteral string
90}
91
92// LongNameWithNamespace returns the option's long name with the group namespaces
93// prepended by walking up the option's group tree. Namespaces and the long name
94// itself are separated by the parser's namespace delimiter. If the long name is
95// empty an empty string is returned.
96func (option *Option) LongNameWithNamespace() string {
97 if len(option.LongName) == 0 {
98 return ""
99 }
100
101 // fetch the namespace delimiter from the parser which is always at the
102 // end of the group hierarchy
103 namespaceDelimiter := ""
104 g := option.group
105
106 for {
107 if p, ok := g.parent.(*Parser); ok {
108 namespaceDelimiter = p.NamespaceDelimiter
109
110 break
111 }
112
113 switch i := g.parent.(type) {
114 case *Command:
115 g = i.Group
116 case *Group:
117 g = i
118 }
119 }
120
121 // concatenate long name with namespace
122 longName := option.LongName
123 g = option.group
124
125 for g != nil {
126 if g.Namespace != "" {
127 longName = g.Namespace + namespaceDelimiter + longName
128 }
129
130 switch i := g.parent.(type) {
131 case *Command:
132 g = i.Group
133 case *Group:
134 g = i
135 case *Parser:
136 g = nil
137 }
138 }
139
140 return longName
141}
142
143// EnvKeyWithNamespace returns the option's env key with the group namespaces
144// prepended by walking up the option's group tree. Namespaces and the env key
145// itself are separated by the parser's namespace delimiter. If the env key is
146// empty an empty string is returned.
147func (option *Option) EnvKeyWithNamespace() string {
148 if len(option.EnvDefaultKey) == 0 {
149 return ""
150 }
151
152 // fetch the namespace delimiter from the parser which is always at the
153 // end of the group hierarchy
154 namespaceDelimiter := ""
155 g := option.group
156
157 for {
158 if p, ok := g.parent.(*Parser); ok {
159 namespaceDelimiter = p.EnvNamespaceDelimiter
160
161 break
162 }
163
164 switch i := g.parent.(type) {
165 case *Command:
166 g = i.Group
167 case *Group:
168 g = i
169 }
170 }
171
172 // concatenate long name with namespace
173 key := option.EnvDefaultKey
174 g = option.group
175
176 for g != nil {
177 if g.EnvNamespace != "" {
178 key = g.EnvNamespace + namespaceDelimiter + key
179 }
180
181 switch i := g.parent.(type) {
182 case *Command:
183 g = i.Group
184 case *Group:
185 g = i
186 case *Parser:
187 g = nil
188 }
189 }
190
191 return key
192}
193
194// String converts an option to a human friendly readable string describing the
195// option.
196func (option *Option) String() string {
197 var s string
198 var short string
199
200 if option.ShortName != 0 {
201 data := make([]byte, utf8.RuneLen(option.ShortName))
202 utf8.EncodeRune(data, option.ShortName)
203 short = string(data)
204
205 if len(option.LongName) != 0 {
206 s = fmt.Sprintf("%s%s, %s%s",
207 string(defaultShortOptDelimiter), short,
208 defaultLongOptDelimiter, option.LongNameWithNamespace())
209 } else {
210 s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short)
211 }
212 } else if len(option.LongName) != 0 {
213 s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongNameWithNamespace())
214 }
215
216 return s
217}
218
219// Value returns the option value as an interface{}.
220func (option *Option) Value() interface{} {
221 return option.value.Interface()
222}
223
224// Field returns the reflect struct field of the option.
225func (option *Option) Field() reflect.StructField {
226 return option.field
227}
228
229// IsSet returns true if option has been set
230func (option *Option) IsSet() bool {
231 return option.isSet
232}
233
234// IsSetDefault returns true if option has been set via the default option tag
235func (option *Option) IsSetDefault() bool {
236 return option.isSetDefault
237}
238
239// Set the value of an option to the specified value. An error will be returned
240// if the specified value could not be converted to the corresponding option
241// value type.
242func (option *Option) set(value *string) error {
243 kind := option.value.Type().Kind()
244
245 if (kind == reflect.Map || kind == reflect.Slice) && option.clearReferenceBeforeSet {
246 option.empty()
247 }
248
249 option.isSet = true
250 option.preventDefault = true
251 option.clearReferenceBeforeSet = false
252
253 if len(option.Choices) != 0 {
254 found := false
255
256 for _, choice := range option.Choices {
257 if choice == *value {
258 found = true
259 break
260 }
261 }
262
263 if !found {
264 allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ")
265
266 if len(option.Choices) > 1 {
267 allowed += " or " + option.Choices[len(option.Choices)-1]
268 }
269
270 return newErrorf(ErrInvalidChoice,
271 "Invalid value `%s' for option `%s'. Allowed values are: %s",
272 *value, option, allowed)
273 }
274 }
275
276 if option.isFunc() {
277 return option.call(value)
278 } else if value != nil {
279 return convert(*value, option.value, option.tag)
280 }
281
282 return convert("", option.value, option.tag)
283}
284
285func (option *Option) setDefault(value *string) error {
286 if option.preventDefault {
287 return nil
288 }
289
290 if err := option.set(value); err != nil {
291 return err
292 }
293
294 option.isSetDefault = true
295 option.preventDefault = false
296
297 return nil
298}
299
300func (option *Option) showInHelp() bool {
301 return !option.Hidden && (option.ShortName != 0 || len(option.LongName) != 0)
302}
303
304func (option *Option) canArgument() bool {
305 if u := option.isUnmarshaler(); u != nil {
306 return true
307 }
308
309 return !option.isBool()
310}
311
312func (option *Option) emptyValue() reflect.Value {
313 tp := option.value.Type()
314
315 if tp.Kind() == reflect.Map {
316 return reflect.MakeMap(tp)
317 }
318
319 return reflect.Zero(tp)
320}
321
322func (option *Option) empty() {
323 if !option.isFunc() {
324 option.value.Set(option.emptyValue())
325 }
326}
327
328func (option *Option) clearDefault() error {
329 if option.preventDefault {
330 return nil
331 }
332
333 usedDefault := option.Default
334
335 if envKey := option.EnvKeyWithNamespace(); envKey != "" {
336 if value, ok := os.LookupEnv(envKey); ok {
337 if option.EnvDefaultDelim != "" {
338 usedDefault = strings.Split(value, option.EnvDefaultDelim)
339 } else {
340 usedDefault = []string{value}
341 }
342 }
343 }
344
345 option.isSetDefault = true
346
347 if len(usedDefault) > 0 {
348 option.empty()
349
350 for _, d := range usedDefault {
351 err := option.setDefault(&d)
352
353 if err != nil {
354 return err
355 }
356 }
357 } else {
358 tp := option.value.Type()
359
360 switch tp.Kind() {
361 case reflect.Map:
362 if option.value.IsNil() {
363 option.empty()
364 }
365 case reflect.Slice:
366 if option.value.IsNil() {
367 option.empty()
368 }
369 }
370 }
371
372 return nil
373}
374
375func (option *Option) valueIsDefault() bool {
376 // Check if the value of the option corresponds to its
377 // default value
378 emptyval := option.emptyValue()
379
380 checkvalptr := reflect.New(emptyval.Type())
381 checkval := reflect.Indirect(checkvalptr)
382
383 checkval.Set(emptyval)
384
385 if len(option.Default) != 0 {
386 for _, v := range option.Default {
387 convert(v, checkval, option.tag)
388 }
389 }
390
391 return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
392}
393
394func (option *Option) isUnmarshaler() Unmarshaler {
395 v := option.value
396
397 for {
398 if !v.CanInterface() {
399 break
400 }
401
402 i := v.Interface()
403
404 if u, ok := i.(Unmarshaler); ok {
405 return u
406 }
407
408 if !v.CanAddr() {
409 break
410 }
411
412 v = v.Addr()
413 }
414
415 return nil
416}
417
418func (option *Option) isValueValidator() ValueValidator {
419 v := option.value
420
421 for {
422 if !v.CanInterface() {
423 break
424 }
425
426 i := v.Interface()
427
428 if u, ok := i.(ValueValidator); ok {
429 return u
430 }
431
432 if !v.CanAddr() {
433 break
434 }
435
436 v = v.Addr()
437 }
438
439 return nil
440}
441
442func (option *Option) isBool() bool {
443 tp := option.value.Type()
444
445 for {
446 switch tp.Kind() {
447 case reflect.Slice, reflect.Ptr:
448 tp = tp.Elem()
449 case reflect.Bool:
450 return true
451 case reflect.Func:
452 return tp.NumIn() == 0
453 default:
454 return false
455 }
456 }
457}
458
459func (option *Option) isSignedNumber() bool {
460 tp := option.value.Type()
461
462 for {
463 switch tp.Kind() {
464 case reflect.Slice, reflect.Ptr:
465 tp = tp.Elem()
466 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
467 return true
468 default:
469 return false
470 }
471 }
472}
473
474func (option *Option) isFunc() bool {
475 return option.value.Type().Kind() == reflect.Func
476}
477
478func (option *Option) call(value *string) error {
479 var retval []reflect.Value
480
481 if value == nil {
482 retval = option.value.Call(nil)
483 } else {
484 tp := option.value.Type().In(0)
485
486 val := reflect.New(tp)
487 val = reflect.Indirect(val)
488
489 if err := convert(*value, val, option.tag); err != nil {
490 return err
491 }
492
493 retval = option.value.Call([]reflect.Value{val})
494 }
495
496 if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
497 if retval[0].Interface() == nil {
498 return nil
499 }
500
501 return retval[0].Interface().(error)
502 }
503
504 return nil
505}
506
507func (option *Option) updateDefaultLiteral() {
508 defs := option.Default
509 def := ""
510
511 if len(defs) == 0 && option.canArgument() {
512 var showdef bool
513
514 switch option.field.Type.Kind() {
515 case reflect.Func, reflect.Ptr:
516 showdef = !option.value.IsNil()
517 case reflect.Slice, reflect.String, reflect.Array:
518 showdef = option.value.Len() > 0
519 case reflect.Map:
520 showdef = !option.value.IsNil() && option.value.Len() > 0
521 default:
522 zeroval := reflect.Zero(option.field.Type)
523 showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
524 }
525
526 if showdef {
527 def, _ = convertToString(option.value, option.tag)
528 }
529 } else if len(defs) != 0 {
530 l := len(defs) - 1
531
532 for i := 0; i < l; i++ {
533 def += quoteIfNeeded(defs[i]) + ", "
534 }
535
536 def += quoteIfNeeded(defs[l])
537 }
538
539 option.defaultLiteral = def
540}
541
542func (option *Option) shortAndLongName() string {
543 ret := &bytes.Buffer{}
544
545 if option.ShortName != 0 {
546 ret.WriteRune(defaultShortOptDelimiter)
547 ret.WriteRune(option.ShortName)
548 }
549
550 if len(option.LongName) != 0 {
551 if option.ShortName != 0 {
552 ret.WriteRune('/')
553 }
554
555 ret.WriteString(option.LongName)
556 }
557
558 return ret.String()
559}
560
561func (option *Option) isValidValue(arg string) error {
562 if validator := option.isValueValidator(); validator != nil {
563 return validator.IsValidValue(arg)
564 }
565 if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') {
566 return fmt.Errorf("expected argument for flag `%s', but got option `%s'", option, arg)
567 }
568 return nil
569}