1package flags
2
3import (
4	"bytes"
5	"fmt"
6	"reflect"
7	"strings"
8	"syscall"
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// IsSet 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		// os.Getenv() makes no distinction between undefined and
265		// empty values, so we use syscall.Getenv()
266		if value, ok := syscall.Getenv(envKey); ok {
267			if option.EnvDefaultDelim != "" {
268				usedDefault = strings.Split(value,
269					option.EnvDefaultDelim)
270			} else {
271				usedDefault = []string{value}
272			}
273		}
274	}
275
276	option.isSetDefault = true
277
278	if len(usedDefault) > 0 {
279		option.empty()
280
281		for _, d := range usedDefault {
282			option.set(&d)
283			option.isSetDefault = true
284		}
285	} else {
286		tp := option.value.Type()
287
288		switch tp.Kind() {
289		case reflect.Map:
290			if option.value.IsNil() {
291				option.empty()
292			}
293		case reflect.Slice:
294			if option.value.IsNil() {
295				option.empty()
296			}
297		}
298	}
299}
300
301func (option *Option) valueIsDefault() bool {
302	// Check if the value of the option corresponds to its
303	// default value
304	emptyval := option.emptyValue()
305
306	checkvalptr := reflect.New(emptyval.Type())
307	checkval := reflect.Indirect(checkvalptr)
308
309	checkval.Set(emptyval)
310
311	if len(option.Default) != 0 {
312		for _, v := range option.Default {
313			convert(v, checkval, option.tag)
314		}
315	}
316
317	return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
318}
319
320func (option *Option) isUnmarshaler() Unmarshaler {
321	v := option.value
322
323	for {
324		if !v.CanInterface() {
325			break
326		}
327
328		i := v.Interface()
329
330		if u, ok := i.(Unmarshaler); ok {
331			return u
332		}
333
334		if !v.CanAddr() {
335			break
336		}
337
338		v = v.Addr()
339	}
340
341	return nil
342}
343
344func (option *Option) isBool() bool {
345	tp := option.value.Type()
346
347	for {
348		switch tp.Kind() {
349		case reflect.Slice, reflect.Ptr:
350			tp = tp.Elem()
351		case reflect.Bool:
352			return true
353		case reflect.Func:
354			return tp.NumIn() == 0
355		default:
356			return false
357		}
358	}
359}
360
361func (option *Option) isSignedNumber() bool {
362	tp := option.value.Type()
363
364	for {
365		switch tp.Kind() {
366		case reflect.Slice, reflect.Ptr:
367			tp = tp.Elem()
368		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
369			return true
370		default:
371			return false
372		}
373	}
374}
375
376func (option *Option) isFunc() bool {
377	return option.value.Type().Kind() == reflect.Func
378}
379
380func (option *Option) call(value *string) error {
381	var retval []reflect.Value
382
383	if value == nil {
384		retval = option.value.Call(nil)
385	} else {
386		tp := option.value.Type().In(0)
387
388		val := reflect.New(tp)
389		val = reflect.Indirect(val)
390
391		if err := convert(*value, val, option.tag); err != nil {
392			return err
393		}
394
395		retval = option.value.Call([]reflect.Value{val})
396	}
397
398	if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
399		if retval[0].Interface() == nil {
400			return nil
401		}
402
403		return retval[0].Interface().(error)
404	}
405
406	return nil
407}
408
409func (option *Option) updateDefaultLiteral() {
410	defs := option.Default
411	def := ""
412
413	if len(defs) == 0 && option.canArgument() {
414		var showdef bool
415
416		switch option.field.Type.Kind() {
417		case reflect.Func, reflect.Ptr:
418			showdef = !option.value.IsNil()
419		case reflect.Slice, reflect.String, reflect.Array:
420			showdef = option.value.Len() > 0
421		case reflect.Map:
422			showdef = !option.value.IsNil() && option.value.Len() > 0
423		default:
424			zeroval := reflect.Zero(option.field.Type)
425			showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
426		}
427
428		if showdef {
429			def, _ = convertToString(option.value, option.tag)
430		}
431	} else if len(defs) != 0 {
432		l := len(defs) - 1
433
434		for i := 0; i < l; i++ {
435			def += quoteIfNeeded(defs[i]) + ", "
436		}
437
438		def += quoteIfNeeded(defs[l])
439	}
440
441	option.defaultLiteral = def
442}
443
444func (option *Option) shortAndLongName() string {
445	ret := &bytes.Buffer{}
446
447	if option.ShortName != 0 {
448		ret.WriteRune(defaultShortOptDelimiter)
449		ret.WriteRune(option.ShortName)
450	}
451
452	if len(option.LongName) != 0 {
453		if option.ShortName != 0 {
454			ret.WriteRune('/')
455		}
456
457		ret.WriteString(option.LongName)
458	}
459
460	return ret.String()
461}
462