From 500d6b04e6dc2b9882c0e555a80df7523a97e05f Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Thu, 30 Jul 2020 12:05:11 +0200 Subject: [PATCH 01/10] Report the source of a value when we cannot parse it If you allow a flag to be set from environment variables or files and a parse error occurs from one of them, it is very useful for the error message to mention where the value came from. Without this, it can be difficult to notice an error caused by an unexpected environment variable being set. Implements #1167. --- flag.go | 8 ++++---- flag_bool.go | 4 ++-- flag_duration.go | 4 ++-- flag_float64.go | 4 ++-- flag_float64_slice.go | 4 ++-- flag_generic.go | 4 ++-- flag_int.go | 4 ++-- flag_int64.go | 4 ++-- flag_int64_slice.go | 4 ++-- flag_int_slice.go | 4 ++-- flag_path.go | 3 ++- flag_string.go | 3 ++- flag_string_slice.go | 4 ++-- flag_test.go | 32 ++++++++++++++++---------------- flag_timestamp.go | 4 ++-- flag_uint.go | 4 ++-- flag_uint64.go | 4 ++-- 17 files changed, 50 insertions(+), 48 deletions(-) diff --git a/flag.go b/flag.go index ad97c2d058..a8edc4fc26 100644 --- a/flag.go +++ b/flag.go @@ -372,17 +372,17 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } -func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) { +func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool, source string) { for _, envVar := range envVars { envVar = strings.TrimSpace(envVar) if val, ok := syscall.Getenv(envVar); ok { - return val, true + return val, true, fmt.Sprintf("from environment variable %q", envVar) } } for _, fileVar := range strings.Split(filePath, ",") { if data, err := ioutil.ReadFile(fileVar); err == nil { - return string(data), true + return string(data), true, fmt.Sprintf("from file %q", filePath) } } - return "", false + return "", false, "" } diff --git a/flag_bool.go b/flag_bool.go index bc9ea35d08..903fd08492 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -60,12 +60,12 @@ func (f *BoolFlag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %s for flag %s: %s", val, source, f.Name, err) } f.Value = valBool diff --git a/flag_duration.go b/flag_duration.go index 22a2e67201..0717135d8e 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -60,12 +60,12 @@ func (f *DurationFlag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %s for flag %s: %s", val, source, f.Name, err) } f.Value = valDuration diff --git a/flag_float64.go b/flag_float64.go index 91c778c873..a152945752 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -60,12 +60,12 @@ func (f *Float64Flag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valFloat, err := strconv.ParseFloat(val, 10) 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 %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 706ee6cd4b..dfd68fdc65 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -119,13 +119,13 @@ func (f *Float64SliceFlag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { f.Value = &Float64Slice{} for _, s := range strings.Split(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 %s for flag %s: %s", f.Value, source, f.Name, err) } } diff --git a/flag_generic.go b/flag_generic.go index b0c8ff44d2..fab517c430 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -69,10 +69,10 @@ func (f *GenericFlag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %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 ac39d4a9e4..7d0a889985 100644 --- a/flag_int.go +++ b/flag_int.go @@ -60,12 +60,12 @@ func (f *IntFlag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %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 e09991269b..401d0b435c 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -60,12 +60,12 @@ func (f *Int64Flag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %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 6c7fd9376d..15ee9fd060 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -120,12 +120,12 @@ func (f *Int64SliceFlag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &Int64Slice{} for _, s := range strings.Split(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 %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_int_slice.go b/flag_int_slice.go index 4e0afc0210..7807e354fe 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -131,12 +131,12 @@ func (f *IntSliceFlag) GetValue() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &IntSlice{} for _, s := range strings.Split(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 %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_path.go b/flag_path.go index 8070dc4b0b..5d33ed8d99 100644 --- a/flag_path.go +++ b/flag_path.go @@ -56,7 +56,8 @@ func (f *PathFlag) GetValue() 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 { + // TODO: how to report the source of parse errors? + if val, ok, _ := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = val f.HasBeenSet = true } diff --git a/flag_string.go b/flag_string.go index 400bb532e7..5fdea3a6a0 100644 --- a/flag_string.go +++ b/flag_string.go @@ -57,7 +57,8 @@ func (f *StringFlag) GetValue() 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 { + // TODO: how to report source? + if val, ok, _ := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = val f.HasBeenSet = true } diff --git a/flag_string_slice.go b/flag_string_slice.go index 35497032cb..2fb62aa0f7 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -123,7 +123,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { } - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if f.Value == nil { f.Value = &StringSlice{} } @@ -134,7 +134,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { for _, s := range strings.Split(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 %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_test.go b/flag_test.go index b1fe70a4fd..8600ba439b 100644 --- a/flag_test.go +++ b/flag_test.go @@ -79,30 +79,30 @@ 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,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"}}, ""}, @@ -110,12 +110,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"}}, ""}, } @@ -1758,7 +1758,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 0382a6b9dc..898083513b 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -123,9 +123,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { } f.Value.SetLayout(f.Layout) - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %s for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true } diff --git a/flag_uint.go b/flag_uint.go index 2e5e76b0ea..127f90b50d 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -54,11 +54,11 @@ func (f *UintFlag) GetUsage() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %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 8fc3289d82..162d44798f 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -54,11 +54,11 @@ func (f *Uint64Flag) GetUsage() 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { 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 %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt From cdc1f6e07c17b57ef6e907bc505fddcae87abebe Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Sat, 7 Nov 2020 14:05:37 +0100 Subject: [PATCH 02/10] fixup! Report the source of a value when we cannot parse it move bool to the end of the return arguments remove "from " prefix in the source/fromWhere description remove TODO notes from functions that don't currently perform error checking --- flag.go | 13 ++++++++----- flag_bool.go | 4 ++-- flag_duration.go | 4 ++-- flag_float64.go | 4 ++-- flag_float64_slice.go | 4 ++-- flag_generic.go | 4 ++-- flag_int.go | 4 ++-- flag_int64.go | 4 ++-- flag_int64_slice.go | 4 ++-- flag_int_slice.go | 4 ++-- flag_path.go | 3 +-- flag_string.go | 3 +-- flag_string_slice.go | 4 ++-- flag_timestamp.go | 4 ++-- flag_uint.go | 4 ++-- flag_uint64.go | 4 ++-- 16 files changed, 36 insertions(+), 35 deletions(-) diff --git a/flag.go b/flag.go index a8edc4fc26..35ec2172fb 100644 --- a/flag.go +++ b/flag.go @@ -372,17 +372,20 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } -func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool, source string) { +// 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, fmt.Sprintf("from environment variable %q", envVar) + if value, found := syscall.Getenv(envVar); found { + return value, fmt.Sprintf("environment variable %q", envVar), true } } for _, fileVar := range strings.Split(filePath, ",") { if data, err := ioutil.ReadFile(fileVar); err == nil { - return string(data), true, fmt.Sprintf("from file %q", filePath) + return string(data), fmt.Sprintf("file %q", filePath), true } } - return "", false, "" + return "", "", false } diff --git a/flag_bool.go b/flag_bool.go index 903fd08492..9462e0670e 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -60,12 +60,12 @@ func (f *BoolFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *BoolFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := 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 %s for flag %s: %s", val, source, 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 0717135d8e..040b5d602b 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -60,12 +60,12 @@ func (f *DurationFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := 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 %s for flag %s: %s", val, source, 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 a152945752..14df60d4c3 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -60,12 +60,12 @@ func (f *Float64Flag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valFloat, err := strconv.ParseFloat(val, 10) if err != nil { - return fmt.Errorf("could not parse %q as float64 value %s for flag %s: %s", val, source, 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 dfd68fdc65..03822292b6 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -119,13 +119,13 @@ func (f *Float64SliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { f.Value = &Float64Slice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as float64 slice value %s for flag %s: %s", f.Value, source, 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 fab517c430..b92c4de584 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -69,10 +69,10 @@ func (f *GenericFlag) GetValue() 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, source := 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 %s as value for flag %s: %s", val, source, 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 7d0a889985..8e0ee599ad 100644 --- a/flag_int.go +++ b/flag_int.go @@ -60,12 +60,12 @@ func (f *IntFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := 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 %s for flag %s: %s", val, source, 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 401d0b435c..0dda82e9b5 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -60,12 +60,12 @@ func (f *Int64Flag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { - if val, ok, source := 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 %s for flag %s: %s", val, source, 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 15ee9fd060..cfb29eb298 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -120,12 +120,12 @@ func (f *Int64SliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = &Int64Slice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int64 slice value %s for flag %s: %s", val, source, 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 7807e354fe..f379039811 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -131,12 +131,12 @@ func (f *IntSliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = &IntSlice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int slice value %s for flag %s: %s", val, source, 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 5d33ed8d99..bc8d5a2821 100644 --- a/flag_path.go +++ b/flag_path.go @@ -56,8 +56,7 @@ func (f *PathFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *PathFlag) Apply(set *flag.FlagSet) error { - // TODO: how to report the source of parse errors? - 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 5fdea3a6a0..5d79ce8e38 100644 --- a/flag_string.go +++ b/flag_string.go @@ -57,8 +57,7 @@ func (f *StringFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *StringFlag) Apply(set *flag.FlagSet) error { - // TODO: how to report source? - 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 2fb62aa0f7..e081c4f5f0 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -123,7 +123,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { } - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if f.Value == nil { f.Value = &StringSlice{} } @@ -134,7 +134,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { for _, s := range strings.Split(val, ",") { if err := destination.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as string value %s for flag %s: %s", val, source, 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_timestamp.go b/flag_timestamp.go index 898083513b..fb174eee97 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -123,9 +123,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { } f.Value.SetLayout(f.Layout) - if val, ok, source := 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 %s for flag %s: %s", val, source, 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 127f90b50d..f64cbe4119 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -54,11 +54,11 @@ func (f *UintFlag) GetUsage() string { // Apply populates the flag given the flag set and environment func (f *UintFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := 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 %s for flag %s: %s", val, source, 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 162d44798f..a6bf63d123 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -54,11 +54,11 @@ func (f *Uint64Flag) GetUsage() string { // Apply populates the flag given the flag set and environment func (f *Uint64Flag) Apply(set *flag.FlagSet) error { - if val, ok, source := 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 %s for flag %s: %s", val, source, 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 From f89647bd19dac132bbf382f3377bcdee022ae791 Mon Sep 17 00:00:00 2001 From: Ihor Urazov Date: Thu, 14 Jan 2021 21:14:32 +0200 Subject: [PATCH 03/10] Simplify zsh completion Completion file shouldn't be sourced. It should provide only completion code (source of _command) for command. It's task for package manager or user to put under $fpath. --- autocomplete/zsh_autocomplete | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index cf39c888af..b3872bf1ac 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -1,23 +1,16 @@ #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)}") +else + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") +fi - 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)}") - else - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") - fi - - if [[ "${opts[1]}" != "" ]]; then - _describe 'values' opts - else - _files - fi - - return -} - -compdef _cli_zsh_autocomplete $PROG +if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts +else + _files +fi From 1150c2e180e571fc3460db4c67bac76bf67cbd80 Mon Sep 17 00:00:00 2001 From: Ihor Urazov Date: Fri, 29 Jan 2021 17:04:54 +0200 Subject: [PATCH 04/10] Properly detect Zsh shell There is no need to define custom shell var, when Zsh can be detected by checking SHELL env var. --- app_test.go | 2 +- autocomplete/zsh_autocomplete | 4 ++-- docs/v2/manual.md | 9 ++++----- help.go | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app_test.go b/app_test.go index 76e211d681..6afc681a83 100644 --- a/app_test.go +++ b/app_test.go @@ -355,7 +355,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 b3872bf1ac..ee1b562743 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -4,9 +4,9 @@ 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 diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 56be65bb68..27b5c62e1f 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1211,14 +1211,13 @@ 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: +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/help.go b/help.go index 0a421ee99a..a7b3a944a2 100644 --- a/help.go +++ b/help.go @@ -102,7 +102,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) { if command.Hidden { continue } - if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { + if strings.Contains(os.Getenv("SHELL"), "zsh") { for _, name := range command.Names() { _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) } From f1d0b0ef43a2f4c816eddcccfa6cb4e8fbe014b6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 08:19:29 -0400 Subject: [PATCH 05/10] Add a security policy document Closes #1342 --- docs/SECURITY.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 docs/SECURITY.md diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000000..a6fe0cc52d --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,24 @@ +# Security Policy + +Hello and thank you for your interest in the `urfave/cli` security +policy! :tada: :lock: + +## Supported Versions + +| Version | Supported | +| --------- | ------------------ | +| >= 2.3.x | :white_check_mark: | +| < 2.3 | :x: | +| >= 1.22.x | :white_check_mark: | +| < 1.22 | :x: | + +## Reporting a Vulnerability + +Please disclose any vulnerabilities by sending an email to: + +[dan+urfave-cli-security@meatballhat.com](mailto:dan+urfave-cli-security@meatballhat.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: From b42bf698a162b6cc6ed90a25f87b814cead70afb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2022 20:24:10 -0400 Subject: [PATCH 06/10] Add note about looming EOL of `v1` --- docs/SECURITY.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index a6fe0cc52d..699538f24a 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -5,12 +5,12 @@ policy! :tada: :lock: ## Supported Versions -| Version | Supported | -| --------- | ------------------ | -| >= 2.3.x | :white_check_mark: | -| < 2.3 | :x: | -| >= 1.22.x | :white_check_mark: | -| < 1.22 | :x: | +| 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 @@ -22,3 +22,6 @@ 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 only receive bug fixes until `v1` + is deemed entirely unsupported From 1bef0318f9995cb1fecf7055b9a0ee7341767200 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 26 Apr 2022 07:52:30 -0400 Subject: [PATCH 07/10] Footnote update per feedback in #1365 --- docs/SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 699538f24a..eae6a22512 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -23,5 +23,5 @@ 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 only receive bug fixes until `v1` - is deemed entirely unsupported +[^1]: The `v1.22.x` series will receive bug fixes and security + patches only. From f3ef95f8ccf8a8d98d87a61009ff8690b0a7bf4a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2022 14:16:54 -0400 Subject: [PATCH 08/10] Tighten up restriction on SHELL match --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index 1455689310..4b03abea1d 100644 --- a/help.go +++ b/help.go @@ -102,7 +102,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) { if command.Hidden { continue } - if strings.Contains(os.Getenv("SHELL"), "zsh") { + if strings.HasSuffix(os.Getenv("SHELL"), "zsh") { for _, name := range command.Names() { _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) } From 32be625ece1fcab837c2984283784e07aca70add Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 09:30:42 -0400 Subject: [PATCH 09/10] Use google group email addresses --- CODE_OF_CONDUCT.md | 11 ++++++----- docs/SECURITY.md | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) 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/docs/SECURITY.md b/docs/SECURITY.md index eae6a22512..8af4ce4da9 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -16,7 +16,7 @@ policy! :tada: :lock: Please disclose any vulnerabilities by sending an email to: -[dan+urfave-cli-security@meatballhat.com](mailto:dan+urfave-cli-security@meatballhat.com) +[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 From e66017d73a69165ac6216f85dffed3d2cc78c68c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2022 09:07:03 -0400 Subject: [PATCH 10/10] Refinements to removal of zsh hack --- app_test.go | 5 ++++- autocomplete/zsh_autocomplete | 30 +++++++++++++++++------------- docs/v2/manual.md | 8 ++++---- help_test.go | 4 ++++ 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/app_test.go b/app_test.go index 5f20078f88..26ae28a95c 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{ diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index ee1b562743..b519666f80 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -1,16 +1,20 @@ #compdef $PROG -local -a opts -local cur -cur=${words[-1]} -if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") -else - opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") -fi +_cli_zsh_autocomplete() { + local -a opts + local cur + cur=${words[-1]} + if [[ "$cur" == "-"* ]]; then + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + else + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") + fi -if [[ "${opts[1]}" != "" ]]; then - _describe 'values' opts -else - _files -fi + if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts + else + _files + fi +} + +compdef _cli_zsh_autocomplete $PROG diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 3d96bb86cc..fd5656a569 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1165,10 +1165,10 @@ func main() { ``` #### ZSH Support -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`) +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: ``` 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