From 77423635009e8706999c64dea75a30cff63627f2 Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Mon, 2 Mar 2020 13:05:43 +0100 Subject: [PATCH 001/111] =?UTF-8?q?Refactor=20'(default:=20=E2=80=A6)'=20f?= =?UTF-8?q?lag=20string=20into=20utility=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usage of the `(default: …)` strings are a bit error prone so we now refactor them into a dedicated helper function. Signed-off-by: Sascha Grunert --- flag.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/flag.go b/flag.go index 524de42..712eca9 100644 --- a/flag.go +++ b/flag.go @@ -244,6 +244,10 @@ func flagValue(f Flag) reflect.Value { return fv } +func formatDefault(format string) string { + return " (default: " + format + ")" +} + func stringifyFlag(f Flag) string { fv := flagValue(f) @@ -269,20 +273,20 @@ func stringifyFlag(f Flag) string { val := fv.FieldByName("Value") if val.IsValid() { needsPlaceholder = val.Kind() != reflect.Bool - defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) + defaultValueString = fmt.Sprintf(formatDefault("%v"), val.Interface()) if val.Kind() == reflect.String && val.String() != "" { - defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) + defaultValueString = fmt.Sprintf(formatDefault("%q"), val.String()) } } helpText := fv.FieldByName("DefaultText") if helpText.IsValid() && helpText.String() != "" { needsPlaceholder = val.Kind() != reflect.Bool - defaultValueString = fmt.Sprintf(" (default: %s)", helpText.String()) + defaultValueString = fmt.Sprintf(formatDefault("%s"), helpText.String()) } - if defaultValueString == " (default: )" { + if defaultValueString == formatDefault("") { defaultValueString = "" } @@ -351,7 +355,7 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { defaultVal := "" if len(defaultVals) > 0 { - defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) + defaultVal = fmt.Sprintf(formatDefault("%s"), strings.Join(defaultVals, ", ")) } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) From 75e7c526bd76bffe1b059a30c5a45ee66ce2a323 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 5 Mar 2020 16:17:18 +0900 Subject: [PATCH 002/111] Add HideHelpCommand While `HideHelp` hides both `help` command and `--help` flag, `HideHelpCommand` only hides `help` command and leave `--help` flag as-is. The behavior of `HideHelp` is untouched in this commit. Fix #523 Replace #636 Signed-off-by: Akihiro Suda --- app.go | 21 +++----- command.go | 6 ++- flag.go | 2 +- help_test.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index dd8f1de..d0c8f84 100644 --- a/app.go +++ b/app.go @@ -43,8 +43,11 @@ type App struct { Flags []Flag // Boolean to enable bash completion commands EnableBashCompletion bool - // Boolean to hide built-in help command + // Boolean to hide built-in help command and help flag HideHelp bool + // Boolean to hide built-in help command but keep help flag. + // Ignored if HideHelp is true. + HideHelpCommand bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool // categories contains the categorized commands and is populated on app startup @@ -170,7 +173,9 @@ func (a *App) Setup() { a.Commands = newCommands if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.appendCommand(helpCommand) + if !a.HideHelpCommand { + a.appendCommand(helpCommand) + } if HelpFlag != nil { a.appendFlag(HelpFlag) @@ -328,19 +333,9 @@ func (a *App) RunAndExitOnError() { // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to // generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { + // Setup also handles HideHelp and HideHelpCommand a.Setup() - // append help to commands - if len(a.Commands) > 0 { - if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.appendCommand(helpCommand) - - if HelpFlag != nil { - a.appendFlag(HelpFlag) - } - } - } - var newCmds []*Command for _, c := range a.Commands { if c.HelpName == "" { diff --git a/command.go b/command.go index db6c802..95840f3 100644 --- a/command.go +++ b/command.go @@ -41,8 +41,11 @@ type Command struct { Flags []Flag // Treat all flags as normal arguments if true SkipFlagParsing bool - // Boolean to hide built-in help command + // Boolean to hide built-in help command and help flag HideHelp bool + // Boolean to hide built-in help command but keep help flag + // Ignored if HideHelp is true. + HideHelpCommand bool // Boolean to hide this command from help or completion Hidden bool // Boolean to enable short-option handling so user can combine several @@ -236,6 +239,7 @@ func (c *Command) startApp(ctx *Context) error { app.Commands = c.Subcommands app.Flags = c.Flags app.HideHelp = c.HideHelp + app.HideHelpCommand = c.HideHelpCommand app.Version = ctx.App.Version app.HideVersion = ctx.App.HideVersion diff --git a/flag.go b/flag.go index 524de42..6257f7d 100644 --- a/flag.go +++ b/flag.go @@ -36,7 +36,7 @@ var VersionFlag Flag = &BoolFlag{ // HelpFlag prints the help for all commands and subcommands. // Set to nil to disable the flag. The subcommand -// will still be added unless HideHelp is set to true. +// will still be added unless HideHelp or HideHelpCommand is set to true. var HelpFlag Flag = &BoolFlag{ Name: "help", Aliases: []string{"h"}, diff --git a/help_test.go b/help_test.go index 9f182e8..5f292b7 100644 --- a/help_test.go +++ b/help_test.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + "io/ioutil" "runtime" "strings" "testing" @@ -762,3 +763,147 @@ VERSION: t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String()) } } + +func TestHideHelpCommand(t *testing.T) { + app := &App{ + HideHelpCommand: true, + Writer: ioutil.Discard, + } + + err := app.Run([]string{"foo", "help"}) + if err == nil { + t.Fatalf("expected a non-nil error") + } + if !strings.Contains(err.Error(), "No help topic for 'help'") { + t.Errorf("Run returned unexpected error: %v", err) + } + + err = app.Run([]string{"foo", "--help"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestHideHelpCommand_False(t *testing.T) { + app := &App{ + HideHelpCommand: false, + Writer: ioutil.Discard, + } + + err := app.Run([]string{"foo", "help"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } + + err = app.Run([]string{"foo", "--help"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestHideHelpCommand_WithHideHelp(t *testing.T) { + app := &App{ + HideHelp: true, // effective (hides both command and flag) + HideHelpCommand: true, // ignored + Writer: ioutil.Discard, + } + + err := app.Run([]string{"foo", "help"}) + if err == nil { + t.Fatalf("expected a non-nil error") + } + if !strings.Contains(err.Error(), "No help topic for 'help'") { + t.Errorf("Run returned unexpected error: %v", err) + } + + err = app.Run([]string{"foo", "--help"}) + if err == nil { + t.Fatalf("expected a non-nil error") + } + if !strings.Contains(err.Error(), "flag: help requested") { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func newContextFromStringSlice(ss []string) *Context { + set := flag.NewFlagSet("", flag.ContinueOnError) + _ = set.Parse(ss) + return &Context{flagSet: set} +} + +func TestHideHelpCommand_RunAsSubcommand(t *testing.T) { + app := &App{ + HideHelpCommand: true, + Writer: ioutil.Discard, + Commands: []*Command{ + { + Name: "dummy", + }, + }, + } + + err := app.RunAsSubcommand(newContextFromStringSlice([]string{"", "help"})) + if err == nil { + t.Fatalf("expected a non-nil error") + } + if !strings.Contains(err.Error(), "No help topic for 'help'") { + t.Errorf("Run returned unexpected error: %v", err) + } + + err = app.RunAsSubcommand(newContextFromStringSlice([]string{"", "--help"})) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestHideHelpCommand_RunAsSubcommand_False(t *testing.T) { + app := &App{ + HideHelpCommand: false, + Writer: ioutil.Discard, + Commands: []*Command{ + { + Name: "dummy", + }, + }, + } + + err := app.RunAsSubcommand(newContextFromStringSlice([]string{"", "help"})) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } + + err = app.RunAsSubcommand(newContextFromStringSlice([]string{"", "--help"})) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestHideHelpCommand_WithSubcommands(t *testing.T) { + app := &App{ + Writer: ioutil.Discard, + Commands: []*Command{ + { + Name: "dummy", + Subcommands: []*Command{ + { + Name: "dummy2", + }, + }, + HideHelpCommand: true, + }, + }, + } + + err := app.Run([]string{"foo", "dummy", "help"}) + if err == nil { + t.Fatalf("expected a non-nil error") + } + if !strings.Contains(err.Error(), "No help topic for 'help'") { + t.Errorf("Run returned unexpected error: %v", err) + } + + err = app.Run([]string{"foo", "dummy", "--help"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} From ff1566ff2b974ea25531f17d209cbc2022eadaa9 Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Sun, 8 Mar 2020 03:58:52 -0700 Subject: [PATCH 003/111] Update CHANGELOG.md --- docs/CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2f67fe7..ecc4cd5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,42 @@ View [unreleased 2.X] series changes. +## [2.2.0] - 2020-03-08 + +These release notes were written for the git hash [d648edd48d89ef3a841b1ec75c2ebbd4de5f748f](https://github.com/urfave/cli/tree/d648edd48d89ef3a841b1ec75c2ebbd4de5f748f) + +https://github.com/urfave/cli/pull/1083 AkihiroSuda + +https://github.com/urfave/cli/pull/1078 davidsbond + +https://github.com/urfave/cli/pull/1062 zhsj + +https://github.com/urfave/cli/pull/1059 masonj188 + +https://github.com/urfave/cli/pull/997 drov0 + +https://github.com/urfave/cli/pull/1054 itchyny + +https://github.com/urfave/cli/pull/1049 saschagrunert + +https://github.com/urfave/cli/pull/1041 saschagrunert + +https://github.com/urfave/cli/pull/1024 vkd + +https://github.com/urfave/cli/pull/1008 lynncyrin + +### Fixed + +* + +### Changed + +* + +### Added + +* + ## [2.1.1] - 2019-12-24 ### Fixed From 97faf2231901e68f15bedcb17da9223156974972 Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Mon, 9 Mar 2020 01:05:04 -0700 Subject: [PATCH 004/111] Update cli.yml --- .github/workflows/cli.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index a779bb2..d79a314 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: [1.11, 1.12, 1.13] + go: [1.12, 1.13, 1.14] name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: @@ -39,7 +39,7 @@ jobs: ref: ${{ github.ref }} - name: GOFMT Check - if: matrix.go == 1.13 && matrix.os == 'ubuntu-latest' + if: matrix.go == 1.14 && matrix.os == 'ubuntu-latest' run: test -z $(gofmt -l .) - name: vet @@ -52,7 +52,7 @@ jobs: run: go run internal/build/build.go check-binary-size - name: Upload coverage to Codecov - if: success() && matrix.go == 1.13 && matrix.os == 'ubuntu-latest' + if: success() && matrix.go == 1.14 && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v1 with: token: 0a8cc73b-bb7c-480b-8626-38a461643761 @@ -62,10 +62,10 @@ jobs: name: test-docs runs-on: ubuntu-latest steps: - - name: Set up Go 1.13 + - name: Set up Go 1.14 uses: actions/setup-go@v1 with: - go-version: 1.13 + go-version: 1.14 - name: Use Node.js 12.x uses: actions/setup-node@v1 From d7919f0fdd7a67e3f9e3447d5fcba89e9f422f2a Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Mon, 9 Mar 2020 01:13:18 -0700 Subject: [PATCH 005/111] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c9237fb..fe2dafc 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Usage documentation exists for each major version. Don't know what version you'r ## Installation -Make sure you have a working Go environment. Go version 1.11+ is supported. [See the install instructions for Go](http://golang.org/doc/install.html). +Using the package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html). Go Modules are strongly recommended when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules). @@ -63,4 +63,4 @@ export PATH=$PATH:$GOPATH/bin cli is tested against multiple versions of Go on Linux, and against the latest released version of Go on OS X and Windows. This project uses Github Actions for -builds. For more build info, please look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml). +builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml). From f4ec4f67b32493eac4a2047f08e2037b436d4e55 Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Mon, 9 Mar 2020 01:14:51 -0700 Subject: [PATCH 006/111] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe2dafc..ae9d19b 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ Usage documentation exists for each major version. Don't know what version you'r ## Installation -Using the package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html). +Using this package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html). -Go Modules are strongly recommended when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules). +Go Modules are required when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules). ### Using `v2` releases From 2604b5e82dc1ac8901788b9473868d3b2d750e8c Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Mon, 9 Mar 2020 01:35:19 -0700 Subject: [PATCH 007/111] Update CHANGELOG.md --- docs/CHANGELOG.md | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ecc4cd5..d9e613d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,37 +10,24 @@ View [unreleased 2.X] series changes. These release notes were written for the git hash [d648edd48d89ef3a841b1ec75c2ebbd4de5f748f](https://github.com/urfave/cli/tree/d648edd48d89ef3a841b1ec75c2ebbd4de5f748f) -https://github.com/urfave/cli/pull/1083 AkihiroSuda - -https://github.com/urfave/cli/pull/1078 davidsbond - -https://github.com/urfave/cli/pull/1062 zhsj - -https://github.com/urfave/cli/pull/1059 masonj188 - -https://github.com/urfave/cli/pull/997 drov0 - -https://github.com/urfave/cli/pull/1054 itchyny - -https://github.com/urfave/cli/pull/1049 saschagrunert - -https://github.com/urfave/cli/pull/1041 saschagrunert - -https://github.com/urfave/cli/pull/1024 vkd - -https://github.com/urfave/cli/pull/1008 lynncyrin - ### Fixed -* +* Fixed zsh completion scripts in [urfave/cli/pull/1062](https://github.com/urfave/cli/pull/1062) via [@zhsj](https://github.com/zhsj) +* Fixed description of subcommand to be more consistent in [urfave/cli/pull/1054](https://github.com/urfave/cli/pull/1054) via [@itchyny](https://github.com/itchyny) +* Fixed possible runtime panic in slice parsing in [https://github.com/urfave/cli/pull/1049](https://github.com/urfave/cli/pull/1049) via [@saschagrunert](https://github.com/saschagrunert) +* Fixed invalid man page header generation in [urfave/cli/pull/1041](https://github.com/urfave/cli/pull/1041) via [@saschagrunert](https://github.com/saschagrunert) ### Changed -* +* Improved auto-completion instructions and added example gifs in [urfave/cli/pull/1059](https://github.com/urfave/cli/pull/1059) via [@masonj188](https://github.com/masonj188) +* Removed the author from generated man pages in [urfave/cli/pull/1041](https://github.com/urfave/cli/pull/1041) via [@saschagrunert](https://github.com/saschagrunert) ### Added -* +* Added destination field to `StringSliceFlag` in [urfave/cli/pull/1078](https://github.com/urfave/cli/pull/1078) via [@davidsbond](https://github.com/davidsbond) +* Added `HideHelpCommand`. While `HideHelp` hides both `help` command and `--help` flag, `HideHelpCommand` only hides `help` command and leave `--help` flag as-is in [urfave/cli/pull/1083](https://github.com/urfave/cli/pull/1083) via [@AkihiroSuda](https://github.com/AkihiroSuda) +* Added timestampFlag docs in [urfave/cli/pull/997](https://github.com/urfave/cli/pull/997) via [@drov0](https://github.com/drov0) +* Added required flags documentation in [urfave/cli/pull/1008](https://github.com/urfave/cli/pull/1008) via [@lynncyrin](https://github.com/lynncyrin), [@anberns](https://github.com/anberns) ## [2.1.1] - 2019-12-24 @@ -591,7 +578,8 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[unreleased 2.X]: https://github.com/urfave/cli/compare/v2.1.1...HEAD +[unreleased 2.X]: https://github.com/urfave/cli/compare/v2.2.0...HEAD +[2.2.0]: https://github.com/urfave/cli/compare/v2.1.1...v2.2.0 [2.1.1]: https://github.com/urfave/cli/compare/v2.1.0...v2.1.1 [2.1.0]: https://github.com/urfave/cli/compare/v2.0.0...v2.1.0 [2.0.0]: https://github.com/urfave/cli/compare/v1.22.2...v2.0.0 From 526f019335a6f8ce5cf9294928200daee0ee49be Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Mon, 9 Mar 2020 01:37:39 -0700 Subject: [PATCH 008/111] Update CHANGELOG.md --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d9e613d..850f049 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -14,7 +14,7 @@ These release notes were written for the git hash [d648edd48d89ef3a841b1ec75c2eb * Fixed zsh completion scripts in [urfave/cli/pull/1062](https://github.com/urfave/cli/pull/1062) via [@zhsj](https://github.com/zhsj) * Fixed description of subcommand to be more consistent in [urfave/cli/pull/1054](https://github.com/urfave/cli/pull/1054) via [@itchyny](https://github.com/itchyny) -* Fixed possible runtime panic in slice parsing in [https://github.com/urfave/cli/pull/1049](https://github.com/urfave/cli/pull/1049) via [@saschagrunert](https://github.com/saschagrunert) +* Fixed possible runtime panic in slice parsing in [urfave/cli/pull/1049](https://github.com/urfave/cli/pull/1049) via [@saschagrunert](https://github.com/saschagrunert) * Fixed invalid man page header generation in [urfave/cli/pull/1041](https://github.com/urfave/cli/pull/1041) via [@saschagrunert](https://github.com/saschagrunert) ### Changed From 32dd20a85ba52ef14ed9dc1b83c978103cf59710 Mon Sep 17 00:00:00 2001 From: Aleksandr Kramarenko Date: Mon, 9 Mar 2020 13:34:05 +0300 Subject: [PATCH 009/111] Fix altsrc nil source flag --- altsrc/default_input_source.go | 6 ++++++ altsrc/json_source_context.go | 6 +++++- altsrc/toml_file_loader.go | 8 ++++++-- altsrc/yaml_file_loader.go | 8 ++++++-- 4 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 altsrc/default_input_source.go diff --git a/altsrc/default_input_source.go b/altsrc/default_input_source.go new file mode 100644 index 0000000..7fda719 --- /dev/null +++ b/altsrc/default_input_source.go @@ -0,0 +1,6 @@ +package altsrc + +// defaultInputSource creates a default InputSourceContext. +func defaultInputSource() (InputSourceContext, error) { + return &MapInputSource{file: "", valueMap: map[interface{}]interface{}{}}, nil +} diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index 7d70a2a..0215ced 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -17,7 +17,11 @@ import ( // by the given flag. func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) { return func(context *cli.Context) (InputSourceContext, error) { - return NewJSONSourceFromFile(context.String(flag)) + if context.IsSet(flag) { + return NewJSONSourceFromFile(context.String(flag)) + } else { + return defaultInputSource() + } } } diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index c679fdb..5c7914a 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -87,8 +87,12 @@ func NewTomlSourceFromFile(file string) (InputSourceContext, error) { // NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context. func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { return func(context *cli.Context) (InputSourceContext, error) { - filePath := context.String(flagFileName) - return NewTomlSourceFromFile(filePath) + if context.IsSet(flagFileName) { + filePath := context.String(flagFileName) + return NewTomlSourceFromFile(filePath) + } else { + return defaultInputSource() + } } } diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index cf2f8a5..7c5ea19 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -33,8 +33,12 @@ func NewYamlSourceFromFile(file string) (InputSourceContext, error) { // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { return func(context *cli.Context) (InputSourceContext, error) { - filePath := context.String(flagFileName) - return NewYamlSourceFromFile(filePath) + if context.IsSet(flagFileName) { + filePath := context.String(flagFileName) + return NewYamlSourceFromFile(filePath) + } else { + return defaultInputSource() + } } } From 8f64abd1768031453e83a0e082e2fafc40e2ab8b Mon Sep 17 00:00:00 2001 From: Aleksandr Kramarenko Date: Thu, 12 Mar 2020 21:20:13 +0300 Subject: [PATCH 010/111] Simplified --- altsrc/json_source_context.go | 4 ++-- altsrc/toml_file_loader.go | 4 ++-- altsrc/yaml_file_loader.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index 0215ced..6e7bf11 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -19,9 +19,9 @@ func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceCon return func(context *cli.Context) (InputSourceContext, error) { if context.IsSet(flag) { return NewJSONSourceFromFile(context.String(flag)) - } else { - return defaultInputSource() } + + return defaultInputSource() } } diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 5c7914a..9b86ee1 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -90,9 +90,9 @@ func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) ( if context.IsSet(flagFileName) { filePath := context.String(flagFileName) return NewTomlSourceFromFile(filePath) - } else { - return defaultInputSource() } + + return defaultInputSource() } } diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 7c5ea19..a49df56 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -36,9 +36,9 @@ func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) ( if context.IsSet(flagFileName) { filePath := context.String(flagFileName) return NewYamlSourceFromFile(filePath) - } else { - return defaultInputSource() } + + return defaultInputSource() } } From f7ef1e38d615f7ae1aa8c7436258ec209facc4cb Mon Sep 17 00:00:00 2001 From: litongxue Date: Wed, 18 Mar 2020 14:32:57 +0800 Subject: [PATCH 011/111] fix typo in v2 manual.md demo code --- docs/v2/manual.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index ce61075..71f5699 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -664,9 +664,7 @@ package main import ( "log" "os" - "strings" - - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func main() { @@ -1012,12 +1010,12 @@ import ( "fmt" "log" "os" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func main() { app := cli.NewApp() app.EnableBashCompletion = true - app.Commands = []cli.Command{ + app.Commands = []*cli.Command{ { Name: "add", Aliases: []string{"a"}, @@ -1040,7 +1038,7 @@ func main() { Name: "template", Aliases: []string{"t"}, Usage: "options for task templates", - Subcommands: []cli.Command{ + Subcommands: []*cli.Command{ { Name: "add", Usage: "add a new template", From 959cf9de8a980537fd3a82e52dc54d3182d06a84 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Tue, 24 Mar 2020 20:32:39 -0400 Subject: [PATCH 012/111] Update docs and tests around cli.Exit Some of the docs were lacking or incomplete concerning how to properly use cli.Exit, cli.HandleExitCoder, and the ExitErrHandler field on cli.App. This change aims to clarify the usage of those pieces. I also noticed that we have two identical functions now: cli.Exit and cli.NewExitError. Since the latter seems to be more recent and less well documented, I went ahead and marked it as deprecated so that we can keep our public interface small. Also added a missing test case for behavior that's been around a while but was not documented or tested. Related: #1089 --- app.go | 5 +++-- errors.go | 28 +++++++++++++++++++--------- errors_test.go | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/app.go b/app.go index d0c8f84..eb68feb 100644 --- a/app.go +++ b/app.go @@ -76,8 +76,9 @@ type App struct { Writer io.Writer // ErrWriter writes error output ErrWriter io.Writer - // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to - // function as a default, so this is optional. + // ExitErrHandler processes any error encountered while running an App before + // it is returned to the caller. If no function is provided, HandleExitCoder + // is used as the default behavior. ExitErrHandler ExitErrHandlerFunc // Other custom info Metadata map[string]interface{} diff --git a/errors.go b/errors.go index 344b436..751ef9b 100644 --- a/errors.go +++ b/errors.go @@ -17,11 +17,10 @@ var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. type MultiError interface { error - // Errors returns a copy of the errors slice Errors() []error } -// NewMultiError creates a new MultiError. Pass in one or more errors. +// newMultiError creates a new MultiError. Pass in one or more errors. func newMultiError(err ...error) MultiError { ret := multiError(err) return &ret @@ -65,13 +64,20 @@ type exitError struct { message interface{} } -// NewExitError makes a new *exitError +// NewExitError calls Exit to create a new ExitCoder. +// +// Deprecated: This function is a duplicate of Exit and will eventually be removed. func NewExitError(message interface{}, exitCode int) ExitCoder { return Exit(message, exitCode) } -// Exit wraps a message and exit code into an ExitCoder suitable for handling by -// HandleExitCoder +// Exit wraps a message and exit code into an error, which by default is +// handled with a call to os.Exit during default error handling. +// +// This is the simplest way to trigger a non-zero exit code for an App without +// having to call os.Exit manually. During testing, this behavior can be avoided +// by overiding the ExitErrHandler function on an App or the package-global +// OsExiter function. func Exit(message interface{}, exitCode int) ExitCoder { return &exitError{ message: message, @@ -87,10 +93,14 @@ func (ee *exitError) ExitCode() int { return ee.exitCode } -// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if -// so prints the error to stderr (if it is non-empty) and calls OsExiter with the -// given exit code. If the given error is a MultiError, then this func is -// called on all members of the Errors slice and calls OsExiter with the last exit code. +// HandleExitCoder handles errors implementing ExitCoder by printing their +// message and calling OsExiter with the given exit code. +// +// If the given error instead implements MultiError, each error will be checked +// for the ExitCoder interface, and OsExiter will be called with the last exit +// code found, or exit code 1 if no ExitCoder is found. +// +// This function is the default error-handling behavior for an App. func HandleExitCoder(err error) { if err == nil { return diff --git a/errors_test.go b/errors_test.go index 9ed3be3..d0b1b4f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -67,6 +67,26 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { expect(t, called, true) } +func TestHandleExitCoder_MultiErrorWithoutExitCoder(t *testing.T) { + exitCode := 0 + called := false + + OsExiter = func(rc int) { + if !called { + exitCode = rc + called = true + } + } + + defer func() { OsExiter = fakeOsExiter }() + + err := newMultiError(errors.New("wowsa"), errors.New("egad")) + HandleExitCoder(err) + + expect(t, exitCode, 1) + expect(t, called, true) +} + // make a stub to not import pkg/errors type ErrorWithFormat struct { error From 710df05119e25b836a2602efa439d22105a3d9d8 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sun, 29 Mar 2020 11:02:05 -0400 Subject: [PATCH 013/111] Add a v1 => v2 migration guide --- docs/v2/manual.md | 160 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 71f5699..2244f0c 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1424,6 +1424,166 @@ In this example the flag could be used like this : Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) +### Converting from v1 to v2 + +v2 has a number of breaking changes but converting is straightforward: +make the changes documented below then resolve any compiler errors. +Typically this is sufficient. + +If you find any issues not covered by this document, please post a +comment on [Issue 921](https://github.com/urfave/cli/issues/921) or +consider sending a PR. + +#### Flags before args + +In v2 flags must come before args. This is more POSIX-compliant. You +may need to update scripts, user documentation, etc. + +This will work: + +``` +cli hello --shout rick +``` + +This will not: + +``` +cli hello rick --shout +``` + +#### Import string changed + +* OLD: `import "github.com/urfave/cli"` +* NEW: `import "github.com/urfave/cli/v2"` + +Check each file for this and make the change. + +Shell command to find them all: `fgrep -rl github.com/urfave/cli *` + +#### Flag aliases are done differently. + +Change `Name: "foo, f` to "Name: "foo", Aliases: []string{"f"}` + +OLD: + +``` +cli.StringFlag{ + Name: "config, cfg" +} +``` + +NEW: + +``` +cli.StringFlag{ + Name: "config", + Aliases: []string{"cfg"}, +} +``` + +Sadly v2 doesn't warn you if a comma is in the name. + +#### Commands are now lists of pointers + +Occurrences of `[]Command` have been changed to `[]*Command`. + +What this means to you: + +Look for `[]cli.Command{}` and change it to `[]*cli.Command{}` + +Example: + +* OLD: `var commands = []cli.Command{}` +* NEW: `var commands = []*cli.Command{}` + +Compiler messages you might see: + +` +commands/commands.go:56:30: cannot convert commands (type []cli.Command) to type cli.CommandsByName +commands/commands.go:57:15: cannot use commands (type []cli.Command) as type []*cli.Command in assignment +` + +#### Lists of commands should be pointers + +If you are building up a list of commands, the individual items should +now be pointers. + +* OLD: `cli.Command{` +* NEW: `&cli.Command{` + +Compiler messages you might see: + +` +./commands.go:32:34: cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to +` + +#### cli.Flag changed + +`cli.Flag` is now a list of pointers. + +What this means to you: + +If you make a list of flags, add a `&` in front of each +item. cli.BoolFlag, cli.StringFlag, etc. + +* OLD: +` + app.Flags = []cli.Flag{ + cli.BoolFlag{ +` + +* NEW: +``` + app.Flags = []cli.Flag{ + &cli.BoolFlag{ +``` + +Compiler messages you might see: + +``` + cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver) +``` + +#### Appending Commands + +Appending to a list of commands needs to be changed since the list is +now pointers. + +* OLD: `commands = append(commands, *c)` +* NEW: `commands = append(commands, c)` + +Compiler messages you might see: + +` +commands/commands.go:28:19: cannot use c (type *cli.Command) as type cli.Command in append +` + +#### Actions returns errors + +A command's `Action:` now returns an `error`. + +* OLD: `Action: func(c *cli.Context) {` +* NEW: `Action: func(c *cli.Context) error {` + +Compiler messages you might see: + +``` +./commands.go:35:2: cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value +``` + +#### Everything else + +Compile the code and worth through any errors it finds. Most should +relate to issues listed above. + +Once it compiles, test the command. Make sure `-h` displays help +properly, then try various flags and subcommands. + +If you find anything not covered by this document please let us know +by submitting a comment on +[Issue 921](https://github.com/urfave/cli/issues/921) +so that others can benefit. + ### Full API Example **Notice**: This is a contrived (functioning) example meant strictly for API From 7a4368def7b62906b7f54ed9e30b4dbf800ada07 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sun, 29 Mar 2020 11:11:03 -0400 Subject: [PATCH 014/111] fixup! --- docs/v2/manual.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 2244f0c..d623277 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -34,6 +34,7 @@ cli v2 manual * [Version Flag](#version-flag) + [Customization](#customization-2) * [Timestamp Flag](#timestamp-flag) + * [Migration Guide: v1 to v2](#migration-guide-v1-to-v2) * [Full API Example](#full-api-example) @@ -1424,15 +1425,16 @@ In this example the flag could be used like this : Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) -### Converting from v1 to v2 +### Migration Guide: v1 to v2 -v2 has a number of breaking changes but converting is straightforward: -make the changes documented below then resolve any compiler errors. -Typically this is sufficient. +v2 has a number of breaking changes but converting is relatively +straightforward: make the changes documented below then resolve any +compiler errors. We hope this will be sufficient for most typical +users. If you find any issues not covered by this document, please post a comment on [Issue 921](https://github.com/urfave/cli/issues/921) or -consider sending a PR. +consider sending a PR to help improve this guide. #### Flags before args @@ -1498,10 +1500,10 @@ Example: Compiler messages you might see: -` +``` commands/commands.go:56:30: cannot convert commands (type []cli.Command) to type cli.CommandsByName commands/commands.go:57:15: cannot use commands (type []cli.Command) as type []*cli.Command in assignment -` +``` #### Lists of commands should be pointers @@ -1513,9 +1515,9 @@ now be pointers. Compiler messages you might see: -` +``` ./commands.go:32:34: cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to -` +``` #### cli.Flag changed @@ -1527,10 +1529,10 @@ If you make a list of flags, add a `&` in front of each item. cli.BoolFlag, cli.StringFlag, etc. * OLD: -` +``` app.Flags = []cli.Flag{ cli.BoolFlag{ -` +``` * NEW: ``` @@ -1554,9 +1556,9 @@ now pointers. Compiler messages you might see: -` +``` commands/commands.go:28:19: cannot use c (type *cli.Command) as type cli.Command in append -` +``` #### Actions returns errors From 228f1c9c58f2c615c81883cd5c98f683c04115b1 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sun, 29 Mar 2020 11:15:13 -0400 Subject: [PATCH 015/111] fixup! --- docs/v2/manual.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index d623277..8ad0e4e 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1575,13 +1575,14 @@ Compiler messages you might see: #### Everything else -Compile the code and worth through any errors it finds. Most should +Compile the code and work through any errors. Most should relate to issues listed above. -Once it compiles, test the command. Make sure `-h` displays help -properly, then try various flags and subcommands. +Once it compiles, test the command. Review the output of `-h` or any +help messages to verify they match the intended flags and subcommands. +Then test the program itself. -If you find anything not covered by this document please let us know +If you find any issues not covered by this document please let us know by submitting a comment on [Issue 921](https://github.com/urfave/cli/issues/921) so that others can benefit. From 6b3b21728cf96c7de5367fe254eb03d632f59f10 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sun, 29 Mar 2020 11:24:23 -0400 Subject: [PATCH 016/111] Fix TOC --- docs/v2/manual.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 8ad0e4e..ea886d6 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -35,6 +35,15 @@ cli v2 manual + [Customization](#customization-2) * [Timestamp Flag](#timestamp-flag) * [Migration Guide: v1 to v2](#migration-guide-v1-to-v2) + [Flags before args](#flags-before-args) + [Import string changed](#import-string-changed) + [Flag aliases are done differently.](#flag-aliases-are-done-differently) + [Commands are now lists of pointers](#commands-are-now-lists-of-pointers) + [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) + [cli.Flag changed](#cliflag-changed) + [Appending Commands](#appending-commands) + [Actions returns errors](#actions-returns-errors) + [Everything else](#everything-else) * [Full API Example](#full-api-example) From 9faa4e4097b194ba49bf3cd6f236ee96a34c867b Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sun, 29 Mar 2020 11:26:02 -0400 Subject: [PATCH 017/111] Fix TOC --- docs/v2/manual.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index ea886d6..099d950 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -35,15 +35,15 @@ cli v2 manual + [Customization](#customization-2) * [Timestamp Flag](#timestamp-flag) * [Migration Guide: v1 to v2](#migration-guide-v1-to-v2) - [Flags before args](#flags-before-args) - [Import string changed](#import-string-changed) - [Flag aliases are done differently.](#flag-aliases-are-done-differently) - [Commands are now lists of pointers](#commands-are-now-lists-of-pointers) - [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) - [cli.Flag changed](#cliflag-changed) - [Appending Commands](#appending-commands) - [Actions returns errors](#actions-returns-errors) - [Everything else](#everything-else) + + [Flags before args](#flags-before-args) + + [Import string changed](#import-string-changed) + + [Flag aliases are done differently.](#flag-aliases-are-done-differently) + + [Commands are now lists of pointers](#commands-are-now-lists-of-pointers) + + [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) + + [cli.Flag changed](#cliflag-changed) + + [Appending Commands](#appending-commands) + + [Actions returns errors](#actions-returns-errors) + + [Everything else](#everything-else) * [Full API Example](#full-api-example) From caa97b585e0fa58bdbc15ef290527a99327f4ef7 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sun, 29 Mar 2020 11:29:34 -0400 Subject: [PATCH 018/111] Fix TOC --- docs/v2/manual.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 099d950..cec89eb 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1475,16 +1475,14 @@ Shell command to find them all: `fgrep -rl github.com/urfave/cli *` Change `Name: "foo, f` to "Name: "foo", Aliases: []string{"f"}` -OLD: - +* OLD: ``` cli.StringFlag{ Name: "config, cfg" } ``` -NEW: - +* NEW: ``` cli.StringFlag{ Name: "config", From dc7ae10a155b9d9d52a9eea61edabe175bd31caa Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Mon, 30 Mar 2020 10:57:50 -0400 Subject: [PATCH 019/111] fixup! --- docs/v2/manual.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index cec89eb..eab76d8 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1438,7 +1438,7 @@ Side note: quotes may be necessary around the date depending on your layout (if v2 has a number of breaking changes but converting is relatively straightforward: make the changes documented below then resolve any -compiler errors. We hope this will be sufficient for most typical +compiler errors. We hope this will be sufficient for most typical users. If you find any issues not covered by this document, please post a @@ -1447,7 +1447,7 @@ consider sending a PR to help improve this guide. #### Flags before args -In v2 flags must come before args. This is more POSIX-compliant. You +In v2 flags must come before args. This is more POSIX-compliant. You may need to update scripts, user documentation, etc. This will work: @@ -1473,17 +1473,17 @@ Shell command to find them all: `fgrep -rl github.com/urfave/cli *` #### Flag aliases are done differently. -Change `Name: "foo, f` to "Name: "foo", Aliases: []string{"f"}` +Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}` * OLD: -``` +```go cli.StringFlag{ Name: "config, cfg" } ``` * NEW: -``` +```go cli.StringFlag{ Name: "config", Aliases: []string{"cfg"}, @@ -1508,8 +1508,8 @@ Example: Compiler messages you might see: ``` -commands/commands.go:56:30: cannot convert commands (type []cli.Command) to type cli.CommandsByName -commands/commands.go:57:15: cannot use commands (type []cli.Command) as type []*cli.Command in assignment +cannot convert commands (type []cli.Command) to type cli.CommandsByName +cannot use commands (type []cli.Command) as type []*cli.Command in assignment ``` #### Lists of commands should be pointers @@ -1523,7 +1523,7 @@ now be pointers. Compiler messages you might see: ``` -./commands.go:32:34: cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to +cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to ``` #### cli.Flag changed @@ -1536,13 +1536,13 @@ If you make a list of flags, add a `&` in front of each item. cli.BoolFlag, cli.StringFlag, etc. * OLD: -``` +```go app.Flags = []cli.Flag{ cli.BoolFlag{ ``` * NEW: -``` +```go app.Flags = []cli.Flag{ &cli.BoolFlag{ ``` @@ -1564,7 +1564,7 @@ now pointers. Compiler messages you might see: ``` -commands/commands.go:28:19: cannot use c (type *cli.Command) as type cli.Command in append +cannot use c (type *cli.Command) as type cli.Command in append ``` #### Actions returns errors @@ -1577,7 +1577,7 @@ A command's `Action:` now returns an `error`. Compiler messages you might see: ``` -./commands.go:35:2: cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value +cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value ``` #### Everything else From 3020108d13f78910a098c9cda67b392ee1a99776 Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Mon, 30 Mar 2020 16:27:15 -0700 Subject: [PATCH 020/111] Update pull_request_template.md --- .github/pull_request_template.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4a30f8f..4e76725 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -29,10 +29,11 @@ _(REQUIRED)_ _(REQUIRED)_ ## Special notes for your reviewer: @@ -66,4 +67,4 @@ _(REQUIRED)_ ```release-note -``` \ No newline at end of file +``` From 22b6dbaad3915cb2c090ef08ea91d4682c7f65d1 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 31 Mar 2020 10:43:36 -0400 Subject: [PATCH 021/111] Set App.ErrWriter in App.Setup() - Defaults to os.Stderr - Remove the App.errWriter() function since it is no longer needed --- app.go | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/app.go b/app.go index eb68feb..9baca25 100644 --- a/app.go +++ b/app.go @@ -163,6 +163,10 @@ func (a *App) Setup() { a.Writer = os.Stdout } + if a.ErrWriter == nil { + a.ErrWriter = os.Stderr + } + var newCommands []*Command for _, c := range a.Commands { @@ -196,10 +200,6 @@ func (a *App) Setup() { if a.Metadata == nil { a.Metadata = make(map[string]interface{}) } - - if a.Writer == nil { - a.Writer = os.Stdout - } } func (a *App) newFlagSet() (*flag.FlagSet, error) { @@ -326,7 +326,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { // code in the cli.ExitCoder func (a *App) RunAndExitOnError() { if err := a.Run(os.Args); err != nil { - _, _ = fmt.Fprintln(a.errWriter(), err) + _, _ = fmt.Fprintln(a.ErrWriter, err) OsExiter(1) } } @@ -480,15 +480,6 @@ func (a *App) VisibleFlags() []Flag { return visibleFlags(a.Flags) } -func (a *App) errWriter() io.Writer { - // When the app ErrWriter is nil use the package level one. - if a.ErrWriter == nil { - return ErrWriter - } - - return a.ErrWriter -} - func (a *App) appendFlag(fl Flag) { if !hasFlag(a.Flags, fl) { a.Flags = append(a.Flags, fl) From 28789a9775ad43d2137ab162346476e027fce5d7 Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Tue, 31 Mar 2020 15:00:40 -0700 Subject: [PATCH 022/111] Update CHANGELOG.md --- docs/CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 850f049..16655b1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -90,6 +90,12 @@ The V2 changes were all shipped in [urfave/cli/pull/892](https://github.com/urfa View [unreleased 1.22.X] series changes. +## [1.22.4] - 2020-02-25 + +### Fixed + +- Fixed a panic with flag completion in [urfave/cli/pull/1101](https://github.com/urfave/cli/pull/1101) via [@unRob](https://github.com/unRob), [@VirrageS](https://github.com/VirrageS) + ## [1.22.3] - 2020-02-25 ### Fixed @@ -584,7 +590,8 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. [2.1.0]: https://github.com/urfave/cli/compare/v2.0.0...v2.1.0 [2.0.0]: https://github.com/urfave/cli/compare/v1.22.2...v2.0.0 -[unreleased 1.22.X]: https://github.com/urfave/cli/compare/v1.22.3...v1 +[unreleased 1.22.X]: https://github.com/urfave/cli/compare/v1.22.4...v1 +[1.22.4]: https://github.com/urfave/cli/compare/v1.22.3...v1.22.4 [1.22.3]: https://github.com/urfave/cli/compare/v1.22.2...v1.22.3 [1.22.2]: https://github.com/urfave/cli/compare/v1.22.1...v1.22.2 [1.22.1]: https://github.com/urfave/cli/compare/v1.22.0...v1.22.1 From f41aa2567f0f5434b9f3e179630fddc4ca990052 Mon Sep 17 00:00:00 2001 From: "lynn [they]" Date: Tue, 31 Mar 2020 15:01:22 -0700 Subject: [PATCH 023/111] Update CHANGELOG.md --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 16655b1..e957d94 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -90,7 +90,7 @@ The V2 changes were all shipped in [urfave/cli/pull/892](https://github.com/urfa View [unreleased 1.22.X] series changes. -## [1.22.4] - 2020-02-25 +## [1.22.4] - 2020-03-31 ### Fixed From 84c98fac3efcfcc289b9046400d470c602c22b56 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Tue, 31 Mar 2020 18:53:45 -0400 Subject: [PATCH 024/111] Move migration to a separate file.Move migration to a separate file --- docs/migrate-v1-to-v2.md | 175 +++++++++++++++++++++++++++++++++++++++ docs/v2/manual.md | 72 +++++++--------- 2 files changed, 206 insertions(+), 41 deletions(-) create mode 100644 docs/migrate-v1-to-v2.md diff --git a/docs/migrate-v1-to-v2.md b/docs/migrate-v1-to-v2.md new file mode 100644 index 0000000..4bb676c --- /dev/null +++ b/docs/migrate-v1-to-v2.md @@ -0,0 +1,175 @@ +Migration Guide: v1 to v2 +=== + + + + * [Flags before args](#flags-before-args) + * [Import string changed](#import-string-changed) + * [Flag aliases are done differently.](#flag-aliases-are-done-differently) + * [Commands are now lists of pointers](#commands-are-now-lists-of-pointers) + * [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) + * [cli.Flag changed](#cliflag-changed) + * [Appending Commands](#appending-commands) + * [Actions returns errors](#actions-returns-errors) + * [Everything else](#everything-else) + * [Full API Example](#full-api-example) + + + +v2 has a number of breaking changes but converting is relatively +straightforward: make the changes documented below then resolve any +compiler errors. We hope this will be sufficient for most typical +users. + +If you find any issues not covered by this document, please post a +comment on [Issue 921](https://github.com/urfave/cli/issues/921) or +consider sending a PR to help improve this guide. + +# Flags before args + +In v2 flags must come before args. This is more POSIX-compliant. You +may need to update scripts, user documentation, etc. + +This will work: + +``` +cli hello --shout rick +``` + +This will not: + +``` +cli hello rick --shout +``` + +# Import string changed + +* OLD: `import "github.com/urfave/cli"` +* NEW: `import "github.com/urfave/cli/v2"` + +Check each file for this and make the change. + +Shell command to find them all: `fgrep -rl github.com/urfave/cli *` + +# Flag aliases are done differently. + +Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}` + +* OLD: +```go +cli.StringFlag{ + Name: "config, cfg" +} +``` + +* NEW: +```go +cli.StringFlag{ + Name: "config", + Aliases: []string{"cfg"}, +} +``` + +Sadly v2 doesn't warn you if a comma is in the name. + +# Commands are now lists of pointers + +Occurrences of `[]Command` have been changed to `[]*Command`. + +What this means to you: + +Look for `[]cli.Command{}` and change it to `[]*cli.Command{}` + +Example: + +* OLD: `var commands = []cli.Command{}` +* NEW: `var commands = []*cli.Command{}` + +Compiler messages you might see: + +``` +cannot convert commands (type []cli.Command) to type cli.CommandsByName +cannot use commands (type []cli.Command) as type []*cli.Command in assignment +``` + +# Lists of commands should be pointers + +If you are building up a list of commands, the individual items should +now be pointers. + +* OLD: `cli.Command{` +* NEW: `&cli.Command{` + +Compiler messages you might see: + +``` +cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to +``` + +# cli.Flag changed + +`cli.Flag` is now a list of pointers. + +What this means to you: + +If you make a list of flags, add a `&` in front of each +item. cli.BoolFlag, cli.StringFlag, etc. + +* OLD: +```go + app.Flags = []cli.Flag{ + cli.BoolFlag{ +``` + +* NEW: +```go + app.Flags = []cli.Flag{ + &cli.BoolFlag{ +``` + +Compiler messages you might see: + +``` + cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver) +``` + +# Appending Commands + +Appending to a list of commands needs to be changed since the list is +now pointers. + +* OLD: `commands = append(commands, *c)` +* NEW: `commands = append(commands, c)` + +Compiler messages you might see: + +``` +cannot use c (type *cli.Command) as type cli.Command in append +``` + +# Actions returns errors + +A command's `Action:` now returns an `error`. + +* OLD: `Action: func(c *cli.Context) {` +* NEW: `Action: func(c *cli.Context) error {` + +Compiler messages you might see: + +``` +cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value +``` + +# Everything else + +Compile the code and work through any errors. Most should +relate to issues listed above. + +Once it compiles, test the command. Review the output of `-h` or any +help messages to verify they match the intended flags and subcommands. +Then test the program itself. + +If you find any issues not covered by this document please let us know +by submitting a comment on +[Issue 921](https://github.com/urfave/cli/issues/921) +so that others can benefit. diff --git a/docs/v2/manual.md b/docs/v2/manual.md index eab76d8..2244f0c 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -34,16 +34,6 @@ cli v2 manual * [Version Flag](#version-flag) + [Customization](#customization-2) * [Timestamp Flag](#timestamp-flag) - * [Migration Guide: v1 to v2](#migration-guide-v1-to-v2) - + [Flags before args](#flags-before-args) - + [Import string changed](#import-string-changed) - + [Flag aliases are done differently.](#flag-aliases-are-done-differently) - + [Commands are now lists of pointers](#commands-are-now-lists-of-pointers) - + [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) - + [cli.Flag changed](#cliflag-changed) - + [Appending Commands](#appending-commands) - + [Actions returns errors](#actions-returns-errors) - + [Everything else](#everything-else) * [Full API Example](#full-api-example) @@ -1434,20 +1424,19 @@ In this example the flag could be used like this : Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) -### Migration Guide: v1 to v2 +### Converting from v1 to v2 -v2 has a number of breaking changes but converting is relatively -straightforward: make the changes documented below then resolve any -compiler errors. We hope this will be sufficient for most typical -users. +v2 has a number of breaking changes but converting is straightforward: +make the changes documented below then resolve any compiler errors. +Typically this is sufficient. If you find any issues not covered by this document, please post a comment on [Issue 921](https://github.com/urfave/cli/issues/921) or -consider sending a PR to help improve this guide. +consider sending a PR. #### Flags before args -In v2 flags must come before args. This is more POSIX-compliant. You +In v2 flags must come before args. This is more POSIX-compliant. You may need to update scripts, user documentation, etc. This will work: @@ -1473,17 +1462,19 @@ Shell command to find them all: `fgrep -rl github.com/urfave/cli *` #### Flag aliases are done differently. -Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}` +Change `Name: "foo, f` to "Name: "foo", Aliases: []string{"f"}` -* OLD: -```go +OLD: + +``` cli.StringFlag{ Name: "config, cfg" } ``` -* NEW: -```go +NEW: + +``` cli.StringFlag{ Name: "config", Aliases: []string{"cfg"}, @@ -1507,10 +1498,10 @@ Example: Compiler messages you might see: -``` -cannot convert commands (type []cli.Command) to type cli.CommandsByName -cannot use commands (type []cli.Command) as type []*cli.Command in assignment -``` +` +commands/commands.go:56:30: cannot convert commands (type []cli.Command) to type cli.CommandsByName +commands/commands.go:57:15: cannot use commands (type []cli.Command) as type []*cli.Command in assignment +` #### Lists of commands should be pointers @@ -1522,9 +1513,9 @@ now be pointers. Compiler messages you might see: -``` -cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to -``` +` +./commands.go:32:34: cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to +` #### cli.Flag changed @@ -1536,13 +1527,13 @@ If you make a list of flags, add a `&` in front of each item. cli.BoolFlag, cli.StringFlag, etc. * OLD: -```go +` app.Flags = []cli.Flag{ cli.BoolFlag{ -``` +` * NEW: -```go +``` app.Flags = []cli.Flag{ &cli.BoolFlag{ ``` @@ -1563,9 +1554,9 @@ now pointers. Compiler messages you might see: -``` -cannot use c (type *cli.Command) as type cli.Command in append -``` +` +commands/commands.go:28:19: cannot use c (type *cli.Command) as type cli.Command in append +` #### Actions returns errors @@ -1577,19 +1568,18 @@ A command's `Action:` now returns an `error`. Compiler messages you might see: ``` -cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value +./commands.go:35:2: cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value ``` #### Everything else -Compile the code and work through any errors. Most should +Compile the code and worth through any errors it finds. Most should relate to issues listed above. -Once it compiles, test the command. Review the output of `-h` or any -help messages to verify they match the intended flags and subcommands. -Then test the program itself. +Once it compiles, test the command. Make sure `-h` displays help +properly, then try various flags and subcommands. -If you find any issues not covered by this document please let us know +If you find anything not covered by this document please let us know by submitting a comment on [Issue 921](https://github.com/urfave/cli/issues/921) so that others can benefit. From be370eb48562419efe420919b7ac1ac3f918d338 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Tue, 31 Mar 2020 18:58:39 -0400 Subject: [PATCH 025/111] Re-order items to be more logical. --- docs/migrate-v1-to-v2.md | 99 ++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/docs/migrate-v1-to-v2.md b/docs/migrate-v1-to-v2.md index 4bb676c..8d56a39 100644 --- a/docs/migrate-v1-to-v2.md +++ b/docs/migrate-v1-to-v2.md @@ -1,6 +1,16 @@ Migration Guide: v1 to v2 === + +v2 has a number of breaking changes but converting is relatively +straightforward: make the changes documented below then resolve any +compiler errors. We hope this will be sufficient for most typical +users. + +If you find any issues not covered by this document, please post a +comment on [Issue 921](https://github.com/urfave/cli/issues/921) or +consider sending a PR to help improve this guide. + * [Flags before args](#flags-before-args) @@ -16,15 +26,6 @@ Migration Guide: v1 to v2 -v2 has a number of breaking changes but converting is relatively -straightforward: make the changes documented below then resolve any -compiler errors. We hope this will be sufficient for most typical -users. - -If you find any issues not covered by this document, please post a -comment on [Issue 921](https://github.com/urfave/cli/issues/921) or -consider sending a PR to help improve this guide. - # Flags before args In v2 flags must come before args. This is more POSIX-compliant. You @@ -72,6 +73,46 @@ cli.StringFlag{ Sadly v2 doesn't warn you if a comma is in the name. +# Actions returns errors + +A command's `Action:` now returns an `error`. + +* OLD: `Action: func(c *cli.Context) {` +* NEW: `Action: func(c *cli.Context) error {` + +Compiler messages you might see: + +``` +cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value +``` + +# cli.Flag changed + +`cli.Flag` is now a list of pointers. + +What this means to you: + +If you make a list of flags, add a `&` in front of each +item. cli.BoolFlag, cli.StringFlag, etc. + +* OLD: +```go + app.Flags = []cli.Flag{ + cli.BoolFlag{ +``` + +* NEW: +```go + app.Flags = []cli.Flag{ + &cli.BoolFlag{ +``` + +Compiler messages you might see: + +``` + cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver) +``` + # Commands are now lists of pointers Occurrences of `[]Command` have been changed to `[]*Command`. @@ -106,33 +147,6 @@ Compiler messages you might see: cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to ``` -# cli.Flag changed - -`cli.Flag` is now a list of pointers. - -What this means to you: - -If you make a list of flags, add a `&` in front of each -item. cli.BoolFlag, cli.StringFlag, etc. - -* OLD: -```go - app.Flags = []cli.Flag{ - cli.BoolFlag{ -``` - -* NEW: -```go - app.Flags = []cli.Flag{ - &cli.BoolFlag{ -``` - -Compiler messages you might see: - -``` - cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver) -``` - # Appending Commands Appending to a list of commands needs to be changed since the list is @@ -147,19 +161,6 @@ Compiler messages you might see: cannot use c (type *cli.Command) as type cli.Command in append ``` -# Actions returns errors - -A command's `Action:` now returns an `error`. - -* OLD: `Action: func(c *cli.Context) {` -* NEW: `Action: func(c *cli.Context) error {` - -Compiler messages you might see: - -``` -cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value -``` - # Everything else Compile the code and work through any errors. Most should From 200fa41ab392d869f062fa05c36c0fb952fc6fee Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Tue, 31 Mar 2020 18:59:44 -0400 Subject: [PATCH 026/111] Revert v2/manual.md --- docs/v2/manual.md | 160 ---------------------------------------------- 1 file changed, 160 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 2244f0c..71f5699 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1424,166 +1424,6 @@ In this example the flag could be used like this : Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) -### Converting from v1 to v2 - -v2 has a number of breaking changes but converting is straightforward: -make the changes documented below then resolve any compiler errors. -Typically this is sufficient. - -If you find any issues not covered by this document, please post a -comment on [Issue 921](https://github.com/urfave/cli/issues/921) or -consider sending a PR. - -#### Flags before args - -In v2 flags must come before args. This is more POSIX-compliant. You -may need to update scripts, user documentation, etc. - -This will work: - -``` -cli hello --shout rick -``` - -This will not: - -``` -cli hello rick --shout -``` - -#### Import string changed - -* OLD: `import "github.com/urfave/cli"` -* NEW: `import "github.com/urfave/cli/v2"` - -Check each file for this and make the change. - -Shell command to find them all: `fgrep -rl github.com/urfave/cli *` - -#### Flag aliases are done differently. - -Change `Name: "foo, f` to "Name: "foo", Aliases: []string{"f"}` - -OLD: - -``` -cli.StringFlag{ - Name: "config, cfg" -} -``` - -NEW: - -``` -cli.StringFlag{ - Name: "config", - Aliases: []string{"cfg"}, -} -``` - -Sadly v2 doesn't warn you if a comma is in the name. - -#### Commands are now lists of pointers - -Occurrences of `[]Command` have been changed to `[]*Command`. - -What this means to you: - -Look for `[]cli.Command{}` and change it to `[]*cli.Command{}` - -Example: - -* OLD: `var commands = []cli.Command{}` -* NEW: `var commands = []*cli.Command{}` - -Compiler messages you might see: - -` -commands/commands.go:56:30: cannot convert commands (type []cli.Command) to type cli.CommandsByName -commands/commands.go:57:15: cannot use commands (type []cli.Command) as type []*cli.Command in assignment -` - -#### Lists of commands should be pointers - -If you are building up a list of commands, the individual items should -now be pointers. - -* OLD: `cli.Command{` -* NEW: `&cli.Command{` - -Compiler messages you might see: - -` -./commands.go:32:34: cannot use cli.Command literal (type cli.Command) as type *cli.Command in argument to -` - -#### cli.Flag changed - -`cli.Flag` is now a list of pointers. - -What this means to you: - -If you make a list of flags, add a `&` in front of each -item. cli.BoolFlag, cli.StringFlag, etc. - -* OLD: -` - app.Flags = []cli.Flag{ - cli.BoolFlag{ -` - -* NEW: -``` - app.Flags = []cli.Flag{ - &cli.BoolFlag{ -``` - -Compiler messages you might see: - -``` - cli.StringFlag does not implement cli.Flag (Apply method has pointer receiver) -``` - -#### Appending Commands - -Appending to a list of commands needs to be changed since the list is -now pointers. - -* OLD: `commands = append(commands, *c)` -* NEW: `commands = append(commands, c)` - -Compiler messages you might see: - -` -commands/commands.go:28:19: cannot use c (type *cli.Command) as type cli.Command in append -` - -#### Actions returns errors - -A command's `Action:` now returns an `error`. - -* OLD: `Action: func(c *cli.Context) {` -* NEW: `Action: func(c *cli.Context) error {` - -Compiler messages you might see: - -``` -./commands.go:35:2: cannot use func literal (type func(*cli.Context)) as type cli.ActionFunc in field value -``` - -#### Everything else - -Compile the code and worth through any errors it finds. Most should -relate to issues listed above. - -Once it compiles, test the command. Make sure `-h` displays help -properly, then try various flags and subcommands. - -If you find anything not covered by this document please let us know -by submitting a comment on -[Issue 921](https://github.com/urfave/cli/issues/921) -so that others can benefit. - ### Full API Example **Notice**: This is a contrived (functioning) example meant strictly for API From fed64f3ad73405c4aa65c8da640d1a8c43667a49 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 1 Apr 2020 09:17:59 -0400 Subject: [PATCH 027/111] add tests --- app.go | 1 + app_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app.go b/app.go index 9baca25..4122596 100644 --- a/app.go +++ b/app.go @@ -118,6 +118,7 @@ func NewApp() *App { Action: helpCommand.Action, Compiled: compileTime(), Writer: os.Stdout, + ErrWriter: os.Stderr, } } diff --git a/app_test.go b/app_test.go index 61c55d8..6c95faa 100644 --- a/app_test.go +++ b/app_test.go @@ -2152,3 +2152,34 @@ func newTestApp() *App { a.Writer = ioutil.Discard return a } + +func TestSetupInitializesBothWriters(t *testing.T) { + a := &App{} + + a.Setup() + + if a.ErrWriter != os.Stderr { + t.Errorf("expected a.ErrWriter to be os.Stderr") + } + + if a.Writer != os.Stdout { + t.Errorf("expected a.Writer to be os.Stdout") + } +} + +func TestSetupInitializesOnlyNilWriters(t *testing.T) { + wr := &bytes.Buffer{} + a := &App{ + ErrWriter: wr, + } + + a.Setup() + + if a.ErrWriter != wr { + t.Errorf("expected a.ErrWriter to be a *bytes.Buffer instance") + } + + if a.Writer != os.Stdout { + t.Errorf("expected a.Writer to be os.Stdout") + } +} From 9fd9cd1117d00d2d637cb0140b05cc3a47be685c Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sat, 4 Apr 2020 06:09:14 -0400 Subject: [PATCH 028/111] Link to migration guide --- README.md | 4 ++++ docs/v1/manual.md | 8 ++++++++ docs/v2/manual.md | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/README.md b/README.md index ae9d19b..408668b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ Usage documentation exists for each major version. Don't know what version you'r - `v2` - [./docs/v2/manual.md](./docs/v2/manual.md) - `v1` - [./docs/v1/manual.md](./docs/v1/manual.md) +Guides for migrating to newer versions: + +- `v1-to-v2` - [./docs/migrate-v1-to-v2.md](./docs/migrate-v1-to-v2.md) + ## Installation Using this package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html). diff --git a/docs/v1/manual.md b/docs/v1/manual.md index 51c5b72..05ea370 100644 --- a/docs/v1/manual.md +++ b/docs/v1/manual.md @@ -27,6 +27,7 @@ cli v1 manual * [Version Flag](#version-flag) + [Customization](#customization-2) + [Full API Example](#full-api-example) + * [Migrating to V2](#migrating-to-v2) @@ -1476,3 +1477,10 @@ func wopAction(c *cli.Context) error { return nil } ``` + +## Migrating to V2 + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 71f5699..750590c 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -3,6 +3,7 @@ cli v2 manual +- [Migrating From v1](#migrating-from-v1) - [Getting Started](#getting-started) - [Examples](#examples) * [Arguments](#arguments) @@ -38,6 +39,13 @@ cli v2 manual +## Migrating From Older Releases + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). + ## Getting Started One of the philosophies behind cli is that an API should be playful and full of From 2f5b961b06c4215ac9085f793eacd224a788a5f3 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Sat, 4 Apr 2020 06:13:16 -0400 Subject: [PATCH 029/111] Link to migration guide --- docs/v2/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 750590c..55008b4 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -3,7 +3,7 @@ cli v2 manual -- [Migrating From v1](#migrating-from-v1) +- [Migrating From Older Releases](#migrating-from-older-releases) - [Getting Started](#getting-started) - [Examples](#examples) * [Arguments](#arguments) From 9eb96e77a165f0236f2c802395d56ee88e381ea6 Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Tue, 14 Apr 2020 07:36:33 +0200 Subject: [PATCH 030/111] Don't assume a fixed windows drive letter in tests Prior to this change, this test was failing for me on a new windows machine, with mismatching drive letters: helpers_test.go:20: (C:/Users/Foo Bar/cli/altsrc/flag_test.go:195) Expected C:\path\to\source\hello (type string) - Got D:\path\to\source\hello (type string) Rather than hard-coding the drive letter in the expected path, we can expect the filepath.Abs'ed version. This should fix part of #1105. --- altsrc/flag_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 59292e4..2048331 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "path/filepath" "runtime" "strings" "testing" @@ -190,7 +191,13 @@ func TestPathApplyInputSourceMethodSet(t *testing.T) { expected := "/path/to/source/hello" if runtime.GOOS == "windows" { - expected = `D:\path\to\source\hello` + var err error + // Prepend the corresponding drive letter (or UNC path?), and change + // to windows-style path: + expected, err = filepath.Abs(expected) + if err != nil { + t.Fatal(err) + } } expect(t, expected, c.String("test")) } From 8d907b532953a230f009bb801bcdff5bdded6ed4 Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Tue, 14 Apr 2020 07:51:30 +0200 Subject: [PATCH 031/111] Save the temp dir before clearing environment variables In some windows setups, os.TempDir() will return something like "C:\WINDOWS" if none of the expected environment variables (TMP, TEMP, USERPROFILE) are set, and then functions that try to create temporary files or directories will fail since this is not writable. Several tests in flag_test.go clear the environment and then set a small number of specific environment variables for the test, triggering the following error in TestFlagFromFile when I run `go test ./...` on a new windows machine: flag_test.go:1667: open C:\WINDOWS\urfave_cli_test851254863: Access is denied. To work around this, we can check the temp directory before calling os.Clearenv() and use that directory explcitly in functions that take the temp directory. This fixes part of #1105. --- flag_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flag_test.go b/flag_test.go index 02f5d7d..8e4a9e8 100644 --- a/flag_test.go +++ b/flag_test.go @@ -14,6 +14,8 @@ import ( "time" ) +var osTempDir = os.TempDir() + var boolFlagTests = []struct { name string expected string @@ -1662,7 +1664,7 @@ func TestFlagFromFile(t *testing.T) { os.Clearenv() os.Setenv("APP_FOO", "123") - temp, err := ioutil.TempFile("", "urfave_cli_test") + temp, err := ioutil.TempFile(osTempDir, "urfave_cli_test") if err != nil { t.Error(err) return From 4853dd3144a3463765613578ecc332be450f68ad Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Wed, 15 Apr 2020 08:32:07 +0200 Subject: [PATCH 032/111] Reset the environment variables after each test that clears them Instead of just resetting the temp dir, let's reset all environment variables. Environment variables are a pain for testing. A more reliable solution would be to refactor all functions that read from the environment to take the environment as an explicit argument, and then provide a consistent environment during testing. But that would be a significantly larger change than this one. Related to #1105. --- context_test.go | 2 ++ flag_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/context_test.go b/context_test.go index ccf8846..f302caf 100644 --- a/context_test.go +++ b/context_test.go @@ -183,6 +183,7 @@ func TestContext_IsSet_fromEnv(t *testing.T) { unparsableIsSet, uIsSet bool ) + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") _ = os.Setenv("APP_PASSWORD", "") @@ -539,6 +540,7 @@ func TestCheckRequiredFlags(t *testing.T) { t.Run(test.testCase, func(t *testing.T) { // setup if test.envVarInput[0] != "" { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv(test.envVarInput[0], test.envVarInput[1]) } diff --git a/flag_test.go b/flag_test.go index 8e4a9e8..431eb03 100644 --- a/flag_test.go +++ b/flag_test.go @@ -14,8 +14,6 @@ import ( "time" ) -var osTempDir = os.TempDir() - var boolFlagTests = []struct { name string expected string @@ -24,6 +22,13 @@ var boolFlagTests = []struct { {"h", "-h\t(default: false)"}, } +func resetEnv(env []string) { + for _, e := range env { + fields := strings.SplitN(e, "=", 2) + os.Setenv(fields[0], fields[1]) + } +} + func TestBoolFlagHelpOutput(t *testing.T) { for _, test := range boolFlagTests { fl := &BoolFlag{Name: test.name} @@ -116,6 +121,7 @@ func TestFlagsFromEnv(t *testing.T) { } for i, test := range flagTests { + defer resetEnv(os.Environ()) os.Clearenv() envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) _ = os.Setenv(envVarSlice.Index(0).String(), test.input) @@ -185,7 +191,7 @@ func TestStringFlagDefaultText(t *testing.T) { } func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { - + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_FOO", "derp") @@ -265,6 +271,7 @@ func TestPathFlagHelpOutput(t *testing.T) { } func TestPathFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_PATH", "/path/to/file") for _, test := range pathFlagTests { @@ -352,6 +359,7 @@ func TestStringSliceFlagHelpOutput(t *testing.T) { } func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_QWWX", "11,4") @@ -398,6 +406,7 @@ func TestIntFlagHelpOutput(t *testing.T) { } func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_BAR", "2") @@ -446,6 +455,7 @@ func TestInt64FlagHelpOutput(t *testing.T) { } func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_BAR", "2") @@ -483,6 +493,7 @@ func TestUintFlagHelpOutput(t *testing.T) { } func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_BAR", "2") @@ -520,6 +531,7 @@ func TestUint64FlagHelpOutput(t *testing.T) { } func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_BAR", "2") @@ -557,6 +569,7 @@ func TestDurationFlagHelpOutput(t *testing.T) { } func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_BAR", "2h3m6s") @@ -608,6 +621,7 @@ func TestIntSliceFlagHelpOutput(t *testing.T) { } func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_SMURF", "42,3") @@ -658,6 +672,7 @@ func TestInt64SliceFlagHelpOutput(t *testing.T) { } func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_SMURF", "42,17179869184") @@ -695,6 +710,7 @@ func TestFloat64FlagHelpOutput(t *testing.T) { } func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_BAZ", "99.4") @@ -747,6 +763,7 @@ func TestFloat64SliceFlagHelpOutput(t *testing.T) { } func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_SMURF", "0.1234,-10.5") for _, test := range float64SliceFlagTests { @@ -784,6 +801,7 @@ func TestGenericFlagHelpOutput(t *testing.T) { } func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_ZAP", "3") @@ -846,6 +864,7 @@ func TestParseDestinationString(t *testing.T) { } func TestParseMultiStringFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_COUNT", "20") _ = (&App{ @@ -865,6 +884,7 @@ func TestParseMultiStringFromEnv(t *testing.T) { } func TestParseMultiStringFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_COUNT", "20") _ = (&App{ @@ -939,6 +959,7 @@ func TestParseMultiStringSliceWithDestination(t *testing.T) { } func TestParseMultiStringSliceWithDestinationAndEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -978,6 +999,7 @@ func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { } func TestParseMultiStringSliceFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -998,6 +1020,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) { } func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -1018,6 +1041,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) { } func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -1038,6 +1062,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { } func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -1058,6 +1083,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) { } func TestParseMultiStringSliceFromEnvWithDestination(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -1114,6 +1140,7 @@ func TestParseDestinationInt(t *testing.T) { } func TestParseMultiIntFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_TIMEOUT_SECONDS", "10") _ = (&App{ @@ -1133,6 +1160,7 @@ func TestParseMultiIntFromEnv(t *testing.T) { } func TestParseMultiIntFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_TIMEOUT_SECONDS", "10") _ = (&App{ @@ -1203,6 +1231,7 @@ func TestParseMultiIntSliceWithDefaultsUnset(t *testing.T) { } func TestParseMultiIntSliceFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -1223,6 +1252,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) { } func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -1243,6 +1273,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) { } func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,40") @@ -1280,6 +1311,7 @@ func TestParseMultiInt64Slice(t *testing.T) { } func TestParseMultiInt64SliceFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,17179869184") @@ -1300,6 +1332,7 @@ func TestParseMultiInt64SliceFromEnv(t *testing.T) { } func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "20,30,17179869184") @@ -1355,6 +1388,7 @@ func TestParseDestinationFloat64(t *testing.T) { } func TestParseMultiFloat64FromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") _ = (&App{ @@ -1374,6 +1408,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) { } func TestParseMultiFloat64FromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5") _ = (&App{ @@ -1393,6 +1428,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) { } func TestParseMultiFloat64SliceFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "0.1,-10.5") @@ -1413,6 +1449,7 @@ func TestParseMultiFloat64SliceFromEnv(t *testing.T) { } func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_INTERVALS", "0.1234,-10.5") @@ -1492,6 +1529,7 @@ func TestParseDestinationBool(t *testing.T) { } func TestParseMultiBoolFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_DEBUG", "1") _ = (&App{ @@ -1511,6 +1549,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) { } func TestParseMultiBoolFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_DEBUG", "1") _ = (&App{ @@ -1541,6 +1580,7 @@ func TestParseBoolFromEnv(t *testing.T) { } for _, test := range boolFlagTests { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("DEBUG", test.input) _ = (&App{ @@ -1617,6 +1657,7 @@ func TestParseGeneric(t *testing.T) { } func TestParseGenericFromEnv(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_SERVE", "20,30") _ = (&App{ @@ -1641,6 +1682,7 @@ func TestParseGenericFromEnv(t *testing.T) { } func TestParseGenericFromEnvCascade(t *testing.T) { + defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("APP_FOO", "99,2000") _ = (&App{ @@ -1661,14 +1703,16 @@ func TestParseGenericFromEnvCascade(t *testing.T) { } func TestFlagFromFile(t *testing.T) { - os.Clearenv() - os.Setenv("APP_FOO", "123") - - temp, err := ioutil.TempFile(osTempDir, "urfave_cli_test") + temp, err := ioutil.TempFile("", "urfave_cli_test") if err != nil { t.Error(err) return } + + defer resetEnv(os.Environ()) + os.Clearenv() + os.Setenv("APP_FOO", "123") + _, _ = io.WriteString(temp, "abc") _ = temp.Close() defer func() { From 33744eb004bfc4ec76049bba7f7b264ff6191817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alo=C3=AFs=20Micard?= Date: Mon, 27 Apr 2020 19:49:29 +0200 Subject: [PATCH 033/111] Harmonize BeforeError handling --- app.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/app.go b/app.go index 4122596..59bf68d 100644 --- a/app.go +++ b/app.go @@ -292,8 +292,6 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - _, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) - _ = ShowAppHelp(context) a.handleExitCoder(context, beforeErr) err = beforeErr return err From c261e4dbd3c72bc9baec4072b5f866d3349a6b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alo=C3=AFs=20Micard?= Date: Tue, 28 Apr 2020 08:03:26 +0200 Subject: [PATCH 034/111] Harmonize BeforeError usage in Command#Run --- command.go | 1 - 1 file changed, 1 deletion(-) diff --git a/command.go b/command.go index 95840f3..daa58c8 100644 --- a/command.go +++ b/command.go @@ -150,7 +150,6 @@ func (c *Command) Run(ctx *Context) (err error) { if c.Before != nil { err = c.Before(context) if err != nil { - _ = ShowCommandHelp(context, c.Name) context.App.handleExitCoder(context, err) return err } From 196b222a8b53ad8abf6463a1edf59f03b72a5f50 Mon Sep 17 00:00:00 2001 From: Sergey Goroshko Date: Fri, 1 May 2020 23:06:09 +0300 Subject: [PATCH 035/111] fix #1121(StringSliceFlag set default value into destination) --- flag_string_slice.go | 8 ++++++++ flag_test.go | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/flag_string_slice.go b/flag_string_slice.go index ac363bf..1a221d0 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -116,6 +116,14 @@ func (f *StringSliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { + + if f.Destination != nil { + if f.Value != nil { + f.Destination.slice = make([]string, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + } + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &StringSlice{} destination := f.Value diff --git a/flag_test.go b/flag_test.go index 431eb03..7dbfbdc 100644 --- a/flag_test.go +++ b/flag_test.go @@ -386,6 +386,18 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } +func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []string{"UA", "US"} + + fl := StringSliceFlag{Name: "country", Value: NewStringSlice(defValue...), Destination: NewStringSlice("CA")} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + var intFlagTests = []struct { name string expected string From 710c8f71c46f4d1d3c54cc554c7479d16c6c0a76 Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Sat, 2 May 2020 19:45:17 +0200 Subject: [PATCH 036/111] test(context): Added regression test for requiredFlagsError This adds a test verifying that the requiredFlagsError does contain the long option of the missing flag, instead of the short option and a space, which was the old behaviour. Signed-off-by: Ole Petter (cherry picked from commit f842187ebb32ee7d5109783d02fe9902b68ee54e) --- context_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/context_test.go b/context_test.go index f302caf..2692b2a 100644 --- a/context_test.go +++ b/context_test.go @@ -534,6 +534,14 @@ func TestCheckRequiredFlags(t *testing.T) { }, parseInput: []string{"-n", "asd", "-n", "qwe"}, }, + { + testCase: "required_flag_with_short_alias_not_printed_on_error", + expectedAnError: true, + expectedErrorContents: []string{"Required flag \"names\" not set"}, + flags: []Flag{ + StringSliceFlag{Name: "names, n", Required: true}, + }, + }, } for _, test := range tdata { From 48392103ce8be0b28d0e9f24660dd71d223e0cf8 Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Sun, 3 May 2020 11:08:30 +0200 Subject: [PATCH 037/111] test(context): Change the StringSliceFlag to a pointer StringSliceFlag needs to be a pointer, and not a struct. Also formatted the test. See: https://github.com/urfave/cli/pull/1126 for a description of the regression tested for. Signed-off-by: Ole Petter --- context_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/context_test.go b/context_test.go index 2692b2a..61d6268 100644 --- a/context_test.go +++ b/context_test.go @@ -535,11 +535,11 @@ func TestCheckRequiredFlags(t *testing.T) { parseInput: []string{"-n", "asd", "-n", "qwe"}, }, { - testCase: "required_flag_with_short_alias_not_printed_on_error", - expectedAnError: true, + testCase: "required_flag_with_short_alias_not_printed_on_error", + expectedAnError: true, expectedErrorContents: []string{"Required flag \"names\" not set"}, flags: []Flag{ - StringSliceFlag{Name: "names, n", Required: true}, + &StringSliceFlag{Name: "names, n", Required: true}, }, }, } From b27d899434f4d1f0e2dd748e3f661d8ec230f3f2 Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Sun, 3 May 2020 12:10:39 +0200 Subject: [PATCH 038/111] fix(zsh_autocomplete): List files on tab with no completion options This will simply list the files in the current directory if there are no auto-completion options available. With this change, the zsh auto-completion will align with the functionality in the bash auto-completion file provided. New functionality: https://asciinema.org/a/EAYRIEVGTGNSS2gCGwSJ4Zw1i Old functionality: https://asciinema.org/a/BfOZz4BHUGwjXMFptbmDHZocH Signed-off-by: Ole Petter --- autocomplete/zsh_autocomplete | 2 ++ 1 file changed, 2 insertions(+) diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index e2c7921..cf39c88 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -13,6 +13,8 @@ _cli_zsh_autocomplete() { if [[ "${opts[1]}" != "" ]]; then _describe 'values' opts + else + _files fi return From 42aad27f4c29006885b0961bd4513cb52d6c6b4f Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Tue, 12 May 2020 08:11:29 -0400 Subject: [PATCH 039/111] Document EnvVar change --- docs/migrate-v1-to-v2.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/migrate-v1-to-v2.md b/docs/migrate-v1-to-v2.md index 8d56a39..74f68f5 100644 --- a/docs/migrate-v1-to-v2.md +++ b/docs/migrate-v1-to-v2.md @@ -16,6 +16,7 @@ consider sending a PR to help improve this guide. * [Flags before args](#flags-before-args) * [Import string changed](#import-string-changed) * [Flag aliases are done differently.](#flag-aliases-are-done-differently) + * [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars) * [Commands are now lists of pointers](#commands-are-now-lists-of-pointers) * [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) * [cli.Flag changed](#cliflag-changed) @@ -59,19 +60,38 @@ Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}` * OLD: ```go cli.StringFlag{ - Name: "config, cfg" + Name: "config, cfg" } ``` * NEW: ```go cli.StringFlag{ - Name: "config", - Aliases: []string{"cfg"}, + Name: "config", + Aliases: []string{"cfg"}, } ``` Sadly v2 doesn't warn you if a comma is in the name. +(https://github.com/urfave/cli/issues/1103) + +# EnvVar is now a list (EnvVars) + +Change `EnvVar: "XXXXX"` to `EnvVars: []string{"XXXXX"}` (plural). + +* OLD: +```go +cli.StringFlag{ + EnvVar: "APP_LANG" +} +``` + +* NEW: +```go +cli.StringFlag{ + EnvVars: []string{"APP_LANG"} +} +``` # Actions returns errors From b236763df53c5eca518081b3dad10dc44efb08d9 Mon Sep 17 00:00:00 2001 From: kirinnee Date: Mon, 18 May 2020 11:19:00 +0800 Subject: [PATCH 040/111] Added manual and script for PowerShell autocomplete --- autocomplete/powershell_autocomplete.ps1 | 9 +++++++++ docs/v2/manual.md | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 autocomplete/powershell_autocomplete.ps1 diff --git a/autocomplete/powershell_autocomplete.ps1 b/autocomplete/powershell_autocomplete.ps1 new file mode 100644 index 0000000..81812a6 --- /dev/null +++ b/autocomplete/powershell_autocomplete.ps1 @@ -0,0 +1,9 @@ +$fn = $($MyInvocation.MyCommand.Name) +$name = $fn -replace "(.*)\.ps1$", '$1' +Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { + param($commandName, $wordToComplete, $cursorPosition) + $other = "$wordToComplete --generate-bash-completion" + Invoke-Expression $other | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } \ No newline at end of file diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 55008b4..63d3bc3 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1222,6 +1222,23 @@ source path/to/autocomplete/zsh_autocomplete #### ZSH custom auto-complete example ![](/docs/v2/images/custom-zsh-autocomplete.gif) +#### PowerShell Support +Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1` +file included in this repo. + +Rename the script to `.ps1` and move it anywhere in your file system. +The location of script does not matter, only the file name of the script has to match +the your program's binary name. + +To activate it, enter `& path/to/autocomplete/.ps1` + +To persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`) +and add the line: +``` +& path/to/autocomplete/.ps1 +``` + + ### Generated Help Text The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked From 10fe017765ab472c4df96091f1527dcbc28d2a9b Mon Sep 17 00:00:00 2001 From: Sergey Goroshko Date: Sun, 31 May 2020 20:52:18 +0300 Subject: [PATCH 041/111] fix nested conditions --- flag_string_slice.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/flag_string_slice.go b/flag_string_slice.go index 1a221d0..74cf0a5 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -117,11 +117,10 @@ func (f *StringSliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { - if f.Destination != nil { - if f.Value != nil { - f.Destination.slice = make([]string, len(f.Value.slice)) - copy(f.Destination.slice, f.Value.slice) - } + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]string, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { From 9523a3481bf492de121be67a254086b0404d1ade Mon Sep 17 00:00:00 2001 From: kirinnee Date: Mon, 8 Jun 2020 10:54:27 +0800 Subject: [PATCH 042/111] Updated Docs Header in table of content for PowerShell support --- docs/v2/manual.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 63d3bc3..951384e 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -30,6 +30,7 @@ cli v2 manual + [ZSH Support](#zsh-support) + [ZSH default auto-complete example](#zsh-default-auto-complete-example) + [ZSH custom auto-complete example](#zsh-custom-auto-complete-example) + + [PowerShell Support](#powershell-support) * [Generated Help Text](#generated-help-text) + [Customization](#customization-1) * [Version Flag](#version-flag) From 5b263d0ace0b2be4c8c83730f1be254a7ca37e4c Mon Sep 17 00:00:00 2001 From: Tiago Matias Date: Mon, 15 Jun 2020 12:04:06 -0300 Subject: [PATCH 043/111] fix link to docs on v2 manual was pointing to v1 docs --- docs/v2/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 55008b4..15673de 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -307,7 +307,7 @@ func main() { } ``` -See full list of flags at http://godoc.org/github.com/urfave/cli +See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 #### Placeholder Values From 6d094981690fd1cff857a94bd1f3d169ecf2ca90 Mon Sep 17 00:00:00 2001 From: Irioth Date: Thu, 18 Jun 2020 00:24:04 +0300 Subject: [PATCH 044/111] hide version flag for subcommands --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index daa58c8..dda2f49 100644 --- a/command.go +++ b/command.go @@ -241,7 +241,7 @@ func (c *Command) startApp(ctx *Context) error { app.HideHelpCommand = c.HideHelpCommand app.Version = ctx.App.Version - app.HideVersion = ctx.App.HideVersion + app.HideVersion = true app.Compiled = ctx.App.Compiled app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter From 44371a2ac6c3bf1372ca23ff24d16038590c6c7c Mon Sep 17 00:00:00 2001 From: Irioth Date: Thu, 18 Jun 2020 00:46:15 +0300 Subject: [PATCH 045/111] test for version flag on commands --- command_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/command_test.go b/command_test.go index 765081e..c1d106d 100644 --- a/command_test.go +++ b/command_test.go @@ -377,3 +377,27 @@ func TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags(t *testing.T) { } } + +func TestCommand_NoVersionFlagOnCommands(t *testing.T) { + app := &App{ + Version: "some version", + Commands: []*Command{ + { + Name: "bar", + Usage: "this is for testing", + Subcommands: []*Command{{}}, // some subcommand + Action: func(c *Context) error { + for _, f := range c.App.VisibleFlags() { + if f == VersionFlag { + t.Fatalf("unexpected version flag") + } + } + return nil + }, + }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + expect(t, err, nil) +} From 1f3e0b52339258611596d10319d35d1e9f38bc55 Mon Sep 17 00:00:00 2001 From: Irioth Date: Thu, 18 Jun 2020 01:02:47 +0300 Subject: [PATCH 046/111] make test more general and stable --- command_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/command_test.go b/command_test.go index c1d106d..a3c3b84 100644 --- a/command_test.go +++ b/command_test.go @@ -386,11 +386,10 @@ func TestCommand_NoVersionFlagOnCommands(t *testing.T) { Name: "bar", Usage: "this is for testing", Subcommands: []*Command{{}}, // some subcommand + HideHelp: true, Action: func(c *Context) error { - for _, f := range c.App.VisibleFlags() { - if f == VersionFlag { - t.Fatalf("unexpected version flag") - } + if len(c.App.VisibleFlags()) != 0 { + t.Fatalf("unexpected flag on command") } return nil }, From ef2d047c454173cb2956531d8a45997edcf7fbb8 Mon Sep 17 00:00:00 2001 From: Irioth Date: Thu, 18 Jun 2020 09:34:54 +0300 Subject: [PATCH 047/111] added test for successfully used -v flag on command with subcommands --- command_test.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/command_test.go b/command_test.go index a3c3b84..6add442 100644 --- a/command_test.go +++ b/command_test.go @@ -389,7 +389,7 @@ func TestCommand_NoVersionFlagOnCommands(t *testing.T) { HideHelp: true, Action: func(c *Context) error { if len(c.App.VisibleFlags()) != 0 { - t.Fatalf("unexpected flag on command") + t.Fatal("unexpected flag on command") } return nil }, @@ -400,3 +400,25 @@ func TestCommand_NoVersionFlagOnCommands(t *testing.T) { err := app.Run([]string{"foo", "bar"}) expect(t, err, nil) } + +func TestCommand_CanAddVFlagOnCommands(t *testing.T) { + app := &App{ + Version: "some version", + Writer: ioutil.Discard, + Commands: []*Command{ + { + Name: "bar", + Usage: "this is for testing", + Subcommands: []*Command{{}}, // some subcommand + Flags: []Flag{ + &BoolFlag{ + Name: "v", + }, + }, + }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + expect(t, err, nil) +} From 5bb54ace578d17a134feb806f22163e7064ede87 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Mon, 22 Jun 2020 17:17:47 -0700 Subject: [PATCH 048/111] fish.go: support PathFlag.TakesFile [#1156] --- fish.go | 4 ++++ fish_test.go | 4 ++++ testdata/expected-fish-full.fish | 1 + 3 files changed, 9 insertions(+) diff --git a/fish.go b/fish.go index 67122c9..588e070 100644 --- a/fish.go +++ b/fish.go @@ -171,6 +171,10 @@ func fishAddFileFlag(flag Flag, completion *strings.Builder) { if f.TakesFile { return } + case *PathFlag: + if f.TakesFile { + return + } } completion.WriteString(" -f") } diff --git a/fish_test.go b/fish_test.go index a4c1871..4ca8c47 100644 --- a/fish_test.go +++ b/fish_test.go @@ -7,6 +7,10 @@ import ( func TestFishCompletion(t *testing.T) { // Given app := testApp() + app.Flags = append(app.Flags, &PathFlag{ + Name: "logfile", + TakesFile: true, + }) // When res, err := app.ToFishCompletion() diff --git a/testdata/expected-fish-full.fish b/testdata/expected-fish-full.fish index b18d51e..dc41e5a 100644 --- a/testdata/expected-fish-full.fish +++ b/testdata/expected-fish-full.fish @@ -12,6 +12,7 @@ end complete -c greet -n '__fish_greet_no_subcommand' -l socket -s s -r -d 'some \'usage\' text' complete -c greet -n '__fish_greet_no_subcommand' -f -l flag -s fl -s f -r complete -c greet -n '__fish_greet_no_subcommand' -f -l another-flag -s b -d 'another usage text' +complete -c greet -n '__fish_greet_no_subcommand' -l logfile -r complete -c greet -n '__fish_greet_no_subcommand' -f -l help -s h -d 'show help' complete -c greet -n '__fish_greet_no_subcommand' -f -l version -s v -d 'print the version' complete -c greet -n '__fish_seen_subcommand_from config c' -f -l help -s h -d 'show help' From dff8accf57cc82e47071d2dccd8176e19ed8ca2e Mon Sep 17 00:00:00 2001 From: Saulius Gurklys Date: Wed, 1 Jul 2020 23:20:25 +0300 Subject: [PATCH 049/111] Fix typo Fixed small typo. --- docs/v2/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 15673de..c39bfb9 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -661,7 +661,7 @@ func main() { You can make a flag required by setting the `Required` field to `true`. If a user does not provide a required flag, they will be shown an error message. -Take for example this app that reqiures the `lang` flag: +Take for example this app that requires the `lang` flag: ``` go diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 94febaf..2243c6d 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -627,7 +627,7 @@ given sources. Here is a more complete sample of a command using YAML support: ``` go From a7dc35be5b64bccdded4bb4e11400bf56c4c2944 Mon Sep 17 00:00:00 2001 From: Zach Wasserman Date: Wed, 27 Jan 2021 09:29:24 -0800 Subject: [PATCH 078/111] Link to godoc for v2 This is consistent with the default tag for the repository. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 408668b..b1cbb05 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ cli === -[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli/v2) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) [![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli) From c3ebeaee7b019d42f61e8bd347292d50d55a76e9 Mon Sep 17 00:00:00 2001 From: "lynn (they)" Date: Wed, 27 Jan 2021 10:37:40 -0800 Subject: [PATCH 079/111] Update stale.yml --- .github/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/stale.yml b/.github/stale.yml index a3282a2..0cc3007 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -14,6 +14,7 @@ onlyLabels: [] exemptLabels: - pinned - security + - "help wanted" - "kind/maintenance" # Set to true to ignore issues in a project (defaults to false) From 06e7bdec349457f1e05f8ef6b567b0214fdf8df6 Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Wed, 27 Jan 2021 18:41:52 +0000 Subject: [PATCH 080/111] add test for nil --- flag_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/flag_test.go b/flag_test.go index 073e74e..e00fd9f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -793,6 +793,21 @@ func TestInt64SliceFlag_SetFromParentContext(t *testing.T) { t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers")) } } +func TestInt64SliceFlag_ReturnNil(t *testing.T) { + fl := &Int64SliceFlag{} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []int64(nil) + if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers")) + } +} var float64FlagTests = []struct { name string From 797d5a8d46b2220c1571ae72f107631b27d28ae6 Mon Sep 17 00:00:00 2001 From: Zach Wasserman Date: Wed, 27 Jan 2021 16:10:42 -0800 Subject: [PATCH 081/111] Link directly to pkg.go.dev --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1cbb05..2b74f1f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ cli === -[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli/v2) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) [![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli) From ed2ee4bc4a8c56b64983e58e1916af40de019260 Mon Sep 17 00:00:00 2001 From: Nobuhiro MIKI Date: Mon, 25 Jan 2021 22:13:27 +0900 Subject: [PATCH 082/111] make the man page section selectable Signed-off-by: Nobuhiro MIKI --- docs.go | 19 ++++++++++++++----- docs_test.go | 28 ++++++++++++++++++++++++++++ template.go | 2 +- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/docs.go b/docs.go index dc16fc8..4c0e1f6 100644 --- a/docs.go +++ b/docs.go @@ -15,31 +15,39 @@ import ( // The function errors if either parsing or writing of the string fails. func (a *App) ToMarkdown() (string, error) { var w bytes.Buffer - if err := a.writeDocTemplate(&w); err != nil { + if err := a.writeDocTemplate(&w, 8); err != nil { return "", err } return w.String(), nil } -// ToMan creates a man page string for the `*App` +// ToMan creates a man page string with section number for the `*App` // The function errors if either parsing or writing of the string fails. -func (a *App) ToMan() (string, error) { +func (a *App) ToManWithSection(sectionNumber int) (string, error) { var w bytes.Buffer - if err := a.writeDocTemplate(&w); err != nil { + if err := a.writeDocTemplate(&w, sectionNumber); err != nil { return "", err } man := md2man.Render(w.Bytes()) return string(man), nil } +// ToMan creates a man page string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToMan() (string, error) { + man, err := a.ToManWithSection(8) + return man, err +} + type cliTemplate struct { App *App + SectionNum int Commands []string GlobalArgs []string SynopsisArgs []string } -func (a *App) writeDocTemplate(w io.Writer) error { +func (a *App) writeDocTemplate(w io.Writer, sectionNum int) error { const name = "cli" t, err := template.New(name).Parse(MarkdownDocTemplate) if err != nil { @@ -47,6 +55,7 @@ func (a *App) writeDocTemplate(w io.Writer) error { } return t.ExecuteTemplate(w, name, &cliTemplate{ App: a, + SectionNum: sectionNum, Commands: prepareCommands(a.Commands, 0), GlobalArgs: prepareArgsWithValues(a.VisibleFlags()), SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()), diff --git a/docs_test.go b/docs_test.go index 46e38dc..fac106f 100644 --- a/docs_test.go +++ b/docs_test.go @@ -2,6 +2,7 @@ package cli import ( "bytes" + "errors" "io/ioutil" "testing" ) @@ -147,3 +148,30 @@ func TestToMan(t *testing.T) { expect(t, err, nil) expectFileContent(t, "testdata/expected-doc-full.man", res) } + +func TestToManParseError(t *testing.T) { + // Given + app := testApp() + + // When + // temporarily change the global variable for testing + tmp := MarkdownDocTemplate + MarkdownDocTemplate = `{{ .App.Name` + _, err := app.ToMan() + MarkdownDocTemplate = tmp + + // Then + expect(t, err, errors.New(`template: cli:1: unclosed action`)) +} + +func TestToManWithSection(t *testing.T) { + // Given + app := testApp() + + // When + res, err := app.ToManWithSection(8) + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-full.man", res) +} diff --git a/template.go b/template.go index 31c03f8..317cc88 100644 --- a/template.go +++ b/template.go @@ -74,7 +74,7 @@ OPTIONS: {{end}}{{end}} ` -var MarkdownDocTemplate = `% {{ .App.Name }} 8 +var MarkdownDocTemplate = `% {{ .App.Name }} {{ .SectionNum }} # NAME From b8debb6845733f41dee438cabd8d11a7797f9ba9 Mon Sep 17 00:00:00 2001 From: Joe Gregorio Date: Sat, 5 Dec 2020 21:39:34 -0500 Subject: [PATCH 083/111] Fix Context.Value. Before this change the added test would crash on a nil pointer dereference because the original code would only look in the local fileSet and not across all the fileSets. --- context.go | 5 ++++- context_test.go | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 74ed519..65e0d1e 100644 --- a/context.go +++ b/context.go @@ -108,7 +108,10 @@ func (c *Context) Lineage() []*Context { // Value returns the value of the flag corresponding to `name` func (c *Context) Value(name string) interface{} { - return c.flagSet.Lookup(name).Value.(flag.Getter).Get() + if fs := lookupFlagSet(name, c); fs != nil { + return fs.Lookup(name).Value.(flag.Getter).Get() + } + return nil } // Args returns the command line arguments associated with the context. diff --git a/context_test.go b/context_test.go index 61d6268..335b071 100644 --- a/context_test.go +++ b/context_test.go @@ -136,6 +136,17 @@ func TestContext_Bool(t *testing.T) { expect(t, c.Bool("top-flag"), true) } +func TestContext_Value(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + parentSet := flag.NewFlagSet("test", 0) + parentSet.Int("top-flag", 13, "doc") + parentCtx := NewContext(nil, parentSet, nil) + c := NewContext(nil, set, parentCtx) + expect(t, c.Value("myflag"), 12) + expect(t, c.Value("top-flag"), 13) +} + func TestContext_Args(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Bool("myflag", false, "doc") From d56c85cef5482c3d42a9c1509063d7453636905d Mon Sep 17 00:00:00 2001 From: Joe Gregorio Date: Sat, 5 Dec 2020 21:59:56 -0500 Subject: [PATCH 084/111] Add test for the else path. --- context_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/context_test.go b/context_test.go index 335b071..35feefe 100644 --- a/context_test.go +++ b/context_test.go @@ -145,6 +145,7 @@ func TestContext_Value(t *testing.T) { c := NewContext(nil, set, parentCtx) expect(t, c.Value("myflag"), 12) expect(t, c.Value("top-flag"), 13) + expect(t, c.Value("unknown-flag"), nil) } func TestContext_Args(t *testing.T) { From 1985ecfdc099d0ef09d0a68eba3f848bcae5be9e Mon Sep 17 00:00:00 2001 From: Jason Travis Date: Fri, 29 Jan 2021 12:10:44 -0700 Subject: [PATCH 085/111] remove repeated comment --- app_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/app_test.go b/app_test.go index 651f0f4..7c38f60 100644 --- a/app_test.go +++ b/app_test.go @@ -315,7 +315,6 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() { } func ExampleApp_Run_bashComplete() { - // set args for examples sake // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} From c98b85d392f18d938a481adc6c933e43c79970ce Mon Sep 17 00:00:00 2001 From: AllyDale Date: Fri, 5 Feb 2021 15:16:50 +0800 Subject: [PATCH 086/111] bug fix #1235 : default value changes with parsed values on slice flags --- .gitignore | 2 ++ flag_float64_slice.go | 19 ++++++++++--- flag_int64_slice.go | 19 ++++++++++--- flag_int_slice.go | 19 ++++++++++--- flag_string_slice.go | 28 ++++++++++++------- flag_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 2d5e149..afdca41 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ vendor .idea internal/*/built-example coverage.txt + +*.exe diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 706ee6c..b625ca1 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -19,6 +19,16 @@ func NewFloat64Slice(defaults ...float64) *Float64Slice { return &Float64Slice{slice: append([]float64{}, defaults...)} } +// clone allocate a copy of self object +func (f *Float64Slice) clone() *Float64Slice { + n := &Float64Slice{ + slice: make([]float64, len(f.slice)), + hasBeenSet: f.hasBeenSet, + } + copy(n.slice, f.slice) + return n +} + // Set parses the value into a float64 and appends it to the list of values func (f *Float64Slice) Set(value string) error { if !f.hasBeenSet { @@ -133,11 +143,12 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { } } + if f.Value == nil { + f.Value = &Float64Slice{} + } + copyValue := f.Value.clone() for _, name := range f.Names() { - if f.Value == nil { - f.Value = &Float64Slice{} - } - set.Var(f.Value, name, f.Usage) + set.Var(copyValue, name, f.Usage) } return nil diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 2c9a15a..d2352c0 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -19,6 +19,16 @@ func NewInt64Slice(defaults ...int64) *Int64Slice { return &Int64Slice{slice: append([]int64{}, defaults...)} } +// clone allocate a copy of self object +func (i *Int64Slice) clone() *Int64Slice { + n := &Int64Slice{ + slice: make([]int64, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + // Set parses the value into an integer and appends it to the list of values func (i *Int64Slice) Set(value string) error { if !i.hasBeenSet { @@ -132,11 +142,12 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { f.HasBeenSet = true } + if f.Value == nil { + f.Value = &Int64Slice{} + } + copyValue := f.Value.clone() for _, name := range f.Names() { - if f.Value == nil { - f.Value = &Int64Slice{} - } - set.Var(f.Value, name, f.Usage) + set.Var(copyValue, name, f.Usage) } return nil diff --git a/flag_int_slice.go b/flag_int_slice.go index a73ca6b..018d56b 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -19,6 +19,16 @@ func NewIntSlice(defaults ...int) *IntSlice { return &IntSlice{slice: append([]int{}, defaults...)} } +// clone allocate a copy of self object +func (i *IntSlice) clone() *IntSlice { + n := &IntSlice{ + slice: make([]int, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + // TODO: Consistently have specific Set function for Int64 and Float64 ? // SetInt directly adds an integer to the list of values func (i *IntSlice) SetInt(value int) { @@ -143,11 +153,12 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { f.HasBeenSet = true } + if f.Value == nil { + f.Value = &IntSlice{} + } + copyValue := f.Value.clone() for _, name := range f.Names() { - if f.Value == nil { - f.Value = &IntSlice{} - } - set.Var(f.Value, name, f.Usage) + set.Var(copyValue, name, f.Usage) } return nil diff --git a/flag_string_slice.go b/flag_string_slice.go index 3549703..0b45ab5 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -18,6 +18,16 @@ func NewStringSlice(defaults ...string) *StringSlice { return &StringSlice{slice: append([]string{}, defaults...)} } +// clone allocate a copy of self object +func (s *StringSlice) clone() *StringSlice { + n := &StringSlice{ + slice: make([]string, len(s.slice)), + hasBeenSet: s.hasBeenSet, + } + copy(n.slice, s.slice) + return n +} + // Set appends the string value to the list of values func (s *StringSlice) Set(value string) error { if !s.hasBeenSet { @@ -144,17 +154,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { f.HasBeenSet = true } + if f.Value == nil { + f.Value = &StringSlice{} + } + setValue := f.Destination + if f.Destination == nil { + setValue = f.Value.clone() + } for _, name := range f.Names() { - if f.Value == nil { - f.Value = &StringSlice{} - } - - if f.Destination != nil { - set.Var(f.Destination, name, f.Usage) - continue - } - - set.Var(f.Value, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil diff --git a/flag_test.go b/flag_test.go index b3b0d7c..0f8e12a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1973,3 +1973,68 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) { err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"}) expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\"")) } + +type flagDefaultTestCase struct { + name string + flag Flag + toParse []string + expect string +} + +func TestFlagDefaultValue(t *testing.T) { + cases := []*flagDefaultTestCase{ + &flagDefaultTestCase{ + name: "stringSclice", + flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, + toParse: []string{"--flag", "parsed"}, + expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`, + }, + &flagDefaultTestCase{ + name: "float64Sclice", + flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, + toParse: []string{"--flag", "13.3"}, + expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`, + }, + &flagDefaultTestCase{ + name: "int64Sclice", + flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + }, + &flagDefaultTestCase{ + name: "intSclice", + flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + }, + &flagDefaultTestCase{ + name: "string", + flag: &StringFlag{Name: "flag", Value: "default"}, + toParse: []string{"--flag", "parsed"}, + expect: `--flag value (default: "default")`, + }, + &flagDefaultTestCase{ + name: "bool", + flag: &BoolFlag{Name: "flag", Value: true}, + toParse: []string{"--flag", "false"}, + expect: `--flag (default: true)`, + }, + &flagDefaultTestCase{ + name: "uint64", + flag: &Uint64Flag{Name: "flag", Value: 1}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1)`, + }, + } + for i, v := range cases { + set := flag.NewFlagSet("test", 0) + set.SetOutput(ioutil.Discard) + _ = v.flag.Apply(set) + if err := set.Parse(v.toParse); err != nil { + t.Error(err) + } + if got := v.flag.String(); got != v.expect { + t.Errorf("TestFlagDefaultValue %d %s\nexpect:%s\ngot:%s", i, v.name, v.expect, got) + } + } +} From f2bed637fda5a18bd7a0f5a670569ffbbd0fa2a5 Mon Sep 17 00:00:00 2001 From: David Bond Date: Sun, 7 Mar 2021 03:36:56 +0000 Subject: [PATCH 087/111] Add Destination field to TimestampFlag Adds a `Destination` field for the `TimestampFlag` type that allows you to specify a pointer to a `Timestamp` rather than having to grab the `Timestamp` from the `cli.Context` using the flag name. --- flag_test.go | 12 ++++++++++++ flag_timestamp.go | 10 ++++++++++ helpers_test.go | 11 ++--------- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/flag_test.go b/flag_test.go index b3b0d7c..1cefe82 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1973,3 +1973,15 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) { err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"}) expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\"")) } + +func TestTimestampFlagApply_WithDestination(t *testing.T) { + var destination Timestamp + expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") + fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: time.RFC3339, Destination: &destination} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"}) + expect(t, err, nil) + expect(t, *fl.Destination.timestamp, expectedResult) +} diff --git a/flag_timestamp.go b/flag_timestamp.go index 0382a6b..da95512 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -71,6 +71,7 @@ type TimestampFlag struct { Value *Timestamp DefaultText string HasBeenSet bool + Destination *Timestamp } // IsSet returns whether or not the flag has been set through env or file @@ -123,6 +124,10 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { } f.Value.SetLayout(f.Layout) + if f.Destination != nil { + f.Destination.SetLayout(f.Layout) + } + if val, ok := 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) @@ -131,6 +136,11 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { } for _, name := range f.Names() { + if f.Destination != nil { + set.Var(f.Destination, name, f.Usage) + continue + } + set.Var(f.Value, name, f.Usage) } return nil diff --git a/helpers_test.go b/helpers_test.go index 9217e89..9ecd8e1 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -3,24 +3,17 @@ package cli import ( "os" "reflect" - "runtime" - "strings" "testing" ) -var ( - wd, _ = os.Getwd() -) - func init() { _ = os.Setenv("CLI_TEMPLATE_REPANIC", "1") } func expect(t *testing.T, a interface{}, b interface{}) { - _, fn, line, _ := runtime.Caller(1) - fn = strings.Replace(fn, wd+"/", "", -1) + t.Helper() if !reflect.DeepEqual(a, b) { - t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } From 9433f216ffc698f8c750bdeb5c63a2c2a267d173 Mon Sep 17 00:00:00 2001 From: cpacifying Date: Mon, 15 Mar 2021 17:03:22 +0200 Subject: [PATCH 088/111] UsageText to be passed from Command to App in startApp --- command.go | 1 + 1 file changed, 1 insertion(+) diff --git a/command.go b/command.go index 3503a55..97c151b 100644 --- a/command.go +++ b/command.go @@ -227,6 +227,7 @@ func (c *Command) startApp(ctx *Context) error { } app.Usage = c.Usage + app.UsageText = c.UsageText app.Description = c.Description app.ArgsUsage = c.ArgsUsage From df595c0d85738225ddf21c251c9cddb9642203cb Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Thu, 25 Mar 2021 20:45:30 -0400 Subject: [PATCH 089/111] Fix(issue #631). Remove reflect calls for Hidden field --- flag.go | 11 +++++++++-- flag_bool.go | 5 +++++ flag_duration.go | 5 +++++ flag_float64.go | 5 +++++ flag_float64_slice.go | 5 +++++ flag_generic.go | 5 +++++ flag_int.go | 5 +++++ flag_int64.go | 5 +++++ flag_int64_slice.go | 5 +++++ flag_int_slice.go | 5 +++++ flag_path.go | 5 +++++ flag_string.go | 5 +++++ flag_string_slice.go | 5 +++++ flag_timestamp.go | 5 +++++ flag_uint.go | 5 +++++ flag_uint64.go | 5 +++++ 16 files changed, 84 insertions(+), 2 deletions(-) diff --git a/flag.go b/flag.go index aff8d5b..45a9e9e 100644 --- a/flag.go +++ b/flag.go @@ -118,6 +118,14 @@ type DocGenerationFlag interface { GetValue() string } +// VisibleFlag is an interface that allows to check if a flag is visible +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) @@ -133,8 +141,7 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { func visibleFlags(fl []Flag) []Flag { var visible []Flag for _, f := range fl { - field := flagValue(f).FieldByName("Hidden") - if !field.IsValid() || !field.Bool() { + if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() { visible = append(visible, f) } } diff --git a/flag_bool.go b/flag_bool.go index bc9ea35..fc829b0 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -58,6 +58,11 @@ func (f *BoolFlag) GetValue() string { return "" } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *BoolFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_duration.go b/flag_duration.go index 22a2e67..6c2055e 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -58,6 +58,11 @@ func (f *DurationFlag) GetValue() string { return f.Value.String() } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *DurationFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_float64.go b/flag_float64.go index 91c778c..d5c62f9 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -58,6 +58,11 @@ func (f *Float64Flag) GetValue() string { return fmt.Sprintf("%f", f.Value) } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64Flag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 706ee6c..b94c662 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -117,6 +117,11 @@ func (f *Float64SliceFlag) GetValue() string { return "" } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64SliceFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_generic.go b/flag_generic.go index b0c8ff4..769c017 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -66,6 +66,11 @@ func (f *GenericFlag) GetValue() string { return "" } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *GenericFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_int.go b/flag_int.go index ac39d4a..d0adfe6 100644 --- a/flag_int.go +++ b/flag_int.go @@ -58,6 +58,11 @@ func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_int64.go b/flag_int64.go index e099912..851cc36 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -58,6 +58,11 @@ func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64Flag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 2c9a15a..a4afac3 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -118,6 +118,11 @@ func (f *Int64SliceFlag) GetValue() string { return "" } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64SliceFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_int_slice.go b/flag_int_slice.go index a73ca6b..b26c42b 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -129,6 +129,11 @@ func (f *IntSliceFlag) GetValue() string { return "" } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntSliceFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_path.go b/flag_path.go index 8070dc4..2cc6ead 100644 --- a/flag_path.go +++ b/flag_path.go @@ -54,6 +54,11 @@ func (f *PathFlag) GetValue() string { return f.Value } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *PathFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_string.go b/flag_string.go index 400bb53..97c4707 100644 --- a/flag_string.go +++ b/flag_string.go @@ -55,6 +55,11 @@ func (f *StringFlag) GetValue() string { return f.Value } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_string_slice.go b/flag_string_slice.go index 3549703..4cc7ee4 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -114,6 +114,11 @@ func (f *StringSliceFlag) GetValue() string { return "" } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringSliceFlag) IsVisible() bool { + return !f.Hidden +} + // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { diff --git a/flag_timestamp.go b/flag_timestamp.go index 0382a6b..2198d10 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -113,6 +113,11 @@ func (f *TimestampFlag) GetValue() string { return "" } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *TimestampFlag) IsVisible() bool { + return !f.Hidden +} + // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { diff --git a/flag_uint.go b/flag_uint.go index 2e5e76b..9081e4a 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -52,6 +52,11 @@ func (f *UintFlag) GetUsage() string { return f.Usage } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *UintFlag) IsVisible() bool { + return !f.Hidden +} + // 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 { diff --git a/flag_uint64.go b/flag_uint64.go index 8fc3289..5505e57 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -52,6 +52,11 @@ func (f *Uint64Flag) GetUsage() string { return f.Usage } +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Uint64Flag) IsVisible() bool { + return !f.Hidden +} + // 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 { From 07e1fdf17d95dd733c368a955983c8d3bd574f73 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Apr 2021 07:57:41 -0400 Subject: [PATCH 090/111] Cleanup context.go --- app.go | 4 +- command.go | 2 +- context.go | 130 ++++++++++-------------------------------- context_test.go | 8 +-- errors.go | 22 +++++++ flag.go | 43 ++++++++++++++ flag_bool.go | 2 +- flag_duration.go | 2 +- flag_float64.go | 2 +- flag_float64_slice.go | 2 +- flag_generic.go | 2 +- flag_int.go | 2 +- flag_int64.go | 2 +- flag_int64_slice.go | 2 +- flag_int_slice.go | 2 +- flag_path.go | 2 +- flag_string.go | 2 +- flag_string_slice.go | 2 +- flag_timestamp.go | 2 +- flag_uint.go | 2 +- flag_uint64.go | 2 +- 21 files changed, 118 insertions(+), 121 deletions(-) diff --git a/app.go b/app.go index 2c97251..5c616e6 100644 --- a/app.go +++ b/app.go @@ -278,7 +278,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return nil } - cerr := checkRequiredFlags(a.Flags, context) + cerr := context.checkRequiredFlags(a.Flags) if cerr != nil { _ = ShowAppHelp(context) return cerr @@ -397,7 +397,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } - cerr := checkRequiredFlags(a.Flags, context) + cerr := context.checkRequiredFlags(a.Flags) if cerr != nil { _ = ShowSubcommandHelp(context) return cerr diff --git a/command.go b/command.go index 3503a55..9a6b877 100644 --- a/command.go +++ b/command.go @@ -127,7 +127,7 @@ func (c *Command) Run(ctx *Context) (err error) { return nil } - cerr := checkRequiredFlags(c.Flags, context) + cerr := context.checkRequiredFlags(c.Flags) if cerr != nil { _ = ShowCommandHelp(context, c.Name) return cerr diff --git a/context.go b/context.go index 65e0d1e..94cbb65 100644 --- a/context.go +++ b/context.go @@ -2,9 +2,7 @@ package cli import ( "context" - "errors" "flag" - "fmt" "strings" ) @@ -53,20 +51,18 @@ func (c *Context) Set(name, value string) error { // IsSet determines if the flag was actually set func (c *Context) IsSet(name string) bool { - if fs := lookupFlagSet(name, c); fs != nil { - if fs := lookupFlagSet(name, c); fs != nil { - isSet := false - fs.Visit(func(f *flag.Flag) { - if f.Name == name { - isSet = true - } - }) - if isSet { - return true + if fs := c.lookupFlagSet(name); fs != nil { + isSet := false + fs.Visit(func(f *flag.Flag) { + if f.Name == name { + isSet = true } + }) + if isSet { + return true } - f := lookupFlag(name, c) + f := c.lookupFlag(name) if f == nil { return false } @@ -108,7 +104,7 @@ func (c *Context) Lineage() []*Context { // Value returns the value of the flag corresponding to `name` func (c *Context) Value(name string) interface{} { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return fs.Lookup(name).Value.(flag.Getter).Get() } return nil @@ -125,7 +121,7 @@ func (c *Context) NArg() int { return c.Args().Len() } -func lookupFlag(name string, ctx *Context) Flag { +func (ctx *Context) lookupFlag(name string) Flag { for _, c := range ctx.Lineage() { if c.Command == nil { continue @@ -153,7 +149,7 @@ func lookupFlag(name string, ctx *Context) Flag { return nil } -func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { +func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet { for _, c := range ctx.Lineage() { if f := c.flagSet.Lookup(name); f != nil { return c.flagSet @@ -163,89 +159,7 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { return nil } -func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { - switch ff.Value.(type) { - case Serializer: - _ = set.Set(name, ff.Value.(Serializer).Serialize()) - default: - _ = set.Set(name, ff.Value.String()) - } -} - -func normalizeFlags(flags []Flag, set *flag.FlagSet) error { - visited := make(map[string]bool) - set.Visit(func(f *flag.Flag) { - visited[f.Name] = true - }) - for _, f := range flags { - parts := f.Names() - if len(parts) == 1 { - continue - } - var ff *flag.Flag - for _, name := range parts { - name = strings.Trim(name, " ") - if visited[name] { - if ff != nil { - return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) - } - ff = set.Lookup(name) - } - } - if ff == nil { - continue - } - for _, name := range parts { - name = strings.Trim(name, " ") - if !visited[name] { - copyFlag(name, ff, set) - } - } - } - return nil -} - -func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { - return func(f *flag.Flag) { - nameParts := strings.Split(f.Name, ",") - name := strings.TrimSpace(nameParts[0]) - - for _, part := range nameParts { - part = strings.TrimSpace(part) - if len(part) > len(name) { - name = part - } - } - - if name != "" { - *names = append(*names, name) - } - } -} - -type requiredFlagsErr interface { - error - getMissingFlags() []string -} - -type errRequiredFlags struct { - missingFlags []string -} - -func (e *errRequiredFlags) Error() string { - numberOfMissingFlags := len(e.missingFlags) - if numberOfMissingFlags == 1 { - return fmt.Sprintf("Required flag %q not set", e.missingFlags[0]) - } - joinedMissingFlags := strings.Join(e.missingFlags, ", ") - return fmt.Sprintf("Required flags %q not set", joinedMissingFlags) -} - -func (e *errRequiredFlags) getMissingFlags() []string { - return e.missingFlags -} - -func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { +func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { var missingFlags []string for _, f := range flags { if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { @@ -274,3 +188,21 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { return nil } + +func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { + return func(f *flag.Flag) { + nameParts := strings.Split(f.Name, ",") + name := strings.TrimSpace(nameParts[0]) + + for _, part := range nameParts { + part = strings.TrimSpace(part) + if len(part) > len(name) { + name = part + } + } + + if name != "" { + *names = append(*names, name) + } + } +} diff --git a/context_test.go b/context_test.go index 35feefe..b37876c 100644 --- a/context_test.go +++ b/context_test.go @@ -316,13 +316,13 @@ func TestContext_lookupFlagSet(t *testing.T) { _ = set.Parse([]string{"--local-flag"}) _ = parentSet.Parse([]string{"--top-flag"}) - fs := lookupFlagSet("top-flag", ctx) + fs := ctx.lookupFlagSet("top-flag") expect(t, fs, parentCtx.flagSet) - fs = lookupFlagSet("local-flag", ctx) + fs = ctx.lookupFlagSet("local-flag") expect(t, fs, ctx.flagSet) - if fs := lookupFlagSet("frob", ctx); fs != nil { + if fs := ctx.lookupFlagSet("frob"); fs != nil { t.Fail() } } @@ -576,7 +576,7 @@ func TestCheckRequiredFlags(t *testing.T) { ctx.Command.Flags = test.flags // logic under test - err := checkRequiredFlags(test.flags, ctx) + err := ctx.checkRequiredFlags(test.flags) // assertions if test.expectedAnError && err == nil { diff --git a/errors.go b/errors.go index 751ef9b..8f641fb 100644 --- a/errors.go +++ b/errors.go @@ -47,6 +47,28 @@ func (m *multiError) Errors() []error { return errs } +type requiredFlagsErr interface { + error + getMissingFlags() []string +} + +type errRequiredFlags struct { + missingFlags []string +} + +func (e *errRequiredFlags) Error() string { + numberOfMissingFlags := len(e.missingFlags) + if numberOfMissingFlags == 1 { + return fmt.Sprintf("Required flag %q not set", e.missingFlags[0]) + } + joinedMissingFlags := strings.Join(e.missingFlags, ", ") + return fmt.Sprintf("Required flags %q not set", joinedMissingFlags) +} + +func (e *errRequiredFlags) getMissingFlags() []string { + return e.missingFlags +} + // ErrorFormatter is the interface that will suitably format the error output type ErrorFormatter interface { Format(s fmt.State, verb rune) diff --git a/flag.go b/flag.go index aff8d5b..a693386 100644 --- a/flag.go +++ b/flag.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "flag" "fmt" "io/ioutil" @@ -130,6 +131,48 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { return set, nil } +func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { + switch ff.Value.(type) { + case Serializer: + _ = set.Set(name, ff.Value.(Serializer).Serialize()) + default: + _ = set.Set(name, ff.Value.String()) + } +} + +func normalizeFlags(flags []Flag, set *flag.FlagSet) error { + visited := make(map[string]bool) + set.Visit(func(f *flag.Flag) { + visited[f.Name] = true + }) + for _, f := range flags { + parts := f.Names() + if len(parts) == 1 { + continue + } + var ff *flag.Flag + for _, name := range parts { + name = strings.Trim(name, " ") + if visited[name] { + if ff != nil { + return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) + } + ff = set.Lookup(name) + } + } + if ff == nil { + continue + } + for _, name := range parts { + name = strings.Trim(name, " ") + if !visited[name] { + copyFlag(name, ff, set) + } + } + } + return nil +} + func visibleFlags(fl []Flag) []Flag { var visible []Flag for _, f := range fl { diff --git a/flag_bool.go b/flag_bool.go index bc9ea35..85270e4 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -87,7 +87,7 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupBool(name, fs) } return false diff --git a/flag_duration.go b/flag_duration.go index 22a2e67..7b59a38 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -86,7 +86,7 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error { // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupDuration(name, fs) } return 0 diff --git a/flag_float64.go b/flag_float64.go index 91c778c..d2a6458 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -87,7 +87,7 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error { // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupFloat64(name, fs) } return 0 diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 706ee6c..49a04d4 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -146,7 +146,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { // Float64Slice looks up the value of a local Float64SliceFlag, returns // nil if not found func (c *Context) Float64Slice(name string) []float64 { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupFloat64Slice(name, fs) } return nil diff --git a/flag_generic.go b/flag_generic.go index b0c8ff4..d6800a8 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -89,7 +89,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error { // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupGeneric(name, fs) } return nil diff --git a/flag_int.go b/flag_int.go index ac39d4a..e9da3fa 100644 --- a/flag_int.go +++ b/flag_int.go @@ -87,7 +87,7 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupInt(name, fs) } return 0 diff --git a/flag_int64.go b/flag_int64.go index e099912..6c55458 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -86,7 +86,7 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupInt64(name, fs) } return 0 diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 2c9a15a..773ef8a 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -145,7 +145,7 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupInt64Slice(name, fs) } return nil diff --git a/flag_int_slice.go b/flag_int_slice.go index a73ca6b..8feef5f 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -156,7 +156,7 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupIntSlice(name, fs) } return nil diff --git a/flag_path.go b/flag_path.go index 8070dc4..37c6f27 100644 --- a/flag_path.go +++ b/flag_path.go @@ -75,7 +75,7 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error { // Path looks up the value of a local PathFlag, returns // "" if not found func (c *Context) Path(name string) string { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupPath(name, fs) } diff --git a/flag_string.go b/flag_string.go index 400bb53..a43f7c2 100644 --- a/flag_string.go +++ b/flag_string.go @@ -76,7 +76,7 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error { // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupString(name, fs) } return "" diff --git a/flag_string_slice.go b/flag_string_slice.go index 3549703..3934a60 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -163,7 +163,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupStringSlice(name, fs) } return nil diff --git a/flag_timestamp.go b/flag_timestamp.go index 0382a6b..cff027d 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -138,7 +138,7 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { // Timestamp gets the timestamp from a flag name func (c *Context) Timestamp(name string) *time.Time { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupTimestamp(name, fs) } return nil diff --git a/flag_uint.go b/flag_uint.go index 2e5e76b..6730e69 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -86,7 +86,7 @@ func (f *UintFlag) GetValue() string { // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupUint(name, fs) } return 0 diff --git a/flag_uint64.go b/flag_uint64.go index 8fc3289..4af65fa 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -86,7 +86,7 @@ func (f *Uint64Flag) GetValue() string { // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { - if fs := lookupFlagSet(name, c); fs != nil { + if fs := c.lookupFlagSet(name); fs != nil { return lookupUint64(name, fs) } return 0 From 80ba835e26876f60c75782fb3b1634b2058a07ab Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Sat, 24 Apr 2021 12:22:33 -0400 Subject: [PATCH 091/111] Update GitHub action Bump go version to 1.16 and drop the codecov token, which is now documented as not required for public repositories. --- .github/workflows/cli.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 06dbb80..edeebdf 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: [1.13, 1.14, 1.15] + go: [1.14, 1.15, 1.16] name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: @@ -38,7 +38,7 @@ jobs: ref: ${{ github.ref }} - name: GOFMT Check - if: matrix.go == 1.15 && matrix.os == 'ubuntu-latest' + if: matrix.go == 1.16 && matrix.os == 'ubuntu-latest' run: test -z $(gofmt -l .) - name: vet @@ -51,19 +51,19 @@ jobs: run: go run internal/build/build.go check-binary-size - name: Upload coverage to Codecov - if: success() && matrix.go == 1.14 && matrix.os == 'ubuntu-latest' + if: success() && matrix.go == 1.16 && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v1 with: - token: 0a8cc73b-bb7c-480b-8626-38a461643761 fail_ci_if_error: true test-docs: name: test-docs runs-on: ubuntu-latest steps: - - name: Set up Go 1.15 + - name: Set up Go uses: actions/setup-go@v1 with: + # Currently fails on 1.16 go-version: 1.15 - name: Use Node.js 12.x From 581b769cf3ee996b7277e895b30afa3f3e6921bf Mon Sep 17 00:00:00 2001 From: Derek Smith Date: Thu, 20 May 2021 20:04:51 -0500 Subject: [PATCH 092/111] feat(docs): add UsageText to docs output for markdown and man page generation (#1171) * feat(docs): add UsageText to docs output for markdown and man page generation * feat(docs): updated tests, DRYd up code, cleaned up string logic * fix(lint): fixed go1.15 lint errors --- docs.go | 48 ++++++++- docs_test.go | 148 ++++++++++++++++++++++++++++ testdata/expected-doc-full.man | 44 ++++++++- testdata/expected-doc-full.md | 26 +++++ testdata/expected-doc-no-authors.md | 26 +++++ testdata/expected-doc-no-flags.md | 26 +++++ testdata/expected-fish-full.fish | 9 +- 7 files changed, 320 insertions(+), 7 deletions(-) diff --git a/docs.go b/docs.go index 4c0e1f6..021cc2b 100644 --- a/docs.go +++ b/docs.go @@ -68,15 +68,16 @@ func prepareCommands(commands []*Command, level int) []string { if command.Hidden { continue } - usage := "" - if command.Usage != "" { - usage = command.Usage - } - prepared := fmt.Sprintf("%s %s\n\n%s\n", + usageText := prepareUsageText(command) + + usage := prepareUsage(command, usageText) + + prepared := fmt.Sprintf("%s %s\n\n%s%s", strings.Repeat("#", level+2), strings.Join(command.Names(), ", "), usage, + usageText, ) flags := prepareArgsWithValues(command.Flags) @@ -155,3 +156,40 @@ func flagDetails(flag DocGenerationFlag) string { } return ": " + description } + +func prepareUsageText(command *Command) string { + if command.UsageText == "" { + return "" + } + + // Remove leading and trailing newlines + preparedUsageText := strings.Trim(command.UsageText, "\n") + + var usageText string + if strings.Contains(preparedUsageText, "\n") { + // Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such + // that it will not break the continuous code block. + for _, ln := range strings.Split(preparedUsageText, "\n") { + usageText += fmt.Sprintf(" %s\n", ln) + } + } else { + // Style a single line as a note + usageText = fmt.Sprintf(">%s\n", preparedUsageText) + } + + return usageText +} + +func prepareUsage(command *Command, usageText string) string { + if command.Usage == "" { + return "" + } + + usage := command.Usage + "\n" + // Add a newline to the Usage IFF there is a UsageText + if usageText != "" { + usage += "\n" + } + + return usage +} diff --git a/docs_test.go b/docs_test.go index fac106f..962c366 100644 --- a/docs_test.go +++ b/docs_test.go @@ -67,6 +67,47 @@ func testApp() *App { }, { Name: "hidden-command", Hidden: true, + }, { + Aliases: []string{"u"}, + Flags: []Flag{ + &StringFlag{ + Name: "flag", + Aliases: []string{"fl", "f"}, + TakesFile: true, + }, + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", + }, + }, + Name: "usage", + Usage: "standard usage text", + UsageText: ` +Usage for the usage text +- formatted: Based on the specified ConfigMap and summon secrets.yml +- list: Inspect the environment for a specific process running on a Pod +- for_effect: Compare 'namespace' environment with 'local' + +` + "```" + ` +func() { ... } +` + "```" + ` + +Should be a part of the same code block +`, + Subcommands: []*Command{{ + Aliases: []string{"su"}, + Flags: []Flag{ + &BoolFlag{ + Name: "sub-command-flag", + Aliases: []string{"s"}, + Usage: "some usage text", + }, + }, + Name: "sub-usage", + Usage: "standard usage text", + UsageText: "Single line of UsageText", + }}, }} app.UsageText = "app [first_arg] [second_arg]" app.Usage = "Some app" @@ -175,3 +216,110 @@ func TestToManWithSection(t *testing.T) { expect(t, err, nil) expectFileContent(t, "testdata/expected-doc-full.man", res) } + +func Test_prepareUsageText(t *testing.T) { + t.Run("no UsageText provided", func(t *testing.T) { + // Given + cmd := Command{} + + // When + res := prepareUsageText(&cmd) + + // Then + expect(t, res, "") + }) + + t.Run("single line UsageText", func(t *testing.T) { + // Given + cmd := Command{UsageText: "Single line usage text"} + + // When + res := prepareUsageText(&cmd) + + // Then + expect(t, res, ">Single line usage text\n") + }) + + t.Run("multiline UsageText", func(t *testing.T) { + // Given + cmd := Command{ + UsageText: ` +Usage for the usage text +- Should be a part of the same code block +`, + } + + // When + res := prepareUsageText(&cmd) + + // Then + test := ` Usage for the usage text + - Should be a part of the same code block +` + expect(t, res, test) + }) + + t.Run("multiline UsageText has formatted embedded markdown", func(t *testing.T) { + // Given + cmd := Command{ + UsageText: ` +Usage for the usage text + +` + "```" + ` +func() { ... } +` + "```" + ` + +Should be a part of the same code block +`, + } + + // When + res := prepareUsageText(&cmd) + + // Then + test := ` Usage for the usage text + + ` + "```" + ` + func() { ... } + ` + "```" + ` + + Should be a part of the same code block +` + expect(t, res, test) + }) +} + +func Test_prepareUsage(t *testing.T) { + t.Run("no Usage provided", func(t *testing.T) { + // Given + cmd := Command{} + + // When + res := prepareUsage(&cmd, "") + + // Then + expect(t, res, "") + }) + + t.Run("simple Usage", func(t *testing.T) { + // Given + cmd := Command{Usage: "simple usage text"} + + // When + res := prepareUsage(&cmd, "") + + // Then + expect(t, res, cmd.Usage+"\n") + }) + + t.Run("simple Usage with UsageText", func(t *testing.T) { + // Given + cmd := Command{Usage: "simple usage text"} + + // When + res := prepareUsage(&cmd, "a non-empty string") + + // Then + expect(t, res, cmd.Usage+"\n\n") + }) +} diff --git a/testdata/expected-doc-full.man b/testdata/expected-doc-full.man index 2bea507..3ac3553 100644 --- a/testdata/expected-doc-full.man +++ b/testdata/expected-doc-full.man @@ -75,4 +75,46 @@ another usage test .PP retrieve generic information -.SH some\-command \ No newline at end of file +.SH some\-command +.SH usage, u +.PP +standard usage text + +.PP +.RS + +.nf +Usage for the usage text +\- formatted: Based on the specified ConfigMap and summon secrets.yml +\- list: Inspect the environment for a specific process running on a Pod +\- for\_effect: Compare 'namespace' environment with 'local' + +``` +func() { ... } +``` + +Should be a part of the same code block + +.fi +.RE + +.PP +\fB\-\-another\-flag, \-b\fP: another usage text + +.PP +\fB\-\-flag, \-\-fl, \-f\fP="": + +.SS sub\-usage, su +.PP +standard usage text + +.PP +.RS + +.PP +Single line of UsageText + +.RE + +.PP +\fB\-\-sub\-command\-flag, \-s\fP: some usage text diff --git a/testdata/expected-doc-full.md b/testdata/expected-doc-full.md index f272274..7b8c0d7 100644 --- a/testdata/expected-doc-full.md +++ b/testdata/expected-doc-full.md @@ -58,3 +58,29 @@ retrieve generic information ## some-command +## usage, u + +standard usage text + + Usage for the usage text + - formatted: Based on the specified ConfigMap and summon secrets.yml + - list: Inspect the environment for a specific process running on a Pod + - for_effect: Compare 'namespace' environment with 'local' + + ``` + func() { ... } + ``` + + Should be a part of the same code block + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-usage, su + +standard usage text + +>Single line of UsageText + +**--sub-command-flag, -s**: some usage text diff --git a/testdata/expected-doc-no-authors.md b/testdata/expected-doc-no-authors.md index f272274..7b8c0d7 100644 --- a/testdata/expected-doc-no-authors.md +++ b/testdata/expected-doc-no-authors.md @@ -58,3 +58,29 @@ retrieve generic information ## some-command +## usage, u + +standard usage text + + Usage for the usage text + - formatted: Based on the specified ConfigMap and summon secrets.yml + - list: Inspect the environment for a specific process running on a Pod + - for_effect: Compare 'namespace' environment with 'local' + + ``` + func() { ... } + ``` + + Should be a part of the same code block + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-usage, su + +standard usage text + +>Single line of UsageText + +**--sub-command-flag, -s**: some usage text diff --git a/testdata/expected-doc-no-flags.md b/testdata/expected-doc-no-flags.md index 2994593..ebd0416 100644 --- a/testdata/expected-doc-no-flags.md +++ b/testdata/expected-doc-no-flags.md @@ -43,3 +43,29 @@ retrieve generic information ## some-command +## usage, u + +standard usage text + + Usage for the usage text + - formatted: Based on the specified ConfigMap and summon secrets.yml + - list: Inspect the environment for a specific process running on a Pod + - for_effect: Compare 'namespace' environment with 'local' + + ``` + func() { ... } + ``` + + Should be a part of the same code block + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-usage, su + +standard usage text + +>Single line of UsageText + +**--sub-command-flag, -s**: some usage text diff --git a/testdata/expected-fish-full.fish b/testdata/expected-fish-full.fish index dc41e5a..cc449c5 100644 --- a/testdata/expected-fish-full.fish +++ b/testdata/expected-fish-full.fish @@ -2,7 +2,7 @@ function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet' for i in (commandline -opc) - if contains -- $i config c sub-config s ss info i in some-command + if contains -- $i config c sub-config s ss info i in some-command usage u sub-usage su return 1 end end @@ -27,3 +27,10 @@ complete -c greet -n '__fish_seen_subcommand_from info i in' -f -l help -s h -d complete -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information' complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help' complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command' +complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_greet_no_subcommand' -a 'usage u' -d 'standard usage text' +complete -c greet -n '__fish_seen_subcommand_from usage u' -l flag -s fl -s f -r +complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l another-flag -s b -d 'another usage text' +complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l help -s h -d 'show help' +complete -r -c greet -n '__fish_seen_subcommand_from usage u' -a 'sub-usage su' -d 'standard usage text' +complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l sub-command-flag -s s -d 'some usage text' From 443c6a54a886a7d3aa79c70a046b4753c493626e Mon Sep 17 00:00:00 2001 From: Derek Smith Date: Thu, 3 Jun 2021 18:19:19 -0500 Subject: [PATCH 093/111] fix(UsageText): consistent indent for help UsageText output (#1279) Signed-off-by: Derek Smith --- help_test.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++ template.go | 6 +-- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/help_test.go b/help_test.go index 407c269..8dd262d 100644 --- a/help_test.go +++ b/help_test.go @@ -511,6 +511,36 @@ func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { } } +func TestShowSubcommandHelp_MultiLine_CommandUsageText(t *testing.T) { + app := &App{ + Commands: []*Command{ + { + Name: "frobbly", + UsageText: `This is a +multi +line +UsageText`, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + + _ = app.Run([]string{"foo", "frobbly", "--help"}) + + expected := `USAGE: + This is a + multi + line + UsageText +` + + if !strings.Contains(output.String(), expected) { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { app := &App{ Commands: []*Command{ @@ -535,6 +565,40 @@ func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { } } +func TestShowSubcommandHelp_MultiLine_SubcommandUsageText(t *testing.T) { + app := &App{ + Commands: []*Command{ + { + Name: "frobbly", + Subcommands: []*Command{ + { + Name: "bobbly", + UsageText: `This is a +multi +line +UsageText`, + }, + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + _ = app.Run([]string{"foo", "frobbly", "bobbly", "--help"}) + + expected := `USAGE: + This is a + multi + line + UsageText +` + + if !strings.Contains(output.String(), expected) { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + func TestShowAppHelp_HiddenCommand(t *testing.T) { app := &App{ Commands: []*Command{ @@ -780,6 +844,56 @@ VERSION: } } +func TestShowAppHelp_UsageText(t *testing.T) { + app := &App{ + UsageText: "This is a sinlge line of UsageText", + Commands: []*Command{ + { + Name: "frobbly", + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + + _ = app.Run([]string{"foo"}) + + if !strings.Contains(output.String(), "This is a sinlge line of UsageText") { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + +func TestShowAppHelp_MultiLine_UsageText(t *testing.T) { + app := &App{ + UsageText: `This is a +multi +line +App UsageText`, + Commands: []*Command{ + { + Name: "frobbly", + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + + _ = app.Run([]string{"foo"}) + + expected := `USAGE: + This is a + multi + line + App UsageText +` + + if !strings.Contains(output.String(), expected) { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + func TestHideHelpCommand(t *testing.T) { app := &App{ HideHelpCommand: true, diff --git a/template.go b/template.go index 317cc88..69e040e 100644 --- a/template.go +++ b/template.go @@ -7,7 +7,7 @@ var AppHelpTemplate = `NAME: {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} @@ -39,7 +39,7 @@ var CommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} @@ -59,7 +59,7 @@ var SubcommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: {{.Description | nindent 3 | trim}}{{end}} From b5d4a04c7fb1c7de98d491ee5af9130006fe06c6 Mon Sep 17 00:00:00 2001 From: Ashwani Date: Sun, 13 Jun 2021 19:06:57 +0530 Subject: [PATCH 094/111] Resolved a grammatical error (#1281) --- funcs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funcs.go b/funcs.go index 474c48f..842b4aa 100644 --- a/funcs.go +++ b/funcs.go @@ -17,7 +17,7 @@ type ActionFunc func(*Context) error // CommandNotFoundFunc is executed if the proper command cannot be found type CommandNotFoundFunc func(*Context, string) -// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying +// OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying // customized usage error messages. This function is able to replace the // original error messages. If this function is not set, the "Incorrect usage" // is displayed and the execution is interrupted. From 6373f5bf650c1be406c1de1e946f45ddecc34977 Mon Sep 17 00:00:00 2001 From: Link Dupont Date: Tue, 6 Jul 2021 20:20:47 -0400 Subject: [PATCH 095/111] feat(docs): Include Description and UsageText in docs output (#1287) Include Description as part of the DESCRIPTION section, and put UsageText (if it is non-zero) into the Usage subsection. --- docs_test.go | 14 +++++ template.go | 10 ++-- testdata/expected-doc-full.man | 4 +- testdata/expected-doc-full.md | 4 +- testdata/expected-doc-no-authors.md | 4 +- testdata/expected-doc-no-commands.md | 4 +- testdata/expected-doc-no-flags.md | 4 +- testdata/expected-doc-no-usagetext.md | 86 +++++++++++++++++++++++++++ 8 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 testdata/expected-doc-no-usagetext.md diff --git a/docs_test.go b/docs_test.go index 962c366..5308f37 100644 --- a/docs_test.go +++ b/docs_test.go @@ -110,6 +110,7 @@ Should be a part of the same code block }}, }} app.UsageText = "app [first_arg] [second_arg]" + app.Description = `Description of the application.` app.Usage = "Some app" app.Authors = []*Author{ {Name: "Harrison", Email: "harrison@lolwut.com"}, @@ -178,6 +179,19 @@ func TestToMarkdownNoAuthors(t *testing.T) { expectFileContent(t, "testdata/expected-doc-no-authors.md", res) } +func TestToMarkdownNoUsageText(t *testing.T) { + // Given + app := testApp() + app.UsageText = "" + + // When + res, err := app.ToMarkdown() + + // Then + expect(t, err, nil) + expectFileContent(t, "testdata/expected-doc-no-usagetext.md", res) +} + func TestToMan(t *testing.T) { // Given app := testApp() diff --git a/template.go b/template.go index 69e040e..708a402 100644 --- a/template.go +++ b/template.go @@ -86,16 +86,18 @@ var MarkdownDocTemplate = `% {{ .App.Name }} {{ .SectionNum }} {{ if .SynopsisArgs }} ` + "```" + ` {{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` -{{ end }}{{ if .App.UsageText }} +{{ end }}{{ if .App.Description }} # DESCRIPTION -{{ .App.UsageText }} +{{ .App.Description }} {{ end }} **Usage**: -` + "```" + ` +` + "```" + `{{ if .App.UsageText }} +{{ .App.UsageText }} +{{ else }} {{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] -` + "```" + ` +{{ end }}` + "```" + ` {{ if .GlobalArgs }} # GLOBAL OPTIONS {{ range $v := .GlobalArgs }} diff --git a/testdata/expected-doc-full.man b/testdata/expected-doc-full.man index 3ac3553..3df4684 100644 --- a/testdata/expected-doc-full.man +++ b/testdata/expected-doc-full.man @@ -24,7 +24,7 @@ greet .SH DESCRIPTION .PP -app [first\_arg] [second\_arg] +Description of the application. .PP \fBUsage\fP: @@ -33,7 +33,7 @@ app [first\_arg] [second\_arg] .RS .nf -greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +app [first\_arg] [second\_arg] .fi .RE diff --git a/testdata/expected-doc-full.md b/testdata/expected-doc-full.md index 7b8c0d7..f3374e5 100644 --- a/testdata/expected-doc-full.md +++ b/testdata/expected-doc-full.md @@ -16,12 +16,12 @@ greet # DESCRIPTION -app [first_arg] [second_arg] +Description of the application. **Usage**: ``` -greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +app [first_arg] [second_arg] ``` # GLOBAL OPTIONS diff --git a/testdata/expected-doc-no-authors.md b/testdata/expected-doc-no-authors.md index 7b8c0d7..f3374e5 100644 --- a/testdata/expected-doc-no-authors.md +++ b/testdata/expected-doc-no-authors.md @@ -16,12 +16,12 @@ greet # DESCRIPTION -app [first_arg] [second_arg] +Description of the application. **Usage**: ``` -greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +app [first_arg] [second_arg] ``` # GLOBAL OPTIONS diff --git a/testdata/expected-doc-no-commands.md b/testdata/expected-doc-no-commands.md index adaedb4..1c4fd07 100644 --- a/testdata/expected-doc-no-commands.md +++ b/testdata/expected-doc-no-commands.md @@ -16,12 +16,12 @@ greet # DESCRIPTION -app [first_arg] [second_arg] +Description of the application. **Usage**: ``` -greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +app [first_arg] [second_arg] ``` # GLOBAL OPTIONS diff --git a/testdata/expected-doc-no-flags.md b/testdata/expected-doc-no-flags.md index ebd0416..cf766ad 100644 --- a/testdata/expected-doc-no-flags.md +++ b/testdata/expected-doc-no-flags.md @@ -10,12 +10,12 @@ greet # DESCRIPTION -app [first_arg] [second_arg] +Description of the application. **Usage**: ``` -greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +app [first_arg] [second_arg] ``` # COMMANDS diff --git a/testdata/expected-doc-no-usagetext.md b/testdata/expected-doc-no-usagetext.md new file mode 100644 index 0000000..da31b38 --- /dev/null +++ b/testdata/expected-doc-no-usagetext.md @@ -0,0 +1,86 @@ +% greet 8 + +# NAME + +greet - Some app + +# SYNOPSIS + +greet + +``` +[--another-flag|-b] +[--flag|--fl|-f]=[value] +[--socket|-s]=[value] +``` + +# DESCRIPTION + +Description of the application. + +**Usage**: + +``` +greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# GLOBAL OPTIONS + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +**--socket, -s**="": some 'usage' text (default: value) + + +# COMMANDS + +## config, c + +another usage test + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-config, s, ss + +another usage test + +**--sub-command-flag, -s**: some usage text + +**--sub-flag, --sub-fl, -s**="": + +## info, i, in + +retrieve generic information + +## some-command + + +## usage, u + +standard usage text + + Usage for the usage text + - formatted: Based on the specified ConfigMap and summon secrets.yml + - list: Inspect the environment for a specific process running on a Pod + - for_effect: Compare 'namespace' environment with 'local' + + ``` + func() { ... } + ``` + + Should be a part of the same code block + +**--another-flag, -b**: another usage text + +**--flag, --fl, -f**="": + +### sub-usage, su + +standard usage text + +>Single line of UsageText + +**--sub-command-flag, -s**: some usage text From 58d113dd731e7ab9520fed165b581b8a50663f66 Mon Sep 17 00:00:00 2001 From: Ally Dale Date: Wed, 7 Jul 2021 08:33:01 +0800 Subject: [PATCH 096/111] fix #1239: slice flag value don't append to default values from ENV or file (#1240) * fix #1239: slice flag value don't append to default values from ENV or file * remove test code --- flag_float64_slice.go | 3 +++ flag_int64_slice.go | 3 +++ flag_int_slice.go | 3 +++ flag_test.go | 13 +++++++++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/flag_float64_slice.go b/flag_float64_slice.go index f752ad7..385732e 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -144,6 +144,9 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { } } + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + f.Value.hasBeenSet = false f.HasBeenSet = true } } diff --git a/flag_int64_slice.go b/flag_int64_slice.go index b4c8bc1..f5c6939 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -144,6 +144,9 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { } } + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + f.Value.hasBeenSet = false f.HasBeenSet = true } diff --git a/flag_int_slice.go b/flag_int_slice.go index d4889b3..94c668e 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -155,6 +155,9 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { } } + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + f.Value.hasBeenSet = false f.HasBeenSet = true } diff --git a/flag_test.go b/flag_test.go index e46270d..c563d6f 100644 --- a/flag_test.go +++ b/flag_test.go @@ -52,15 +52,21 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) { } func TestFlagsFromEnv(t *testing.T) { + newSetFloat64Slice := func(defaults ...float64) Float64Slice { + s := NewFloat64Slice(defaults...) + s.hasBeenSet = false + return *s + } + newSetIntSlice := func(defaults ...int) IntSlice { s := NewIntSlice(defaults...) - s.hasBeenSet = true + s.hasBeenSet = false return *s } newSetInt64Slice := func(defaults ...int64) Int64Slice { s := NewInt64Slice(defaults...) - s.hasBeenSet = true + s.hasBeenSet = false return *s } @@ -96,6 +102,9 @@ func TestFlagsFromEnv(t *testing.T) { {"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.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: .*`}, + {"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: .*`}, From 67d7f9403dee81f1d2629f3079b4274ccfb9c41d Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Thu, 9 Sep 2021 05:31:46 -0400 Subject: [PATCH 097/111] Remove stalebot (#1300) --- .github/stale.yml | 64 ----------------------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 0cc3007..0000000 --- a/.github/stale.yml +++ /dev/null @@ -1,64 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 90 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 30 - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - pinned - - security - - "help wanted" - - "kind/maintenance" - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: "status/stale" - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue or PR has been automatically marked as stale because it has not had - recent activity. Please add a comment bumping this if you're still - interested in it's resolution! Thanks for your help, please let us know - if you need anything else. - -# Comment to post when removing the stale label. -unmarkComment: > - This issue or PR has been bumped and is no longer marked as stale! Feel free - to bump it again in the future, if it's still relevant. - -# Comment to post when closing a stale Issue or Pull Request. -closeComment: > - Closing this as it has become stale. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -# pulls: -# daysUntilStale: 30 -# markComment: > -# This pull request has been automatically marked as stale because it has not had -# recent activity. It will be closed if no further activity occurs. Thank you -# for your contributions. - -# issues: -# exemptLabels: -# - confirmed From 1259f1efc973b210de2c7b448b6302d6f52081d1 Mon Sep 17 00:00:00 2001 From: JayCeeJr <1265665+JayCeeJr@users.noreply.github.com> Date: Wed, 15 Sep 2021 22:16:31 -0500 Subject: [PATCH 098/111] Unnecessary words (#1304) It is unclear what `as the default` means. Much more concise to remove it. --- docs/v2/manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index af09010..56be65b 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -514,7 +514,7 @@ func main() { ``` If `EnvVars` contains more than one string, the first environment variable that -resolves is used as the default. +resolves is used. @@ -53,7 +56,7 @@ Check each file for this and make the change. Shell command to find them all: `fgrep -rl github.com/urfave/cli *` -# Flag aliases are done differently. +# Flag aliases are done differently Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}`