From ff1c0b58dd975249d2e49a4893438a872d85ceed Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 01:41:06 -0500 Subject: [PATCH 01/12] Start of category flag support This adds what I think needs to be done to support categories for flags but we will see if that works. It also forces the scripts to use python2 since they blow up under python3 which is becoming the default python on many linux systems. Small fix to app_test as well so it conforms to the new Flag interface. --- app_test.go | 4 +++ category.go | 43 +++++++++++++++++++++++++ flag.go | 3 ++ flag_generated.go | 78 +++++++++++++++++++++++++++++++++++++++++++++ generate-flag-types | 8 ++++- runtests | 2 +- 6 files changed, 136 insertions(+), 2 deletions(-) diff --git a/app_test.go b/app_test.go index 629681e68b..95ff66b194 100644 --- a/app_test.go +++ b/app_test.go @@ -1603,6 +1603,10 @@ func (c *customBoolFlag) GetName() string { return c.Nombre } +func (c *customBoolFlag) GetHidden() bool { + return false +} + func (c *customBoolFlag) Apply(set *flag.FlagSet) { set.String(c.Nombre, c.Nombre, "") } diff --git a/category.go b/category.go index bf3c73c55e..af478a5648 100644 --- a/category.go +++ b/category.go @@ -42,3 +42,46 @@ func (c *CommandCategory) VisibleCommands() []Command { } return ret } + +// FlagCategories is a slice of *FlagCategory. +type FlagCategories []*FlagCategory + +// FlagCategory is a category containing commands. +type FlagCategory struct { + Name string + Flags Flags +} + +func (f FlagCategories) Less(i, j int) bool { + return lexicographicLess(f[i].Name, f[j].Name) +} + +func (f FlagCategories) Len() int { + return len(f) +} + +func (f FlagCategories) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +// AddFlags adds a command to a category. +func (f FlagCategories) AddFlag(category string, flag Flag) FlagCategories { + for _, flagCategory := range f { + if flagCategory.Name == category { + flagCategory.Flags = append(flagCategory.Flags, flag) + return f + } + } + return append(f, &FlagCategory{Name: category, Flags: []Flag{flag}}) +} + +// VisibleFlags returns a slice of the Flags with Hidden=false +func (c *FlagCategory) VisibleFlags() []Flag { + ret := []Flag{} + for _, flag := range c.Flags { + if !flag.GetHidden() { + ret = append(ret, flag) + } + } + return ret +} diff --git a/flag.go b/flag.go index b0cffc006e..f3274cdb0c 100644 --- a/flag.go +++ b/flag.go @@ -73,8 +73,11 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) GetName() string + GetHidden() bool } +type Flags []Flag + // errorableFlag is an interface that allows us to return errors during apply // it allows flags defined in this library to return errors in a fashion backwards compatible // TODO remove in v2 and modify the existing Flag interface to return errors diff --git a/flag_generated.go b/flag_generated.go index 001576c8b3..b699b91e56 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -15,6 +15,7 @@ type BoolFlag struct { EnvVar string FilePath string Hidden bool + Category string Destination *bool } @@ -29,6 +30,11 @@ func (f BoolFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f BoolFlag) GetHidden() bool { + return f.Hidden +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { @@ -63,6 +69,7 @@ type BoolTFlag struct { EnvVar string FilePath string Hidden bool + Category string Destination *bool } @@ -77,6 +84,11 @@ func (f BoolTFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f BoolTFlag) GetHidden() bool { + return f.Hidden +} + // BoolT looks up the value of a local BoolTFlag, returns // false if not found func (c *Context) BoolT(name string) bool { @@ -111,6 +123,7 @@ type DurationFlag struct { EnvVar string FilePath string Hidden bool + Category string Value time.Duration Destination *time.Duration } @@ -126,6 +139,11 @@ func (f DurationFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f DurationFlag) GetHidden() bool { + return f.Hidden +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { @@ -160,6 +178,7 @@ type Float64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value float64 Destination *float64 } @@ -175,6 +194,11 @@ func (f Float64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Float64Flag) GetHidden() bool { + return f.Hidden +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { @@ -209,6 +233,7 @@ type GenericFlag struct { EnvVar string FilePath string Hidden bool + Category string Value Generic } @@ -223,6 +248,11 @@ func (f GenericFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f GenericFlag) GetHidden() bool { + return f.Hidden +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { @@ -257,6 +287,7 @@ type Int64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value int64 Destination *int64 } @@ -272,6 +303,11 @@ func (f Int64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Int64Flag) GetHidden() bool { + return f.Hidden +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { @@ -306,6 +342,7 @@ type IntFlag struct { EnvVar string FilePath string Hidden bool + Category string Value int Destination *int } @@ -321,6 +358,11 @@ func (f IntFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f IntFlag) GetHidden() bool { + return f.Hidden +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { @@ -355,6 +397,7 @@ type IntSliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *IntSlice } @@ -369,6 +412,11 @@ func (f IntSliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f IntSliceFlag) GetHidden() bool { + return f.Hidden +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { @@ -403,6 +451,7 @@ type Int64SliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *Int64Slice } @@ -417,6 +466,11 @@ func (f Int64SliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Int64SliceFlag) GetHidden() bool { + return f.Hidden +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { @@ -451,6 +505,7 @@ type StringFlag struct { EnvVar string FilePath string Hidden bool + Category string Value string Destination *string } @@ -466,6 +521,11 @@ func (f StringFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f StringFlag) GetHidden() bool { + return f.Hidden +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { @@ -500,6 +560,7 @@ type StringSliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *StringSlice } @@ -514,6 +575,11 @@ func (f StringSliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f StringSliceFlag) GetHidden() bool { + return f.Hidden +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { @@ -548,6 +614,7 @@ type Uint64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value uint64 Destination *uint64 } @@ -563,6 +630,11 @@ func (f Uint64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Uint64Flag) GetHidden() bool { + return f.Hidden +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { @@ -597,6 +669,7 @@ type UintFlag struct { EnvVar string FilePath string Hidden bool + Category string Value uint Destination *uint } @@ -612,6 +685,11 @@ func (f UintFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f UintFlag) GetHidden() bool { + return f.Hidden +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { diff --git a/generate-flag-types b/generate-flag-types index 1358857312..324b3c1e44 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 """ The flag types that ship with the cli library have many things in common, and so we can take advantage of the `go generate` command to create much of the @@ -144,6 +144,7 @@ def _write_cli_flag_types(outfile, types): EnvVar string FilePath string Hidden bool + Category string """.format(**typedef)) if typedef['value']: @@ -170,6 +171,11 @@ def _write_cli_flag_types(outfile, types): return f.Name }} + // GetHidden lets us know if the flag is hidden or not + func (f {name}Flag) GetHidden() bool {{ + return f.Hidden + }} + // {name} looks up the value of a local {name}Flag, returns // {context_default} if not found func (c *Context) {name}(name string) {context_type} {{ diff --git a/runtests b/runtests index ee22bdeed5..92d2081212 100755 --- a/runtests +++ b/runtests @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 from __future__ import print_function import argparse From 7c383b0d1628d83e3fd938e95de545e6cb87fc73 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 01:44:36 -0500 Subject: [PATCH 02/12] Go Fmt --- command.go | 2 +- command_test.go | 22 +++++++++++----------- flag_test.go | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/command.go b/command.go index 3d44404f10..5bb2adaa04 100644 --- a/command.go +++ b/command.go @@ -266,7 +266,7 @@ func reorderArgs(args []string) []string { } func translateShortOptions(set *flag.FlagSet, flagArgs Args) []string { - allCharsFlags := func (s string) bool { + allCharsFlags := func(s string) bool { for i := range s { f := set.Lookup(string(s[i])) if f == nil { diff --git a/command_test.go b/command_test.go index 8c2650e550..6ef920279b 100644 --- a/command_test.go +++ b/command_test.go @@ -59,9 +59,9 @@ func TestCommandFlagParsing(t *testing.T) { func TestParseAndRunShortOpts(t *testing.T) { cases := []struct { - testArgs []string - expectedErr error - expectedArgs []string + testArgs []string + expectedErr error + expectedArgs []string }{ {[]string{"foo", "test", "-a"}, nil, []string{}}, {[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}}, @@ -71,18 +71,18 @@ func TestParseAndRunShortOpts(t *testing.T) { {[]string{"foo", "test", "-cf"}, nil, []string{}}, {[]string{"foo", "test", "-acf"}, nil, []string{}}, {[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}}, - {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1" ,"-invalid"}}, + {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}}, } var args []string cmd := Command{ - Name: "test", - Usage: "this is for testing", - Description: "testing", - Action: func(c *Context) error { - args = c.Args() - return nil - }, + Name: "test", + Usage: "this is for testing", + Description: "testing", + Action: func(c *Context) error { + args = c.Args() + return nil + }, SkipArgReorder: true, UseShortOptionHandling: true, Flags: []Flag{ diff --git a/flag_test.go b/flag_test.go index da9fd731a7..ac3e70ce0a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1052,7 +1052,7 @@ func TestParseBoolShortOptionHandle(t *testing.T) { a := App{ Commands: []Command{ { - Name: "foobar", + Name: "foobar", UseShortOptionHandling: true, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { From 9dd96e9e90d7ff68de22a954ebbd977bbb900d79 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 11:41:54 -0500 Subject: [PATCH 03/12] Add in Category to Flag interface --- app.go | 14 +++++++++- app_test.go | 4 +++ flag.go | 1 + flag_generated.go | 65 +++++++++++++++++++++++++++++++++++++++++++++ generate-flag-types | 5 ++++ 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 9add067b05..677ba02f1d 100644 --- a/app.go +++ b/app.go @@ -51,6 +51,8 @@ type App struct { HideVersion bool // Populate on app startup, only gettable through method Categories() categories CommandCategories + // Populate on app startup, only gettable through method Categories() + flagCategories FlagCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -164,7 +166,7 @@ func (a *App) Setup() { } sort.Sort(a.categories) - if a.Metadata == nil { +if a.Metadata == nil { a.Metadata = make(map[string]interface{}) } @@ -192,6 +194,11 @@ func (a *App) Run(arguments []string) (err error) { return err } + a.flagCategories = FlagCategories{} + for _, flag := range a.Flags { + a.flagCategories = a.flagCategories.AddFlag(flag.GetCategory(), flag) + } + set.SetOutput(ioutil.Discard) err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) @@ -437,6 +444,11 @@ func (a *App) VisibleCommands() []Command { return ret } +// Categories returns a slice containing all the categories with the commands they contain +func (a *App) FlagCategories() FlagCategories { + return a.flagCategories +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (a *App) VisibleFlags() []Flag { return visibleFlags(a.Flags) diff --git a/app_test.go b/app_test.go index 95ff66b194..ceb0ee98a0 100644 --- a/app_test.go +++ b/app_test.go @@ -1607,6 +1607,10 @@ func (c *customBoolFlag) GetHidden() bool { return false } +func (c *customBoolFlag) GetCategory() string { + return "" +} + func (c *customBoolFlag) Apply(set *flag.FlagSet) { set.String(c.Nombre, c.Nombre, "") } diff --git a/flag.go b/flag.go index f3274cdb0c..8d01ca9069 100644 --- a/flag.go +++ b/flag.go @@ -73,6 +73,7 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) GetName() string + GetCategory() string GetHidden() bool } diff --git a/flag_generated.go b/flag_generated.go index b699b91e56..b3ebfbce2c 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -35,6 +35,11 @@ func (f BoolFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f BoolFlag) GetCategory() string { + return f.Category +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { @@ -89,6 +94,11 @@ func (f BoolTFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f BoolTFlag) GetCategory() string { + return f.Category +} + // BoolT looks up the value of a local BoolTFlag, returns // false if not found func (c *Context) BoolT(name string) bool { @@ -144,6 +154,11 @@ func (f DurationFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f DurationFlag) GetCategory() string { + return f.Category +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { @@ -199,6 +214,11 @@ func (f Float64Flag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Float64Flag) GetCategory() string { + return f.Category +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { @@ -253,6 +273,11 @@ func (f GenericFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f GenericFlag) GetCategory() string { + return f.Category +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { @@ -308,6 +333,11 @@ func (f Int64Flag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Int64Flag) GetCategory() string { + return f.Category +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { @@ -363,6 +393,11 @@ func (f IntFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f IntFlag) GetCategory() string { + return f.Category +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { @@ -417,6 +452,11 @@ func (f IntSliceFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f IntSliceFlag) GetCategory() string { + return f.Category +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { @@ -471,6 +511,11 @@ func (f Int64SliceFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Int64SliceFlag) GetCategory() string { + return f.Category +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { @@ -526,6 +571,11 @@ func (f StringFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f StringFlag) GetCategory() string { + return f.Category +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { @@ -580,6 +630,11 @@ func (f StringSliceFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f StringSliceFlag) GetCategory() string { + return f.Category +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { @@ -635,6 +690,11 @@ func (f Uint64Flag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Uint64Flag) GetCategory() string { + return f.Category +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { @@ -690,6 +750,11 @@ func (f UintFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f UintFlag) GetCategory() string { + return f.Category +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { diff --git a/generate-flag-types b/generate-flag-types index 324b3c1e44..9cea29eb2a 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -176,6 +176,11 @@ def _write_cli_flag_types(outfile, types): return f.Hidden }} + // GetCategory lets us access the flag category + func (f {name}Flag) GetCategory() string {{ + return f.Category + }} + // {name} looks up the value of a local {name}Flag, returns // {context_default} if not found func (c *Context) {name}(name string) {context_type} {{ From 51aebb5a03a97ecc2783b585cf742035b7bdfe57 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 12:00:16 -0500 Subject: [PATCH 04/12] Add mod files Let us use this repo via replace locally. --- go.mod | 7 +++++++ go.sum | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..8899222758 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/urfave/cli + +require ( + github.com/BurntSushi/toml v0.3.1 + gopkg.in/urfave/cli.v1 v1.20.0 + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000..46f9ae29da --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 9720ac029c97c01ec70333b4a7f2aa6dc04536b9 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 15:48:00 -0500 Subject: [PATCH 05/12] Basic working flag category support --- app.go | 17 ++++++++++------- command.go | 7 +++++++ help.go | 10 +++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 677ba02f1d..94834820cb 100644 --- a/app.go +++ b/app.go @@ -145,6 +145,14 @@ func (a *App) Setup() { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } + + fc := FlagCategories{} + for _, flag := range c.Flags { + fc = fc.AddFlag(flag.GetCategory(), flag) + } + + sort.Sort(fc) + c.FlagCategories = fc newCmds = append(newCmds, c) } a.Commands = newCmds @@ -166,7 +174,7 @@ func (a *App) Setup() { } sort.Sort(a.categories) -if a.Metadata == nil { + if a.Metadata == nil { a.Metadata = make(map[string]interface{}) } @@ -194,11 +202,6 @@ func (a *App) Run(arguments []string) (err error) { return err } - a.flagCategories = FlagCategories{} - for _, flag := range a.Flags { - a.flagCategories = a.flagCategories.AddFlag(flag.GetCategory(), flag) - } - set.SetOutput(ioutil.Discard) err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) @@ -445,7 +448,7 @@ func (a *App) VisibleCommands() []Command { } // Categories returns a slice containing all the categories with the commands they contain -func (a *App) FlagCategories() FlagCategories { +func (a *App) VisibleFlagCategories() FlagCategories { return a.flagCategories } diff --git a/command.go b/command.go index 5bb2adaa04..1e6ee9f3e7 100644 --- a/command.go +++ b/command.go @@ -45,6 +45,8 @@ type Command struct { Subcommands Commands // List of flags to parse Flags []Flag + // List of all flag categories + FlagCategories FlagCategories // Treat all flags as normal arguments if true SkipFlagParsing bool // Skip argument reordering which attempts to move flags before arguments, @@ -377,6 +379,11 @@ func (c Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } +// Categories returns a slice containing all the categories with the commands they contain +func (c Command) VisibleFlagCategories() FlagCategories { + return c.FlagCategories +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (c Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) diff --git a/help.go b/help.go index 65874fa2fc..18e185ef19 100644 --- a/help.go +++ b/help.go @@ -54,9 +54,10 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} + {{.Description}}{{end}} -OPTIONS: +OPTIONS:{{range .VisibleFlagCategories}} + {{.Name}} {{range .VisibleFlags}}{{.}} {{end}}{{end}} ` @@ -250,7 +251,10 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc m // If the writer is closed, t.Execute will fail, and there's nothing // we can do to recover. if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { - fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) + // Generic error message + fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR DEBUG: %#v\n", err) + // Helpful error message + fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR DEBUG: %#v\n", err.Error()) } return } From 50b52ca9d52b24529cdfeb6dcbff3aa80b69922f Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 17:25:59 -0500 Subject: [PATCH 06/12] Fix unit tests --- app_test.go | 75 ++++++++++++++++++++++++++++++----------------------- help.go | 5 ++-- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/app_test.go b/app_test.go index ceb0ee98a0..f779d69a91 100644 --- a/app_test.go +++ b/app_test.go @@ -1381,20 +1381,23 @@ func TestApp_VisibleCategories(t *testing.T) { app.HideHelp = true app.Commands = []Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", + Name: "command2", + Category: "2", + HelpName: "foo command2", + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, } @@ -1421,21 +1424,24 @@ func TestApp_VisibleCategories(t *testing.T) { app.HideHelp = true app.Commands = []Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, } @@ -1456,22 +1462,25 @@ func TestApp_VisibleCategories(t *testing.T) { app.HideHelp = true app.Commands = []Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + FlagCategories: FlagCategories{}, }, } diff --git a/help.go b/help.go index 18e185ef19..b80bac3ce6 100644 --- a/help.go +++ b/help.go @@ -54,12 +54,11 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}} - + {{.Description}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}} {{.Name}} {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. From 75e4ee69e98bcd59c97c16cdc276d0352afa5ee0 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 15:00:43 -0400 Subject: [PATCH 07/12] Porting remainder of #796 --- app.go | 6 ++++-- category.go | 14 ++++++++------ flag.go | 10 ++++++++-- flag_bool.go | 6 ++++++ flag_duration.go | 6 ++++++ flag_float64.go | 6 ++++++ flag_float64_slice.go | 6 ++++++ flag_generic.go | 6 ++++++ flag_int.go | 6 ++++++ flag_int64.go | 6 ++++++ flag_int64_slice.go | 8 +++++++- flag_int_slice.go | 8 +++++++- flag_path.go | 6 ++++++ flag_string.go | 6 ++++++ flag_string_slice.go | 6 ++++++ flag_timestamp.go | 6 ++++++ flag_uint.go | 6 ++++++ flag_uint64.go | 6 ++++++ 18 files changed, 112 insertions(+), 12 deletions(-) diff --git a/app.go b/app.go index dd7f026498..65813c6158 100644 --- a/app.go +++ b/app.go @@ -185,8 +185,10 @@ func (a *App) Setup() { } fc := FlagCategories{} - for _, flag := range c.Flags { - fc = fc.AddFlag(flag.GetCategory(), flag) + for _, fl := range c.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + fc = fc.AddFlag(cf.GetCategory(), cf) + } } sort.Sort(fc) diff --git a/category.go b/category.go index f9ba86a299..7580e90d6e 100644 --- a/category.go +++ b/category.go @@ -84,7 +84,7 @@ type FlagCategories []*FlagCategory // FlagCategory is a category containing commands. type FlagCategory struct { Name string - Flags Flags + Flags []Flag } func (f FlagCategories) Less(i, j int) bool { @@ -111,11 +111,13 @@ func (f FlagCategories) AddFlag(category string, flag Flag) FlagCategories { } // VisibleFlags returns a slice of the Flags with Hidden=false -func (c *FlagCategory) VisibleFlags() []Flag { - ret := []Flag{} - for _, flag := range c.Flags { - if !flag.GetHidden() { - ret = append(ret, flag) +func (c *FlagCategory) VisibleFlags() []VisibleFlag { + ret := []VisibleFlag{} + for _, fl := range c.Flags { + if vf, ok := fl.(VisibleFlag); ok { + if vf.IsVisible() { + ret = append(ret, vf) + } } } return ret diff --git a/flag.go b/flag.go index 60d592e52c..16778d77e0 100644 --- a/flag.go +++ b/flag.go @@ -94,8 +94,6 @@ type Flag interface { Apply(*flag.FlagSet) error Names() []string IsSet() bool - GetCategory() string - GetHidden() bool } // RequiredFlag is an interface that allows us to mark flags as required @@ -135,6 +133,14 @@ type VisibleFlag interface { IsVisible() bool } +// CategorizableFlag is an interface that allows us to potentially +// use a flag in a categorized representation. +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string +} + func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) diff --git a/flag_bool.go b/flag_bool.go index b8e625a1d1..ef5f6346f2 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -19,6 +19,7 @@ type BoolFlag struct { DefaultText string Destination *bool HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *BoolFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *BoolFlag) 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 *BoolFlag) GetValue() string { diff --git a/flag_duration.go b/flag_duration.go index e8ca15ec02..7592180fa2 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -19,6 +19,7 @@ type DurationFlag struct { DefaultText string Destination *time.Duration HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *DurationFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *DurationFlag) 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 *DurationFlag) GetValue() string { diff --git a/flag_float64.go b/flag_float64.go index 0ac5b43f0d..1cda961438 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -19,6 +19,7 @@ type Float64Flag struct { DefaultText string Destination *float64 HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *Float64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Float64Flag) 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 *Float64Flag) GetValue() string { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 984f77f7f3..d2328b6765 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -85,6 +85,7 @@ type Float64SliceFlag struct { Value *Float64Slice DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -118,6 +119,11 @@ func (f *Float64SliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Float64SliceFlag) 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 *Float64SliceFlag) GetValue() string { diff --git a/flag_generic.go b/flag_generic.go index d159507147..6c973f99de 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -24,6 +24,7 @@ type GenericFlag struct { Value Generic DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -57,6 +58,11 @@ func (f *GenericFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *GenericFlag) 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 *GenericFlag) GetValue() string { diff --git a/flag_int.go b/flag_int.go index 62c0848688..7979c6409a 100644 --- a/flag_int.go +++ b/flag_int.go @@ -19,6 +19,7 @@ type IntFlag struct { DefaultText string Destination *int HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *IntFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *IntFlag) 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 *IntFlag) GetValue() string { diff --git a/flag_int64.go b/flag_int64.go index 2f0be7aa06..7083c60c30 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -19,6 +19,7 @@ type Int64Flag struct { DefaultText string Destination *int64 HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *Int64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Int64Flag) 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 *Int64Flag) GetValue() string { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index a53b185078..5f883bf3ec 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -86,6 +86,7 @@ type Int64SliceFlag struct { Value *Int64Slice DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -115,10 +116,15 @@ func (f *Int64SliceFlag) TakesValue() bool { } // GetUsage returns the usage string for the flag -func (f Int64SliceFlag) GetUsage() string { +func (f *Int64SliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Int64SliceFlag) 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 *Int64SliceFlag) GetValue() string { diff --git a/flag_int_slice.go b/flag_int_slice.go index 5f3bd88f05..1a2c679872 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -97,6 +97,7 @@ type IntSliceFlag struct { Value *IntSlice DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -126,10 +127,15 @@ func (f *IntSliceFlag) TakesValue() bool { } // GetUsage returns the usage string for the flag -func (f IntSliceFlag) GetUsage() string { +func (f *IntSliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *IntSliceFlag) 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 *IntSliceFlag) GetValue() string { diff --git a/flag_path.go b/flag_path.go index 4010e84c62..8a90685878 100644 --- a/flag_path.go +++ b/flag_path.go @@ -18,6 +18,7 @@ type PathFlag struct { DefaultText string Destination *string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -51,6 +52,11 @@ func (f *PathFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *PathFlag) 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 *PathFlag) GetValue() string { diff --git a/flag_string.go b/flag_string.go index cd3c7dff4a..7d904a0296 100644 --- a/flag_string.go +++ b/flag_string.go @@ -19,6 +19,7 @@ type StringFlag struct { DefaultText string Destination *string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *StringFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *StringFlag) 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 *StringFlag) GetValue() string { diff --git a/flag_string_slice.go b/flag_string_slice.go index 166424775f..a280505e52 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -82,6 +82,7 @@ type StringSliceFlag struct { DefaultText string HasBeenSet bool Destination *StringSlice + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -115,6 +116,11 @@ func (f *StringSliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *StringSliceFlag) 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 *StringSliceFlag) GetValue() string { diff --git a/flag_timestamp.go b/flag_timestamp.go index ed06418284..14be5f8c82 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -72,6 +72,7 @@ type TimestampFlag struct { DefaultText string HasBeenSet bool Destination *Timestamp + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -105,6 +106,11 @@ func (f *TimestampFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *TimestampFlag) 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 *TimestampFlag) GetValue() string { diff --git a/flag_uint.go b/flag_uint.go index dd10e1c01d..f7efed11a0 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -19,6 +19,7 @@ type UintFlag struct { DefaultText string Destination *uint HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *UintFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *UintFlag) GetCategory() string { + return f.Category +} + // IsVisible returns true if the flag is not hidden, otherwise false func (f *UintFlag) IsVisible() bool { return !f.Hidden diff --git a/flag_uint64.go b/flag_uint64.go index 017db53c1d..0b3edfe9b8 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -19,6 +19,7 @@ type Uint64Flag struct { DefaultText string Destination *uint64 HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *Uint64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Uint64Flag) GetCategory() string { + return f.Category +} + // IsVisible returns true if the flag is not hidden, otherwise false func (f *Uint64Flag) IsVisible() bool { return !f.Hidden From e4580f0c50b5921206bb0cd474a9ba450466f3e2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 15:44:59 -0400 Subject: [PATCH 08/12] Extend flag categorization to top-level (global) flags --- app.go | 10 ++++++- app_test.go | 83 ++++++++++++++++++++++++++++++++--------------------- template.go | 18 +++++++++--- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/app.go b/app.go index 65813c6158..fdc4ea6ad0 100644 --- a/app.go +++ b/app.go @@ -52,7 +52,7 @@ type App struct { HideVersion bool // categories contains the categorized commands and is populated on app startup categories CommandCategories - // Populate on app startup, only gettable through method Categories() + // flagCategories contains the categorized flags and is populated on app startup flagCategories FlagCategories // An action to execute when the shell completion flag is set BashComplete BashCompleteFunc @@ -217,6 +217,14 @@ func (a *App) Setup() { } sort.Sort(a.categories.(*commandCategories)) + a.flagCategories = FlagCategories{} + for _, fl := range a.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + a.flagCategories.AddFlag(cf.GetCategory(), cf) + } + } + sort.Sort(a.flagCategories) + if a.Metadata == nil { a.Metadata = make(map[string]interface{}) } diff --git a/app_test.go b/app_test.go index 76e211d681..3ba194574e 100644 --- a/app_test.go +++ b/app_test.go @@ -1789,20 +1789,23 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", + Name: "command2", + Category: "2", + HelpName: "foo command2", + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, }, } @@ -1830,21 +1833,24 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, }, } @@ -1866,22 +1872,25 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + FlagCategories: FlagCategories{}, }, }, } @@ -1890,6 +1899,14 @@ func TestApp_VisibleCategories(t *testing.T) { expect(t, []CommandCategory{}, app.VisibleCategories()) } +func TestApp_VisibleFlagCategories(t *testing.T) { + app := &App{} + vfc := app.VisibleFlagCategories() + if len(vfc) != 0 { + t.Errorf("unexpected visible flag categories %+v", vfc) + } +} + func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := &App{ Action: func(c *Context) error { return nil }, diff --git a/template.go b/template.go index 39fa4db080..8b7ea63a7a 100644 --- a/template.go +++ b/template.go @@ -22,11 +22,16 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{.Name}} + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}}{{end}} @@ -45,11 +50,16 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{.Name}} + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. From ddac788d8515c4354497becb29feafd055c3d9ec Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2022 20:42:24 -0400 Subject: [PATCH 09/12] Correct doc comment per feedback --- category.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/category.go b/category.go index 7580e90d6e..60e4e9b61e 100644 --- a/category.go +++ b/category.go @@ -81,7 +81,7 @@ func (c *commandCategory) VisibleCommands() []*Command { // FlagCategories is a slice of *FlagCategory. type FlagCategories []*FlagCategory -// FlagCategory is a category containing commands. +// FlagCategory is a category containing flags. type FlagCategory struct { Name string Flags []Flag From 156eaafb22ac81600ca4bec47d3408f1a6dfb3f7 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2022 08:59:50 -0400 Subject: [PATCH 10/12] Rework flag categories a bit with internal maps instead of slices and slightly less public API surface area --- app.go | 19 +++++------ app_test.go | 77 ++++++++++++++++++++------------------------- category.go | 90 +++++++++++++++++++++++++++++++++++++---------------- command.go | 14 +++++---- template.go | 8 ++--- 5 files changed, 119 insertions(+), 89 deletions(-) diff --git a/app.go b/app.go index 83ebe30c0d..73793884df 100644 --- a/app.go +++ b/app.go @@ -184,15 +184,14 @@ func (a *App) Setup() { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } - fc := FlagCategories{} + fc := newFlagCategories() for _, fl := range c.Flags { if cf, ok := fl.(CategorizableFlag); ok { - fc = fc.AddFlag(cf.GetCategory(), cf) + fc.AddFlag(cf.GetCategory(), cf) } } - sort.Sort(fc) - c.FlagCategories = fc + c.flagCategories = fc newCommands = append(newCommands, c) } a.Commands = newCommands @@ -217,13 +216,12 @@ func (a *App) Setup() { } sort.Sort(a.categories.(*commandCategories)) - a.flagCategories = FlagCategories{} + a.flagCategories = newFlagCategories() for _, fl := range a.Flags { if cf, ok := fl.(CategorizableFlag); ok { a.flagCategories.AddFlag(cf.GetCategory(), cf) } } - sort.Sort(a.flagCategories) if a.Metadata == nil { a.Metadata = make(map[string]interface{}) @@ -501,9 +499,12 @@ func (a *App) VisibleCommands() []*Command { return ret } -// Categories returns a slice containing all the categories with the commands they contain -func (a *App) VisibleFlagCategories() FlagCategories { - return a.flagCategories +// VisibleFlagCategories returns a slice containing all the categories with the flags they contain +func (a *App) VisibleFlagCategories() []VisibleFlagCategory { + if a.flagCategories == nil { + a.flagCategories = newFlagCategories() + } + return a.flagCategories.VisibleCategories() } // VisibleFlags returns a slice of the Flags with Hidden=false diff --git a/app_test.go b/app_test.go index 38c7ce8b88..52b7209220 100644 --- a/app_test.go +++ b/app_test.go @@ -142,8 +142,8 @@ func ExampleApp_Run_appHelp() { // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: - // --name value a name to say (default: "bob") // --help, -h show help (default: false) + // --name value a name to say (default: "bob") // --version, -v print the version (default: false) } @@ -1823,23 +1823,20 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - FlagCategories: FlagCategories{}, + Name: "command2", + Category: "2", + HelpName: "foo command2", }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - FlagCategories: FlagCategories{}, + Name: "command3", + Category: "3", + HelpName: "foo command3", }, }, } @@ -1867,24 +1864,21 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - FlagCategories: FlagCategories{}, + Name: "command3", + Category: "3", + HelpName: "foo command3", }, }, } @@ -1906,25 +1900,22 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, }, }, } diff --git a/category.go b/category.go index 60e4e9b61e..7bbe4a5d70 100644 --- a/category.go +++ b/category.go @@ -1,10 +1,12 @@ package cli +import "sort" + // CommandCategories interface allows for category manipulation type CommandCategories interface { // AddCommand adds a command to a category, creating a new category if necessary. AddCommand(category string, command *Command) - // categories returns a copy of the category slice + // Categories returns a slice of categories sorted by name Categories() []CommandCategory } @@ -78,47 +80,81 @@ func (c *commandCategory) VisibleCommands() []*Command { return ret } -// FlagCategories is a slice of *FlagCategory. -type FlagCategories []*FlagCategory - -// FlagCategory is a category containing flags. -type FlagCategory struct { - Name string - Flags []Flag +// FlagCategories interface allows for category manipulation +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory } -func (f FlagCategories) Less(i, j int) bool { - return lexicographicLess(f[i].Name, f[j].Name) +type defaultFlagCategories struct { + m map[string]*defaultVisibleFlagCategory } -func (f FlagCategories) Len() int { - return len(f) +func newFlagCategories() FlagCategories { + return &defaultFlagCategories{ + m: map[string]*defaultVisibleFlagCategory{}, + } } -func (f FlagCategories) Swap(i, j int) { - f[i], f[j] = f[j], f[i] +func (f *defaultFlagCategories) AddFlag(category string, fl Flag) { + if _, ok := f.m[category]; !ok { + f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}} + } + + f.m[category].m[fl.String()] = fl } -// AddFlags adds a command to a category. -func (f FlagCategories) AddFlag(category string, flag Flag) FlagCategories { - for _, flagCategory := range f { - if flagCategory.Name == category { - flagCategory.Flags = append(flagCategory.Flags, flag) - return f - } +func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory { + catNames := []string{} + for name := range f.m { + catNames = append(catNames, name) } - return append(f, &FlagCategory{Name: category, Flags: []Flag{flag}}) + + sort.Strings(catNames) + + ret := make([]VisibleFlagCategory, len(catNames)) + for i, name := range catNames { + ret[i] = f.m[name] + } + + return ret } -// VisibleFlags returns a slice of the Flags with Hidden=false -func (c *FlagCategory) VisibleFlags() []VisibleFlag { - ret := []VisibleFlag{} - for _, fl := range c.Flags { +// VisibleFlagCategory is a category containing flags. +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + +type defaultVisibleFlagCategory struct { + name string + m map[string]Flag +} + +func (fc *defaultVisibleFlagCategory) Name() string { + return fc.name +} + +func (fc *defaultVisibleFlagCategory) Flags() []VisibleFlag { + vfNames := []string{} + for flName, fl := range fc.m { if vf, ok := fl.(VisibleFlag); ok { if vf.IsVisible() { - ret = append(ret, vf) + vfNames = append(vfNames, flName) } } } + + sort.Strings(vfNames) + + ret := make([]VisibleFlag, len(vfNames)) + for i, flName := range vfNames { + ret[i] = fc.m[flName].(VisibleFlag) + } + return ret } diff --git a/command.go b/command.go index 1f24dc7b73..02ea5ffac7 100644 --- a/command.go +++ b/command.go @@ -38,9 +38,8 @@ type Command struct { // List of child commands Subcommands []*Command // List of flags to parse - Flags []Flag - // List of all flag categories - FlagCategories FlagCategories + Flags []Flag + flagCategories FlagCategories // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -282,9 +281,12 @@ func (c *Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } -// Categories returns a slice containing all the categories with the commands they contain -func (c Command) VisibleFlagCategories() FlagCategories { - return c.FlagCategories +// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain +func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { + if c.flagCategories == nil { + c.flagCategories = newFlagCategories() + } + return c.flagCategories.VisibleCategories() } // VisibleFlags returns a slice of the Flags with Hidden=false diff --git a/template.go b/template.go index 8b7ea63a7a..264eb856bb 100644 --- a/template.go +++ b/template.go @@ -25,8 +25,8 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}} - {{.Name}} - {{range .VisibleFlags}}{{.}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: @@ -53,8 +53,8 @@ DESCRIPTION: {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}} - {{.Name}} - {{range .VisibleFlags}}{{.}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: From 16d5d5a3df85096946ba70a48bf465224a1f7367 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 6 May 2022 22:23:17 -0400 Subject: [PATCH 11/12] Some changes per feedback in #1368 --- app.go | 11 ++--------- category.go | 11 +++++++++++ command.go | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 73793884df..ebbf1928a9 100644 --- a/app.go +++ b/app.go @@ -184,14 +184,7 @@ func (a *App) Setup() { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } - fc := newFlagCategories() - for _, fl := range c.Flags { - if cf, ok := fl.(CategorizableFlag); ok { - fc.AddFlag(cf.GetCategory(), cf) - } - } - - c.flagCategories = fc + c.flagCategories = newFlagCategoriesFromFlags(c.Flags) newCommands = append(newCommands, c) } a.Commands = newCommands @@ -502,7 +495,7 @@ func (a *App) VisibleCommands() []*Command { // VisibleFlagCategories returns a slice containing all the categories with the flags they contain func (a *App) VisibleFlagCategories() []VisibleFlagCategory { if a.flagCategories == nil { - a.flagCategories = newFlagCategories() + return []VisibleFlagCategory{} } return a.flagCategories.VisibleCategories() } diff --git a/category.go b/category.go index 7bbe4a5d70..8bf325e203 100644 --- a/category.go +++ b/category.go @@ -98,6 +98,17 @@ func newFlagCategories() FlagCategories { } } +func newFlagCategoriesFromFlags(fs []Flag) FlagCategories { + fc := newFlagCategories() + for _, fl := range fs { + if cf, ok := fl.(CategorizableFlag); ok { + fc.AddFlag(cf.GetCategory(), cf) + } + } + + return fc +} + func (f *defaultFlagCategories) AddFlag(category string, fl Flag) { if _, ok := f.m[category]; !ok { f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}} diff --git a/command.go b/command.go index 02ea5ffac7..1e39c9e5a2 100644 --- a/command.go +++ b/command.go @@ -284,7 +284,7 @@ func (c *Command) startApp(ctx *Context) error { // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { - c.flagCategories = newFlagCategories() + return []VisibleFlagCategory{} } return c.flagCategories.VisibleCategories() } From 21d435d4d19d206426351afb509ea520bb42ff25 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 08:50:09 -0400 Subject: [PATCH 12/12] Adapt flag generation for flag categories --- godoc-current.txt | 117 +++++++++++++++++++++++++++-- internal/genflags/generated.gotmpl | 1 + testdata/godoc-v2.x.txt | 117 +++++++++++++++++++++++++++-- zz_generated.flags.go | 15 ++++ 4 files changed, 236 insertions(+), 14 deletions(-) diff --git a/godoc-current.txt b/godoc-current.txt index 1bddc54736..62642bbd28 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -45,11 +45,16 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}}{{end}} @@ -68,11 +73,16 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{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 @@ -367,6 +377,10 @@ func (a *App) VisibleCategories() []CommandCategory func (a *App) VisibleCommands() []*Command VisibleCommands returns a slice of the Commands with Hidden=false +func (a *App) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the categories with the + flags they contain + func (a *App) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false @@ -407,6 +421,7 @@ type BeforeFunc func(*Context) error type BoolFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -429,6 +444,9 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error func (f *BoolFlag) Get(ctx *Context) bool Get returns the flag’s value in the given Context. +func (f *BoolFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *BoolFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -460,6 +478,14 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string +} + CategorizableFlag is an interface that allows us to potentially use a flag + in a categorized representation. + type Command struct { // The name of the command Name string @@ -491,6 +517,7 @@ type Command struct { Subcommands []*Command // List of flags to parse Flags []Flag + // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -530,13 +557,17 @@ 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) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the visible flag + categories with the flags they contain + func (c *Command) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false type CommandCategories interface { // AddCommand adds a command to a category, creating a new category if necessary. AddCommand(category string, command *Command) - // categories returns a copy of the category slice + // Categories returns a slice of categories sorted by name Categories() []CommandCategory } CommandCategories interface allows for category manipulation @@ -680,6 +711,7 @@ type DocGenerationFlag interface { type DurationFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -702,6 +734,9 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error func (f *DurationFlag) Get(ctx *Context) time.Duration Get returns the flag’s value in the given Context. +func (f *DurationFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *DurationFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -797,6 +832,14 @@ var VersionFlag Flag = &BoolFlag{ } VersionFlag prints the version for the application +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory +} + FlagCategories interface allows for category manipulation + type FlagEnvHintFunc func(envVars []string, str string) string FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help with the environment variable details. @@ -841,6 +884,7 @@ func (f FlagsByName) Swap(i, j int) type Float64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -863,6 +907,9 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error func (f *Float64Flag) Get(ctx *Context) float64 Get returns the flag’s value in the given Context. +func (f *Float64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -920,6 +967,7 @@ func (f *Float64Slice) Value() []float64 type Float64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -942,6 +990,9 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error func (f *Float64SliceFlag) Get(ctx *Context) []float64 Get returns the flag’s value in the given Context. +func (f *Float64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -982,6 +1033,7 @@ type Generic interface { type GenericFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1007,6 +1059,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error func (f *GenericFlag) Get(ctx *Context) interface{} Get returns the flag’s value in the given Context. +func (f *GenericFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *GenericFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1041,6 +1096,7 @@ func (f *GenericFlag) TakesValue() bool type Int64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1063,6 +1119,9 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error func (f *Int64Flag) Get(ctx *Context) int64 Get returns the flag’s value in the given Context. +func (f *Int64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1120,6 +1179,7 @@ func (i *Int64Slice) Value() []int64 type Int64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1142,13 +1202,16 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error func (f *Int64SliceFlag) Get(ctx *Context) []int64 Get returns the flag’s value in the given Context. +func (f *Int64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *Int64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f Int64SliceFlag) GetUsage() string +func (f *Int64SliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *Int64SliceFlag) GetValue() string @@ -1176,6 +1239,7 @@ func (f *Int64SliceFlag) TakesValue() bool type IntFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1198,6 +1262,9 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error func (f *IntFlag) Get(ctx *Context) int Get returns the flag’s value in the given Context. +func (f *IntFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1259,6 +1326,7 @@ func (i *IntSlice) Value() []int type IntSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1281,13 +1349,16 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error func (f *IntSliceFlag) Get(ctx *Context) []int Get returns the flag’s value in the given Context. +func (f *IntSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *IntSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f IntSliceFlag) GetUsage() string +func (f *IntSliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *IntSliceFlag) GetValue() string @@ -1329,6 +1400,7 @@ type Path = string type PathFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1353,6 +1425,9 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error func (f *PathFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *PathFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *PathFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1401,6 +1476,7 @@ type Serializer interface { type StringFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1425,6 +1501,9 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error func (f *StringFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *StringFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1482,6 +1561,7 @@ func (s *StringSlice) Value() []string type StringSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1506,6 +1586,9 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error func (f *StringSliceFlag) Get(ctx *Context) []string Get returns the flag’s value in the given Context. +func (f *StringSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1566,6 +1649,7 @@ func (t *Timestamp) Value() *time.Time type TimestampFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1590,6 +1674,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error func (f *TimestampFlag) Get(ctx *Context) *time.Time Get returns the flag’s value in the given Context. +func (f *TimestampFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *TimestampFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1624,6 +1711,7 @@ func (f *TimestampFlag) TakesValue() bool type Uint64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1646,6 +1734,9 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error func (f *Uint64Flag) Get(ctx *Context) uint64 Get returns the flag’s value in the given Context. +func (f *Uint64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Uint64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1680,6 +1771,7 @@ func (f *Uint64Flag) TakesValue() bool type UintFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1702,6 +1794,9 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error func (f *UintFlag) Get(ctx *Context) uint Get returns the flag’s value in the given Context. +func (f *UintFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *UintFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1741,6 +1836,14 @@ type VisibleFlag interface { } VisibleFlag is an interface that allows to check if a flag is visible +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + VisibleFlagCategory is a category containing flags. + package altsrc // import "github.com/urfave/cli/v2/altsrc" diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl index 5d6f0dde1a..13d006aeef 100644 --- a/internal/genflags/generated.gotmpl +++ b/internal/genflags/generated.gotmpl @@ -7,6 +7,7 @@ package {{.PackageName}} type {{.TypeName}} struct { Name string + Category string DefaultText string FilePath string Usage string diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 1bddc54736..62642bbd28 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -45,11 +45,16 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}}{{end}} @@ -68,11 +73,16 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{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 @@ -367,6 +377,10 @@ func (a *App) VisibleCategories() []CommandCategory func (a *App) VisibleCommands() []*Command VisibleCommands returns a slice of the Commands with Hidden=false +func (a *App) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the categories with the + flags they contain + func (a *App) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false @@ -407,6 +421,7 @@ type BeforeFunc func(*Context) error type BoolFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -429,6 +444,9 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error func (f *BoolFlag) Get(ctx *Context) bool Get returns the flag’s value in the given Context. +func (f *BoolFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *BoolFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -460,6 +478,14 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string +} + CategorizableFlag is an interface that allows us to potentially use a flag + in a categorized representation. + type Command struct { // The name of the command Name string @@ -491,6 +517,7 @@ type Command struct { Subcommands []*Command // List of flags to parse Flags []Flag + // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -530,13 +557,17 @@ 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) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the visible flag + categories with the flags they contain + func (c *Command) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false type CommandCategories interface { // AddCommand adds a command to a category, creating a new category if necessary. AddCommand(category string, command *Command) - // categories returns a copy of the category slice + // Categories returns a slice of categories sorted by name Categories() []CommandCategory } CommandCategories interface allows for category manipulation @@ -680,6 +711,7 @@ type DocGenerationFlag interface { type DurationFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -702,6 +734,9 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error func (f *DurationFlag) Get(ctx *Context) time.Duration Get returns the flag’s value in the given Context. +func (f *DurationFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *DurationFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -797,6 +832,14 @@ var VersionFlag Flag = &BoolFlag{ } VersionFlag prints the version for the application +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory +} + FlagCategories interface allows for category manipulation + type FlagEnvHintFunc func(envVars []string, str string) string FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help with the environment variable details. @@ -841,6 +884,7 @@ func (f FlagsByName) Swap(i, j int) type Float64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -863,6 +907,9 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error func (f *Float64Flag) Get(ctx *Context) float64 Get returns the flag’s value in the given Context. +func (f *Float64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -920,6 +967,7 @@ func (f *Float64Slice) Value() []float64 type Float64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -942,6 +990,9 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error func (f *Float64SliceFlag) Get(ctx *Context) []float64 Get returns the flag’s value in the given Context. +func (f *Float64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -982,6 +1033,7 @@ type Generic interface { type GenericFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1007,6 +1059,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error func (f *GenericFlag) Get(ctx *Context) interface{} Get returns the flag’s value in the given Context. +func (f *GenericFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *GenericFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1041,6 +1096,7 @@ func (f *GenericFlag) TakesValue() bool type Int64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1063,6 +1119,9 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error func (f *Int64Flag) Get(ctx *Context) int64 Get returns the flag’s value in the given Context. +func (f *Int64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1120,6 +1179,7 @@ func (i *Int64Slice) Value() []int64 type Int64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1142,13 +1202,16 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error func (f *Int64SliceFlag) Get(ctx *Context) []int64 Get returns the flag’s value in the given Context. +func (f *Int64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *Int64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f Int64SliceFlag) GetUsage() string +func (f *Int64SliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *Int64SliceFlag) GetValue() string @@ -1176,6 +1239,7 @@ func (f *Int64SliceFlag) TakesValue() bool type IntFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1198,6 +1262,9 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error func (f *IntFlag) Get(ctx *Context) int Get returns the flag’s value in the given Context. +func (f *IntFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1259,6 +1326,7 @@ func (i *IntSlice) Value() []int type IntSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1281,13 +1349,16 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error func (f *IntSliceFlag) Get(ctx *Context) []int Get returns the flag’s value in the given Context. +func (f *IntSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *IntSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f IntSliceFlag) GetUsage() string +func (f *IntSliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *IntSliceFlag) GetValue() string @@ -1329,6 +1400,7 @@ type Path = string type PathFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1353,6 +1425,9 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error func (f *PathFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *PathFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *PathFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1401,6 +1476,7 @@ type Serializer interface { type StringFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1425,6 +1501,9 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error func (f *StringFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *StringFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1482,6 +1561,7 @@ func (s *StringSlice) Value() []string type StringSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1506,6 +1586,9 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error func (f *StringSliceFlag) Get(ctx *Context) []string Get returns the flag’s value in the given Context. +func (f *StringSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1566,6 +1649,7 @@ func (t *Timestamp) Value() *time.Time type TimestampFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1590,6 +1674,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error func (f *TimestampFlag) Get(ctx *Context) *time.Time Get returns the flag’s value in the given Context. +func (f *TimestampFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *TimestampFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1624,6 +1711,7 @@ func (f *TimestampFlag) TakesValue() bool type Uint64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1646,6 +1734,9 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error func (f *Uint64Flag) Get(ctx *Context) uint64 Get returns the flag’s value in the given Context. +func (f *Uint64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Uint64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1680,6 +1771,7 @@ func (f *Uint64Flag) TakesValue() bool type UintFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1702,6 +1794,9 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error func (f *UintFlag) Get(ctx *Context) uint Get returns the flag’s value in the given Context. +func (f *UintFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *UintFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1741,6 +1836,14 @@ type VisibleFlag interface { } VisibleFlag is an interface that allows to check if a flag is visible +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + VisibleFlagCategory is a category containing flags. + package altsrc // import "github.com/urfave/cli/v2/altsrc" diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 703e66a406..6f39539277 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -8,6 +8,7 @@ import "time" type Float64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -37,6 +38,7 @@ func (f *Float64SliceFlag) Names() []string { type GenericFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -73,6 +75,7 @@ func (f *GenericFlag) Names() []string { type Int64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -102,6 +105,7 @@ func (f *Int64SliceFlag) Names() []string { type IntSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -131,6 +135,7 @@ func (f *IntSliceFlag) Names() []string { type PathFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -167,6 +172,7 @@ func (f *PathFlag) Names() []string { type StringSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -198,6 +204,7 @@ func (f *StringSliceFlag) Names() []string { type TimestampFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -234,6 +241,7 @@ func (f *TimestampFlag) Names() []string { type BoolFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -268,6 +276,7 @@ func (f *BoolFlag) Names() []string { type Float64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -302,6 +311,7 @@ func (f *Float64Flag) Names() []string { type IntFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -336,6 +346,7 @@ func (f *IntFlag) Names() []string { type Int64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -370,6 +381,7 @@ func (f *Int64Flag) Names() []string { type StringFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -406,6 +418,7 @@ func (f *StringFlag) Names() []string { type DurationFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -440,6 +453,7 @@ func (f *DurationFlag) Names() []string { type UintFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -474,6 +488,7 @@ func (f *UintFlag) Names() []string { type Uint64Flag struct { Name string + Category string DefaultText string FilePath string Usage string