1package 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}
460