package altsrc import ( "flag" "fmt" "os" "strconv" "strings" "github.com/codegangsta/cli" ) // FlagInputSourceExtension is an extension interface of cli.Flag that // allows a value to be set on the existing parsed flags. type FlagInputSourceExtension interface { cli.Flag ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error } // ApplyInputSourceValues iterates over all provided flags and // executes ApplyInputSourceValue on flags implementing the // FlagInputSourceExtension interface to initialize these flags // to an alternate input source. func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error { for _, f := range flags { inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension) if isType { err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext) if err != nil { return err } } } return nil } // InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new // input source based on the func provided. If there is no error it will then apply the new input source to any flags // that are supported by the input source func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc { return func(context *cli.Context) (int, error) { inputSource, err := createInputSource() if err != nil { return cli.DefaultErrorExitCode, fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) } return cli.DefaultSuccessExitCode, ApplyInputSourceValues(context, inputSource, flags) } } // InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new // input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is // no error it will then apply the new input source to any flags that are supported by the input source func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc { return func(context *cli.Context) (int, error) { inputSource, err := createInputSource(context) if err != nil { return cli.DefaultErrorExitCode, fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) } return cli.DefaultSuccessExitCode, ApplyInputSourceValues(context, inputSource, flags) } } // GenericFlag is the flag type that wraps cli.GenericFlag to allow // for other values to be specified type GenericFlag struct { cli.GenericFlag set *flag.FlagSet } // NewGenericFlag creates a new GenericFlag func NewGenericFlag(flag cli.GenericFlag) *GenericFlag { return &GenericFlag{GenericFlag: flag, set: nil} } // ApplyInputSourceValue applies a generic value to the flagSet if required func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { value, err := isc.Generic(f.GenericFlag.Name) if err != nil { return err } if value != nil { eachName(f.Name, func(name string) { f.set.Set(f.Name, value.String()) }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped GenericFlag.Apply func (f *GenericFlag) Apply(set *flag.FlagSet) { f.set = set f.GenericFlag.Apply(set) } // StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow // for other values to be specified type StringSliceFlag struct { cli.StringSliceFlag set *flag.FlagSet } // NewStringSliceFlag creates a new StringSliceFlag func NewStringSliceFlag(flag cli.StringSliceFlag) *StringSliceFlag { return &StringSliceFlag{StringSliceFlag: flag, set: nil} } // ApplyInputSourceValue applies a StringSlice value to the flagSet if required func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { value, err := isc.StringSlice(f.StringSliceFlag.Name) if err != nil { return err } if value != nil { var sliceValue cli.StringSlice = value eachName(f.Name, func(name string) { underlyingFlag := f.set.Lookup(f.Name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped StringSliceFlag.Apply func (f *StringSliceFlag) Apply(set *flag.FlagSet) { f.set = set f.StringSliceFlag.Apply(set) } // IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow // for other values to be specified type IntSliceFlag struct { cli.IntSliceFlag set *flag.FlagSet } // NewIntSliceFlag creates a new IntSliceFlag func NewIntSliceFlag(flag cli.IntSliceFlag) *IntSliceFlag { return &IntSliceFlag{IntSliceFlag: flag, set: nil} } // ApplyInputSourceValue applies a IntSlice value if required func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { value, err := isc.IntSlice(f.IntSliceFlag.Name) if err != nil { return err } if value != nil { var sliceValue cli.IntSlice = value eachName(f.Name, func(name string) { underlyingFlag := f.set.Lookup(f.Name) if underlyingFlag != nil { underlyingFlag.Value = &sliceValue } }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped IntSliceFlag.Apply func (f *IntSliceFlag) Apply(set *flag.FlagSet) { f.set = set f.IntSliceFlag.Apply(set) } // BoolFlag is the flag type that wraps cli.BoolFlag to allow // for other values to be specified type BoolFlag struct { cli.BoolFlag set *flag.FlagSet } // NewBoolFlag creates a new BoolFlag func NewBoolFlag(flag cli.BoolFlag) *BoolFlag { return &BoolFlag{BoolFlag: flag, set: nil} } // ApplyInputSourceValue applies a Bool value to the flagSet if required func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { value, err := isc.Bool(f.BoolFlag.Name) if err != nil { return err } if value { eachName(f.Name, func(name string) { f.set.Set(f.Name, strconv.FormatBool(value)) }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped BoolFlag.Apply func (f *BoolFlag) Apply(set *flag.FlagSet) { f.set = set f.BoolFlag.Apply(set) } // BoolTFlag is the flag type that wraps cli.BoolTFlag to allow // for other values to be specified type BoolTFlag struct { cli.BoolTFlag set *flag.FlagSet } // NewBoolTFlag creates a new BoolTFlag func NewBoolTFlag(flag cli.BoolTFlag) *BoolTFlag { return &BoolTFlag{BoolTFlag: flag, set: nil} } // ApplyInputSourceValue applies a BoolT value to the flagSet if required func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { value, err := isc.BoolT(f.BoolTFlag.Name) if err != nil { return err } if !value { eachName(f.Name, func(name string) { f.set.Set(f.Name, strconv.FormatBool(value)) }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped BoolTFlag.Apply func (f *BoolTFlag) Apply(set *flag.FlagSet) { f.set = set f.BoolTFlag.Apply(set) } // StringFlag is the flag type that wraps cli.StringFlag to allow // for other values to be specified type StringFlag struct { cli.StringFlag set *flag.FlagSet } // NewStringFlag creates a new StringFlag func NewStringFlag(flag cli.StringFlag) *StringFlag { return &StringFlag{StringFlag: flag, set: nil} } // ApplyInputSourceValue applies a String value to the flagSet if required func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { value, err := isc.String(f.StringFlag.Name) if err != nil { return err } if value != "" { eachName(f.Name, func(name string) { f.set.Set(f.Name, value) }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped StringFlag.Apply func (f *StringFlag) Apply(set *flag.FlagSet) { f.set = set f.StringFlag.Apply(set) } // IntFlag is the flag type that wraps cli.IntFlag to allow // for other values to be specified type IntFlag struct { cli.IntFlag set *flag.FlagSet } // NewIntFlag creates a new IntFlag func NewIntFlag(flag cli.IntFlag) *IntFlag { return &IntFlag{IntFlag: flag, set: nil} } // ApplyInputSourceValue applies a int value to the flagSet if required func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { value, err := isc.Int(f.IntFlag.Name) if err != nil { return err } if value > 0 { eachName(f.Name, func(name string) { f.set.Set(f.Name, strconv.FormatInt(int64(value), 10)) }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped IntFlag.Apply func (f *IntFlag) Apply(set *flag.FlagSet) { f.set = set f.IntFlag.Apply(set) } // DurationFlag is the flag type that wraps cli.DurationFlag to allow // for other values to be specified type DurationFlag struct { cli.DurationFlag set *flag.FlagSet } // NewDurationFlag creates a new DurationFlag func NewDurationFlag(flag cli.DurationFlag) *DurationFlag { return &DurationFlag{DurationFlag: flag, set: nil} } // ApplyInputSourceValue applies a Duration value to the flagSet if required func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { value, err := isc.Duration(f.DurationFlag.Name) if err != nil { return err } if value > 0 { eachName(f.Name, func(name string) { f.set.Set(f.Name, value.String()) }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped DurationFlag.Apply func (f *DurationFlag) Apply(set *flag.FlagSet) { f.set = set f.DurationFlag.Apply(set) } // Float64Flag is the flag type that wraps cli.Float64Flag to allow // for other values to be specified type Float64Flag struct { cli.Float64Flag set *flag.FlagSet } // NewFloat64Flag creates a new Float64Flag func NewFloat64Flag(flag cli.Float64Flag) *Float64Flag { return &Float64Flag{Float64Flag: flag, set: nil} } // ApplyInputSourceValue applies a Float64 value to the flagSet if required func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { if f.set != nil { if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { value, err := isc.Float64(f.Float64Flag.Name) if err != nil { return err } if value > 0 { floatStr := float64ToString(value) eachName(f.Name, func(name string) { f.set.Set(f.Name, floatStr) }) } } } return nil } // Apply saves the flagSet for later usage then calls // the wrapped Float64Flag.Apply func (f *Float64Flag) Apply(set *flag.FlagSet) { f.set = set f.Float64Flag.Apply(set) } func isEnvVarSet(envVars string) bool { for _, envVar := range strings.Split(envVars, ",") { envVar = strings.TrimSpace(envVar) if envVal := os.Getenv(envVar); envVal != "" { // TODO: Can't use this for bools as // set means that it was true or false based on // Bool flag type, should work for other types if len(envVal) > 0 { return true } } } return false } func float64ToString(f float64) string { return fmt.Sprintf("%v", f) } func eachName(longName string, fn func(string)) { parts := strings.Split(longName, ",") for _, name := range parts { name = strings.Trim(name, " ") fn(name) } }