From a6f5ca69f4df8bffe894af051c571e267fcb45c5 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Mon, 3 Oct 2022 18:53:26 +0300 Subject: [PATCH 01/24] wrap: Avoid trailing whitespace for empty lines --- help.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/help.go b/help.go index ba5f803176..77be2b060a 100644 --- a/help.go +++ b/help.go @@ -476,7 +476,9 @@ func wrap(input string, offset int, wrapAt int) string { for i, line := range lines { if i != 0 { - sb.WriteString(padding) + if len(line) > 0 { + sb.WriteString(padding) + } } sb.WriteString(wrapLine(line, offset, wrapAt, padding)) From d3bb381a0bd5b6e049ac8bdb8fabaf27802d5aaf Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Mon, 3 Oct 2022 19:05:46 +0300 Subject: [PATCH 02/24] Fix test for removed trailing whitespace --- help_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/help_test.go b/help_test.go index 5a50586483..9c99b98913 100644 --- a/help_test.go +++ b/help_test.go @@ -1276,7 +1276,7 @@ DESCRIPTION: App.Description string long enough that it should be wrapped in this test - + with a newline and an indented line @@ -1294,8 +1294,8 @@ COPYRIGHT: that it should be wrapped. Including newlines. And also indented lines. - - + + And then another long line. Blah blah blah does anybody ever read these things? From 05fb755b6a135b2702bc480cba1d9a975727dc7b Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Tue, 4 Oct 2022 10:30:31 +0300 Subject: [PATCH 03/24] wrap: Simplify loop logic Suggested by @julian7 --- help.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/help.go b/help.go index 77be2b060a..697f0e174b 100644 --- a/help.go +++ b/help.go @@ -475,10 +475,14 @@ func wrap(input string, offset int, wrapAt int) string { padding := strings.Repeat(" ", offset) for i, line := range lines { + if line == "" { + sb.WriteString("\n") + continue + } + + // the first line is not indented if i != 0 { - if len(line) > 0 { - sb.WriteString(padding) - } + sb.WriteString(padding) } sb.WriteString(wrapLine(line, offset, wrapAt, padding)) From e14dca7a181fdda99f95833f1cd1df315ff5d607 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Tue, 4 Oct 2022 10:40:11 +0300 Subject: [PATCH 04/24] Run `go fmt` --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index 697f0e174b..5db669665c 100644 --- a/help.go +++ b/help.go @@ -479,7 +479,7 @@ func wrap(input string, offset int, wrapAt int) string { sb.WriteString("\n") continue } - + // the first line is not indented if i != 0 { sb.WriteString(padding) From 4959a9fa9bd327058c66be0f1d782ec0e70d3965 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Tue, 4 Oct 2022 13:13:52 +0300 Subject: [PATCH 05/24] Refactor `wrap()` and add test for empty line --- help.go | 23 ++++++++++------------- help_test.go | 7 +++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/help.go b/help.go index 5db669665c..6929a226ab 100644 --- a/help.go +++ b/help.go @@ -468,7 +468,7 @@ func nindent(spaces int, v string) string { } func wrap(input string, offset int, wrapAt int) string { - var sb strings.Builder + var ss []string lines := strings.Split(input, "\n") @@ -476,23 +476,20 @@ func wrap(input string, offset int, wrapAt int) string { for i, line := range lines { if line == "" { - sb.WriteString("\n") - continue - } - - // the first line is not indented - if i != 0 { - sb.WriteString(padding) - } + ss = append(ss, line) + } else { + wrapped := wrapLine(line, offset, wrapAt, padding) + if i == 0 { + ss = append(ss, wrapped) + } else { + ss = append(ss, padding+wrapped) - sb.WriteString(wrapLine(line, offset, wrapAt, padding)) + } - if i != len(lines)-1 { - sb.WriteString("\n") } } - return sb.String() + return strings.Join(ss, "\n") } func wrapLine(input string, offset int, wrapAt int, padding string) string { diff --git a/help_test.go b/help_test.go index 9c99b98913..d7cdfa2d8f 100644 --- a/help_test.go +++ b/help_test.go @@ -1213,6 +1213,13 @@ func TestDefaultCompleteWithFlags(t *testing.T) { } } +func TestWrap(t *testing.T) { + emptywrap := wrap("", 4, 16) + if emptywrap != "" { + t.Errorf("Wrapping empty line should return empty line. Got '%s'.", emptywrap) + } +} + func TestWrappedHelp(t *testing.T) { // Reset HelpPrinter after this test. From 82ea9f70c03602d9bf7a4d7d07e789c757c5bdc8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 30 Sep 2022 15:01:22 +0200 Subject: [PATCH 06/24] Call FlagStringer in String() method of slice flags The default help template relies on the String() method of Flag to render the flag. For most flag types, String() indirects through FlagStringer, so that is the best place to customize flag rendering. FlagStringer was not called for slice flags because their help output differs from other flags in two ways: there can be multiple default values, and the flag name is shown two times to indicate that the flag can be specified multiple times. To make multiple values work in the FlagStringer, I simply changed GetValue() to return all values. Showing the flag more than once is achieved through a new interface, DocGenerationSliceFlag, which the FlagStringer uses to decide whether the flag is a slice flag type. --- flag.go | 29 +++++++++++++---------------- flag_float64_slice.go | 28 ++++++++++++---------------- flag_int64_slice.go | 27 ++++++++++++--------------- flag_int_slice.go | 27 ++++++++++++--------------- flag_string_slice.go | 31 ++++++++++++++----------------- flag_test.go | 20 ++++++++++---------- flag_uint64_slice.go | 27 ++++++++++++--------------- flag_uint_slice.go | 27 ++++++++++++--------------- 8 files changed, 97 insertions(+), 119 deletions(-) diff --git a/flag.go b/flag.go index aade1de2e8..6aff19b509 100644 --- a/flag.go +++ b/flag.go @@ -126,6 +126,14 @@ type Flag interface { RunAction(*Context) error } +// DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. +type DocGenerationSliceFlag interface { + DocGenerationFlag + + // IsSliceFlag returns true for flags that can be given multiple times. + IsSliceFlag() bool +} + // Countable is an interface to enable detection of flag values which support // repetitive flags type Countable interface { @@ -300,24 +308,13 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return withEnvHint(f.GetEnvVars(), - fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) -} - -func stringifySliceFlag(usage string, names, defaultVals []string) string { - placeholder, usage := unquoteUsage(usage) - if placeholder == "" { - placeholder = defaultPlaceholder - } - - defaultVal := "" - if len(defaultVals) > 0 { - defaultVal = fmt.Sprintf(formatDefault("%s"), strings.Join(defaultVals, ", ")) + pn := prefixedNames(df.Names(), placeholder) + sliceFlag, ok := f.(DocGenerationSliceFlag) + if ok && sliceFlag.IsSliceFlag() { + pn = pn + " [ " + pn + " ]" } - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - pn := prefixedNames(names, placeholder) - return fmt.Sprintf("%s [ %s ]\t%s", pn, pn, usageWithDefault) + return withEnvHint(df.GetEnvVars(), fmt.Sprintf("%s\t%s", pn, usageWithDefault)) } func hasFlag(flags []Flag, fl Flag) bool { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 5bb2693114..3f8aef1d55 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -83,16 +83,24 @@ func (f *Float64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Float64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Float64SliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *Float64SliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -141,18 +149,6 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64 { return ctx.Float64Slice(f.Name) } -func (f *Float64SliceFlag) stringify() string { - var defaultVals []string - - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *Float64SliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 89fd44550e..6844e6d1a6 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -84,16 +84,24 @@ func (i *Int64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Int64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Int64SliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *Int64SliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -140,17 +148,6 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64 { return ctx.Int64Slice(f.Name) } -func (f *Int64SliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *Int64SliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_int_slice.go b/flag_int_slice.go index f7a6b06f0c..d3d3ea750b 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -95,16 +95,24 @@ func (i *IntSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *IntSliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.Itoa(i)) + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *IntSliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -151,17 +159,6 @@ func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) } -func (f *IntSliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.Itoa(i)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *IntSliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_string_slice.go b/flag_string_slice.go index 2a24ec56e5..bdeb674fb2 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -74,16 +74,26 @@ func (s *StringSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *StringSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *StringSliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, s := range f.Value.Value() { + if len(s) > 0 { + defaultVals = append(defaultVals, strconv.Quote(s)) + } + } } - return "" + return strings.Join(defaultVals, ", ") +} + +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *StringSliceFlag) IsSliceFlag() bool { + return true } // Apply populates the flag given the flag set and environment @@ -130,19 +140,6 @@ func (f *StringSliceFlag) Get(ctx *Context) []string { return ctx.StringSlice(f.Name) } -func (f *StringSliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, s := range f.Value.Value() { - if len(s) > 0 { - defaultVals = append(defaultVals, strconv.Quote(s)) - } - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *StringSliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_test.go b/flag_test.go index e7fa309fc6..6b82339892 100644 --- a/flag_test.go +++ b/flag_test.go @@ -298,12 +298,12 @@ func TestFlagStringifying(t *testing.T) { { name: "float64-slice-flag", fl: &Float64SliceFlag{Name: "pizzas"}, - expected: "--pizzas value\t", + expected: "--pizzas value [ --pizzas value ]\t", }, { name: "float64-slice-flag-with-default-text", fl: &Float64SliceFlag{Name: "pepperonis", DefaultText: "shaved"}, - expected: "--pepperonis value\t(default: shaved)", + expected: "--pepperonis value [ --pepperonis value ]\t(default: shaved)", }, { name: "generic-flag", @@ -328,7 +328,7 @@ func TestFlagStringifying(t *testing.T) { { name: "int-slice-flag", fl: &IntSliceFlag{Name: "pencils"}, - expected: "--pencils value\t", + expected: "--pencils value [ --pencils value ]\t", }, { name: "int-slice-flag-with-default-text", @@ -338,7 +338,7 @@ func TestFlagStringifying(t *testing.T) { { name: "uint-slice-flag", fl: &UintSliceFlag{Name: "pencils"}, - expected: "--pencils value\t", + expected: "--pencils value [ --pencils value ]\t", }, { name: "uint-slice-flag-with-default-text", @@ -358,22 +358,22 @@ func TestFlagStringifying(t *testing.T) { { name: "int64-slice-flag", fl: &Int64SliceFlag{Name: "drawers"}, - expected: "--drawers value\t", + expected: "--drawers value [ --drawers value ]\t", }, { name: "int64-slice-flag-with-default-text", fl: &Int64SliceFlag{Name: "handles", DefaultText: "-2"}, - expected: "--handles value\t(default: -2)", + expected: "--handles value [ --handles value ]\t(default: -2)", }, { name: "uint64-slice-flag", fl: &Uint64SliceFlag{Name: "drawers"}, - expected: "--drawers value\t", + expected: "--drawers value [ --drawers value ]\t", }, { name: "uint64-slice-flag-with-default-text", fl: &Uint64SliceFlag{Name: "handles", DefaultText: "-2"}, - expected: "--handles value\t(default: -2)", + expected: "--handles value [ --handles value ]\t(default: -2)", }, { name: "path-flag", @@ -398,12 +398,12 @@ func TestFlagStringifying(t *testing.T) { { name: "string-slice-flag", fl: &StringSliceFlag{Name: "meow-sounds"}, - expected: "--meow-sounds value\t", + expected: "--meow-sounds value [ --meow-sounds value ]\t", }, { name: "string-slice-flag-with-default-text", fl: &StringSliceFlag{Name: "moo-sounds", DefaultText: "awoo"}, - expected: "--moo-sounds value\t(default: awoo)", + expected: "--moo-sounds value [ --moo-sounds value ]\t(default: awoo)", }, { name: "timestamp-flag", diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index 662a03eac6..873ad9bd55 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -88,7 +88,7 @@ func (i *Uint64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Uint64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // TakesValue returns true of the flag takes a value, otherwise false @@ -109,10 +109,13 @@ func (f *Uint64SliceFlag) GetCategory() string { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Uint64SliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) + } } - return "" + return strings.Join(defaultVals, ", ") } // GetDefaultText returns the default text for this flag @@ -128,6 +131,11 @@ func (f *Uint64SliceFlag) GetEnvVars() []string { return f.EnvVars } +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *Uint64SliceFlag) IsSliceFlag() bool { + return true +} + // Apply populates the flag given the flag set and environment func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error { // apply any default @@ -172,17 +180,6 @@ func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 { return ctx.Uint64Slice(f.Name) } -func (f *Uint64SliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *Uint64SliceFlag) RunAction(c *Context) error { if f.Action != nil { diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 3689eaa39b..2a1fcc17f1 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -99,7 +99,7 @@ func (i *UintSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *UintSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), f.stringify()) + return FlagStringer(f) } // TakesValue returns true of the flag takes a value, otherwise false @@ -120,10 +120,13 @@ func (f *UintSliceFlag) GetCategory() string { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *UintSliceFlag) GetValue() string { - if f.Value != nil { - return f.Value.String() + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) + } } - return "" + return strings.Join(defaultVals, ", ") } // GetDefaultText returns the default text for this flag @@ -139,6 +142,11 @@ func (f *UintSliceFlag) GetEnvVars() []string { return f.EnvVars } +// IsSliceFlag implements DocGenerationSliceFlag. +func (f *UintSliceFlag) IsSliceFlag() bool { + return true +} + // Apply populates the flag given the flag set and environment func (f *UintSliceFlag) Apply(set *flag.FlagSet) error { // apply any default @@ -183,17 +191,6 @@ func (f *UintSliceFlag) Get(ctx *Context) []uint { return ctx.UintSlice(f.Name) } -func (f *UintSliceFlag) stringify() string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - // RunAction executes flag action if set func (f *UintSliceFlag) RunAction(c *Context) error { if f.Action != nil { From 65c98c86ad1e753645287bbae0b7f418376a72d3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 29 Sep 2022 11:31:40 -0400 Subject: [PATCH 07/24] Ensure "generate" step runs in CI prior to diff check --- .github/workflows/cli.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 800bbeb2dd..54afe58f4d 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -43,6 +43,7 @@ jobs: GFLAGS: -tags urfave_cli_no_docs - run: make check-binary-size - run: make yamlfmt + - run: make generate - run: make diffcheck - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make v2diff From c5057d195efd46b589b7e2ecddf800f5f44f2068 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 29 Sep 2022 20:44:37 -0400 Subject: [PATCH 08/24] Only run generate on go 1.19 --- .github/workflows/cli.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 54afe58f4d..39298b851b 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -43,7 +43,8 @@ jobs: GFLAGS: -tags urfave_cli_no_docs - run: make check-binary-size - run: make yamlfmt - - run: make generate + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make generate - run: make diffcheck - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make v2diff From 01bdec784f5289c8ed4ce6b9520a9571686e976e Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 26 Jul 2022 16:16:02 +0200 Subject: [PATCH 09/24] Set destination in GenericFlag apply function The function was missing destination configuration. --- flag_generic.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flag_generic.go b/flag_generic.go index 9f0633808e..daed474833 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -34,6 +34,10 @@ func (f *GenericFlag) 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) } From b82e628617754df5f25c0e160679a714755c377b Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 26 Jul 2022 16:16:43 +0200 Subject: [PATCH 10/24] Add unit test for GenericFlag Destination parsing The test checks if Destination provided in GenericFlag is being set as expected. --- flag_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/flag_test.go b/flag_test.go index 6b82339892..8e4033915e 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2710,6 +2710,53 @@ func TestParseGeneric(t *testing.T) { }).Run([]string{"run", "-s", "10,20"}) } +type genericType struct { + s []string +} + +func (g *genericType) Set(value string) error { + g.s = strings.Split(value, "-") + return nil +} + +func (g *genericType) String() string { + return strings.Join(g.s, "-") +} + +func TestParseDestinationGeneric(t *testing.T) { + expectedString := "abc1-123d" + expectedGeneric := &genericType{[]string{"abc1", "123d"}} + dest := &genericType{} + + _ = (&App{ + Flags: []Flag{ + &GenericFlag{ + Name: "dest", + Destination: dest, + }, + }, + Action: func(ctx *Context) error { + + if !reflect.DeepEqual(dest, expectedGeneric) { + t.Errorf( + "expected destination generic: %+v, actual: %+v", + expectedGeneric, + dest, + ) + } + + if dest.String() != expectedString { + t.Errorf( + "expected destination string: %s, actual: %s", + expectedString, + dest.String(), + ) + } + return nil + }, + }).Run([]string{"run", "--dest", expectedString}) +} + func TestParseGenericFromEnv(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() From 13860a7c4db6bc0169afc52b95872b4b258a0939 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Thu, 29 Sep 2022 20:49:22 -0400 Subject: [PATCH 11/24] Fix:(issue_1505) Fix flag alignment in help --- template.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/template.go b/template.go index 9e13604f35..53a76b74cf 100644 --- a/template.go +++ b/template.go @@ -56,8 +56,8 @@ OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} +OPTIONS:{{range .VisibleFlags}} + {{.}}{{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. @@ -70,15 +70,24 @@ USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}} + {{wrap .Description 3}}{{end}}{{if .VisibleCommands}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}} +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}}{{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} + +OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} + {{wrap $option.String 6}}{{end}}{{end}}{{end}} ` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} From ae8c5118f2154b1cac5f3988347a0b87a5aaed0d Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Thu, 29 Sep 2022 21:06:07 -0400 Subject: [PATCH 12/24] Fix command help subcommand --- command.go | 11 +++++++++++ template.go | 11 ++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/command.go b/command.go index decfb7359c..b7f6b73b1f 100644 --- a/command.go +++ b/command.go @@ -295,6 +295,17 @@ func (c *Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } +// VisibleCommands returns a slice of the Commands with Hidden=false +func (c *Command) VisibleCommands() []*Command { + var ret []*Command + for _, command := range c.Subcommands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} + // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { diff --git a/template.go b/template.go index 53a76b74cf..bb9cbc42b8 100644 --- a/template.go +++ b/template.go @@ -72,16 +72,9 @@ USAGE: DESCRIPTION: {{wrap .Description 3}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{if .VisibleFlagCategories}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} - OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}}{{end}}{{range .Flags}}{{.}} {{end}}{{end}}{{else}}{{if .VisibleFlags}} From c86805de7c074ef985d3f8c7b3e4ac853ca9d24f Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 30 Sep 2022 08:23:18 -0400 Subject: [PATCH 13/24] Add test case --- app_test.go | 2 +- help.go | 6 +++- help_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ template.go | 8 +++--- 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/app_test.go b/app_test.go index 1428c391dd..600f2cbc63 100644 --- a/app_test.go +++ b/app_test.go @@ -177,7 +177,7 @@ func ExampleApp_Run_commandHelp() { // greet describeit - use it to see a description // // USAGE: - // greet describeit [command options] [arguments...] + // greet describeit [arguments...] // // DESCRIPTION: // This is how we describe describeit the function diff --git a/help.go b/help.go index 6929a226ab..45e6f2b3cd 100644 --- a/help.go +++ b/help.go @@ -242,7 +242,11 @@ func ShowCommandHelp(ctx *Context, command string) error { c.Subcommands = append(c.Subcommands, helpCommandDontUse) } if !ctx.App.HideHelp && HelpFlag != nil { - c.appendFlag(HelpFlag) + if c.flagCategories == nil { + c.flagCategories = newFlagCategoriesFromFlags([]Flag{HelpFlag}) + } else { + c.flagCategories.AddFlag("", HelpFlag) + } } templ := c.CustomHelpTemplate if templ == "" { diff --git a/help_test.go b/help_test.go index d7cdfa2d8f..6b1e8c6b4e 100644 --- a/help_test.go +++ b/help_test.go @@ -1367,6 +1367,7 @@ DESCRIPTION: OPTIONS: --help, -h show help (default: false) + ` if output.String() != expected { @@ -1436,6 +1437,84 @@ USAGE: OPTIONS: --help, -h show help (default: false) + +` + + if output.String() != expected { + t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + output.String(), expected) + } +} + +func TestWrappedHelpSubcommand(t *testing.T) { + + // Reset HelpPrinter after this test. + defer func(old helpPrinter) { + HelpPrinter = old + }(HelpPrinter) + + output := new(bytes.Buffer) + app := &App{ + Name: "cli.test", + Writer: output, + Commands: []*Command{ + { + Name: "bar", + Aliases: []string{"a"}, + Usage: "add a task to the list", + UsageText: "this is an even longer way of describing adding a task to the list", + Description: "and a description long enough to wrap in this test case", + Action: func(c *Context) error { + return nil + }, + Subcommands: []*Command{ + { + Name: "grok", + Usage: "remove an existing template", + UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more", + Action: func(c *Context) error { + return nil + }, + Flags: []Flag{ + &StringFlag{ + Name: "test-f", + Usage: "my test usage", + }, + }, + }, + }, + }, + }, + } + + HelpPrinter = func(w io.Writer, templ string, data interface{}) { + funcMap := map[string]interface{}{ + "wrapAt": func() int { + return 30 + }, + } + + HelpPrinterCustom(w, templ, data, funcMap) + } + + _ = app.Run([]string{"foo", "bar", "help", "grok"}) + + expected := `NAME: + cli.test bar grok - remove + an + existing + template + +USAGE: + longer usage text goes + here, la la la, hopefully + this is long enough to wrap + even more + +OPTIONS: + --help, -h show help (default: false) + --test-f value my test usage + ` if output.String() != expected { diff --git a/template.go b/template.go index bb9cbc42b8..d67931bba0 100644 --- a/template.go +++ b/template.go @@ -54,10 +54,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} - -OPTIONS:{{range .VisibleFlags}} - {{.}}{{end}}{{end}}{{end}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} +OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} + {{wrap $option.String 6}}{{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. From dccd762cbb153cff25c4d924312a77787d05a39f Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 30 Sep 2022 12:12:41 -0400 Subject: [PATCH 14/24] Componentize template --- help.go | 11 +++++++ help_test.go | 9 ++---- template.go | 81 ++++++++++++++++++++++++++++------------------------ 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/help.go b/help.go index 45e6f2b3cd..d78444a0c6 100644 --- a/help.go +++ b/help.go @@ -362,6 +362,17 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + t.New("helpNameTemplate").Parse(helpNameTemplate) + t.New("usageTemplate").Parse(usageTemplate) + t.New("descriptionTemplate").Parse(descriptionTemplate) + t.New("visibleCommandTemplate").Parse(visibleCommandTemplate) + t.New("copyrightTemplate").Parse(copyrightTemplate) + t.New("versionTemplate").Parse(versionTemplate) + t.New("visibleFlagCategoryTemplate").Parse(visibleFlagCategoryTemplate) + t.New("visibleFlagTemplate").Parse(visibleFlagTemplate) + t.New("visibleGlobalFlagCategoryTemplate").Parse(strings.Replace(visibleFlagCategoryTemplate, "OPTIONS", "GLOBAL OPTIONS", -1)) + t.New("authorsTemplate").Parse(authorsTemplate) + t.New("visibleCommandCategoryTemplate").Parse(visibleCommandCategoryTemplate) err := t.Execute(w, data) if err != nil { diff --git a/help_test.go b/help_test.go index 6b1e8c6b4e..de8e2f0cd9 100644 --- a/help_test.go +++ b/help_test.go @@ -1367,8 +1367,7 @@ DESCRIPTION: OPTIONS: --help, -h show help (default: false) - -` + ` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s", @@ -1437,8 +1436,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) - -` + ` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", @@ -1514,8 +1512,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) --test-f value my test usage - -` + ` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", diff --git a/template.go b/template.go index d67931bba0..dfdd434cce 100644 --- a/template.go +++ b/template.go @@ -1,10 +1,36 @@ package cli +var helpNameTemplate = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}` +var usageTemplate = `{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}` +var descriptionTemplate = `{{wrap .Description 3}}` +var authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}` +var visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}` +var visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}` +var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}` + +var visibleFlagTemplate = `{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} + {{wrap $option.String 6}}{{end}}` + +var versionTemplate = `{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}` + +var copyrightTemplate = `{{wrap .Copyright 3}}` + // AppHelpTemplate is the text template for the Default help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var AppHelpTemplate = `NAME: - {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{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}} @@ -13,75 +39,56 @@ VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if len .Authors}} + {{template "descriptionTemplate" .}}{{end}}{{if len .Authors}} -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} +AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -GLOBAL OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} +GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} +GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} COPYRIGHT: - {{wrap .Copyright 3}}{{end}} + {{template "copyrightTemplate" .}}{{end}} ` // CommandHelpTemplate is the text template for the command help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{template "usageTemplate" .}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} -OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} - {{wrap $option.String 6}}{{end}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} + +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` // SubcommandHelpTemplate is the text template for the subcommand help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if .VisibleCommands}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}}{{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS:{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} - {{wrap $option.String 6}}{{end}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} From a4b7759ad11e9d93120d68b9b34284130c10caec Mon Sep 17 00:00:00 2001 From: dearchap Date: Wed, 5 Oct 2022 08:35:11 -0400 Subject: [PATCH 15/24] Update template.go Co-authored-by: Anatoli Babenia --- template.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/template.go b/template.go index dfdd434cce..f48e5099f3 100644 --- a/template.go +++ b/template.go @@ -39,7 +39,8 @@ VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{template "descriptionTemplate" .}}{{end}}{{if len .Authors}} + {{template "descriptionTemplate" .}}{{end}} +{{- if len .Authors}} AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} From 9a9461928aa1c0917f6526c0077c4c9ac6203067 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 5 Oct 2022 13:52:05 -0400 Subject: [PATCH 16/24] Add test coverage for Command.VisibleCommands() --- command_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/command_test.go b/command_test.go index 9dfd46f476..a953e943cd 100644 --- a/command_test.go +++ b/command_test.go @@ -422,3 +422,30 @@ func TestCommand_CanAddVFlagOnCommands(t *testing.T) { err := app.Run([]string{"foo", "bar"}) expect(t, err, nil) } + +func TestCommand_VisibleSubcCommands(t *testing.T) { + + subc1 := &Command{ + Name: "subc1", + Usage: "subc1 command1", + } + subc3 := &Command{ + Name: "subc3", + Usage: "subc3 command2", + } + c := &Command{ + Name: "bar", + Usage: "this is for testing", + Subcommands: []*Command{ + subc1, + { + Name: "subc2", + Usage: "subc2 command2", + Hidden: true, + }, + subc3, + }, + } + + expect(t, c.VisibleCommands(), []*Command{subc1, subc3}) +} From ea2893084b92f3cdd9a3bb7dcf1b85bfc12fa595 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 5 Oct 2022 14:40:59 -0400 Subject: [PATCH 17/24] Remove extra 3 spaces in last line --- help_test.go | 6 +++--- template.go | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/help_test.go b/help_test.go index de8e2f0cd9..37410a5449 100644 --- a/help_test.go +++ b/help_test.go @@ -1367,7 +1367,7 @@ DESCRIPTION: OPTIONS: --help, -h show help (default: false) - ` +` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s", @@ -1436,7 +1436,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) - ` +` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", @@ -1512,7 +1512,7 @@ USAGE: OPTIONS: --help, -h show help (default: false) --test-f value my test usage - ` +` if output.String() != expected { t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", diff --git a/template.go b/template.go index f48e5099f3..1133b8907c 100644 --- a/template.go +++ b/template.go @@ -13,8 +13,10 @@ var visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}` var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}` + + {{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}} +{{else}}{{$e}} + {{end}}{{end}}{{end}}` var visibleFlagTemplate = `{{range $index, $option := .VisibleFlags}}{{if $index}}{{end}} {{wrap $option.String 6}}{{end}}` From 924ebdaab2b099fd121e721f1f118cfc0d625f64 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Thu, 29 Sep 2022 10:33:48 -0400 Subject: [PATCH 18/24] Fix:(issue_1500). Fix slice flag value duplication issue --- app_test.go | 31 +++++++++++++++++++++++++++++++ flag.go | 27 ++++++++++++++++++++++++--- parse.go | 5 ++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/app_test.go b/app_test.go index 600f2cbc63..9a1fcd1586 100644 --- a/app_test.go +++ b/app_test.go @@ -956,6 +956,37 @@ func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) { expect(t, err, errors.New("flag needs an argument: -n")) } +func TestApp_UseShortOptionAfterSliceFlag(t *testing.T) { + var one, two bool + var name string + var sliceValDest StringSlice + var sliceVal []string + expected := "expectedName" + + app := newTestApp() + app.UseShortOptionHandling = true + app.Flags = []Flag{ + &StringSliceFlag{Name: "env", Aliases: []string{"e"}, Destination: &sliceValDest}, + &BoolFlag{Name: "one", Aliases: []string{"o"}}, + &BoolFlag{Name: "two", Aliases: []string{"t"}}, + &StringFlag{Name: "name", Aliases: []string{"n"}}, + } + app.Action = func(c *Context) error { + sliceVal = c.StringSlice("env") + one = c.Bool("one") + two = c.Bool("two") + name = c.String("name") + return nil + } + + _ = app.Run([]string{"", "-e", "foo", "-on", expected}) + expect(t, sliceVal, []string{"foo"}) + expect(t, sliceValDest.Value(), []string{"foo"}) + expect(t, one, true) + expect(t, two, false) + expect(t, name, expected) +} + func TestApp_Float64Flag(t *testing.T) { var meters float64 diff --git a/flag.go b/flag.go index 6aff19b509..b719f8b772 100644 --- a/flag.go +++ b/flag.go @@ -126,6 +126,21 @@ type Flag interface { RunAction(*Context) error } +// DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + // DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. type DocGenerationSliceFlag interface { DocGenerationFlag @@ -293,8 +308,14 @@ func formatDefault(format string) string { } func stringifyFlag(f Flag) string { - placeholder, usage := unquoteUsage(f.GetUsage()) - needsPlaceholder := f.TakesValue() + // enforce DocGeneration interface on flags to avoid reflection + df, ok := f.(DocGenerationFlag) + if !ok { + return "" + } + + placeholder, usage := unquoteUsage(df.GetUsage()) + needsPlaceholder := df.TakesValue() if needsPlaceholder && placeholder == "" { placeholder = defaultPlaceholder @@ -302,7 +323,7 @@ func stringifyFlag(f Flag) string { defaultValueString := "" - if s := f.GetDefaultText(); s != "" { + if s := df.GetDefaultText(); s != "" { defaultValueString = fmt.Sprintf(formatDefault("%s"), s) } diff --git a/parse.go b/parse.go index a2db306e12..28e24f1020 100644 --- a/parse.go +++ b/parse.go @@ -46,7 +46,10 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple } // swap current argument with the split version - args = append(args[:i], append(shortOpts, args[i+1:]...)...) + // do not include args that parsed correctly so far as it would + // trigger Value.Set() on those args and would result in + // duplicates for slice type flags + args = append(shortOpts, args[i+1:]...) argsWereSplit = true break } From fcb0bce79727532b01100803da496a00f940dcb0 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 5 Oct 2022 09:33:41 -0400 Subject: [PATCH 19/24] Fix failed test --- parse.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/parse.go b/parse.go index 28e24f1020..d79f15a18e 100644 --- a/parse.go +++ b/parse.go @@ -59,13 +59,6 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple if !argsWereSplit { return err } - - // Since custom parsing failed, replace the flag set before retrying - newSet, err := ip.newFlagSet() - if err != nil { - return err - } - *set = *newSet } } From 96216756c27da395338d9b79f62fbb4227b7a1a3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 9 Oct 2022 12:41:30 -0400 Subject: [PATCH 20/24] Remove duplicate DocGenerationFlag interface introduced via merge --- flag.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/flag.go b/flag.go index 6e4a51edec..cec8266b72 100644 --- a/flag.go +++ b/flag.go @@ -135,21 +135,6 @@ type DocGenerationFlag interface { GetEnvVars() []string } -// DocGenerationFlag is an interface that allows documentation generation for the flag -type DocGenerationFlag interface { - Flag - - // TakesValue returns true if the flag takes a value, otherwise false - TakesValue() bool - - // GetValue returns the flags value as string representation and an empty - // string if the flag takes no value at all. - GetValue() string - - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string -} - // DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. type DocGenerationSliceFlag interface { DocGenerationFlag From 6404f1d1b4cedcc47e6bf82caee8eeffa910bdda Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 10 Oct 2022 10:57:06 -0400 Subject: [PATCH 21/24] Backfill drop of generated GetDefaultText method --- cmd/urfave-cli-genflags/generated.gotmpl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index 85bd66fddf..bee11d52da 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -75,16 +75,6 @@ func (f *{{.TypeName}}) TakesValue() bool { return "{{.TypeName }}" != "BoolFlag" } -{{if .GenerateDefaultText}} -// GetDefaultText returns the default text for this flag -func (f *{{.TypeName}}) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} -{{end}} - {{end}}{{/* /if .GenerateFlagInterface */}} {{end}}{{/* /range .SortedFlagTypes */}} From b45820714d0990850b6208bbc02c4a0b4c989e34 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 9 Oct 2022 11:25:02 -0400 Subject: [PATCH 22/24] Build and run `urfave-cli-genflags` via its `Makefile` so that `go.mod` files don't get all confused --- cli.go | 2 +- cmd/urfave-cli-genflags/Makefile | 4 ++++ cmd/urfave-cli-genflags/main.go | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cli.go b/cli.go index b3b864cf5a..28ad0582b6 100644 --- a/cli.go +++ b/cli.go @@ -22,4 +22,4 @@ // } package cli -//go:generate go run cmd/urfave-cli-genflags/main.go +//go:generate make -C cmd/urfave-cli-genflags run diff --git a/cmd/urfave-cli-genflags/Makefile b/cmd/urfave-cli-genflags/Makefile index 3b11415d62..95b932d7b5 100644 --- a/cmd/urfave-cli-genflags/Makefile +++ b/cmd/urfave-cli-genflags/Makefile @@ -19,3 +19,7 @@ smoke-test: build .PHONY: show-cover show-cover: go tool cover -func main.coverprofile + +.PHONY: run +run: build + ./urfave-cli-genflags diff --git a/cmd/urfave-cli-genflags/main.go b/cmd/urfave-cli-genflags/main.go index 1b0fe532a6..7f8deb6d46 100644 --- a/cmd/urfave-cli-genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -4,6 +4,7 @@ import ( "bytes" "context" _ "embed" + "fmt" "log" "os" "os/exec" @@ -37,6 +38,9 @@ var ( func sh(ctx context.Context, exe string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, exe, args...) cmd.Stderr = os.Stderr + + fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd) + outBytes, err := cmd.Output() return string(outBytes), err } @@ -89,10 +93,18 @@ func main() { Aliases: []string{"N"}, Value: "cli.", }, + &cli.PathFlag{ + Name: "goimports", + Value: filepath.Join(top, ".local/bin/goimports"), + }, }, Action: runGenFlags, } + if err := os.Chdir(top); err != nil { + log.Fatal(err) + } + if err := app.RunContext(ctx, os.Args); err != nil { log.Fatal(err) } @@ -163,11 +175,11 @@ func runGenFlags(cCtx *cli.Context) error { return err } - if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-output")); err != nil { + if _, err := sh(cCtx.Context, cCtx.Path("goimports"), "-w", cCtx.Path("generated-output")); err != nil { return err } - if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-test-output")); err != nil { + if _, err := sh(cCtx.Context, cCtx.Path("goimports"), "-w", cCtx.Path("generated-test-output")); err != nil { return err } From 75aabac5941f414b03946948d36669e1bd47b3f1 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 10 Oct 2022 08:48:50 -0400 Subject: [PATCH 23/24] Use existing goimports installation if available --- cmd/urfave-cli-genflags/Makefile | 3 +++ cmd/urfave-cli-genflags/main.go | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/urfave-cli-genflags/Makefile b/cmd/urfave-cli-genflags/Makefile index 95b932d7b5..acede8e8e5 100644 --- a/cmd/urfave-cli-genflags/Makefile +++ b/cmd/urfave-cli-genflags/Makefile @@ -1,6 +1,9 @@ +GOIMPORTS_BIN ?= $(shell which goimports || true) GOTEST_FLAGS ?= -v --coverprofile main.coverprofile --covermode count --cover github.com/urfave/cli/v2/cmd/urfave-cli-genflags GOBUILD_FLAGS ?= -x +export GOIMPORTS_BIN + .PHONY: all all: test build smoke-test diff --git a/cmd/urfave-cli-genflags/main.go b/cmd/urfave-cli-genflags/main.go index 7f8deb6d46..54cc83e39a 100644 --- a/cmd/urfave-cli-genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -94,8 +94,9 @@ func main() { Value: "cli.", }, &cli.PathFlag{ - Name: "goimports", - Value: filepath.Join(top, ".local/bin/goimports"), + Name: "goimports", + EnvVars: []string{"GOIMPORTS_BIN"}, + Value: filepath.Join(top, ".local/bin/goimports"), }, }, Action: runGenFlags, From 85ff0c550a4124bfa742cc97b90034b638ca0ece Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 13 Oct 2022 08:15:14 -0400 Subject: [PATCH 24/24] Un-regress from v3 porting losses --- .github/workflows/cli.yml | 5 +- Makefile | 2 +- cmd/urfave-cli-genflags/generated_test.gotmpl | 4 +- flag_duration.go | 8 ++ flag_float64.go | 8 ++ flag_float64_slice.go | 8 ++ flag_generic.go | 8 ++ flag_int.go | 8 ++ flag_int64.go | 8 ++ flag_int64_slice.go | 8 ++ flag_int_slice.go | 8 ++ flag_string_slice.go | 8 ++ flag_test.go | 2 +- flag_timestamp.go | 8 ++ flag_uint.go | 8 ++ flag_uint64.go | 8 ++ flag_uint64_slice.go | 20 --- flag_uint_slice.go | 20 --- godoc-current.txt | 91 +++++++----- zz_generated.flags.go | 136 ++++++------------ zz_generated.flags_test.go | 24 ++++ 21 files changed, 220 insertions(+), 180 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 39298b851b..5c8db7a668 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -46,8 +46,11 @@ jobs: - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make generate - run: make diffcheck - - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + # TODO: switch once v3 is released {{ + # - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + - if: 'false' run: make v2diff + # }} - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: diff --git a/Makefile b/Makefile index f0d41905ea..a167052506 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ GO_RUN_BUILD := go run internal/build/build.go .PHONY: all -all: generate vet test check-binary-size gfmrun yamlfmt v2diff +all: generate vet test check-binary-size gfmrun yamlfmt # NOTE: this is a special catch-all rule to run any of the commands # defined in internal/build/build.go with optional arguments passed diff --git a/cmd/urfave-cli-genflags/generated_test.gotmpl b/cmd/urfave-cli-genflags/generated_test.gotmpl index c91f562ef2..83229b06ba 100644 --- a/cmd/urfave-cli-genflags/generated_test.gotmpl +++ b/cmd/urfave-cli-genflags/generated_test.gotmpl @@ -12,13 +12,13 @@ func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { } func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsRequired() } func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { - var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} _ = f.IsVisible() } diff --git a/flag_duration.go b/flag_duration.go index b6adce3f48..06391f38e9 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -12,6 +12,14 @@ func (f *DurationFlag) GetValue() string { return f.Value.String() } +// GetDefaultText returns the default text for this flag +func (f *DurationFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64.go b/flag_float64.go index 3e21a01df7..d522ac0436 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -12,6 +12,14 @@ func (f *Float64Flag) GetValue() string { return fmt.Sprintf("%v", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Float64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 3f8aef1d55..d060432f06 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -98,6 +98,14 @@ func (f *Float64SliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *Float64SliceFlag) IsSliceFlag() bool { return true diff --git a/flag_generic.go b/flag_generic.go index daed474833..d0f6527ce1 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -20,6 +20,14 @@ func (f *GenericFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *GenericFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // 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 bcaab7fa7e..a716f3914f 100644 --- a/flag_int.go +++ b/flag_int.go @@ -12,6 +12,14 @@ func (f *IntFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *IntFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64.go b/flag_int64.go index d8e591d864..05d8506bed 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -12,6 +12,14 @@ func (f *Int64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Int64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 6844e6d1a6..2db2ceab24 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -99,6 +99,14 @@ func (f *Int64SliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *Int64SliceFlag) IsSliceFlag() bool { return true diff --git a/flag_int_slice.go b/flag_int_slice.go index d3d3ea750b..a2c9f21301 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -110,6 +110,14 @@ func (f *IntSliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *IntSliceFlag) IsSliceFlag() bool { return true diff --git a/flag_string_slice.go b/flag_string_slice.go index bdeb674fb2..f53c0e6098 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -91,6 +91,14 @@ func (f *StringSliceFlag) GetValue() string { return strings.Join(defaultVals, ", ") } +// GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // IsSliceFlag implements DocGenerationSliceFlag. func (f *StringSliceFlag) IsSliceFlag() bool { return true diff --git a/flag_test.go b/flag_test.go index 3aafa8bea2..7601b4afec 100644 --- a/flag_test.go +++ b/flag_test.go @@ -225,7 +225,7 @@ func TestFlagsFromEnv(t *testing.T) { f, ok := test.flag.(DocGenerationFlag) if !ok { - t.Errorf("flag %v needs to implement DocGenerationFlag to retrieve env vars", test.flag) + t.Errorf("flag %[1]q (%[1]T) needs to implement DocGenerationFlag to retrieve env vars", test.flag) } envVarSlice := f.GetEnvVars() _ = os.Setenv(envVarSlice[0], test.input) diff --git a/flag_timestamp.go b/flag_timestamp.go index 671be8b986..4302f130ed 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -81,6 +81,14 @@ func (f *TimestampFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *TimestampFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // 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 e1b04508c8..2214386849 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -37,6 +37,14 @@ func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *UintFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Get returns the flag’s value in the given Context. func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) diff --git a/flag_uint64.go b/flag_uint64.go index 593b2606ae..e314a4a307 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -37,6 +37,14 @@ func (f *Uint64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Uint64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + // Get returns the flag’s value in the given Context. func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index 873ad9bd55..ae251ac793 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -91,21 +91,6 @@ func (f *Uint64SliceFlag) String() string { return FlagStringer(f) } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *Uint64SliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *Uint64SliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *Uint64SliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Uint64SliceFlag) GetValue() string { @@ -126,11 +111,6 @@ func (f *Uint64SliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *Uint64SliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // IsSliceFlag implements DocGenerationSliceFlag. func (f *Uint64SliceFlag) IsSliceFlag() bool { return true diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 2a1fcc17f1..92848f22fc 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -102,21 +102,6 @@ func (f *UintSliceFlag) String() string { return FlagStringer(f) } -// TakesValue returns true of the flag takes a value, otherwise false -func (f *UintSliceFlag) TakesValue() bool { - return true -} - -// GetUsage returns the usage string for the flag -func (f *UintSliceFlag) GetUsage() string { - return f.Usage -} - -// GetCategory returns the category for the flag -func (f *UintSliceFlag) GetCategory() string { - return f.Category -} - // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *UintSliceFlag) GetValue() string { @@ -137,11 +122,6 @@ func (f *UintSliceFlag) GetDefaultText() string { return f.GetValue() } -// GetEnvVars returns the env vars for this flag -func (f *UintSliceFlag) GetEnvVars() []string { - return f.EnvVars -} - // IsSliceFlag implements DocGenerationSliceFlag. func (f *UintSliceFlag) IsSliceFlag() bool { return true diff --git a/godoc-current.txt b/godoc-current.txt index 03b76f8c79..e5d2116983 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -32,7 +32,7 @@ var ( SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) var AppHelpTemplate = `NAME: - {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{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}} @@ -41,52 +41,39 @@ VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if len .Authors}} + {{template "descriptionTemplate" .}}{{end}} +{{- if len .Authors}} -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} +AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -GLOBAL OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} +GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} +GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} COPYRIGHT: - {{wrap .Copyright 3}}{{end}} + {{template "copyrightTemplate" .}}{{end}} ` AppHelpTemplate is the text template for the Default help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{template "usageTemplate" .}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS:{{range .VisibleFlagCategories}} - {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` CommandHelpTemplate is the text template for the command help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. @@ -145,22 +132,19 @@ var OsExiter = os.Exit os.Exit. var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{template "helpNameTemplate" .}} USAGE: {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}} + {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} - {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -OPTIONS: - {{range .VisibleFlags}}{{.}}{{end}}{{end}} -` +OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} + +OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}` SubcommandHelpTemplate is the text template for the subcommand help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. @@ -586,6 +570,9 @@ func (c *Command) Run(ctx *Context) (err error) Run invokes the command given the context, parses ctx.Args() to generate command-specific flags +func (c *Command) VisibleCommands() []*Command + VisibleCommands returns a slice of the Commands with Hidden=false + func (c *Command) VisibleFlagCategories() []VisibleFlagCategory VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain @@ -754,6 +741,14 @@ type DocGenerationFlag interface { DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationSliceFlag interface { + DocGenerationFlag + + // IsSliceFlag returns true for flags that can be given multiple times. + IsSliceFlag() bool +} + DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags. + type DurationFlag struct { Name string @@ -1077,6 +1072,9 @@ func (f *Float64SliceFlag) IsRequired() bool func (f *Float64SliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *Float64SliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *Float64SliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -1312,6 +1310,9 @@ func (f *Int64SliceFlag) IsRequired() bool func (f *Int64SliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *Int64SliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *Int64SliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -1477,6 +1478,9 @@ func (f *IntSliceFlag) IsRequired() bool func (f *IntSliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *IntSliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *IntSliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -1817,6 +1821,9 @@ func (f *StringSliceFlag) IsRequired() bool func (f *StringSliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *StringSliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *StringSliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -2057,7 +2064,7 @@ func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 Get returns the flag’s value in the given Context. func (f *Uint64SliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *Uint64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -2078,6 +2085,9 @@ func (f *Uint64SliceFlag) IsRequired() bool func (f *Uint64SliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *Uint64SliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *Uint64SliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -2091,7 +2101,7 @@ func (f *Uint64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *Uint64SliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type UintFlag struct { Name string @@ -2216,7 +2226,7 @@ func (f *UintSliceFlag) Get(ctx *Context) []uint Get returns the flag’s value in the given Context. func (f *UintSliceFlag) GetCategory() string - GetCategory returns the category for the flag + GetCategory returns the category of the flag func (f *UintSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -2237,6 +2247,9 @@ func (f *UintSliceFlag) IsRequired() bool func (f *UintSliceFlag) IsSet() bool IsSet returns whether or not the flag has been set through env or file +func (f *UintSliceFlag) IsSliceFlag() bool + IsSliceFlag implements DocGenerationSliceFlag. + func (f *UintSliceFlag) IsVisible() bool IsVisible returns true if the flag is not hidden, otherwise false @@ -2250,7 +2263,7 @@ func (f *UintSliceFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *UintSliceFlag) TakesValue() bool - TakesValue returns true of the flag takes a value, otherwise false + TakesValue returns true if the flag takes a value, otherwise false type VisibleFlag interface { Flag diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 68934c7707..8c4e6face5 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -66,14 +66,6 @@ func (f *Float64SliceFlag) TakesValue() bool { return "Float64SliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Float64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -143,14 +135,6 @@ func (f *GenericFlag) TakesValue() bool { return "GenericFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *GenericFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -213,14 +197,6 @@ func (f *Int64SliceFlag) TakesValue() bool { return "Int64SliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Int64SliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -283,14 +259,6 @@ func (f *IntSliceFlag) TakesValue() bool { return "IntSliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *IntSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -424,14 +392,6 @@ func (f *StringSliceFlag) TakesValue() bool { return "StringSliceFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *StringSliceFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -503,14 +463,6 @@ func (f *TimestampFlag) TakesValue() bool { return "TimestampFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *TimestampFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Uint64SliceFlag is a flag with type *Uint64Slice type Uint64SliceFlag struct { Name string @@ -553,6 +505,26 @@ func (f *Uint64SliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *Uint64SliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *Uint64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *Uint64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Uint64SliceFlag) TakesValue() bool { + return "Uint64SliceFlag" != "BoolFlag" +} + // UintSliceFlag is a flag with type *UintSlice type UintSliceFlag struct { Name string @@ -595,6 +567,26 @@ func (f *UintSliceFlag) IsVisible() bool { return !f.Hidden } +// GetCategory returns the category of the flag +func (f *UintSliceFlag) GetCategory() string { + return f.Category +} + +// GetUsage returns the usage string for the flag +func (f *UintSliceFlag) GetUsage() string { + return f.Usage +} + +// GetEnvVars returns the env vars for this flag +func (f *UintSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *UintSliceFlag) TakesValue() bool { + return "UintSliceFlag" != "BoolFlag" +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string @@ -731,14 +723,6 @@ func (f *Float64Flag) TakesValue() bool { return "Float64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Float64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // IntFlag is a flag with type int type IntFlag struct { Name string @@ -808,14 +792,6 @@ func (f *IntFlag) TakesValue() bool { return "IntFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *IntFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -885,14 +861,6 @@ func (f *Int64Flag) TakesValue() bool { return "Int64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Int64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // StringFlag is a flag with type string type StringFlag struct { Name string @@ -1029,14 +997,6 @@ func (f *DurationFlag) TakesValue() bool { return "DurationFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *DurationFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -1106,14 +1066,6 @@ func (f *UintFlag) TakesValue() bool { return "UintFlag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *UintFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -1183,12 +1135,4 @@ func (f *Uint64Flag) TakesValue() bool { return "Uint64Flag" != "BoolFlag" } -// GetDefaultText returns the default text for this flag -func (f *Uint64Flag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - // vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index 6862afc36e..b7c68153c4 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -167,6 +167,18 @@ func TestUint64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestUint64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Uint64SliceFlag{} + + _ = f.IsRequired() +} + +func TestUint64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Uint64SliceFlag{} + + _ = f.IsVisible() +} + func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.UintSliceFlag{} @@ -174,6 +186,18 @@ func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestUintSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.UintSliceFlag{} + + _ = f.IsRequired() +} + +func TestUintSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.UintSliceFlag{} + + _ = f.IsVisible() +} + func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{}