diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 41ba294f6d..9fee14807e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,11 +55,12 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be -reviewed and investigated and will result in a response that is deemed necessary -and appropriate to the circumstances. The project team is obligated to maintain -confidentiality with regard to the reporter of an incident. Further details of -specific enforcement policies may be posted separately. +reported by contacting urfave-governance@googlegroups.com, a members-only group +that is world-postable. All complaints will be reviewed and investigated and +will result in a response that is deemed necessary and appropriate to the +circumstances. The project team is obligated to maintain confidentiality with +regard to the reporter of an incident. Further details of specific enforcement +policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other diff --git a/app_test.go b/app_test.go index 52b7209220..3dd73abc16 100644 --- a/app_test.go +++ b/app_test.go @@ -228,6 +228,7 @@ func ExampleApp_Run_subcommandNoAction() { } func ExampleApp_Run_bashComplete_withShortFlag() { + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "-", "--generate-bash-completion"} app := NewApp() @@ -255,6 +256,7 @@ func ExampleApp_Run_bashComplete_withShortFlag() { } func ExampleApp_Run_bashComplete_withLongFlag() { + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "--s", "--generate-bash-completion"} app := NewApp() @@ -283,6 +285,7 @@ func ExampleApp_Run_bashComplete_withLongFlag() { // --similar-flag } func ExampleApp_Run_bashComplete_withMultipleLongFlag() { + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "--st", "--generate-bash-completion"} app := NewApp() @@ -315,7 +318,7 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() { } func ExampleApp_Run_bashComplete() { - // set args for examples sake + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "--generate-bash-completion"} app := &App{ @@ -355,7 +358,7 @@ func ExampleApp_Run_bashComplete() { func ExampleApp_Run_zshComplete() { // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} - _ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1") + _ = os.Setenv("SHELL", "/usr/bin/zsh") app := NewApp() app.Name = "greet" diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index cf39c888af..b519666f80 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -1,14 +1,13 @@ #compdef $PROG _cli_zsh_autocomplete() { - local -a opts local cur cur=${words[-1]} if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") else - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") fi if [[ "${opts[1]}" != "" ]]; then @@ -16,8 +15,6 @@ _cli_zsh_autocomplete() { else _files fi - - return } compdef _cli_zsh_autocomplete $PROG diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000000..8af4ce4da9 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +Hello and thank you for your interest in the `urfave/cli` security +policy! :tada: :lock: + +## Supported Versions + +| Version | Supported | +| ------------ | ------------------------------------- | +| `>= v2.3.x` | :white_check_mark: | +| `< v2.3` | :x: | +| `>= v1.22.x` | :white_check_mark: :lady_beetle: [^1] | +| `< v1.22` | :x: | + +## Reporting a Vulnerability + +Please disclose any vulnerabilities by sending an email to: + +[urfave-security@googlegroups.com](mailto:urfave-security@googlegroups.com) + +You should expect a response within 48 hours and further +communications to be decided via email. The `urfave/cli` maintainer +team comprises volunteers who contribute when possible, so please +have patience :bow: + +[^1]: The `v1.22.x` series will receive bug fixes and security + patches only. diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 3e0bf956a0..fd5656a569 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1165,15 +1165,14 @@ func main() { ``` #### ZSH Support -Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` -file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. -Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and -then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH -configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: +Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` +file included in this repo. One environment variable is used, `PROG`. Set +`PROG` to the program name as before, and then `source path/to/autocomplete/zsh_autocomplete`. +Adding the following lines to your ZSH configuration file (usually `.zshrc`) +will allow the auto-completion to persist across new shells: ``` PROG= -_CLI_ZSH_AUTOCOMPLETE_HACK=1 source path/to/autocomplete/zsh_autocomplete ``` #### ZSH default auto-complete example diff --git a/flag.go b/flag.go index 21832260f4..dbed577cdc 100644 --- a/flag.go +++ b/flag.go @@ -394,21 +394,24 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } -func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) { +// Return the first value from a list of environment variables and files +// (which may or may not exist), a description of where the value was found, +// and a boolean which is true if a value was found. +func flagFromEnvOrFile(envVars []string, filePath string) (value string, fromWhere string, found bool) { for _, envVar := range envVars { envVar = strings.TrimSpace(envVar) - if val, ok := syscall.Getenv(envVar); ok { - return val, true + if value, found := syscall.Getenv(envVar); found { + return value, fmt.Sprintf("environment variable %q", envVar), true } } for _, fileVar := range strings.Split(filePath, ",") { if fileVar != "" { if data, err := ioutil.ReadFile(fileVar); err == nil { - return string(data), true + return string(data), fmt.Sprintf("file %q", filePath), true } } } - return "", false + return "", "", false } func flagSplitMultiValues(val string) []string { diff --git a/flag_bool.go b/flag_bool.go index 03bd18d8bd..b21d5163c9 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -42,12 +42,12 @@ func (f *BoolFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *BoolFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valBool, err := strconv.ParseBool(val) if err != nil { - return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as bool value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valBool diff --git a/flag_duration.go b/flag_duration.go index b8fadc80e3..5178c6ae12 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -42,12 +42,12 @@ func (f *DurationFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valDuration, err := time.ParseDuration(val) if err != nil { - return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as duration value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valDuration diff --git a/flag_float64.go b/flag_float64.go index a36b578e41..2d31739bc6 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -42,11 +42,11 @@ func (f *Float64Flag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valFloat, err := strconv.ParseFloat(val, 64) if err != nil { - return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as float64 value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valFloat diff --git a/flag_float64_slice.go b/flag_float64_slice.go index ea19c21fe5..bc347ccdb2 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -120,13 +120,13 @@ func (f *Float64SliceFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { f.Value = &Float64Slice{} for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) + return fmt.Errorf("could not parse %q as float64 slice value from %s for flag %s: %s", f.Value, source, f.Name, err) } } diff --git a/flag_generic.go b/flag_generic.go index 6d5d518909..680eeb9d71 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -51,10 +51,10 @@ 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 { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q from %s as value for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true diff --git a/flag_int.go b/flag_int.go index 941e0ab24b..c70b889858 100644 --- a/flag_int.go +++ b/flag_int.go @@ -42,12 +42,12 @@ func (f *IntFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = int(valInt) diff --git a/flag_int64.go b/flag_int64.go index 7ca1010b36..5e7038cfb1 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -42,12 +42,12 @@ func (f *Int64Flag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt diff --git a/flag_int64_slice.go b/flag_int64_slice.go index c7adfb1011..5f3d5cd4ea 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -121,12 +121,12 @@ func (f *Int64SliceFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = &Int64Slice{} for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int64 slice value from %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_int_slice.go b/flag_int_slice.go index d49e156271..2ddf805967 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -132,12 +132,12 @@ func (f *IntSliceFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = &IntSlice{} for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int slice value from %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_path.go b/flag_path.go index fdd3057134..7c87a8900d 100644 --- a/flag_path.go +++ b/flag_path.go @@ -46,7 +46,7 @@ func (f *PathFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *PathFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + 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 1b2f94f0cf..c8da38f92d 100644 --- a/flag_string.go +++ b/flag_string.go @@ -44,7 +44,7 @@ func (f *StringFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *StringFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = val f.HasBeenSet = true } diff --git a/flag_string_slice.go b/flag_string_slice.go index 2e74b57251..599f42c7fb 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -122,7 +122,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { } - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if f.Value == nil { f.Value = &StringSlice{} } @@ -133,7 +133,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { for _, s := range flagSplitMultiValues(val) { if err := destination.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as string value from %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_test.go b/flag_test.go index 9433fc9d1c..ba90e91c05 100644 --- a/flag_test.go +++ b/flag_test.go @@ -96,33 +96,33 @@ func TestFlagsFromEnv(t *testing.T) { {"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, - {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value for flag debug: .*`}, + {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value from environment variable "DEBUG" for flag debug: .*`}, {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""}, - {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value for flag time: .*`}, + {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value from environment variable "TIME" for flag time: .*`}, {"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value for flag seconds: .*`}, + {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value from environment variable "SECONDS" for flag seconds: .*`}, {"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, - {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, + {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`}, {"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, - {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, + {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`}, {"1.0,2", newSetFloat64Slice(1, 2), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "\[\]float64{}" as float64 slice value for flag seconds: .*`}, + {"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "\[\]float64{}" as float64 slice value from environment variable "SECONDS" for flag seconds: .*`}, {"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`}, - {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`}, + {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value from environment variable "SECONDS" for flag seconds: .*`}, {"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value for flag seconds: .*`}, - {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value for flag seconds: .*`}, + {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`}, {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, {"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""}, @@ -130,12 +130,12 @@ func TestFlagsFromEnv(t *testing.T) { {"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, {"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value for flag seconds: .*`}, - {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value for flag seconds: .*`}, + {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value from environment variable "SECONDS" for flag seconds: .*`}, {"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value for flag seconds: .*`}, - {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value for flag seconds: .*`}, + {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value from environment variable "SECONDS" for flag seconds: .*`}, {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } @@ -2157,7 +2157,7 @@ func TestFlagFromFile(t *testing.T) { } for _, filePathTest := range filePathTests { - got, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path) + got, _, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path) if want := filePathTest.expected; got != want { t.Errorf("Did not expect %v - Want %v", got, want) } diff --git a/flag_timestamp.go b/flag_timestamp.go index d0c232dbff..052247795c 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -109,9 +109,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { f.Destination.SetLayout(f.Layout) } - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as timestamp value from %s for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true } diff --git a/flag_uint.go b/flag_uint.go index 58c20fd096..6092b1ad69 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -23,11 +23,11 @@ func (f *UintFlag) GetCategory() string { // Apply populates the flag given the flag set and environment func (f *UintFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = uint(valInt) diff --git a/flag_uint64.go b/flag_uint64.go index 407eb7f2ea..a37f30d9fc 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -23,11 +23,11 @@ func (f *Uint64Flag) GetCategory() string { // Apply populates the flag given the flag set and environment func (f *Uint64Flag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint64 value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt diff --git a/help.go b/help.go index 51033928cb..26c497befb 100644 --- a/help.go +++ b/help.go @@ -107,7 +107,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) { if command.Hidden { continue } - if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { + if strings.HasSuffix(os.Getenv("SHELL"), "zsh") { for _, name := range command.Names() { _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) } diff --git a/help_test.go b/help_test.go index 98530dd2a6..17a263deb6 100644 --- a/help_test.go +++ b/help_test.go @@ -1040,12 +1040,16 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) { } func TestDefaultCompleteWithFlags(t *testing.T) { + origEnv := os.Environ() origArgv := os.Args t.Cleanup(func() { os.Args = origArgv + resetEnv(origEnv) }) + os.Setenv("SHELL", "bash") + for _, tc := range []struct { name string c *Context