diff --git a/app_test.go b/app_test.go index 834b37dde9..844aa65441 100644 --- a/app_test.go +++ b/app_test.go @@ -2625,19 +2625,6 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { } } -type stringGeneric struct { - value string -} - -func (s *stringGeneric) Set(value string) error { - s.value = value - return nil -} - -func (s *stringGeneric) String() string { - return s.value -} - func TestFlagAction(t *testing.T) { stringFlag := &StringFlag{ Name: "f_string", diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index 91312f8d5a..3720e6a1b8 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -22,6 +22,8 @@ type {{.TypeName}} struct { Aliases []string EnvVars []string + defaultValue {{if .ValuePointer}}*{{end}}{{.GoType}} + {{range .StructFields}} {{.Name}} {{if .Pointer}}*{{end}}{{.Type}} {{end}} diff --git a/flag_bool.go b/flag_bool.go index 0be27e3aa6..f64d1cd92c 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -84,7 +84,7 @@ func (f *BoolFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return fmt.Sprintf("%v", f.Value) + return fmt.Sprintf("%v", f.defaultValue) } // GetEnvVars returns the env vars for this flag @@ -103,6 +103,9 @@ func (f *BoolFlag) RunAction(c *Context) error { // Apply populates the flag given the flag set and environment func (f *BoolFlag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valBool, err := strconv.ParseBool(val) diff --git a/flag_duration.go b/flag_duration.go index 31db4102e6..eac4cb8332 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -32,7 +32,7 @@ func (f *DurationFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return f.GetValue() + return f.defaultValue.String() } // GetEnvVars returns the env vars for this flag @@ -42,6 +42,9 @@ func (f *DurationFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valDuration, err := time.ParseDuration(val) diff --git a/flag_generic.go b/flag_generic.go index 358bd966e0..4f9ac0a7ff 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -11,6 +11,19 @@ type Generic interface { String() string } +type stringGeneric struct { + value string +} + +func (s *stringGeneric) Set(value string) error { + s.value = value + return nil +} + +func (s *stringGeneric) String() string { + return s.value +} + // TakesValue returns true of the flag takes a value, otherwise false func (f *GenericFlag) TakesValue() bool { return true @@ -40,7 +53,10 @@ func (f *GenericFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return f.GetValue() + if f.defaultValue != nil { + return f.defaultValue.String() + } + return "" } // GetEnvVars returns the env vars for this flag @@ -51,6 +67,11 @@ func (f *GenericFlag) GetEnvVars() []string { // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f *GenericFlag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + if f.Value != nil { + f.defaultValue = &stringGeneric{value: f.Value.String()} + } + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { if err := f.Value.Set(val); err != nil { diff --git a/flag_int.go b/flag_int.go index af98e936fb..d681270a23 100644 --- a/flag_int.go +++ b/flag_int.go @@ -32,7 +32,7 @@ func (f *IntFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return f.GetValue() + return fmt.Sprintf("%d", f.defaultValue) } // GetEnvVars returns the env vars for this flag @@ -42,6 +42,9 @@ func (f *IntFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, f.Base, 64) diff --git a/flag_int64.go b/flag_int64.go index ebe46d21fc..6f8c8d27d8 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -32,7 +32,7 @@ func (f *Int64Flag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return f.GetValue() + return fmt.Sprintf("%d", f.defaultValue) } // GetEnvVars returns the env vars for this flag @@ -42,6 +42,9 @@ func (f *Int64Flag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, f.Base, 64) diff --git a/flag_path.go b/flag_path.go index 911819db94..6434d3224f 100644 --- a/flag_path.go +++ b/flag_path.go @@ -33,10 +33,10 @@ func (f *PathFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - if f.Value == "" { - return f.Value + if f.defaultValue == "" { + return f.defaultValue } - return fmt.Sprintf("%q", f.Value) + return fmt.Sprintf("%q", f.defaultValue) } // GetEnvVars returns the env vars for this flag @@ -46,6 +46,9 @@ func (f *PathFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *PathFlag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = val f.HasBeenSet = true diff --git a/flag_string.go b/flag_string.go index b7163ba6f6..3050086e87 100644 --- a/flag_string.go +++ b/flag_string.go @@ -31,10 +31,10 @@ func (f *StringFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - if f.Value == "" { - return f.Value + if f.defaultValue == "" { + return f.defaultValue } - return fmt.Sprintf("%q", f.Value) + return fmt.Sprintf("%q", f.defaultValue) } // GetEnvVars returns the env vars for this flag @@ -44,6 +44,9 @@ func (f *StringFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *StringFlag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = val f.HasBeenSet = true diff --git a/flag_test.go b/flag_test.go index 3aafa8bea2..6e03582ff4 100644 --- a/flag_test.go +++ b/flag_test.go @@ -8,7 +8,6 @@ import ( "os" "reflect" "regexp" - "runtime" "strings" "testing" "time" @@ -477,6 +476,9 @@ var stringFlagTests = []struct { func TestStringFlagHelpOutput(t *testing.T) { for _, test := range stringFlagTests { fl := &StringFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + // create a tmp flagset + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) output := fl.String() if output != test.expected { @@ -504,10 +506,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { fl := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}} output := fl.String() - expectedSuffix := " [$APP_FOO]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_FOO%]" - } + expectedSuffix := withEnvHint([]string{"APP_FOO"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -575,6 +574,11 @@ var pathFlagTests = []struct { func TestPathFlagHelpOutput(t *testing.T) { for _, test := range pathFlagTests { fl := &PathFlag{Name: test.name, Aliases: test.aliases, Usage: test.usage, Value: test.value} + + // create a temporary flag set to apply + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) + output := fl.String() if output != test.expected { @@ -591,10 +595,7 @@ func TestPathFlagWithEnvVarHelpOutput(t *testing.T) { fl := &PathFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_PATH"}} output := fl.String() - expectedSuffix := " [$APP_PATH]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_PATH%]" - } + expectedSuffix := withEnvHint([]string{"APP_PATH"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -688,10 +689,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { fl := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}} output := fl.String() - expectedSuffix := " [$APP_QWWX]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_QWWX%]" - } + expectedSuffix := withEnvHint([]string{"APP_QWWX"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%q does not end with"+expectedSuffix, output) } @@ -767,6 +765,11 @@ var intFlagTests = []struct { func TestIntFlagHelpOutput(t *testing.T) { for _, test := range intFlagTests { fl := &IntFlag{Name: test.name, Value: 9} + + // create a temporary flag set to apply + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) + output := fl.String() if output != test.expected { @@ -784,10 +787,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { fl := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := fl.String() - expectedSuffix := " [$APP_BAR]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_BAR%]" - } + expectedSuffix := withEnvHint([]string{"APP_BAR"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -824,6 +824,11 @@ var int64FlagTests = []struct { func TestInt64FlagHelpOutput(t *testing.T) { for _, test := range int64FlagTests { fl := Int64Flag{Name: test.name, Value: 8589934592} + + // create a temporary flag set to apply + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) + output := fl.String() if output != test.expected { @@ -841,10 +846,7 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { fl := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := fl.String() - expectedSuffix := " [$APP_BAR]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_BAR%]" - } + expectedSuffix := withEnvHint([]string{"APP_BAR"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -870,6 +872,11 @@ var uintFlagTests = []struct { func TestUintFlagHelpOutput(t *testing.T) { for _, test := range uintFlagTests { fl := UintFlag{Name: test.name, Value: 41} + + // create a temporary flag set to apply + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) + output := fl.String() if output != test.expected { @@ -887,10 +894,7 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := fl.String() - expectedSuffix := " [$APP_BAR]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_BAR%]" - } + expectedSuffix := withEnvHint([]string{"APP_BAR"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -916,6 +920,11 @@ var uint64FlagTests = []struct { func TestUint64FlagHelpOutput(t *testing.T) { for _, test := range uint64FlagTests { fl := Uint64Flag{Name: test.name, Value: 8589934582} + + // create a temporary flag set to apply + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) + output := fl.String() if output != test.expected { @@ -933,10 +942,7 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := fl.String() - expectedSuffix := " [$APP_BAR]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_BAR%]" - } + expectedSuffix := withEnvHint([]string{"APP_BAR"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -962,6 +968,11 @@ var durationFlagTests = []struct { func TestDurationFlagHelpOutput(t *testing.T) { for _, test := range durationFlagTests { fl := &DurationFlag{Name: test.name, Value: 1 * time.Second} + + // create a temporary flag set to apply + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) + output := fl.String() if output != test.expected { @@ -979,10 +990,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { fl := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} output := fl.String() - expectedSuffix := " [$APP_BAR]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_BAR%]" - } + expectedSuffix := withEnvHint([]string{"APP_BAR"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -1039,10 +1047,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { fl := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := fl.String() - expectedSuffix := " [$APP_SMURF]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_SMURF%]" - } + expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%q does not end with"+expectedSuffix, output) } @@ -1178,10 +1183,7 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { fl := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := fl.String() - expectedSuffix := " [$APP_SMURF]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_SMURF%]" - } + expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%q does not end with"+expectedSuffix, output) } @@ -1332,10 +1334,7 @@ func TestUintSliceFlagWithEnvVarHelpOutput(t *testing.T) { fl := UintSliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := fl.String() - expectedSuffix := " [$APP_SMURF]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_SMURF%]" - } + expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%q does not end with"+expectedSuffix, output) } @@ -1478,10 +1477,7 @@ func TestUint64SliceFlagWithEnvVarHelpOutput(t *testing.T) { fl := Uint64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := fl.String() - expectedSuffix := " [$APP_SMURF]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_SMURF%]" - } + expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%q does not end with"+expectedSuffix, output) } @@ -1620,10 +1616,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { fl := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}} output := fl.String() - expectedSuffix := " [$APP_BAZ]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_BAZ%]" - } + expectedSuffix := withEnvHint([]string{"APP_BAZ"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -1680,10 +1673,7 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { fl := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} output := fl.String() - expectedSuffix := " [$APP_SMURF]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_SMURF%]" - } + expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%q does not end with"+expectedSuffix, output) } @@ -1783,6 +1773,9 @@ var genericFlagTests = []struct { func TestGenericFlagHelpOutput(t *testing.T) { for _, test := range genericFlagTests { fl := &GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} + // create a temporary flag set to apply + tfs := flag.NewFlagSet("test", 0) + fl.Apply(tfs) output := fl.String() if output != test.expected { @@ -1800,10 +1793,7 @@ func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { fl := &GenericFlag{Name: test.name, EnvVars: []string{"APP_ZAP"}} output := fl.String() - expectedSuffix := " [$APP_ZAP]" - if runtime.GOOS == "windows" { - expectedSuffix = " [%APP_ZAP%]" - } + expectedSuffix := withEnvHint([]string{"APP_ZAP"}, "") if !strings.HasSuffix(output, expectedSuffix) { t.Errorf("%s does not end with"+expectedSuffix, output) } @@ -3100,6 +3090,186 @@ func TestFlagDefaultValue(t *testing.T) { } } +type flagDefaultTestCaseWithEnv struct { + name string + flag Flag + toParse []string + expect string + environ map[string]string +} + +func TestFlagDefaultValueWithEnv(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + + ts, err := time.Parse(time.RFC3339, "2005-01-02T15:04:05Z") + if err != nil { + t.Fatal(err) + } + cases := []*flagDefaultTestCaseWithEnv{ + { + name: "stringSlice", + flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2"), EnvVars: []string{"ssflag"}}, + toParse: []string{"--flag", "parsed"}, + expect: `--flag value [ --flag value ] (default: "default1", "default2")` + withEnvHint([]string{"ssflag"}, ""), + environ: map[string]string{ + "ssflag": "some-other-env_value", + }, + }, + { + name: "float64Slice", + flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2), EnvVars: []string{"fsflag"}}, + toParse: []string{"--flag", "13.3"}, + expect: `--flag value [ --flag value ] (default: 1.1, 2.2)` + withEnvHint([]string{"fsflag"}, ""), + environ: map[string]string{ + "fsflag": "20304.222", + }, + }, + { + name: "int64Slice", + flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2), EnvVars: []string{"isflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value [ --flag value ] (default: 1, 2)` + withEnvHint([]string{"isflag"}, ""), + environ: map[string]string{ + "isflag": "101", + }, + }, + { + name: "intSlice", + flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2), EnvVars: []string{"isflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value [ --flag value ] (default: 1, 2)` + withEnvHint([]string{"isflag"}, ""), + environ: map[string]string{ + "isflag": "101", + }, + }, + { + name: "uint64Slice", + flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2), EnvVars: []string{"uisflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value [ --flag value ] (default: 1, 2)` + withEnvHint([]string{"uisflag"}, ""), + environ: map[string]string{ + "uisflag": "3", + }, + }, + { + name: "uintSlice", + flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2), EnvVars: []string{"uisflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value [ --flag value ] (default: 1, 2)` + withEnvHint([]string{"uisflag"}, ""), + environ: map[string]string{ + "uisflag": "3", + }, + }, + { + name: "string", + flag: &StringFlag{Name: "flag", Value: "default", EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "parsed"}, + expect: `--flag value (default: "default")` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "some-other-string", + }, + }, + { + name: "bool", + flag: &BoolFlag{Name: "flag", Value: true, EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "false"}, + expect: `--flag (default: true)` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "false", + }, + }, + { + name: "uint64", + flag: &Uint64Flag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1)` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "10", + }, + }, + { + name: "uint", + flag: &UintFlag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1)` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "10", + }, + }, + { + name: "int64", + flag: &Int64Flag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1)` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "10", + }, + }, + { + name: "int", + flag: &IntFlag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1)` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "10", + }, + }, + { + name: "duration", + flag: &DurationFlag{Name: "flag", Value: time.Second, EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "2m"}, + expect: `--flag value (default: 1s)` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "2h4m10s", + }, + }, + { + name: "path", + flag: &PathFlag{Name: "flag", Value: "/tmp/foo", EnvVars: []string{"uflag"}}, + toParse: []string{"--flag", "/bar/ddfr"}, + expect: `--flag value (default: "/tmp/foo")` + withEnvHint([]string{"uflag"}, ""), + environ: map[string]string{ + "uflag": "/bar/t/err", + }, + }, + { + name: "timestamp", + flag: &TimestampFlag{Name: "flag", Value: NewTimestamp(ts), Layout: time.RFC3339, EnvVars: []string{"tflag"}}, + toParse: []string{"--flag", "2006-11-02T15:04:05Z"}, + expect: `--flag value (default: 2005-01-02 15:04:05 +0000 UTC)` + withEnvHint([]string{"tflag"}, ""), + environ: map[string]string{ + "tflag": "2010-01-02T15:04:05Z", + }, + }, + { + name: "generic", + flag: &GenericFlag{Name: "flag", Value: &Parser{"11", "12"}, EnvVars: []string{"gflag"}}, + toParse: []string{"--flag", "15,16"}, + expect: `--flag value (default: 11,12)` + withEnvHint([]string{"gflag"}, ""), + environ: map[string]string{ + "gflag": "13,14", + }, + }, + } + for i, v := range cases { + for key, val := range v.environ { + os.Setenv(key, val) + } + set := flag.NewFlagSet("test", 0) + set.SetOutput(ioutil.Discard) + if err := v.flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse(v.toParse); err != nil { + t.Error(err) + } + if got := v.flag.String(); got != v.expect { + t.Errorf("TestFlagDefaultValue %d %s\nexpect:%s\ngot:%s", i, v.name, v.expect, got) + } + } +} + type flagValueTestCase struct { name string flag Flag diff --git a/flag_timestamp.go b/flag_timestamp.go index 17bc8d7571..fa0671fe4e 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -72,6 +72,25 @@ func (t *Timestamp) Get() interface{} { return *t } +// clone timestamp +func (t *Timestamp) clone() *Timestamp { + tc := &Timestamp{ + timestamp: nil, + hasBeenSet: t.hasBeenSet, + layout: t.layout, + location: nil, + } + if t.timestamp != nil { + tts := *t.timestamp + tc.timestamp = &tts + } + if t.location != nil { + loc := *t.location + tc.location = &loc + } + return tc +} + // TakesValue returns true of the flag takes a value, otherwise false func (f *TimestampFlag) TakesValue() bool { return true @@ -101,7 +120,11 @@ func (f *TimestampFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return f.GetValue() + if f.defaultValue != nil && f.defaultValue.timestamp != nil { + return f.defaultValue.timestamp.String() + } + + return "" } // GetEnvVars returns the env vars for this flag @@ -120,6 +143,8 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { f.Value.SetLayout(f.Layout) f.Value.SetLocation(f.Timezone) + f.defaultValue = f.Value.clone() + if f.Destination != nil { f.Destination.SetLayout(f.Layout) f.Destination.SetLocation(f.Timezone) diff --git a/flag_uint.go b/flag_uint.go index f9acb6d06c..d11aa0a891 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -23,6 +23,9 @@ func (f *UintFlag) GetCategory() string { // Apply populates the flag given the flag set and environment func (f *UintFlag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, f.Base, 64) @@ -66,7 +69,7 @@ func (f *UintFlag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return f.GetValue() + return fmt.Sprintf("%d", f.defaultValue) } // GetEnvVars returns the env vars for this flag diff --git a/flag_uint64.go b/flag_uint64.go index 09590d7353..ea73100a97 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -23,6 +23,9 @@ func (f *Uint64Flag) GetCategory() string { // Apply populates the flag given the flag set and environment func (f *Uint64Flag) Apply(set *flag.FlagSet) error { + // set default value so that environment wont be able to overwrite it + f.defaultValue = f.Value + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, f.Base, 64) @@ -66,7 +69,7 @@ func (f *Uint64Flag) GetDefaultText() string { if f.DefaultText != "" { return f.DefaultText } - return f.GetValue() + return fmt.Sprintf("%d", f.defaultValue) } // GetEnvVars returns the env vars for this flag diff --git a/godoc-current.txt b/godoc-current.txt index 975819fc95..80344ecedb 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -447,6 +447,7 @@ type BoolFlag struct { Count *int Action func(*Context, bool) error + // Has unexported fields. } BoolFlag is a flag with type bool @@ -775,6 +776,7 @@ type DurationFlag struct { EnvVars []string Action func(*Context, time.Duration) error + // Has unexported fields. } DurationFlag is a flag with type time.Duration @@ -953,6 +955,7 @@ type Float64Flag struct { EnvVars []string Action func(*Context, float64) error + // Has unexported fields. } Float64Flag is a flag with type float64 @@ -1041,6 +1044,7 @@ type Float64SliceFlag struct { EnvVars []string Action func(*Context, []float64) error + // Has unexported fields. } Float64SliceFlag is a flag with type *Float64Slice @@ -1123,6 +1127,7 @@ type GenericFlag struct { TakesFile bool Action func(*Context, interface{}) error + // Has unexported fields. } GenericFlag is a flag with type Generic @@ -1191,6 +1196,7 @@ type Int64Flag struct { Base int Action func(*Context, int64) error + // Has unexported fields. } Int64Flag is a flag with type int64 @@ -1279,6 +1285,7 @@ type Int64SliceFlag struct { EnvVars []string Action func(*Context, []int64) error + // Has unexported fields. } Int64SliceFlag is a flag with type *Int64Slice @@ -1355,6 +1362,7 @@ type IntFlag struct { Base int Action func(*Context, int) error + // Has unexported fields. } IntFlag is a flag with type int @@ -1447,6 +1455,7 @@ type IntSliceFlag struct { EnvVars []string Action func(*Context, []int) error + // Has unexported fields. } IntSliceFlag is a flag with type *IntSlice @@ -1557,6 +1566,7 @@ type PathFlag struct { TakesFile bool Action func(*Context, Path) error + // Has unexported fields. } PathFlag is a flag with type Path @@ -1699,6 +1709,7 @@ type StringFlag struct { TakesFile bool Action func(*Context, string) error + // Has unexported fields. } StringFlag is a flag with type string @@ -1789,6 +1800,7 @@ type StringSliceFlag struct { TakesFile bool Action func(*Context, []string) error + // Has unexported fields. } StringSliceFlag is a flag with type *StringSlice @@ -1900,6 +1912,7 @@ type TimestampFlag struct { Timezone *time.Location Action func(*Context, *time.Time) error + // Has unexported fields. } TimestampFlag is a flag with type *Timestamp @@ -1967,6 +1980,7 @@ type Uint64Flag struct { Base int Action func(*Context, uint64) error + // Has unexported fields. } Uint64Flag is a flag with type uint64 @@ -2055,6 +2069,7 @@ type Uint64SliceFlag struct { EnvVars []string Action func(*Context, []uint64) error + // Has unexported fields. } Uint64SliceFlag is a flag with type *Uint64Slice @@ -2122,6 +2137,7 @@ type UintFlag struct { Base int Action func(*Context, uint) error + // Has unexported fields. } UintFlag is a flag with type uint @@ -2214,6 +2230,7 @@ type UintSliceFlag struct { EnvVars []string Action func(*Context, []uint) error + // Has unexported fields. } UintSliceFlag is a flag with type *UintSlice diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 975819fc95..80344ecedb 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -447,6 +447,7 @@ type BoolFlag struct { Count *int Action func(*Context, bool) error + // Has unexported fields. } BoolFlag is a flag with type bool @@ -775,6 +776,7 @@ type DurationFlag struct { EnvVars []string Action func(*Context, time.Duration) error + // Has unexported fields. } DurationFlag is a flag with type time.Duration @@ -953,6 +955,7 @@ type Float64Flag struct { EnvVars []string Action func(*Context, float64) error + // Has unexported fields. } Float64Flag is a flag with type float64 @@ -1041,6 +1044,7 @@ type Float64SliceFlag struct { EnvVars []string Action func(*Context, []float64) error + // Has unexported fields. } Float64SliceFlag is a flag with type *Float64Slice @@ -1123,6 +1127,7 @@ type GenericFlag struct { TakesFile bool Action func(*Context, interface{}) error + // Has unexported fields. } GenericFlag is a flag with type Generic @@ -1191,6 +1196,7 @@ type Int64Flag struct { Base int Action func(*Context, int64) error + // Has unexported fields. } Int64Flag is a flag with type int64 @@ -1279,6 +1285,7 @@ type Int64SliceFlag struct { EnvVars []string Action func(*Context, []int64) error + // Has unexported fields. } Int64SliceFlag is a flag with type *Int64Slice @@ -1355,6 +1362,7 @@ type IntFlag struct { Base int Action func(*Context, int) error + // Has unexported fields. } IntFlag is a flag with type int @@ -1447,6 +1455,7 @@ type IntSliceFlag struct { EnvVars []string Action func(*Context, []int) error + // Has unexported fields. } IntSliceFlag is a flag with type *IntSlice @@ -1557,6 +1566,7 @@ type PathFlag struct { TakesFile bool Action func(*Context, Path) error + // Has unexported fields. } PathFlag is a flag with type Path @@ -1699,6 +1709,7 @@ type StringFlag struct { TakesFile bool Action func(*Context, string) error + // Has unexported fields. } StringFlag is a flag with type string @@ -1789,6 +1800,7 @@ type StringSliceFlag struct { TakesFile bool Action func(*Context, []string) error + // Has unexported fields. } StringSliceFlag is a flag with type *StringSlice @@ -1900,6 +1912,7 @@ type TimestampFlag struct { Timezone *time.Location Action func(*Context, *time.Time) error + // Has unexported fields. } TimestampFlag is a flag with type *Timestamp @@ -1967,6 +1980,7 @@ type Uint64Flag struct { Base int Action func(*Context, uint64) error + // Has unexported fields. } Uint64Flag is a flag with type uint64 @@ -2055,6 +2069,7 @@ type Uint64SliceFlag struct { EnvVars []string Action func(*Context, []uint64) error + // Has unexported fields. } Uint64SliceFlag is a flag with type *Uint64Slice @@ -2122,6 +2137,7 @@ type UintFlag struct { Base int Action func(*Context, uint) error + // Has unexported fields. } UintFlag is a flag with type uint @@ -2214,6 +2230,7 @@ type UintSliceFlag struct { EnvVars []string Action func(*Context, []uint) error + // Has unexported fields. } UintSliceFlag is a flag with type *UintSlice diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 0309a28689..7d0a267f78 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -23,6 +23,8 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + defaultValue *Float64Slice + Action func(*Context, []float64) error } @@ -65,6 +67,8 @@ type GenericFlag struct { Aliases []string EnvVars []string + defaultValue Generic + TakesFile bool Action func(*Context, interface{}) error @@ -114,6 +118,8 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string + defaultValue *Int64Slice + Action func(*Context, []int64) error } @@ -156,6 +162,8 @@ type IntSliceFlag struct { Aliases []string EnvVars []string + defaultValue *IntSlice + Action func(*Context, []int) error } @@ -198,6 +206,8 @@ type PathFlag struct { Aliases []string EnvVars []string + defaultValue Path + TakesFile bool Action func(*Context, Path) error @@ -247,6 +257,8 @@ type StringSliceFlag struct { Aliases []string EnvVars []string + defaultValue *StringSlice + TakesFile bool Action func(*Context, []string) error @@ -291,6 +303,8 @@ type TimestampFlag struct { Aliases []string EnvVars []string + defaultValue *Timestamp + Layout string Timezone *time.Location @@ -342,6 +356,8 @@ type Uint64SliceFlag struct { Aliases []string EnvVars []string + defaultValue *Uint64Slice + Action func(*Context, []uint64) error } @@ -384,6 +400,8 @@ type UintSliceFlag struct { Aliases []string EnvVars []string + defaultValue *UintSlice + Action func(*Context, []uint) error } @@ -426,6 +444,8 @@ type BoolFlag struct { Aliases []string EnvVars []string + defaultValue bool + Count *int Action func(*Context, bool) error @@ -475,6 +495,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + defaultValue float64 + Action func(*Context, float64) error } @@ -522,6 +544,8 @@ type IntFlag struct { Aliases []string EnvVars []string + defaultValue int + Base int Action func(*Context, int) error @@ -571,6 +595,8 @@ type Int64Flag struct { Aliases []string EnvVars []string + defaultValue int64 + Base int Action func(*Context, int64) error @@ -620,6 +646,8 @@ type StringFlag struct { Aliases []string EnvVars []string + defaultValue string + TakesFile bool Action func(*Context, string) error @@ -669,6 +697,8 @@ type DurationFlag struct { Aliases []string EnvVars []string + defaultValue time.Duration + Action func(*Context, time.Duration) error } @@ -716,6 +746,8 @@ type UintFlag struct { Aliases []string EnvVars []string + defaultValue uint + Base int Action func(*Context, uint) error @@ -765,6 +797,8 @@ type Uint64Flag struct { Aliases []string EnvVars []string + defaultValue uint64 + Base int Action func(*Context, uint64) error