From b236763df53c5eca518081b3dad10dc44efb08d9 Mon Sep 17 00:00:00 2001 From: kirinnee Date: Mon, 18 May 2020 11:19:00 +0800 Subject: [PATCH 01/74] 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 0000000000..81812a6a43 --- /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 55008b4b55..63d3bc375a 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 9523a3481bf492de121be67a254086b0404d1ade Mon Sep 17 00:00:00 2001 From: kirinnee Date: Mon, 8 Jun 2020 10:54:27 +0800 Subject: [PATCH 02/74] 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 63d3bc375a..951384e7c7 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 5bb54ace578d17a134feb806f22163e7064ede87 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Mon, 22 Jun 2020 17:17:47 -0700 Subject: [PATCH 03/74] 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 67122c9fe7..588e070eab 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 a4c1871438..4ca8c47903 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 b18d51e8fe..dc41e5aa9a 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 bed468fb766d264d983dbcceea530350ae28e812 Mon Sep 17 00:00:00 2001 From: Alberts Zemzale Date: Thu, 13 Aug 2020 19:15:35 +0300 Subject: [PATCH 04/74] Fix v1 aliases syntax in v2 docs --- docs/v2/manual.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index c39bfb9954..fea20ec591 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -425,14 +425,17 @@ import ( func main() { app := &cli.App{ Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "Language for the greeting", - }, - &cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "Language for the greeting", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, }, }, Commands: []*cli.Command{ @@ -570,7 +573,8 @@ func main() { app.Flags = []cli.Flag { &cli.StringFlag{ - Name: "password, p", + Name: "password", + Aliases: []string{"p"}, Usage: "password for the mysql database", FilePath: "/etc/mysql/password", }, @@ -1309,7 +1313,8 @@ import ( func main() { cli.HelpFlag = &cli.BoolFlag{ - Name: "haaaaalp", Aliases: []string{"halp"}, + Name: "haaaaalp", + Aliases: []string{"halp"}, Usage: "HALP", EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, } @@ -1344,7 +1349,8 @@ import ( func main() { cli.VersionFlag = &cli.BoolFlag{ - Name: "print-version", Aliases: []string{"V"}, + Name: "print-version", + Aliases: []string{"V"}, Usage: "print only the version", } From be9c0378066dc5baf84a56005f90317cca6ecd84 Mon Sep 17 00:00:00 2001 From: Steven Imle Date: Mon, 17 Aug 2020 16:02:40 -0400 Subject: [PATCH 05/74] Added template functions for multi-line Description formatting. --- help.go | 24 ++++++++++++++++++------ help_test.go | 16 ++++++++++++++++ template.go | 6 +++--- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/help.go b/help.go index c1e974a481..efcaf1c203 100644 --- a/help.go +++ b/help.go @@ -72,13 +72,13 @@ func ShowAppHelpAndExit(c *Context, exitCode int) { // ShowAppHelp is an action that displays the help. func ShowAppHelp(c *Context) error { - template := c.App.CustomAppHelpTemplate - if template == "" { - template = AppHelpTemplate + tpl := c.App.CustomAppHelpTemplate + if tpl == "" { + tpl = AppHelpTemplate } if c.App.ExtraInfo == nil { - HelpPrinter(c.App.Writer, template, c.App) + HelpPrinter(c.App.Writer, tpl, c.App) return nil } @@ -87,7 +87,7 @@ func ShowAppHelp(c *Context) error { "ExtraInfo": c.App.ExtraInfo, } } - HelpPrinterCustom(c.App.Writer, template, c.App, customAppData()) + HelpPrinterCustom(c.App.Writer, tpl, c.App, customAppData()) return nil } @@ -263,7 +263,10 @@ func ShowCommandCompletions(ctx *Context, command string) { // allow using arbitrary functions in template rendering. func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) { funcMap := template.FuncMap{ - "join": strings.Join, + "join": strings.Join, + "indent": indent, + "nindent": nindent, + "trim": strings.TrimSpace, } for key, value := range customFuncs { funcMap[key] = value @@ -366,3 +369,12 @@ func checkCommandCompletions(c *Context, name string) bool { ShowCommandCompletions(c, name) return true } + +func indent(spaces int, v string) string { + pad := strings.Repeat(" ", spaces) + return pad + strings.Replace(v, "\n", "\n"+pad, -1) +} + +func nindent(spaces int, v string) string { + return "\n" + indent(spaces, v) +} diff --git a/help_test.go b/help_test.go index 5f292b77e5..407c269173 100644 --- a/help_test.go +++ b/help_test.go @@ -54,6 +54,22 @@ func Test_ShowAppHelp_HideVersion(t *testing.T) { } } +func Test_ShowAppHelp_MultiLineDescription(t *testing.T) { + output := new(bytes.Buffer) + app := &App{Writer: output} + + app.HideVersion = true + app.Description = "multi\n line" + + c := NewContext(app, nil, nil) + + _ = ShowAppHelp(c) + + if !bytes.Contains(output.Bytes(), []byte("DESCRIPTION:\n multi\n line")) { + t.Errorf("expected\n%s\nto include\n%s", output.String(), "DESCRIPTION:\n multi\n line") + } +} + func Test_Help_Custom_Flags(t *testing.T) { oldFlag := HelpFlag defer func() { diff --git a/template.go b/template.go index aee3e0494f..31c03f81c7 100644 --- a/template.go +++ b/template.go @@ -13,7 +13,7 @@ VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} + {{.Description | nindent 3 | trim}}{{end}}{{if len .Authors}} AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{range $index, $author := .Authors}}{{if $index}} @@ -45,7 +45,7 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} + {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} @@ -62,7 +62,7 @@ USAGE: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}} + {{.Description | nindent 3 | trim}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} From d2739475928a1a62b41c45506278ddd700517ddc Mon Sep 17 00:00:00 2001 From: Alberts Zemzale Date: Sun, 23 Aug 2020 13:35:59 +0300 Subject: [PATCH 06/74] Remove incorrect bracket in v2 docs example --- docs/v2/manual.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index fea20ec591..266abd537a 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -436,7 +436,6 @@ func main() { Aliases: []string{"c"}, Usage: "Load configuration from `FILE`", }, - }, }, Commands: []*cli.Command{ { From 2ae03fa69ce7b6030174af976b8d51d24386394c Mon Sep 17 00:00:00 2001 From: Alex Couture-Beil Date: Mon, 24 Aug 2020 13:59:26 -0700 Subject: [PATCH 07/74] bugfix: don't overwrite existing stringslice refence fixes an issue where values set by environment var are not saved to existing stringslice reference. --- flag_string_slice.go | 4 +++- flag_test.go | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/flag_string_slice.go b/flag_string_slice.go index 74cf0a51f2..35497032cb 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -124,7 +124,9 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { } if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { - f.Value = &StringSlice{} + if f.Value == nil { + f.Value = &StringSlice{} + } destination := f.Value if f.Destination != nil { destination = f.Destination diff --git a/flag_test.go b/flag_test.go index ced5514714..b1fe70a4fd 100644 --- a/flag_test.go +++ b/flag_test.go @@ -386,6 +386,20 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } +func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "vincent van goat,scape goat") + var val StringSlice + fl := StringSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse(nil) + expect(t, err, nil) + expect(t, val.Value(), NewStringSlice("vincent van goat", "scape goat").Value()) +} + func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) { defValue := []string{"UA", "US"} From 74af85a00da15038fa915e6be00fb0679c113376 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Tue, 8 Sep 2020 17:53:16 +0300 Subject: [PATCH 08/74] feat(help): add ShowSubcommandHelpAndExit --- help.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/help.go b/help.go index c1e974a481..7f33a23645 100644 --- a/help.go +++ b/help.go @@ -214,6 +214,12 @@ func ShowCommandHelp(ctx *Context, command string) error { return nil } +// ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits with exit code. +func ShowSubcommandHelpAndExit(c *Context, exitCode int) { + _ = ShowSubcommandHelp(c) + os.Exit(exitCode) +} + // ShowSubcommandHelp prints help for the given subcommand func ShowSubcommandHelp(c *Context) error { if c == nil { From 8e9b8ccfa6b17fca64311e7a7125c510912a1558 Mon Sep 17 00:00:00 2001 From: Lucas Charles Date: Fri, 18 Sep 2020 09:16:14 -0700 Subject: [PATCH 09/74] Bump gopkg.in/yaml.v2 to v2.2.3 Fixes susceptibility to billion laughs attack, see GitLab Gemnasium advisory for details https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/blob/d769b9b5f0ae0c94bba8de1f67f19d6d0cfe630a/go/gopkg.in/yaml.v2/GMS-2019-2.yml --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c38d41c14b..1139664326 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.11 require ( github.com/BurntSushi/toml v0.3.1 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.3 ) diff --git a/go.sum b/go.sum index ef121ff5db..663ad72768 100644 --- a/go.sum +++ b/go.sum @@ -10,5 +10,5 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 342ce5d654ae779f664a81a0d3e31b67cf94573d Mon Sep 17 00:00:00 2001 From: Adam Farden Date: Fri, 2 Oct 2020 12:39:44 +0200 Subject: [PATCH 10/74] feature: Add a App.Reader that defaults to os.Stdin Closes: #1190 --- app.go | 7 +++++++ app_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/app.go b/app.go index 59bf68df95..1767c0e5b0 100644 --- a/app.go +++ b/app.go @@ -72,6 +72,8 @@ type App struct { Authors []*Author // Copyright of the binary if any Copyright string + // Reader reader to write input to (useful for tests) + Reader io.Reader // Writer writer to write output to Writer io.Writer // ErrWriter writes error output @@ -117,6 +119,7 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), + Reader: os.Stdin, Writer: os.Stdout, ErrWriter: os.Stderr, } @@ -160,6 +163,10 @@ func (a *App) Setup() { a.Compiled = compileTime() } + if a.Reader == nil { + a.Reader = os.Stdin + } + if a.Writer == nil { a.Writer = os.Stdout } diff --git a/app_test.go b/app_test.go index 6c95faa612..57c850d1ed 100644 --- a/app_test.go +++ b/app_test.go @@ -433,6 +433,12 @@ func TestApp_Command(t *testing.T) { } } +func TestApp_Setup_defaultsReader(t *testing.T) { + app := &App{} + app.Setup() + expect(t, app.Reader, os.Stdin) +} + func TestApp_Setup_defaultsWriter(t *testing.T) { app := &App{} app.Setup() @@ -850,6 +856,15 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { } } +func TestApp_DefaultStdin(t *testing.T) { + app := &App{} + app.Setup() + + if app.Reader != os.Stdin { + t.Error("Default input reader not set.") + } +} + func TestApp_DefaultStdout(t *testing.T) { app := &App{} app.Setup() @@ -859,6 +874,29 @@ func TestApp_DefaultStdout(t *testing.T) { } } +func TestApp_SetStdin(t *testing.T) { + buf := make([]byte, 12) + + app := &App{ + Name: "test", + Reader: strings.NewReader("Hello World!"), + Action: func(c *Context) error { + _, err := c.App.Reader.Read(buf) + return err + }, + } + + err := app.Run([]string{"help"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if string(buf) != "Hello World!" { + t.Error("App did not read input from desired reader.") + } +} + func TestApp_SetStdout(t *testing.T) { var w bytes.Buffer From e5407cccf73a18f73fa035692a038db790da1025 Mon Sep 17 00:00:00 2001 From: Charles Kenney Date: Tue, 6 Oct 2020 22:24:27 -0400 Subject: [PATCH 11/74] fix grammatical error --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 59bf68df95..6f2f21fcf4 100644 --- a/app.go +++ b/app.go @@ -64,7 +64,7 @@ type App struct { Action ActionFunc // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc - // Execute this function if an usage error occurs + // Execute this function if a usage error occurs OnUsageError OnUsageErrorFunc // Compilation date Compiled time.Time From 27a8625ccde346a659c6df56bf44ae5065e0e3c7 Mon Sep 17 00:00:00 2001 From: Igor Artasevych Date: Thu, 8 Oct 2020 18:38:59 +0300 Subject: [PATCH 12/74] #1193 Expose constructor of MapInputSource type --- altsrc/map_input_source.go | 5 +++++ altsrc/map_input_source_test.go | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 661b7856d4..b144cf64c0 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -16,6 +16,11 @@ type MapInputSource struct { valueMap map[interface{}]interface{} } +//NewMapInputSource create a new MapInputSource type +func NewMapInputSource(file string, valueMap map[interface{}]interface{}) *MapInputSource { + return &MapInputSource{file: file, valueMap: valueMap} +} + // nestedVal checks if the name has '.' delimiters. // If so, it tries to traverse the tree by the '.' delimited sections to find // a nested value for the key. diff --git a/altsrc/map_input_source_test.go b/altsrc/map_input_source_test.go index 5046d1485d..b90f796b30 100644 --- a/altsrc/map_input_source_test.go +++ b/altsrc/map_input_source_test.go @@ -6,14 +6,13 @@ import ( ) func TestMapDuration(t *testing.T) { - inputSource := &MapInputSource{ - file: "test", - valueMap: map[interface{}]interface{}{ + inputSource := NewMapInputSource( + "test", + map[interface{}]interface{}{ "duration_of_duration_type": time.Minute, "duration_of_string_type": "1m", "duration_of_int_type": 1000, - }, - } + }) d, err := inputSource.Duration("duration_of_duration_type") expect(t, time.Minute, d) expect(t, nil, err) From fcce511478c1aabd0e4a6af313acd74a89aaa21c Mon Sep 17 00:00:00 2001 From: Erin Call Date: Tue, 13 Oct 2020 12:55:44 -0700 Subject: [PATCH 13/74] Refactor fishAddFileFlag for better flexibility [#1156] Performing reflection on the given flag ensures that fishAddFileFlag will behave correctly if any flag types get a TakesFile field in the future. --- fish.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/fish.go b/fish.go index 588e070eab..474c5c7be1 100644 --- a/fish.go +++ b/fish.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "reflect" "strings" "text/template" ) @@ -158,24 +159,17 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string } func fishAddFileFlag(flag Flag, completion *strings.Builder) { - switch f := flag.(type) { - case *GenericFlag: - if f.TakesFile { - return - } - case *StringFlag: - if f.TakesFile { - return - } - case *StringSliceFlag: - if f.TakesFile { - return - } - case *PathFlag: - if f.TakesFile { + val := reflect.ValueOf(flag) + // if flag is a non-nil pointer to a struct... + if val.Kind() != reflect.Invalid && val.Elem().Kind() == reflect.Struct { + field := val.Elem().FieldByName("TakesFile") + // if flag's underlying type has a bool field called TakesFile, whose value is true... + if field.Kind() == reflect.Bool && field.Bool() { + // don't append '-f' return } } + // append '-f', indicating that arguments to this flag are *not* meant to be file paths completion.WriteString(" -f") } From 7f005015c80472e56697d0e8b5abf9b59f9f0731 Mon Sep 17 00:00:00 2001 From: Igor Artasevych Date: Thu, 22 Oct 2020 10:00:45 +0300 Subject: [PATCH 14/74] Update altsrc/map_input_source.go Co-authored-by: Robert Liebowitz --- altsrc/map_input_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index b144cf64c0..117461f5a0 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -16,7 +16,7 @@ type MapInputSource struct { valueMap map[interface{}]interface{} } -//NewMapInputSource create a new MapInputSource type +// NewMapInputSource creates a new MapInputSource for implementing custom input sources. func NewMapInputSource(file string, valueMap map[interface{}]interface{}) *MapInputSource { return &MapInputSource{file: file, valueMap: valueMap} } From 5c8d915be7a2444901068fb08544b0b405bae178 Mon Sep 17 00:00:00 2001 From: Erin Call Date: Thu, 22 Oct 2020 14:22:01 -0700 Subject: [PATCH 15/74] Revert "Refactor fishAddFileFlag for better flexibility [#1156]" This reverts commit fcce511478c1aabd0e4a6af313acd74a89aaa21c. --- fish.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/fish.go b/fish.go index 474c5c7be1..588e070eab 100644 --- a/fish.go +++ b/fish.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "io" - "reflect" "strings" "text/template" ) @@ -159,17 +158,24 @@ func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string } func fishAddFileFlag(flag Flag, completion *strings.Builder) { - val := reflect.ValueOf(flag) - // if flag is a non-nil pointer to a struct... - if val.Kind() != reflect.Invalid && val.Elem().Kind() == reflect.Struct { - field := val.Elem().FieldByName("TakesFile") - // if flag's underlying type has a bool field called TakesFile, whose value is true... - if field.Kind() == reflect.Bool && field.Bool() { - // don't append '-f' + switch f := flag.(type) { + case *GenericFlag: + if f.TakesFile { + return + } + case *StringFlag: + if f.TakesFile { + return + } + case *StringSliceFlag: + if f.TakesFile { + return + } + case *PathFlag: + if f.TakesFile { return } } - // append '-f', indicating that arguments to this flag are *not* meant to be file paths completion.WriteString(" -f") } From 2a5a092888029d130b40e2849aab312543df91cb Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sat, 31 Oct 2020 00:08:57 +0530 Subject: [PATCH 16/74] Add Support for Go 1.15 And also drop support for Go 1.12 in line with Go's Release Policy https://golang.org/doc/devel/release.html#policy --- .github/workflows/cli.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index d79a31438f..9fa056ea16 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.12, 1.13, 1.14] + go: [1.13, 1.14, 1.15] 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.14 && matrix.os == 'ubuntu-latest' + if: matrix.go == 1.15 && matrix.os == 'ubuntu-latest' run: test -z $(gofmt -l .) - name: vet @@ -62,10 +62,10 @@ jobs: name: test-docs runs-on: ubuntu-latest steps: - - name: Set up Go 1.14 + - name: Set up Go 1.15 uses: actions/setup-go@v1 with: - go-version: 1.14 + go-version: 1.15 - name: Use Node.js 12.x uses: actions/setup-node@v1 From 06e570991ca9a60e3d959d8865b6605ce1de99d7 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sat, 31 Oct 2020 00:14:08 +0530 Subject: [PATCH 17/74] Reduce desiredMinBinarySize Updating the Go version has also optimized the binary size, so the desiredMinBinarySize is being updated to be 1.9 --- internal/build/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/build/build.go b/internal/build/build.go index 197cfa5b60..d0be855ebc 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -193,7 +193,7 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { cliBuiltFilePath = "./internal/example-cli/built-example" helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go" helloBuiltFilePath = "./internal/example-hello-world/built-example" - desiredMinBinarySize = 2.0 + desiredMinBinarySize = 1.9 desiredMaxBinarySize = 2.1 badNewsEmoji = "🚨" goodNewsEmoji = "✨" From e40b83a394281160fc853b8a7cec5b591275cb5d Mon Sep 17 00:00:00 2001 From: Alexander Frolov <9749087+fxrlv@users.noreply.github.com> Date: Tue, 3 Nov 2020 13:22:01 +0300 Subject: [PATCH 18/74] use Name as default to HelpName --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index f801fce910..779733ee68 100644 --- a/app.go +++ b/app.go @@ -140,7 +140,7 @@ func (a *App) Setup() { } if a.HelpName == "" { - a.HelpName = filepath.Base(os.Args[0]) + a.HelpName = a.Name } if a.Usage == "" { From 7c756af2b40ecdd2e547dd656c7e5a55b1ea097b Mon Sep 17 00:00:00 2001 From: Benjamin Kane Date: Fri, 27 Nov 2020 21:30:44 -0800 Subject: [PATCH 19/74] Add link to pkg.go.dev 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 e99afc1fb3..94febaf2f8 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -45,7 +45,7 @@ cli v2 manual 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). +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. ## Getting Started From ea14b62eccd2f92b573a9bfbe1b410bffce542e6 Mon Sep 17 00:00:00 2001 From: Madhur batra Date: Tue, 1 Dec 2020 13:20:17 +0530 Subject: [PATCH 20/74] Issue #1066: Display `(accepts multiple inputs)` for SliceFlags in the help description. --- flag.go | 6 +++++- flag_test.go | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/flag.go b/flag.go index ad97c2d058..aff8d5be63 100644 --- a/flag.go +++ b/flag.go @@ -359,7 +359,11 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault) + multiInputString := "(accepts multiple inputs)" + if usageWithDefault != "" { + multiInputString = "\t" + multiInputString + } + return fmt.Sprintf("%s\t%s%s", prefixedNames(names, placeholder), usageWithDefault, multiInputString) } func hasFlag(flags []Flag, fl Flag) bool { diff --git a/flag_test.go b/flag_test.go index b1fe70a4fd..cf7d0cc22a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -340,11 +340,11 @@ var stringSliceFlagTests = []struct { value *StringSlice expected string }{ - {"foo", nil, NewStringSlice(""), "--foo value\t"}, - {"f", nil, NewStringSlice(""), "-f value\t"}, - {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"}, - {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"}, - {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, + {"foo", nil, NewStringSlice(""), "--foo value\t(accepts multiple inputs)"}, + {"f", nil, NewStringSlice(""), "-f value\t(accepts multiple inputs)"}, + {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")\t(accepts multiple inputs)"}, + {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")\t(accepts multiple inputs)"}, + {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")\t(accepts multiple inputs)"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { @@ -630,9 +630,9 @@ var intSliceFlagTests = []struct { value *IntSlice expected string }{ - {"heads", nil, NewIntSlice(), "--heads value\t"}, - {"H", nil, NewIntSlice(), "-H value\t"}, - {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"}, + {"heads", nil, NewIntSlice(), "--heads value\t(accepts multiple inputs)"}, + {"H", nil, NewIntSlice(), "-H value\t(accepts multiple inputs)"}, + {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)\t(accepts multiple inputs)"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { @@ -680,10 +680,10 @@ var int64SliceFlagTests = []struct { value *Int64Slice expected string }{ - {"heads", nil, NewInt64Slice(), "--heads value\t"}, - {"H", nil, NewInt64Slice(), "-H value\t"}, + {"heads", nil, NewInt64Slice(), "--heads value\t(accepts multiple inputs)"}, + {"H", nil, NewInt64Slice(), "-H value\t(accepts multiple inputs)"}, {"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)), - "--heads value, -H value\t(default: 2, 17179869184)"}, + "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, } func TestInt64SliceFlagHelpOutput(t *testing.T) { @@ -771,10 +771,10 @@ var float64SliceFlagTests = []struct { value *Float64Slice expected string }{ - {"heads", nil, NewFloat64Slice(), "--heads value\t"}, - {"H", nil, NewFloat64Slice(), "-H value\t"}, + {"heads", nil, NewFloat64Slice(), "--heads value\t(accepts multiple inputs)"}, + {"H", nil, NewFloat64Slice(), "-H value\t(accepts multiple inputs)"}, {"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5), - "--heads value, -H value\t(default: 0.1234, -10.5)"}, + "--heads value, -H value\t(default: 0.1234, -10.5)\t(accepts multiple inputs)"}, } func TestFloat64SliceFlagHelpOutput(t *testing.T) { From d5643c884a0c70a2b995f757aa28e31693615d5d Mon Sep 17 00:00:00 2001 From: Joe Gregorio Date: Sun, 6 Dec 2020 10:00:13 -0500 Subject: [PATCH 21/74] Fix workflows/cli.yml. The ::set-env and ::add-path commands no longer work and need to to be replaced. See: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/ --- .github/workflows/cli.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 9fa056ea16..06dbb80836 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -11,7 +11,6 @@ on: - v1 jobs: - test: strategy: matrix: @@ -27,10 +26,10 @@ jobs: - name: Set GOPATH, PATH and ENV run: | - echo "::set-env name=GOPATH::$(dirname $GITHUB_WORKSPACE)" - echo "::set-env name=GO111MODULE::on" - echo "::set-env name=GOPROXY::https://proxy.golang.org" - echo "::add-path::$(dirname $GITHUB_WORKSPACE)/bin" + echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV + echo "GO111MODULE=on" >> $GITHUB_ENV + echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV + echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH shell: bash - name: Checkout Code @@ -74,10 +73,10 @@ jobs: - name: Set GOPATH, PATH and ENV run: | - echo "::set-env name=GOPATH::$(dirname $GITHUB_WORKSPACE)" - echo "::set-env name=GO111MODULE::on" - echo "::set-env name=GOPROXY::https://proxy.golang.org" - echo "::add-path::$(dirname $GITHUB_WORKSPACE)/bin" + echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV + echo "GO111MODULE=on" >> $GITHUB_ENV + echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV + echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH shell: bash - name: Checkout Code From 2144cc5ad452f481b653ad978718c06235f421c6 Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Wed, 9 Dec 2020 11:17:21 +0000 Subject: [PATCH 22/74] fix for parent context not setting child flags --- flag_int64_slice.go | 5 ++++- flag_int_slice.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 6c7fd9376d..2c9a15af3e 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -145,7 +145,10 @@ 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 { - return lookupInt64Slice(name, c.flagSet) + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64Slice(name, fs) + } + return nil } func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { diff --git a/flag_int_slice.go b/flag_int_slice.go index 4e0afc0210..a73ca6b81c 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -157,7 +157,7 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { // nil if not found func (c *Context) IntSlice(name string) []int { if fs := lookupFlagSet(name, c); fs != nil { - return lookupIntSlice(name, c.flagSet) + return lookupIntSlice(name, fs) } return nil } From a27ce0e881cf595969a2ff74beb7b6d5727f987c Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Wed, 9 Dec 2020 11:48:33 +0000 Subject: [PATCH 23/74] unable to see whats happening here with lack of debug but this seems to resolve the failing doc test --- 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 e99afc1fb3..fc3c1ba094 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -649,7 +649,7 @@ func main() { app := &cli.App{ Action: func(c *cli.Context) error { - fmt.Println("yaml ist rad") + fmt.Println("--test value.*default: 0") return nil }, Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), From 55992853bf271b53549dc850f70344ffc9d9f3ea Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Wed, 9 Dec 2020 11:59:50 +0000 Subject: [PATCH 24/74] add flag tests --- flag_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/flag_test.go b/flag_test.go index b1fe70a4fd..99e6ea563b 100644 --- a/flag_test.go +++ b/flag_test.go @@ -674,6 +674,29 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } +func TestIntSliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &IntSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewIntSlice(1, 2, 3)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []int{1, 2, 3} + if !reflect.DeepEqual(ctx.IntSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("numbers")) + } + if !reflect.DeepEqual(ctx.IntSlice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + var int64SliceFlagTests = []struct { name string aliases []string @@ -716,6 +739,29 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestInt64SliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &Int64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewInt64Slice(1, 2, 3)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []int64{1, 2, 3} + if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers")) + } + if !reflect.DeepEqual(ctx.Int64Slice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + var float64FlagTests = []struct { name string expected string From 02f3866db27692ef9bf87fbb183ec72ad4e5a799 Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Wed, 9 Dec 2020 12:26:08 +0000 Subject: [PATCH 25/74] add nother test --- flag_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/flag_test.go b/flag_test.go index 99e6ea563b..5d94992afe 100644 --- a/flag_test.go +++ b/flag_test.go @@ -697,6 +697,22 @@ func TestIntSliceFlagApply_ParentContext(t *testing.T) { }).Run([]string{"run", "child"}) } +func TestIntSliceFlag_SetFromParentContext(t *testing.T) { + fl := &IntSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewIntSlice(1, 2, 3, 4)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []int{1, 2, 3, 4} + if !reflect.DeepEqual(ctx.IntSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("numbers")) + } +} + var int64SliceFlagTests = []struct { name string aliases []string From ebe2c0ea70cee5e140c2a21670dbfc6fa5307ce6 Mon Sep 17 00:00:00 2001 From: Andrew Nicoll Date: Wed, 9 Dec 2020 12:27:56 +0000 Subject: [PATCH 26/74] add for int64 --- flag_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/flag_test.go b/flag_test.go index 5d94992afe..073e74ebab 100644 --- a/flag_test.go +++ b/flag_test.go @@ -778,6 +778,22 @@ func TestInt64SliceFlagApply_ParentContext(t *testing.T) { }).Run([]string{"run", "child"}) } +func TestInt64SliceFlag_SetFromParentContext(t *testing.T) { + fl := &Int64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewInt64Slice(1, 2, 3, 4)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []int64{1, 2, 3, 4} + 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 expected string From 57d60cd57026a7f5a90b32f854a4b3674ca1b2ae Mon Sep 17 00:00:00 2001 From: Wlad Gumenyuk <50965068+wgumenyuk@users.noreply.github.com> Date: Sun, 27 Dec 2020 18:30:11 +0100 Subject: [PATCH 27/74] Fix: typo Corrected "eror" to "error" on line 331. --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 779733ee68..2c97251650 100644 --- a/app.go +++ b/app.go @@ -328,7 +328,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { // RunAndExitOnError calls .Run() and exits non-zero if an error was returned // // Deprecated: instead you should return an error that fulfills cli.ExitCoder -// to cli.App.Run. This will cause the application to exit with the given eror +// to cli.App.Run. This will cause the application to exit with the given error // code in the cli.ExitCoder func (a *App) RunAndExitOnError() { if err := a.Run(os.Args); err != nil { From 17032bc33c126b3f837c54b514034ea16dfd0486 Mon Sep 17 00:00:00 2001 From: Travis Lane Date: Sun, 24 Jan 2021 19:49:13 -0500 Subject: [PATCH 28/74] fix: Propagate App.Reader to subcommands This change copies the `Reader` set in `App` to the new `App` created for subcommands. I've also added a basic test to demonstrate the issue. --- app_test.go | 33 +++++++++++++++++++++++++++++++++ command.go | 1 + 2 files changed, 34 insertions(+) diff --git a/app_test.go b/app_test.go index 57c850d1ed..651f0f4f5d 100644 --- a/app_test.go +++ b/app_test.go @@ -897,6 +897,39 @@ func TestApp_SetStdin(t *testing.T) { } } +func TestApp_SetStdin_Subcommand(t *testing.T) { + buf := make([]byte, 12) + + app := &App{ + Name: "test", + Reader: strings.NewReader("Hello World!"), + Commands: []*Command{ + { + Name: "command", + Subcommands: []*Command{ + { + Name: "subcommand", + Action: func(c *Context) error { + _, err := c.App.Reader.Read(buf) + return err + }, + }, + }, + }, + }, + } + + err := app.Run([]string{"test", "command", "subcommand"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if string(buf) != "Hello World!" { + t.Error("App did not read input from desired reader.") + } +} + func TestApp_SetStdout(t *testing.T) { var w bytes.Buffer diff --git a/command.go b/command.go index dda2f49a0a..3503a555b0 100644 --- a/command.go +++ b/command.go @@ -243,6 +243,7 @@ func (c *Command) startApp(ctx *Context) error { app.Version = ctx.App.Version app.HideVersion = true app.Compiled = ctx.App.Compiled + app.Reader = ctx.App.Reader app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter app.ExitErrHandler = ctx.App.ExitErrHandler From 1f380b0733a39bd3166dfe6e2564c54312163bf7 Mon Sep 17 00:00:00 2001 From: Travis Lane Date: Sun, 24 Jan 2021 20:54:48 -0500 Subject: [PATCH 29/74] fix: Fixup runnable example input arguments This command has no name, so drop the `test-cmd` in `args`. --- docs/v1/manual.md | 2 +- docs/v2/manual.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/v1/manual.md b/docs/v1/manual.md index 05ea37032e..dd22bdbdc7 100644 --- a/docs/v1/manual.md +++ b/docs/v1/manual.md @@ -612,7 +612,7 @@ given sources. Here is a more complete sample of a command using YAML support: ``` go diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 94febaf2f8..2243c6d86e 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 30/74] 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 408668bc3e..b1cbb05cfb 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 31/74] Update stale.yml --- .github/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/stale.yml b/.github/stale.yml index a3282a2727..0cc30077be 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 32/74] add test for nil --- flag_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/flag_test.go b/flag_test.go index 073e74ebab..e00fd9f31c 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 33/74] 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 b1cbb05cfb..2b74f1f2c9 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 34/74] 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 dc16fc82d4..4c0e1f677c 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 46e38dc196..fac106ff30 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 31c03f81c7..317cc8817d 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 35/74] 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 74ed51912e..65e0d1e4cb 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 61d6268452..335b071790 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 36/74] 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 335b071790..35feefedec 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 37/74] remove repeated comment --- app_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/app_test.go b/app_test.go index 651f0f4f5d..7c38f60489 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 38/74] 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 2d5e149b43..afdca418ca 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 706ee6cd4b..b625ca1756 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 2c9a15af3e..d2352c08fa 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 a73ca6b81c..018d56b625 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 35497032cb..0b45ab5044 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 b3b0d7c587..0f8e12adef 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 39/74] 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 b3b0d7c587..1cefe8286b 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 0382a6b9dc..da95512a9d 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 9217e89e8e..9ecd8e18ae 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 40/74] 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 3503a555b0..97c151b3d7 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 41/74] 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 aff8d5be63..45a9e9e4b5 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 bc9ea35d08..fc829b0160 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 22a2e67201..6c2055e606 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 91c778c873..d5c62f9044 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 706ee6cd4b..b94c66255b 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 b0c8ff44d2..769c017efc 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 ac39d4a9e4..d0adfe661b 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 e09991269b..851cc36871 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 2c9a15af3e..a4afac3b12 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 a73ca6b81c..b26c42baa1 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 8070dc4b0b..2cc6ead104 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 400bb532e7..97c4707faa 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 35497032cb..4cc7ee4d77 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 0382a6b9dc..2198d10dad 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 2e5e76b0ea..9081e4a8cb 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 8fc3289d82..5505e57896 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 42/74] 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 2c97251650..5c616e6f51 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 3503a555b0..9a6b877153 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 65e0d1e4cb..94cbb65909 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 35feefedec..b37876c033 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 751ef9b166..8f641fb64f 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 aff8d5be63..a693386524 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 bc9ea35d08..85270e44ba 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 22a2e67201..7b59a38651 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 91c778c873..d2a6458657 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 706ee6cd4b..49a04d4099 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 b0c8ff44d2..d6800a858d 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 ac39d4a9e4..e9da3fa47c 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 e09991269b..6c5545883c 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 2c9a15af3e..773ef8a6d2 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 a73ca6b81c..8feef5f343 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 8070dc4b0b..37c6f27d35 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 400bb532e7..a43f7c20cb 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 35497032cb..3934a60624 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 0382a6b9dc..cff027dcda 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 2e5e76b0ea..6730e696ff 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 8fc3289d82..4af65fa9e4 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 43/74] 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 06dbb80836..edeebdf765 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 44/74] 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 4c0e1f677c..021cc2b5b2 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 fac106ff30..962c366640 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 2bea50731e..3ac35539cb 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 f272274061..7b8c0d7e9e 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 f272274061..7b8c0d7e9e 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 2994593e8a..ebd0416ecd 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 dc41e5aa9a..cc449c5f46 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 45/74] 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 407c269173..8dd262d56c 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 317cc8817d..69e040e7fb 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 46/74] Resolved a grammatical error (#1281) --- funcs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funcs.go b/funcs.go index 474c48faf9..842b4aa99c 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 47/74] 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 962c366640..5308f372f7 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 69e040e7fb..708a402813 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 3ac35539cb..3df4684a2d 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 7b8c0d7e9e..f3374e5b1c 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 7b8c0d7e9e..f3374e5b1c 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 adaedb43e0..1c4fd075ed 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 ebd0416ecd..cf766ad9ca 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 0000000000..da31b38ef1 --- /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 48/74] 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 f752ad7534..385732e17c 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 b4c8bc1496..f5c6939639 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 d4889b382c..94c668e9f1 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 e46270d034..c563d6f346 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 49/74] 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 0cc30077be..0000000000 --- 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 50/74] 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 af09010b9a..56be65bb68 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"}` From 12b9c9d42002b6ad33e8572430ab446448c117fb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 19 Apr 2022 16:32:23 -0400 Subject: [PATCH 64/74] Bump matrix of supported Go versions to test on the latest release (^1.18) and drop testing/supporting ^1.15. --- .github/workflows/cli.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index e3b7a9773a..2752ec2a20 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.15, 1.16, 1.17] + go: [^1.16, ^1.17, ^1.18] 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.17 && matrix.os == 'ubuntu-latest' + if: matrix.go == '^1.17' && matrix.os == 'ubuntu-latest' run: test -z $(gofmt -l .) - name: vet @@ -51,7 +51,7 @@ jobs: run: go run internal/build/build.go check-binary-size - name: Upload coverage to Codecov - if: success() && matrix.go == 1.17 && matrix.os == 'ubuntu-latest' + if: success() && matrix.go == '^1.17' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v1 with: fail_ci_if_error: true @@ -63,8 +63,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v1 with: - # Currently fails on 1.16+ - go-version: 1.15 + go-version: ^1.18 - name: Use Node.js 12.x uses: actions/setup-node@v1 From e61b99e19a022a8db3ac0538518c781676f06362 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 19 Apr 2022 16:51:13 -0400 Subject: [PATCH 65/74] Add Stale bot configuration per docs with (greatly) extended values for `daysUntilStale` and `daysUntilClose` per suggestion. --- .github/stale.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000..f050e9b7ae --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 365 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 90 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue 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. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false From d7bc33018bd2b0f391def3e9c31293d835747663 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 19 Apr 2022 16:57:25 -0400 Subject: [PATCH 66/74] Drop references/usage of GO111MODULE --- .github/workflows/cli.yml | 2 -- README.md | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index e3b7a9773a..e80305856d 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -27,7 +27,6 @@ jobs: - name: Set GOPATH, PATH and ENV run: | echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV - echo "GO111MODULE=on" >> $GITHUB_ENV echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH shell: bash @@ -74,7 +73,6 @@ jobs: - name: Set GOPATH, PATH and ENV run: | echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV - echo "GO111MODULE=on" >> $GITHUB_ENV echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH shell: bash diff --git a/README.md b/README.md index 2b74f1f2c9..eab737192a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Go Modules are required when using this package. [See the go blog guide on using ### Using `v2` releases ``` -$ GO111MODULE=on go get github.com/urfave/cli/v2 +$ go get github.com/urfave/cli/v2 ``` ```go @@ -44,7 +44,7 @@ import ( ### Using `v1` releases ``` -$ GO111MODULE=on go get github.com/urfave/cli +$ go get github.com/urfave/cli ``` ```go From 17aa508d22ae7997bc9b650556f27caa0f24265c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 20 Apr 2022 14:38:32 -0400 Subject: [PATCH 67/74] Switch to gfmrun v1.3.0 --- .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 2752ec2a20..8edeb914bf 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -29,7 +29,7 @@ jobs: echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV echo "GO111MODULE=on" >> $GITHUB_ENV echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV - echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH + echo "${GITHUB_WORKSPACE}/.local/bin" >> $GITHUB_PATH shell: bash - name: Checkout Code @@ -75,7 +75,7 @@ jobs: echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV echo "GO111MODULE=on" >> $GITHUB_ENV echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV - echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH + echo "${GITHUB_WORKSPACE}/.local/bin" >> $GITHUB_PATH shell: bash - name: Checkout Code @@ -85,9 +85,9 @@ jobs: - name: Install Dependencies run: | - mkdir -p $GOPATH/bin - curl -L -o $GOPATH/bin/gfmrun "https://github.com/urfave/gfmrun/releases/download/v1.2.14/gfmrun-$(go env GOOS)-amd64-v1.2.14" - chmod +x $GOPATH/bin/gfmrun + mkdir -p "${GITHUB_WORKSPACE}/.local/bin" && \ + curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env goarch)-v1.3.0" && \ + chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" && \ npm install -g markdown-toc@1.2.0 - name: Run Tests (v1) From 8c33a078d1fd99ad15144ff275f4cc767301b806 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 20 Apr 2022 14:41:26 -0400 Subject: [PATCH 68/74] Use `go env` correctly --- .github/workflows/cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 8edeb914bf..480a4a0368 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -86,7 +86,7 @@ jobs: - name: Install Dependencies run: | mkdir -p "${GITHUB_WORKSPACE}/.local/bin" && \ - curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env goarch)-v1.3.0" && \ + curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" && \ chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" && \ npm install -g markdown-toc@1.2.0 From 5c6ccfb097f10c64582ed943d52d9cac21dd5106 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 20 Apr 2022 14:45:04 -0400 Subject: [PATCH 69/74] Add missing `"fmt"` import in example --- .gitignore | 1 + docs/v2/manual.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index afdca418ca..e0c50aba49 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ vendor .idea internal/*/built-example coverage.txt +/.local/ *.exe diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 56be65bb68..b480dd685f 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -674,8 +674,10 @@ Take for example this app that requires the `lang` flag: package main import ( + "fmt" "log" "os" + "github.com/urfave/cli/v2" ) From e63054a42e87bc5710b8911d8650f33f8cde2027 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 21 Apr 2022 15:07:45 -0400 Subject: [PATCH 70/74] Attempting to soften the barrier to entry and also removing issue title formatting that overlaps with label functionality --- .github/ISSUE_TEMPLATE/question.md | 8 ++-- .github/ISSUE_TEMPLATE/v1-bug-report.md | 39 +++++++++++++------- .github/ISSUE_TEMPLATE/v2-bug-report.md | 39 +++++++++++++------- .github/ISSUE_TEMPLATE/v2-feature-request.md | 16 +++++--- .github/pull_request_template.md | 13 +++++-- 5 files changed, 72 insertions(+), 43 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index bca4fea10d..b9680757c2 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -1,12 +1,10 @@ --- name: ask a question -about: ask us question - assume stackoverflow's guidelines apply here -title: 'q: ( your question title goes here )' +about: ask a question - assume stackoverflow's guidelines apply here +title: your question title goes here labels: 'kind/question, status/triage, area/v2' assignees: '' --- -## my question is... - -_**( Put the question text here )**_ +my question is... diff --git a/.github/ISSUE_TEMPLATE/v1-bug-report.md b/.github/ISSUE_TEMPLATE/v1-bug-report.md index aabfe820a6..b37f40e590 100644 --- a/.github/ISSUE_TEMPLATE/v1-bug-report.md +++ b/.github/ISSUE_TEMPLATE/v1-bug-report.md @@ -1,28 +1,32 @@ --- name: v1 bug report about: Create a report to help us fix v1 bugs -title: 'v1 bug: ( your bug title goes here )' +title: 'your bug title goes here' labels: 'kind/bug, status/triage, area/v1' assignees: '' --- -## my urfave/cli version is +## My urfave/cli version is _**( Put the version of urfave/cli that you are using here )**_ ## Checklist -* [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases). -* [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/master/docs/v1/manual.md) -* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. +- [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases). +- [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/master/docs/v1/manual.md). +- [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. ## Dependency Management -- [ ] My project is using go modules. -- [ ] My project is using vendoring. -- [ ] My project is automatically downloading the latest version. -- [ ] I am unsure of what my dependency management setup is. + + +- My project is using go modules. +- My project is using vendoring. +- My project is automatically downloading the latest version. +- I am unsure of what my dependency management setup is. ## Describe the bug @@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior ## Observed behavior -What did you see happen immediately after the reproduction steps above? +What did you see happen immediately after the reproduction steps +above? ## Expected behavior -What would you have expected to happen immediately after the reproduction steps above? +What would you have expected to happen immediately after the +reproduction steps above? ## Additional context Add any other context about the problem here. -If the issue relates to a specific open source Github repo, please link that repo here. +If the issue relates to a specific open source Github repo, please +link that repo here. -If you can reproduce this issue with a public CI system, please link a failing build here. +If you can reproduce this issue with a public CI system, please +link a failing build here. ## Want to fix this yourself? -We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it. You'll want to base the PR off the `v1` branch, all `v1` bug fix releases will be made from that branch. +We'd love to have more contributors on this project! If the fix for +this bug is easily explained and very small, free free to create a +pull request for it. You'll want to base the PR off the `v1` +branch, all `v1` bug fix releases will be made from that branch. ## Run `go version` and paste its output here diff --git a/.github/ISSUE_TEMPLATE/v2-bug-report.md b/.github/ISSUE_TEMPLATE/v2-bug-report.md index 9944769b3e..6561ebf9ca 100644 --- a/.github/ISSUE_TEMPLATE/v2-bug-report.md +++ b/.github/ISSUE_TEMPLATE/v2-bug-report.md @@ -1,28 +1,32 @@ --- name: v2 bug report about: Create a report to help us fix v2 bugs -title: 'v2 bug: ( your bug title goes here )' +title: 'your bug title goes here' labels: 'kind/bug, area/v2, status/triage' assignees: '' --- -## my urfave/cli version is +## My urfave/cli version is _**( Put the version of urfave/cli that you are using here )**_ ## Checklist -* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases). -* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md) -* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. +- [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases). +- [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md) +- [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. ## Dependency Management -- [ ] My project is using go modules. -- [ ] My project is using vendoring. -- [ ] My project is automatically downloading the latest version. -- [ ] I am unsure of what my dependency management setup is. + + +- My project is using go modules. +- My project is using vendoring. +- My project is automatically downloading the latest version. +- I am unsure of what my dependency management setup is. ## Describe the bug @@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior ## Observed behavior -What did you see happen immediately after the reproduction steps above? +What did you see happen immediately after the reproduction steps +above? ## Expected behavior -What would you have expected to happen immediately after the reproduction steps above? +What would you have expected to happen immediately after the +reproduction steps above? ## Additional context Add any other context about the problem here. -If the issue relates to a specific open source Github repo, please link that repo here. +If the issue relates to a specific open source Github repo, please +link that repo here. -If you can reproduce this issue with a public CI system, please link a failing build here. +If you can reproduce this issue with a public CI system, please +link a failing build here. ## Want to fix this yourself? -We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it. +We'd love to have more contributors on this project! If the fix for +this bug is easily explained and very small, free free to create a +pull request for it. + ## Run `go version` and paste its output here ``` diff --git a/.github/ISSUE_TEMPLATE/v2-feature-request.md b/.github/ISSUE_TEMPLATE/v2-feature-request.md index bc527b1c6e..e830446926 100644 --- a/.github/ISSUE_TEMPLATE/v2-feature-request.md +++ b/.github/ISSUE_TEMPLATE/v2-feature-request.md @@ -1,7 +1,7 @@ --- name: v2 feature request about: Suggest an improvement for v2 -title: 'v2 feature: ( your feature title goes here )' +title: 'your feature title goes here' labels: 'type/feature, area/v2, status/triage' assignees: '' @@ -10,16 +10,19 @@ assignees: '' ## Checklist * [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases). -* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md) +* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md). * [ ] Did you perform a search about this feature? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. ## What problem does this solve? A clear and concise description of what problem this feature would solve. For example: -- needing to type out the full flag name takes a long time, so I would like to suggest adding auto-complete -- I use (osx, windows, linux) and would like support for (some existing feature) to be extended to my platform -- the terminal output for a particular error case is confusing, and I think it could be improved +- needing to type out the full flag name takes a long time, so I + would like to suggest adding auto-complete +- I use (osx, windows, linux) and would like support for (some + existing feature) to be extended to my platform +- the terminal output for a particular error case is confusing, and + I think it could be improved ## Solution description @@ -27,4 +30,5 @@ A detailed description of what you want to happen. ## Describe alternatives you've considered -A clear and concise description of any alternative solutions or features you've considered. +A clear and concise description of any alternative solutions or +features you've considered. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4e76725765..47348f85ca 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,10 +8,14 @@ _(REQUIRED)_ -- [ ] bug -- [ ] cleanup -- [ ] documentation -- [ ] feature + + +- bug +- cleanup +- documentation +- feature ## What this PR does / why we need it: @@ -28,6 +32,7 @@ _(REQUIRED)_ ## Which issue(s) this PR fixes: _(REQUIRED)_ +