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