diff --git a/context.go b/context.go index 012b9b5..552ee74 100644 --- a/context.go +++ b/context.go @@ -3,6 +3,7 @@ package cli import ( "errors" "flag" + "os" "reflect" "strings" "syscall" @@ -93,18 +94,26 @@ func (c *Context) IsSet(name string) bool { val = val.Elem() } - envVarValue := val.FieldByName("EnvVar") - if !envVarValue.IsValid() { - return + filePathValue := val.FieldByName("FilePath") + if filePathValue.IsValid() { + eachName(filePathValue.String(), func(filePath string) { + if _, err := os.Stat(filePath); err == nil { + c.setFlags[name] = true + return + } + }) } - eachName(envVarValue.String(), func(envVar string) { - envVar = strings.TrimSpace(envVar) - if _, ok := syscall.Getenv(envVar); ok { - c.setFlags[name] = true - return - } - }) + envVarValue := val.FieldByName("EnvVar") + if envVarValue.IsValid() { + eachName(envVarValue.String(), func(envVar string) { + envVar = strings.TrimSpace(envVar) + if _, ok := syscall.Getenv(envVar); ok { + c.setFlags[name] = true + return + } + }) + } }) } } diff --git a/flag.go b/flag.go index 53fb8bb..4042093 100644 --- a/flag.go +++ b/flag.go @@ -3,6 +3,7 @@ package cli import ( "flag" "fmt" + "io/ioutil" "reflect" "runtime" "strconv" @@ -120,15 +121,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) { // provided by the user for parsing by the flag func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { val := f.Value - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if err := val.Set(envVal); err != nil { - return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) - } - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if err := val.Set(envVal); err != nil { + return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) } } @@ -171,21 +166,15 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &StringSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) } } + f.Value = newVal } eachName(f.Name, func(name string) { @@ -234,21 +223,15 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &IntSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) } } + f.Value = newVal } eachName(f.Name, func(name string) { @@ -297,21 +280,15 @@ func (f Int64SliceFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &Int64Slice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + newVal := &Int64Slice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) } } + f.Value = newVal } eachName(f.Name, func(name string) { @@ -332,23 +309,15 @@ func (f BoolFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { val := false - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if envVal == "" { + val = false + } else { + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + val = envValBool } } @@ -372,23 +341,16 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { val := true - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + if envVal == "" { + val = false + } else { + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) } + val = envValBool } } @@ -411,14 +373,8 @@ func (f StringFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - f.Value = envVal - break - } - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + f.Value = envVal } eachName(f.Name, func(name string) { @@ -440,18 +396,12 @@ func (f IntFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - f.Value = int(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + f.Value = int(envValInt) } eachName(f.Name, func(name string) { @@ -473,19 +423,13 @@ func (f Int64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValInt - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValInt } eachName(f.Name, func(name string) { @@ -507,19 +451,13 @@ func (f UintFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint(envValInt) } eachName(f.Name, func(name string) { @@ -541,19 +479,13 @@ func (f Uint64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint64(envValInt) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = uint64(envValInt) } eachName(f.Name, func(name string) { @@ -575,19 +507,13 @@ func (f DurationFlag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValDuration, err := time.ParseDuration(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValDuration - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValDuration, err := time.ParseDuration(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) } + + f.Value = envValDuration } eachName(f.Name, func(name string) { @@ -609,19 +535,13 @@ func (f Float64Flag) Apply(set *flag.FlagSet) { // ApplyWithError populates the flag given the flag set and environment func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValFloat, err := strconv.ParseFloat(envVal, 10) - if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = float64(envValFloat) - break - } + if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err != nil { + return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) } + + f.Value = float64(envValFloat) } eachName(f.Name, func(name string) { @@ -805,3 +725,18 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) return fmt.Sprintf("%s\t%s", FlagNamePrefixer(name, placeholder), usageWithDefault) } + +func flagFromFileEnv(filePath, envName string) (val string, ok bool) { + for _, envVar := range strings.Split(envName, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + return envVal, true + } + } + if filePath != "" { + if data, err := ioutil.ReadFile(filePath); err == nil { + return string(data), true + } + } + return +} diff --git a/flag_generated.go b/flag_generated.go index 491b619..001576c 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -13,6 +13,7 @@ type BoolFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Destination *bool } @@ -60,6 +61,7 @@ type BoolTFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Destination *bool } @@ -107,6 +109,7 @@ type DurationFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value time.Duration Destination *time.Duration @@ -155,6 +158,7 @@ type Float64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value float64 Destination *float64 @@ -200,11 +204,12 @@ func lookupFloat64(name string, set *flag.FlagSet) float64 { // GenericFlag is a flag with type Generic type GenericFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value Generic + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value Generic } // String returns a readable representation of this value @@ -250,6 +255,7 @@ type Int64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value int64 Destination *int64 @@ -298,6 +304,7 @@ type IntFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value int Destination *int @@ -343,11 +350,12 @@ func lookupInt(name string, set *flag.FlagSet) int { // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *IntSlice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *IntSlice } // String returns a readable representation of this value @@ -390,11 +398,12 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int { // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *Int64Slice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *Int64Slice } // String returns a readable representation of this value @@ -440,6 +449,7 @@ type StringFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value string Destination *string @@ -485,11 +495,12 @@ func lookupString(name string, set *flag.FlagSet) string { // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *StringSlice + Name string + Usage string + EnvVar string + FilePath string + Hidden bool + Value *StringSlice } // String returns a readable representation of this value @@ -535,6 +546,7 @@ type Uint64Flag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value uint64 Destination *uint64 @@ -583,6 +595,7 @@ type UintFlag struct { Name string Usage string EnvVar string + FilePath string Hidden bool Value uint Destination *uint diff --git a/flag_test.go b/flag_test.go index bc840b5..66836f3 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1290,3 +1290,23 @@ func TestParseGenericFromEnvCascade(t *testing.T) { } a.Run([]string{"run"}) } + +// func TestFlagFromFile(t *testing.T) { +// temp, err := ioutil.TempFile("", "urfave_cli_test") +// if err != nil { +// t.Error(err) +// return +// } +// io.WriteString(temp, "abc") +// temp.Close() +// defer func() { +// os.Remove(temp.Name()) +// }() +// +// if want, got := flagFromFileEnv("file-does-not-exist", "123"), "123"; want != got { +// t.Errorf("Did not expect %v - Got %v", want, got) +// } +// if want, got := flagFromFile(temp.Name(), "123"), "abc"; want != got { +// t.Errorf("Did not expect %v - Got %v", want, got) +// } +// } diff --git a/generate-flag-types b/generate-flag-types index 7147381..1358857 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -142,6 +142,7 @@ def _write_cli_flag_types(outfile, types): Name string Usage string EnvVar string + FilePath string Hidden bool """.format(**typedef))