Merge remote-tracking branch 'origin/main' into michaeljs1990-add-flag-category-support

This commit is contained in:
Dan Buch 2022-05-22 11:06:38 -04:00
commit 4bca72cea1
Signed by: meatballhat
GPG Key ID: A12F782281063434
24 changed files with 103 additions and 69 deletions

View File

@ -55,11 +55,12 @@ further defined and clarified by project maintainers.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be reported by contacting urfave-governance@googlegroups.com, a members-only group
reviewed and investigated and will result in a response that is deemed necessary that is world-postable. All complaints will be reviewed and investigated and
and appropriate to the circumstances. The project team is obligated to maintain will result in a response that is deemed necessary and appropriate to the
confidentiality with regard to the reporter of an incident. Further details of circumstances. The project team is obligated to maintain confidentiality with
specific enforcement policies may be posted separately. 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 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 faith may face temporary or permanent repercussions as determined by other

View File

@ -228,6 +228,7 @@ func ExampleApp_Run_subcommandNoAction() {
} }
func ExampleApp_Run_bashComplete_withShortFlag() { func ExampleApp_Run_bashComplete_withShortFlag() {
os.Setenv("SHELL", "bash")
os.Args = []string{"greet", "-", "--generate-bash-completion"} os.Args = []string{"greet", "-", "--generate-bash-completion"}
app := NewApp() app := NewApp()
@ -255,6 +256,7 @@ func ExampleApp_Run_bashComplete_withShortFlag() {
} }
func ExampleApp_Run_bashComplete_withLongFlag() { func ExampleApp_Run_bashComplete_withLongFlag() {
os.Setenv("SHELL", "bash")
os.Args = []string{"greet", "--s", "--generate-bash-completion"} os.Args = []string{"greet", "--s", "--generate-bash-completion"}
app := NewApp() app := NewApp()
@ -283,6 +285,7 @@ func ExampleApp_Run_bashComplete_withLongFlag() {
// --similar-flag // --similar-flag
} }
func ExampleApp_Run_bashComplete_withMultipleLongFlag() { func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
os.Setenv("SHELL", "bash")
os.Args = []string{"greet", "--st", "--generate-bash-completion"} os.Args = []string{"greet", "--st", "--generate-bash-completion"}
app := NewApp() app := NewApp()
@ -315,7 +318,7 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
} }
func ExampleApp_Run_bashComplete() { func ExampleApp_Run_bashComplete() {
// set args for examples sake os.Setenv("SHELL", "bash")
os.Args = []string{"greet", "--generate-bash-completion"} os.Args = []string{"greet", "--generate-bash-completion"}
app := &App{ app := &App{
@ -355,7 +358,7 @@ func ExampleApp_Run_bashComplete() {
func ExampleApp_Run_zshComplete() { func ExampleApp_Run_zshComplete() {
// set args for examples sake // set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"} os.Args = []string{"greet", "--generate-bash-completion"}
_ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1") _ = os.Setenv("SHELL", "/usr/bin/zsh")
app := NewApp() app := NewApp()
app.Name = "greet" app.Name = "greet"

View File

@ -1,14 +1,13 @@
#compdef $PROG #compdef $PROG
_cli_zsh_autocomplete() { _cli_zsh_autocomplete() {
local -a opts local -a opts
local cur local cur
cur=${words[-1]} cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then 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 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 fi
if [[ "${opts[1]}" != "" ]]; then if [[ "${opts[1]}" != "" ]]; then
@ -16,8 +15,6 @@ _cli_zsh_autocomplete() {
else else
_files _files
fi fi
return
} }
compdef _cli_zsh_autocomplete $PROG compdef _cli_zsh_autocomplete $PROG

27
docs/SECURITY.md Normal file
View File

@ -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.

View File

@ -1166,14 +1166,13 @@ func main() {
#### ZSH Support #### ZSH Support
Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` 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`. file included in this repo. One environment variable is used, `PROG`. Set
Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and `PROG` to the program name as before, and then `source path/to/autocomplete/zsh_autocomplete`.
then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH Adding the following lines to your ZSH configuration file (usually `.zshrc`)
configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: will allow the auto-completion to persist across new shells:
``` ```
PROG=<myprogram> PROG=<myprogram>
_CLI_ZSH_AUTOCOMPLETE_HACK=1
source path/to/autocomplete/zsh_autocomplete source path/to/autocomplete/zsh_autocomplete
``` ```
#### ZSH default auto-complete example #### ZSH default auto-complete example

13
flag.go
View File

@ -394,21 +394,24 @@ func hasFlag(flags []Flag, fl Flag) bool {
return false 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 { for _, envVar := range envVars {
envVar = strings.TrimSpace(envVar) envVar = strings.TrimSpace(envVar)
if val, ok := syscall.Getenv(envVar); ok { if value, found := syscall.Getenv(envVar); found {
return val, true return value, fmt.Sprintf("environment variable %q", envVar), true
} }
} }
for _, fileVar := range strings.Split(filePath, ",") { for _, fileVar := range strings.Split(filePath, ",") {
if fileVar != "" { if fileVar != "" {
if data, err := ioutil.ReadFile(fileVar); err == nil { 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 { func flagSplitMultiValues(val string) []string {

View File

@ -42,12 +42,12 @@ func (f *BoolFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *BoolFlag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
valBool, err := strconv.ParseBool(val) valBool, err := strconv.ParseBool(val)
if err != nil { 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 f.Value = valBool

View File

@ -42,12 +42,12 @@ func (f *DurationFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *DurationFlag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
valDuration, err := time.ParseDuration(val) valDuration, err := time.ParseDuration(val)
if err != nil { 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 f.Value = valDuration

View File

@ -42,11 +42,11 @@ func (f *Float64Flag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Float64Flag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
valFloat, err := strconv.ParseFloat(val, 64) valFloat, err := strconv.ParseFloat(val, 64)
if err != nil { 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 f.Value = valFloat

View File

@ -120,13 +120,13 @@ func (f *Float64SliceFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
f.Value = &Float64Slice{} f.Value = &Float64Slice{}
for _, s := range flagSplitMultiValues(val) { for _, s := range flagSplitMultiValues(val) {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil { 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)
} }
} }

View File

@ -51,10 +51,10 @@ func (f *GenericFlag) GetEnvVars() []string {
// Apply takes the flagset and calls Set on the generic flag with the value // Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag // provided by the user for parsing by the flag
func (f GenericFlag) Apply(set *flag.FlagSet) error { 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 val != "" {
if err := f.Value.Set(val); err != nil { 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 f.HasBeenSet = true

View File

@ -42,12 +42,12 @@ func (f *IntFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *IntFlag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
valInt, err := strconv.ParseInt(val, 0, 64) valInt, err := strconv.ParseInt(val, 0, 64)
if err != nil { 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) f.Value = int(valInt)

View File

@ -42,12 +42,12 @@ func (f *Int64Flag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Int64Flag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
valInt, err := strconv.ParseInt(val, 0, 64) valInt, err := strconv.ParseInt(val, 0, 64)
if err != nil { 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 f.Value = valInt

View File

@ -121,12 +121,12 @@ func (f *Int64SliceFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { 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{} f.Value = &Int64Slice{}
for _, s := range flagSplitMultiValues(val) { for _, s := range flagSplitMultiValues(val) {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil { 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)
} }
} }

View File

@ -132,12 +132,12 @@ func (f *IntSliceFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { 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{} f.Value = &IntSlice{}
for _, s := range flagSplitMultiValues(val) { for _, s := range flagSplitMultiValues(val) {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil { 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)
} }
} }

View File

@ -46,7 +46,7 @@ func (f *PathFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *PathFlag) Apply(set *flag.FlagSet) error { 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.Value = val
f.HasBeenSet = true f.HasBeenSet = true
} }

View File

@ -44,7 +44,7 @@ func (f *StringFlag) GetEnvVars() []string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *StringFlag) Apply(set *flag.FlagSet) error { 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.Value = val
f.HasBeenSet = true f.HasBeenSet = true
} }

View File

@ -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 { if f.Value == nil {
f.Value = &StringSlice{} f.Value = &StringSlice{}
} }
@ -133,7 +133,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
for _, s := range flagSplitMultiValues(val) { for _, s := range flagSplitMultiValues(val) {
if err := destination.Set(strings.TrimSpace(s)); err != nil { 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)
} }
} }

View File

@ -96,33 +96,33 @@ func TestFlagsFromEnv(t *testing.T) {
{"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
{"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
{"false", false, &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"}}, ""}, {"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.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"1", 1.0, &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", 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: .*`}, {"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 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", 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: .*`}, {"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 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"}}, ""}, {"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", 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: .*`}, {"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 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", 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: .*`}, {"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 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"}}, ""}, {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""},
{"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""}, {"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"}}, ""}, {"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""},
{"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"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: .*`}, {"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 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", 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: .*`}, {"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 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"}}, ""}, {"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 { for _, filePathTest := range filePathTests {
got, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path) got, _, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path)
if want := filePathTest.expected; got != want { if want := filePathTest.expected; got != want {
t.Errorf("Did not expect %v - Want %v", got, want) t.Errorf("Did not expect %v - Want %v", got, want)
} }

View File

@ -109,9 +109,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
f.Destination.SetLayout(f.Layout) 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 { 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 f.HasBeenSet = true
} }

View File

@ -23,11 +23,11 @@ func (f *UintFlag) GetCategory() string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *UintFlag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
valInt, err := strconv.ParseUint(val, 0, 64) valInt, err := strconv.ParseUint(val, 0, 64)
if err != nil { 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) f.Value = uint(valInt)

View File

@ -23,11 +23,11 @@ func (f *Uint64Flag) GetCategory() string {
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Uint64Flag) Apply(set *flag.FlagSet) error { 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 != "" { if val != "" {
valInt, err := strconv.ParseUint(val, 0, 64) valInt, err := strconv.ParseUint(val, 0, 64)
if err != nil { 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 f.Value = valInt

View File

@ -107,7 +107,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) {
if command.Hidden { if command.Hidden {
continue continue
} }
if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { if strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
for _, name := range command.Names() { for _, name := range command.Names() {
_, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
} }

View File

@ -1040,12 +1040,16 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) {
} }
func TestDefaultCompleteWithFlags(t *testing.T) { func TestDefaultCompleteWithFlags(t *testing.T) {
origEnv := os.Environ()
origArgv := os.Args origArgv := os.Args
t.Cleanup(func() { t.Cleanup(func() {
os.Args = origArgv os.Args = origArgv
resetEnv(origEnv)
}) })
os.Setenv("SHELL", "bash")
for _, tc := range []struct { for _, tc := range []struct {
name string name string
c *Context c *Context