1package cli
2
3import (
4	"flag"
5	"fmt"
6	"reflect"
7	"runtime"
8	"strconv"
9	"strings"
10	"syscall"
11	"time"
12)
13
14const defaultPlaceholder = "value"
15
16// BashCompletionFlag enables bash-completion for all commands and subcommands
17var BashCompletionFlag Flag = BoolFlag{
18	Name:   "generate-bash-completion",
19	Hidden: true,
20}
21
22// VersionFlag prints the version for the application
23var VersionFlag Flag = BoolFlag{
24	Name:  "version, v",
25	Usage: "print the version",
26}
27
28// HelpFlag prints the help for all commands and subcommands
29// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
30// unless HideHelp is set to true)
31var HelpFlag Flag = BoolFlag{
32	Name:  "help, h",
33	Usage: "show help",
34}
35
36// FlagStringer converts a flag definition to a string. This is used by help
37// to display a flag.
38var FlagStringer FlagStringFunc = stringifyFlag
39
40// FlagsByName is a slice of Flag.
41type FlagsByName []Flag
42
43func (f FlagsByName) Len() int {
44	return len(f)
45}
46
47func (f FlagsByName) Less(i, j int) bool {
48	return f[i].GetName() < f[j].GetName()
49}
50
51func (f FlagsByName) Swap(i, j int) {
52	f[i], f[j] = f[j], f[i]
53}
54
55// Flag is a common interface related to parsing flags in cli.
56// For more advanced flag parsing techniques, it is recommended that
57// this interface be implemented.
58type Flag interface {
59	fmt.Stringer
60	// Apply Flag settings to the given flag set
61	Apply(*flag.FlagSet)
62	GetName() string
63}
64
65// errorableFlag is an interface that allows us to return errors during apply
66// it allows flags defined in this library to return errors in a fashion backwards compatible
67// TODO remove in v2 and modify the existing Flag interface to return errors
68type errorableFlag interface {
69	Flag
70
71	ApplyWithError(*flag.FlagSet) error
72}
73
74func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
75	set := flag.NewFlagSet(name, flag.ContinueOnError)
76
77	for _, f := range flags {
78		//TODO remove in v2 when errorableFlag is removed
79		if ef, ok := f.(errorableFlag); ok {
80			if err := ef.ApplyWithError(set); err != nil {
81				return nil, err
82			}
83		} else {
84			f.Apply(set)
85		}
86	}
87	return set, nil
88}
89
90func eachName(longName string, fn func(string)) {
91	parts := strings.Split(longName, ",")
92	for _, name := range parts {
93		name = strings.Trim(name, " ")
94		fn(name)
95	}
96}
97
98// Generic is a generic parseable type identified by a specific flag
99type Generic interface {
100	Set(value string) error
101	String() string
102}
103
104// Apply takes the flagset and calls Set on the generic flag with the value
105// provided by the user for parsing by the flag
106// Ignores parsing errors
107func (f GenericFlag) Apply(set *flag.FlagSet) {
108	f.ApplyWithError(set)
109}
110
111// ApplyWithError takes the flagset and calls Set on the generic flag with the value
112// provided by the user for parsing by the flag
113func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error {
114	val := f.Value
115	if f.EnvVar != "" {
116		for _, envVar := range strings.Split(f.EnvVar, ",") {
117			envVar = strings.TrimSpace(envVar)
118			if envVal, ok := syscall.Getenv(envVar); ok {
119				if err := val.Set(envVal); err != nil {
120					return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err)
121				}
122				break
123			}
124		}
125	}
126
127	eachName(f.Name, func(name string) {
128		set.Var(f.Value, name, f.Usage)
129	})
130
131	return nil
132}
133
134// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
135type StringSlice []string
136
137// Set appends the string value to the list of values
138func (f *StringSlice) Set(value string) error {
139	*f = append(*f, value)
140	return nil
141}
142
143// String returns a readable representation of this value (for usage defaults)
144func (f *StringSlice) String() string {
145	return fmt.Sprintf("%s", *f)
146}
147
148// Value returns the slice of strings set by this flag
149func (f *StringSlice) Value() []string {
150	return *f
151}
152
153// Get returns the slice of strings set by this flag
154func (f *StringSlice) Get() interface{} {
155	return *f
156}
157
158// Apply populates the flag given the flag set and environment
159// Ignores errors
160func (f StringSliceFlag) Apply(set *flag.FlagSet) {
161	f.ApplyWithError(set)
162}
163
164// ApplyWithError populates the flag given the flag set and environment
165func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
166	if f.EnvVar != "" {
167		for _, envVar := range strings.Split(f.EnvVar, ",") {
168			envVar = strings.TrimSpace(envVar)
169			if envVal, ok := syscall.Getenv(envVar); ok {
170				newVal := &StringSlice{}
171				for _, s := range strings.Split(envVal, ",") {
172					s = strings.TrimSpace(s)
173					if err := newVal.Set(s); err != nil {
174						return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err)
175					}
176				}
177				f.Value = newVal
178				break
179			}
180		}
181	}
182
183	eachName(f.Name, func(name string) {
184		if f.Value == nil {
185			f.Value = &StringSlice{}
186		}
187		set.Var(f.Value, name, f.Usage)
188	})
189
190	return nil
191}
192
193// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
194type IntSlice []int
195
196// Set parses the value into an integer and appends it to the list of values
197func (f *IntSlice) Set(value string) error {
198	tmp, err := strconv.Atoi(value)
199	if err != nil {
200		return err
201	}
202	*f = append(*f, tmp)
203	return nil
204}
205
206// String returns a readable representation of this value (for usage defaults)
207func (f *IntSlice) String() string {
208	return fmt.Sprintf("%#v", *f)
209}
210
211// Value returns the slice of ints set by this flag
212func (f *IntSlice) Value() []int {
213	return *f
214}
215
216// Get returns the slice of ints set by this flag
217func (f *IntSlice) Get() interface{} {
218	return *f
219}
220
221// Apply populates the flag given the flag set and environment
222// Ignores errors
223func (f IntSliceFlag) Apply(set *flag.FlagSet) {
224	f.ApplyWithError(set)
225}
226
227// ApplyWithError populates the flag given the flag set and environment
228func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
229	if f.EnvVar != "" {
230		for _, envVar := range strings.Split(f.EnvVar, ",") {
231			envVar = strings.TrimSpace(envVar)
232			if envVal, ok := syscall.Getenv(envVar); ok {
233				newVal := &IntSlice{}
234				for _, s := range strings.Split(envVal, ",") {
235					s = strings.TrimSpace(s)
236					if err := newVal.Set(s); err != nil {
237						return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err)
238					}
239				}
240				f.Value = newVal
241				break
242			}
243		}
244	}
245
246	eachName(f.Name, func(name string) {
247		if f.Value == nil {
248			f.Value = &IntSlice{}
249		}
250		set.Var(f.Value, name, f.Usage)
251	})
252
253	return nil
254}
255
256// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
257type Int64Slice []int64
258
259// Set parses the value into an integer and appends it to the list of values
260func (f *Int64Slice) Set(value string) error {
261	tmp, err := strconv.ParseInt(value, 10, 64)
262	if err != nil {
263		return err
264	}
265	*f = append(*f, tmp)
266	return nil
267}
268
269// String returns a readable representation of this value (for usage defaults)
270func (f *Int64Slice) String() string {
271	return fmt.Sprintf("%#v", *f)
272}
273
274// Value returns the slice of ints set by this flag
275func (f *Int64Slice) Value() []int64 {
276	return *f
277}
278
279// Get returns the slice of ints set by this flag
280func (f *Int64Slice) Get() interface{} {
281	return *f
282}
283
284// Apply populates the flag given the flag set and environment
285// Ignores errors
286func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
287	f.ApplyWithError(set)
288}
289
290// ApplyWithError populates the flag given the flag set and environment
291func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
292	if f.EnvVar != "" {
293		for _, envVar := range strings.Split(f.EnvVar, ",") {
294			envVar = strings.TrimSpace(envVar)
295			if envVal, ok := syscall.Getenv(envVar); ok {
296				newVal := &Int64Slice{}
297				for _, s := range strings.Split(envVal, ",") {
298					s = strings.TrimSpace(s)
299					if err := newVal.Set(s); err != nil {
300						return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
301					}
302				}
303				f.Value = newVal
304				break
305			}
306		}
307	}
308
309	eachName(f.Name, func(name string) {
310		if f.Value == nil {
311			f.Value = &Int64Slice{}
312		}
313		set.Var(f.Value, name, f.Usage)
314	})
315	return nil
316}
317
318// Apply populates the flag given the flag set and environment
319// Ignores errors
320func (f BoolFlag) Apply(set *flag.FlagSet) {
321	f.ApplyWithError(set)
322}
323
324// ApplyWithError populates the flag given the flag set and environment
325func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error {
326	val := false
327	if f.EnvVar != "" {
328		for _, envVar := range strings.Split(f.EnvVar, ",") {
329			envVar = strings.TrimSpace(envVar)
330			if envVal, ok := syscall.Getenv(envVar); ok {
331				if envVal == "" {
332					val = false
333					break
334				}
335
336				envValBool, err := strconv.ParseBool(envVal)
337				if err != nil {
338					return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
339				}
340
341				val = envValBool
342				break
343			}
344		}
345	}
346
347	eachName(f.Name, func(name string) {
348		if f.Destination != nil {
349			set.BoolVar(f.Destination, name, val, f.Usage)
350			return
351		}
352		set.Bool(name, val, f.Usage)
353	})
354
355	return nil
356}
357
358// Apply populates the flag given the flag set and environment
359// Ignores errors
360func (f BoolTFlag) Apply(set *flag.FlagSet) {
361	f.ApplyWithError(set)
362}
363
364// ApplyWithError populates the flag given the flag set and environment
365func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
366	val := true
367	if f.EnvVar != "" {
368		for _, envVar := range strings.Split(f.EnvVar, ",") {
369			envVar = strings.TrimSpace(envVar)
370			if envVal, ok := syscall.Getenv(envVar); ok {
371				if envVal == "" {
372					val = false
373					break
374				}
375
376				envValBool, err := strconv.ParseBool(envVal)
377				if err != nil {
378					return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err)
379				}
380
381				val = envValBool
382				break
383			}
384		}
385	}
386
387	eachName(f.Name, func(name string) {
388		if f.Destination != nil {
389			set.BoolVar(f.Destination, name, val, f.Usage)
390			return
391		}
392		set.Bool(name, val, f.Usage)
393	})
394
395	return nil
396}
397
398// Apply populates the flag given the flag set and environment
399// Ignores errors
400func (f StringFlag) Apply(set *flag.FlagSet) {
401	f.ApplyWithError(set)
402}
403
404// ApplyWithError populates the flag given the flag set and environment
405func (f StringFlag) ApplyWithError(set *flag.FlagSet) error {
406	if f.EnvVar != "" {
407		for _, envVar := range strings.Split(f.EnvVar, ",") {
408			envVar = strings.TrimSpace(envVar)
409			if envVal, ok := syscall.Getenv(envVar); ok {
410				f.Value = envVal
411				break
412			}
413		}
414	}
415
416	eachName(f.Name, func(name string) {
417		if f.Destination != nil {
418			set.StringVar(f.Destination, name, f.Value, f.Usage)
419			return
420		}
421		set.String(name, f.Value, f.Usage)
422	})
423
424	return nil
425}
426
427// Apply populates the flag given the flag set and environment
428// Ignores errors
429func (f IntFlag) Apply(set *flag.FlagSet) {
430	f.ApplyWithError(set)
431}
432
433// ApplyWithError populates the flag given the flag set and environment
434func (f IntFlag) ApplyWithError(set *flag.FlagSet) error {
435	if f.EnvVar != "" {
436		for _, envVar := range strings.Split(f.EnvVar, ",") {
437			envVar = strings.TrimSpace(envVar)
438			if envVal, ok := syscall.Getenv(envVar); ok {
439				envValInt, err := strconv.ParseInt(envVal, 0, 64)
440				if err != nil {
441					return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
442				}
443				f.Value = int(envValInt)
444				break
445			}
446		}
447	}
448
449	eachName(f.Name, func(name string) {
450		if f.Destination != nil {
451			set.IntVar(f.Destination, name, f.Value, f.Usage)
452			return
453		}
454		set.Int(name, f.Value, f.Usage)
455	})
456
457	return nil
458}
459
460// Apply populates the flag given the flag set and environment
461// Ignores errors
462func (f Int64Flag) Apply(set *flag.FlagSet) {
463	f.ApplyWithError(set)
464}
465
466// ApplyWithError populates the flag given the flag set and environment
467func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error {
468	if f.EnvVar != "" {
469		for _, envVar := range strings.Split(f.EnvVar, ",") {
470			envVar = strings.TrimSpace(envVar)
471			if envVal, ok := syscall.Getenv(envVar); ok {
472				envValInt, err := strconv.ParseInt(envVal, 0, 64)
473				if err != nil {
474					return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err)
475				}
476
477				f.Value = envValInt
478				break
479			}
480		}
481	}
482
483	eachName(f.Name, func(name string) {
484		if f.Destination != nil {
485			set.Int64Var(f.Destination, name, f.Value, f.Usage)
486			return
487		}
488		set.Int64(name, f.Value, f.Usage)
489	})
490
491	return nil
492}
493
494// Apply populates the flag given the flag set and environment
495// Ignores errors
496func (f UintFlag) Apply(set *flag.FlagSet) {
497	f.ApplyWithError(set)
498}
499
500// ApplyWithError populates the flag given the flag set and environment
501func (f UintFlag) ApplyWithError(set *flag.FlagSet) error {
502	if f.EnvVar != "" {
503		for _, envVar := range strings.Split(f.EnvVar, ",") {
504			envVar = strings.TrimSpace(envVar)
505			if envVal, ok := syscall.Getenv(envVar); ok {
506				envValInt, err := strconv.ParseUint(envVal, 0, 64)
507				if err != nil {
508					return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err)
509				}
510
511				f.Value = uint(envValInt)
512				break
513			}
514		}
515	}
516
517	eachName(f.Name, func(name string) {
518		if f.Destination != nil {
519			set.UintVar(f.Destination, name, f.Value, f.Usage)
520			return
521		}
522		set.Uint(name, f.Value, f.Usage)
523	})
524
525	return nil
526}
527
528// Apply populates the flag given the flag set and environment
529// Ignores errors
530func (f Uint64Flag) Apply(set *flag.FlagSet) {
531	f.ApplyWithError(set)
532}
533
534// ApplyWithError populates the flag given the flag set and environment
535func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
536	if f.EnvVar != "" {
537		for _, envVar := range strings.Split(f.EnvVar, ",") {
538			envVar = strings.TrimSpace(envVar)
539			if envVal, ok := syscall.Getenv(envVar); ok {
540				envValInt, err := strconv.ParseUint(envVal, 0, 64)
541				if err != nil {
542					return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err)
543				}
544
545				f.Value = uint64(envValInt)
546				break
547			}
548		}
549	}
550
551	eachName(f.Name, func(name string) {
552		if f.Destination != nil {
553			set.Uint64Var(f.Destination, name, f.Value, f.Usage)
554			return
555		}
556		set.Uint64(name, f.Value, f.Usage)
557	})
558
559	return nil
560}
561
562// Apply populates the flag given the flag set and environment
563// Ignores errors
564func (f DurationFlag) Apply(set *flag.FlagSet) {
565	f.ApplyWithError(set)
566}
567
568// ApplyWithError populates the flag given the flag set and environment
569func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error {
570	if f.EnvVar != "" {
571		for _, envVar := range strings.Split(f.EnvVar, ",") {
572			envVar = strings.TrimSpace(envVar)
573			if envVal, ok := syscall.Getenv(envVar); ok {
574				envValDuration, err := time.ParseDuration(envVal)
575				if err != nil {
576					return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err)
577				}
578
579				f.Value = envValDuration
580				break
581			}
582		}
583	}
584
585	eachName(f.Name, func(name string) {
586		if f.Destination != nil {
587			set.DurationVar(f.Destination, name, f.Value, f.Usage)
588			return
589		}
590		set.Duration(name, f.Value, f.Usage)
591	})
592
593	return nil
594}
595
596// Apply populates the flag given the flag set and environment
597// Ignores errors
598func (f Float64Flag) Apply(set *flag.FlagSet) {
599	f.ApplyWithError(set)
600}
601
602// ApplyWithError populates the flag given the flag set and environment
603func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error {
604	if f.EnvVar != "" {
605		for _, envVar := range strings.Split(f.EnvVar, ",") {
606			envVar = strings.TrimSpace(envVar)
607			if envVal, ok := syscall.Getenv(envVar); ok {
608				envValFloat, err := strconv.ParseFloat(envVal, 10)
609				if err != nil {
610					return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err)
611				}
612
613				f.Value = float64(envValFloat)
614				break
615			}
616		}
617	}
618
619	eachName(f.Name, func(name string) {
620		if f.Destination != nil {
621			set.Float64Var(f.Destination, name, f.Value, f.Usage)
622			return
623		}
624		set.Float64(name, f.Value, f.Usage)
625	})
626
627	return nil
628}
629
630func visibleFlags(fl []Flag) []Flag {
631	visible := []Flag{}
632	for _, flag := range fl {
633		field := flagValue(flag).FieldByName("Hidden")
634		if !field.IsValid() || !field.Bool() {
635			visible = append(visible, flag)
636		}
637	}
638	return visible
639}
640
641func prefixFor(name string) (prefix string) {
642	if len(name) == 1 {
643		prefix = "-"
644	} else {
645		prefix = "--"
646	}
647
648	return
649}
650
651// Returns the placeholder, if any, and the unquoted usage string.
652func unquoteUsage(usage string) (string, string) {
653	for i := 0; i < len(usage); i++ {
654		if usage[i] == '`' {
655			for j := i + 1; j < len(usage); j++ {
656				if usage[j] == '`' {
657					name := usage[i+1 : j]
658					usage = usage[:i] + name + usage[j+1:]
659					return name, usage
660				}
661			}
662			break
663		}
664	}
665	return "", usage
666}
667
668func prefixedNames(fullName, placeholder string) string {
669	var prefixed string
670	parts := strings.Split(fullName, ",")
671	for i, name := range parts {
672		name = strings.Trim(name, " ")
673		prefixed += prefixFor(name) + name
674		if placeholder != "" {
675			prefixed += " " + placeholder
676		}
677		if i < len(parts)-1 {
678			prefixed += ", "
679		}
680	}
681	return prefixed
682}
683
684func withEnvHint(envVar, str string) string {
685	envText := ""
686	if envVar != "" {
687		prefix := "$"
688		suffix := ""
689		sep := ", $"
690		if runtime.GOOS == "windows" {
691			prefix = "%"
692			suffix = "%"
693			sep = "%, %"
694		}
695		envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix)
696	}
697	return str + envText
698}
699
700func flagValue(f Flag) reflect.Value {
701	fv := reflect.ValueOf(f)
702	for fv.Kind() == reflect.Ptr {
703		fv = reflect.Indirect(fv)
704	}
705	return fv
706}
707
708func stringifyFlag(f Flag) string {
709	fv := flagValue(f)
710
711	switch f.(type) {
712	case IntSliceFlag:
713		return withEnvHint(fv.FieldByName("EnvVar").String(),
714			stringifyIntSliceFlag(f.(IntSliceFlag)))
715	case Int64SliceFlag:
716		return withEnvHint(fv.FieldByName("EnvVar").String(),
717			stringifyInt64SliceFlag(f.(Int64SliceFlag)))
718	case StringSliceFlag:
719		return withEnvHint(fv.FieldByName("EnvVar").String(),
720			stringifyStringSliceFlag(f.(StringSliceFlag)))
721	}
722
723	placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
724
725	needsPlaceholder := false
726	defaultValueString := ""
727
728	if val := fv.FieldByName("Value"); val.IsValid() {
729		needsPlaceholder = true
730		defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
731
732		if val.Kind() == reflect.String && val.String() != "" {
733			defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
734		}
735	}
736
737	if defaultValueString == " (default: )" {
738		defaultValueString = ""
739	}
740
741	if needsPlaceholder && placeholder == "" {
742		placeholder = defaultPlaceholder
743	}
744
745	usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
746
747	return withEnvHint(fv.FieldByName("EnvVar").String(),
748		fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
749}
750
751func stringifyIntSliceFlag(f IntSliceFlag) string {
752	defaultVals := []string{}
753	if f.Value != nil && len(f.Value.Value()) > 0 {
754		for _, i := range f.Value.Value() {
755			defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
756		}
757	}
758
759	return stringifySliceFlag(f.Usage, f.Name, defaultVals)
760}
761
762func stringifyInt64SliceFlag(f Int64SliceFlag) string {
763	defaultVals := []string{}
764	if f.Value != nil && len(f.Value.Value()) > 0 {
765		for _, i := range f.Value.Value() {
766			defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
767		}
768	}
769
770	return stringifySliceFlag(f.Usage, f.Name, defaultVals)
771}
772
773func stringifyStringSliceFlag(f StringSliceFlag) string {
774	defaultVals := []string{}
775	if f.Value != nil && len(f.Value.Value()) > 0 {
776		for _, s := range f.Value.Value() {
777			if len(s) > 0 {
778				defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
779			}
780		}
781	}
782
783	return stringifySliceFlag(f.Usage, f.Name, defaultVals)
784}
785
786func stringifySliceFlag(usage, name string, defaultVals []string) string {
787	placeholder, usage := unquoteUsage(usage)
788	if placeholder == "" {
789		placeholder = defaultPlaceholder
790	}
791
792	defaultVal := ""
793	if len(defaultVals) > 0 {
794		defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
795	}
796
797	usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
798	return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
799}
800