1package altsrc
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
7	"syscall"
8
9	"gopkg.in/urfave/cli.v1"
10)
11
12// FlagInputSourceExtension is an extension interface of cli.Flag that
13// allows a value to be set on the existing parsed flags.
14type FlagInputSourceExtension interface {
15	cli.Flag
16	ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error
17}
18
19// ApplyInputSourceValues iterates over all provided flags and
20// executes ApplyInputSourceValue on flags implementing the
21// FlagInputSourceExtension interface to initialize these flags
22// to an alternate input source.
23func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error {
24	for _, f := range flags {
25		inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension)
26		if isType {
27			err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext)
28			if err != nil {
29				return err
30			}
31		}
32	}
33
34	return nil
35}
36
37// InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
38// input source based on the func provided. If there is no error it will then apply the new input source to any flags
39// that are supported by the input source
40func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc {
41	return func(context *cli.Context) error {
42		inputSource, err := createInputSource()
43		if err != nil {
44			return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error())
45		}
46
47		return ApplyInputSourceValues(context, inputSource, flags)
48	}
49}
50
51// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
52// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is
53// no error it will then apply the new input source to any flags that are supported by the input source
54func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
55	return func(context *cli.Context) error {
56		inputSource, err := createInputSource(context)
57		if err != nil {
58			return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error())
59		}
60
61		return ApplyInputSourceValues(context, inputSource, flags)
62	}
63}
64
65// ApplyInputSourceValue applies a generic value to the flagSet if required
66func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
67	if f.set != nil {
68		if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
69			value, err := isc.Generic(f.GenericFlag.Name)
70			if err != nil {
71				return err
72			}
73			if value != nil {
74				eachName(f.Name, func(name string) {
75					f.set.Set(f.Name, value.String())
76				})
77			}
78		}
79	}
80
81	return nil
82}
83
84// ApplyInputSourceValue applies a StringSlice value to the flagSet if required
85func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
86	if f.set != nil {
87		if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
88			value, err := isc.StringSlice(f.StringSliceFlag.Name)
89			if err != nil {
90				return err
91			}
92			if value != nil {
93				var sliceValue cli.StringSlice = value
94				eachName(f.Name, func(name string) {
95					underlyingFlag := f.set.Lookup(f.Name)
96					if underlyingFlag != nil {
97						underlyingFlag.Value = &sliceValue
98					}
99				})
100			}
101		}
102	}
103	return nil
104}
105
106// ApplyInputSourceValue applies a IntSlice value if required
107func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
108	if f.set != nil {
109		if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
110			value, err := isc.IntSlice(f.IntSliceFlag.Name)
111			if err != nil {
112				return err
113			}
114			if value != nil {
115				var sliceValue cli.IntSlice = value
116				eachName(f.Name, func(name string) {
117					underlyingFlag := f.set.Lookup(f.Name)
118					if underlyingFlag != nil {
119						underlyingFlag.Value = &sliceValue
120					}
121				})
122			}
123		}
124	}
125	return nil
126}
127
128// ApplyInputSourceValue applies a Bool value to the flagSet if required
129func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
130	if f.set != nil {
131		if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
132			value, err := isc.Bool(f.BoolFlag.Name)
133			if err != nil {
134				return err
135			}
136			if value {
137				eachName(f.Name, func(name string) {
138					f.set.Set(f.Name, strconv.FormatBool(value))
139				})
140			}
141		}
142	}
143	return nil
144}
145
146// ApplyInputSourceValue applies a BoolT value to the flagSet if required
147func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
148	if f.set != nil {
149		if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
150			value, err := isc.BoolT(f.BoolTFlag.Name)
151			if err != nil {
152				return err
153			}
154			if !value {
155				eachName(f.Name, func(name string) {
156					f.set.Set(f.Name, strconv.FormatBool(value))
157				})
158			}
159		}
160	}
161	return nil
162}
163
164// ApplyInputSourceValue applies a String value to the flagSet if required
165func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
166	if f.set != nil {
167		if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
168			value, err := isc.String(f.StringFlag.Name)
169			if err != nil {
170				return err
171			}
172			if value != "" {
173				eachName(f.Name, func(name string) {
174					f.set.Set(f.Name, value)
175				})
176			}
177		}
178	}
179	return nil
180}
181
182// ApplyInputSourceValue applies a int value to the flagSet if required
183func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
184	if f.set != nil {
185		if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
186			value, err := isc.Int(f.IntFlag.Name)
187			if err != nil {
188				return err
189			}
190			if value > 0 {
191				eachName(f.Name, func(name string) {
192					f.set.Set(f.Name, strconv.FormatInt(int64(value), 10))
193				})
194			}
195		}
196	}
197	return nil
198}
199
200// ApplyInputSourceValue applies a Duration value to the flagSet if required
201func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
202	if f.set != nil {
203		if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
204			value, err := isc.Duration(f.DurationFlag.Name)
205			if err != nil {
206				return err
207			}
208			if value > 0 {
209				eachName(f.Name, func(name string) {
210					f.set.Set(f.Name, value.String())
211				})
212			}
213		}
214	}
215	return nil
216}
217
218// ApplyInputSourceValue applies a Float64 value to the flagSet if required
219func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
220	if f.set != nil {
221		if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
222			value, err := isc.Float64(f.Float64Flag.Name)
223			if err != nil {
224				return err
225			}
226			if value > 0 {
227				floatStr := float64ToString(value)
228				eachName(f.Name, func(name string) {
229					f.set.Set(f.Name, floatStr)
230				})
231			}
232		}
233	}
234	return nil
235}
236
237func isEnvVarSet(envVars string) bool {
238	for _, envVar := range strings.Split(envVars, ",") {
239		envVar = strings.TrimSpace(envVar)
240		if _, ok := syscall.Getenv(envVar); ok {
241			// TODO: Can't use this for bools as
242			// set means that it was true or false based on
243			// Bool flag type, should work for other types
244			return true
245		}
246	}
247
248	return false
249}
250
251func float64ToString(f float64) string {
252	return fmt.Sprintf("%v", f)
253}
254
255func eachName(longName string, fn func(string)) {
256	parts := strings.Split(longName, ",")
257	for _, name := range parts {
258		name = strings.Trim(name, " ")
259		fn(name)
260	}
261}
262