Merge pull request #306 from ChrisPRobinson/inputfilesupport
Update to add yaml support
This commit is contained in:
commit
6bb6787f33
42
README.md
42
README.md
@ -238,6 +238,48 @@ app.Flags = []cli.Flag {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Values from alternate input sources (YAML and others)
|
||||||
|
|
||||||
|
There is a separate package altsrc that adds support for getting flag values from other input sources like YAML.
|
||||||
|
|
||||||
|
In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
altsrc.NewIntFlag(cli.IntFlag{Name: "test"})
|
||||||
|
```
|
||||||
|
|
||||||
|
Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
```
|
||||||
|
|
||||||
|
The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context.
|
||||||
|
It will then use that file name to initialize the yaml input source for any flags that are defined on that command.
|
||||||
|
As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work.
|
||||||
|
|
||||||
|
Currently only YAML files are supported but developers can add support for other input sources by implementing the
|
||||||
|
altsrc.InputSourceContext for their given sources.
|
||||||
|
|
||||||
|
Here is a more complete sample of a command using YAML support:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
// Action to run
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
err := command.Run(c)
|
||||||
|
```
|
||||||
|
|
||||||
### Subcommands
|
### Subcommands
|
||||||
|
|
||||||
Subcommands can be defined for a more git-like command line app.
|
Subcommands can be defined for a more git-like command line app.
|
||||||
|
439
altsrc/flag.go
Normal file
439
altsrc/flag.go
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
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)) func(context *cli.Context) error {
|
||||||
|
return func(context *cli.Context) error {
|
||||||
|
inputSource, err := createInputSource()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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)) func(context *cli.Context) error {
|
||||||
|
return func(context *cli.Context) error {
|
||||||
|
inputSource, err := createInputSource(context)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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)
|
||||||
|
}
|
||||||
|
}
|
336
altsrc/flag_test.go
Normal file
336
altsrc/flag_test.go
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
package altsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testApplyInputSource struct {
|
||||||
|
Flag FlagInputSourceExtension
|
||||||
|
FlagName string
|
||||||
|
FlagSetName string
|
||||||
|
Expected string
|
||||||
|
ContextValueString string
|
||||||
|
ContextValue flag.Value
|
||||||
|
EnvVarValue string
|
||||||
|
EnvVarName string
|
||||||
|
MapValue interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericApplyInputSourceValue(t *testing.T) {
|
||||||
|
v := &Parser{"abc", "def"}
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: v,
|
||||||
|
})
|
||||||
|
expect(t, v, c.Generic("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
p := &Parser{"abc", "def"}
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: &Parser{"efg", "hig"},
|
||||||
|
ContextValueString: p.String(),
|
||||||
|
})
|
||||||
|
expect(t, p, c.Generic("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: &Parser{"efg", "hij"},
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: "abc,def",
|
||||||
|
})
|
||||||
|
expect(t, &Parser{"abc", "def"}, c.Generic("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceApplyInputSourceValue(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: []string{"hello", "world"},
|
||||||
|
})
|
||||||
|
expect(t, c.StringSlice("test"), []string{"hello", "world"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: []string{"hello", "world"},
|
||||||
|
ContextValueString: "ohno",
|
||||||
|
})
|
||||||
|
expect(t, c.StringSlice("test"), []string{"ohno"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: []string{"hello", "world"},
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: "oh,no",
|
||||||
|
})
|
||||||
|
expect(t, c.StringSlice("test"), []string{"oh", "no"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceApplyInputSourceValue(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: []int{1, 2},
|
||||||
|
})
|
||||||
|
expect(t, c.IntSlice("test"), []int{1, 2})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: []int{1, 2},
|
||||||
|
ContextValueString: "3",
|
||||||
|
})
|
||||||
|
expect(t, c.IntSlice("test"), []int{3})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: []int{1, 2},
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: "3,4",
|
||||||
|
})
|
||||||
|
expect(t, c.IntSlice("test"), []int{3, 4})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolApplyInputSourceMethodSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: true,
|
||||||
|
})
|
||||||
|
expect(t, true, c.Bool("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: false,
|
||||||
|
ContextValueString: "true",
|
||||||
|
})
|
||||||
|
expect(t, true, c.Bool("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: false,
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: "true",
|
||||||
|
})
|
||||||
|
expect(t, true, c.Bool("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolTApplyInputSourceMethodSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: false,
|
||||||
|
})
|
||||||
|
expect(t, false, c.BoolT("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: true,
|
||||||
|
ContextValueString: "false",
|
||||||
|
})
|
||||||
|
expect(t, false, c.BoolT("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: true,
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: "false",
|
||||||
|
})
|
||||||
|
expect(t, false, c.BoolT("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringApplyInputSourceMethodSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: "hello",
|
||||||
|
})
|
||||||
|
expect(t, "hello", c.String("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: "hello",
|
||||||
|
ContextValueString: "goodbye",
|
||||||
|
})
|
||||||
|
expect(t, "goodbye", c.String("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: "hello",
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: "goodbye",
|
||||||
|
})
|
||||||
|
expect(t, "goodbye", c.String("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntApplyInputSourceMethodSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: 15,
|
||||||
|
})
|
||||||
|
expect(t, 15, c.Int("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: 15,
|
||||||
|
ContextValueString: "7",
|
||||||
|
})
|
||||||
|
expect(t, 7, c.Int("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: 15,
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: "12",
|
||||||
|
})
|
||||||
|
expect(t, 12, c.Int("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: time.Duration(30 * time.Second),
|
||||||
|
})
|
||||||
|
expect(t, time.Duration(30*time.Second), c.Duration("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: time.Duration(30 * time.Second),
|
||||||
|
ContextValueString: time.Duration(15 * time.Second).String(),
|
||||||
|
})
|
||||||
|
expect(t, time.Duration(15*time.Second), c.Duration("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: time.Duration(30 * time.Second),
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: time.Duration(15 * time.Second).String(),
|
||||||
|
})
|
||||||
|
expect(t, time.Duration(15*time.Second), c.Duration("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: 1.3,
|
||||||
|
})
|
||||||
|
expect(t, 1.3, c.Float64("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: 1.3,
|
||||||
|
ContextValueString: fmt.Sprintf("%v", 1.4),
|
||||||
|
})
|
||||||
|
expect(t, 1.4, c.Float64("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: 1.3,
|
||||||
|
EnvVarName: "TEST",
|
||||||
|
EnvVarValue: fmt.Sprintf("%v", 1.4),
|
||||||
|
})
|
||||||
|
expect(t, 1.4, c.Float64("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
||||||
|
inputSource := &MapInputSource{valueMap: map[string]interface{}{test.FlagName: test.MapValue}}
|
||||||
|
set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
|
||||||
|
c := cli.NewContext(nil, set, nil)
|
||||||
|
if test.EnvVarName != "" && test.EnvVarValue != "" {
|
||||||
|
os.Setenv(test.EnvVarName, test.EnvVarValue)
|
||||||
|
defer os.Setenv(test.EnvVarName, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Flag.Apply(set)
|
||||||
|
if test.ContextValue != nil {
|
||||||
|
flag := set.Lookup(test.FlagName)
|
||||||
|
flag.Value = test.ContextValue
|
||||||
|
}
|
||||||
|
if test.ContextValueString != "" {
|
||||||
|
set.Set(test.FlagName, test.ContextValueString)
|
||||||
|
}
|
||||||
|
test.Flag.ApplyInputSourceValue(c, inputSource)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parser [2]string
|
||||||
|
|
||||||
|
func (p *Parser) Set(value string) error {
|
||||||
|
parts := strings.Split(value, ",")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
(*p)[0] = parts[0]
|
||||||
|
(*p)[1] = parts[1]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) String() string {
|
||||||
|
return fmt.Sprintf("%s,%s", p[0], p[1])
|
||||||
|
}
|
18
altsrc/helpers_test.go
Normal file
18
altsrc/helpers_test.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package altsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||||
|
if !reflect.DeepEqual(b, a) {
|
||||||
|
t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refute(t *testing.T, a interface{}, b interface{}) {
|
||||||
|
if a == b {
|
||||||
|
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||||
|
}
|
||||||
|
}
|
21
altsrc/input_source_context.go
Normal file
21
altsrc/input_source_context.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package altsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InputSourceContext is an interface used to allow
|
||||||
|
// other input sources to be implemented as needed.
|
||||||
|
type InputSourceContext interface {
|
||||||
|
Int(name string) (int, error)
|
||||||
|
Duration(name string) (time.Duration, error)
|
||||||
|
Float64(name string) (float64, error)
|
||||||
|
String(name string) (string, error)
|
||||||
|
StringSlice(name string) ([]string, error)
|
||||||
|
IntSlice(name string) ([]int, error)
|
||||||
|
Generic(name string) (cli.Generic, error)
|
||||||
|
Bool(name string) (bool, error)
|
||||||
|
BoolT(name string) (bool, error)
|
||||||
|
}
|
152
altsrc/map_input_source.go
Normal file
152
altsrc/map_input_source.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package altsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MapInputSource implements InputSourceContext to return
|
||||||
|
// data from the map that is loaded.
|
||||||
|
type MapInputSource struct {
|
||||||
|
valueMap map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns an int from the map if it exists otherwise returns 0
|
||||||
|
func (fsm *MapInputSource) Int(name string) (int, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.(int)
|
||||||
|
if !isType {
|
||||||
|
return 0, incorrectTypeForFlagError(name, "int", otherGenericValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration returns a duration from the map if it exists otherwise returns 0
|
||||||
|
func (fsm *MapInputSource) Duration(name string) (time.Duration, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.(time.Duration)
|
||||||
|
if !isType {
|
||||||
|
return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns an float64 from the map if it exists otherwise returns 0
|
||||||
|
func (fsm *MapInputSource) Float64(name string) (float64, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.(float64)
|
||||||
|
if !isType {
|
||||||
|
return 0, incorrectTypeForFlagError(name, "float64", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string from the map if it exists otherwise returns an empty string
|
||||||
|
func (fsm *MapInputSource) String(name string) (string, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.(string)
|
||||||
|
if !isType {
|
||||||
|
return "", incorrectTypeForFlagError(name, "string", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice returns an []string from the map if it exists otherwise returns nil
|
||||||
|
func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.([]string)
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntSlice returns an []int from the map if it exists otherwise returns nil
|
||||||
|
func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.([]int)
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic returns an cli.Generic from the map if it exists otherwise returns nil
|
||||||
|
func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.(cli.Generic)
|
||||||
|
if !isType {
|
||||||
|
return nil, incorrectTypeForFlagError(name, "cli.Generic", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns an bool from the map otherwise returns false
|
||||||
|
func (fsm *MapInputSource) Bool(name string) (bool, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.(bool)
|
||||||
|
if !isType {
|
||||||
|
return false, incorrectTypeForFlagError(name, "bool", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolT returns an bool from the map otherwise returns true
|
||||||
|
func (fsm *MapInputSource) BoolT(name string) (bool, error) {
|
||||||
|
otherGenericValue, exists := fsm.valueMap[name]
|
||||||
|
if exists {
|
||||||
|
otherValue, isType := otherGenericValue.(bool)
|
||||||
|
if !isType {
|
||||||
|
return true, incorrectTypeForFlagError(name, "bool", otherGenericValue)
|
||||||
|
}
|
||||||
|
return otherValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error {
|
||||||
|
valueType := reflect.TypeOf(value)
|
||||||
|
valueTypeName := ""
|
||||||
|
if valueType != nil {
|
||||||
|
valueTypeName = valueType.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Mismatched type for flag '%s'. Expected '%s' but actual is '%s'", name, expectedTypeName, valueTypeName)
|
||||||
|
}
|
172
altsrc/yaml_command_test.go
Normal file
172
altsrc/yaml_command_test.go
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// Disabling building of yaml support in cases where golang is 1.0 or 1.1
|
||||||
|
// as the encoding library is not implemented or supported.
|
||||||
|
|
||||||
|
// +build !go1,!go1.1
|
||||||
|
|
||||||
|
package altsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandYamlFileTest(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
val := c.Int("test")
|
||||||
|
expect(t, val, 15)
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
os.Setenv("THE_TEST", "10")
|
||||||
|
defer os.Setenv("THE_TEST", "")
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
val := c.Int("test")
|
||||||
|
expect(t, val, 10)
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
val := c.Int("test")
|
||||||
|
expect(t, val, 7)
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
val := c.Int("test")
|
||||||
|
expect(t, val, 15)
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||||
|
defer os.Remove("current.yaml")
|
||||||
|
|
||||||
|
os.Setenv("THE_TEST", "11")
|
||||||
|
defer os.Setenv("THE_TEST", "")
|
||||||
|
|
||||||
|
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := &cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
val := c.Int("test")
|
||||||
|
expect(t, val, 11)
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
|
||||||
|
cli.StringFlag{Name: "load"}},
|
||||||
|
}
|
||||||
|
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
84
altsrc/yaml_file_loader.go
Normal file
84
altsrc/yaml_file_loader.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Disabling building of yaml support in cases where golang is 1.0 or 1.1
|
||||||
|
// as the encoding library is not implemented or supported.
|
||||||
|
|
||||||
|
// +build !go1,!go1.1
|
||||||
|
|
||||||
|
package altsrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type yamlSourceContext struct {
|
||||||
|
FilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath.
|
||||||
|
func NewYamlSourceFromFile(file string) (InputSourceContext, error) {
|
||||||
|
ymlLoader := &yamlSourceLoader{FilePath: file}
|
||||||
|
var results map[string]interface{}
|
||||||
|
err := readCommandYaml(ysl.FilePath, &results)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", filePath, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MapInputSource{valueMap: results}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context.
|
||||||
|
func NewYamlSourceFromFlagFunc(flagFileName string) func(InputSourceContext, error) {
|
||||||
|
return func(context cli.Context) {
|
||||||
|
filePath := context.String(flagFileName)
|
||||||
|
return NewYamlSourceFromFile(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readCommandYaml(filePath string, container interface{}) (err error) {
|
||||||
|
b, err := loadDataFrom(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(b, container)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDataFrom(filePath string) ([]byte, error) {
|
||||||
|
u, err := url.Parse(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Host != "" { // i have a host, now do i support the scheme?
|
||||||
|
switch u.Scheme {
|
||||||
|
case "http", "https":
|
||||||
|
res, err := http.Get(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.ReadAll(res.Body)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("scheme of %s is unsupported", filePath)
|
||||||
|
}
|
||||||
|
} else if u.Path != "" { // i dont have a host, but I have a path. I am a local file.
|
||||||
|
if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
|
||||||
|
}
|
||||||
|
return ioutil.ReadFile(filePath)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user