From 0ee87b411a343a85c770a3f493d2ef3aae4ad163 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 21 Aug 2022 14:21:23 -0400 Subject: [PATCH 1/5] Fix:(issue_557) Change app help name --- app.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app.go b/app.go index 2ffacd5..93ceba8 100644 --- a/app.go +++ b/app.go @@ -133,6 +133,7 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), + HelpName: "", // setup will fill this later Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, From e925d26b97287729ae1d3a61a5766ba03ce411a2 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 20 Sep 2022 21:18:03 -0400 Subject: [PATCH 2/5] Fix:(issue_557) Make help output consistent between different invocations --- app_test.go | 5 +++- command.go | 50 +++++++++++++++++++++++++++++++++--- godoc-current.txt | 17 ++++++++----- help.go | 64 ++++++++++++++++++++++++++++++++++++----------- help_test.go | 12 +++++---- template.go | 9 +++---- 6 files changed, 122 insertions(+), 35 deletions(-) diff --git a/app_test.go b/app_test.go index 64316fc..321c7db 100644 --- a/app_test.go +++ b/app_test.go @@ -175,10 +175,13 @@ func ExampleApp_Run_commandHelp() { // greet describeit - use it to see a description // // USAGE: - // greet describeit [arguments...] + // greet describeit [command options] [arguments...] // // DESCRIPTION: // This is how we describe describeit the function + // + // OPTIONS: + // --help, -h show help (default: false) } func ExampleApp_Run_noAction() { diff --git a/command.go b/command.go index 13b79de..ddc79b4 100644 --- a/command.go +++ b/command.go @@ -62,6 +62,9 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string + + // categories contains the categorized commands and is populated on app startup + categories CommandCategories } type Commands []*Command @@ -166,7 +169,7 @@ func (c *Command) Run(ctx *Context) (err error) { } if c.Action == nil { - c.Action = helpSubcommand.Action + c.Action = helpCommand.Action } cCtx.Command = c @@ -280,7 +283,7 @@ func (c *Command) startApp(ctx *Context) error { if c.Action != nil { app.Action = c.Action } else { - app.Action = helpSubcommand.Action + app.Action = helpCommand.Action } app.OnUsageError = c.OnUsageError @@ -294,7 +297,12 @@ 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 { - return []VisibleFlagCategory{} + c.flagCategories = newFlagCategories() + for _, fl := range c.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + c.flagCategories.AddFlag(cf.GetCategory(), cf) + } + } } return c.flagCategories.VisibleCategories() } @@ -304,6 +312,42 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } +// VisibleCategories returns a slice of categories and commands that are +// Hidden=false +func (c *Command) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + if c.categories == nil { + c.categories = newCommandCategories() + for _, command := range c.Subcommands { + c.categories.AddCommand(command.Category, command) + } + sort.Sort(c.categories.(*commandCategories)) + } + for _, category := range c.categories.Categories() { + if visible := func() CommandCategory { + if len(category.VisibleCommands()) > 0 { + return category + } + return nil + }(); visible != nil { + ret = append(ret, visible) + } + } + + return ret +} + +// 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 +} + func (c *Command) appendFlag(fl Flag) { if !hasFlag(c.Flags, fl) { c.Flags = append(c.Flags, fl) diff --git a/godoc-current.txt b/godoc-current.txt index fbeeb5f..b29442f 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -82,12 +82,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{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 @@ -161,8 +159,7 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}} ` SubcommandHelpTemplate is the text template for the subcommand help topic. cli.go uses text/template to render templates. You can render custom help @@ -558,6 +555,7 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string + // Has unexported fields. } Command is a subcommand for a cli.App. @@ -576,6 +574,13 @@ 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) VisibleCategories() []CommandCategory + VisibleCategories returns a slice of categories and commands that are + Hidden=false + +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 diff --git a/help.go b/help.go index d6caea4..ba5f803 100644 --- a/help.go +++ b/help.go @@ -15,33 +15,59 @@ const ( helpAlias = "h" ) -var helpCommand = &Command{ +// this instance is to avoid recursion in the ShowCommandHelp which can +// add a help command again +var helpCommandDontUse = &Command{ Name: helpName, Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(cCtx *Context) error { - args := cCtx.Args() - if args.Present() { - return ShowCommandHelp(cCtx, args.First()) - } - - _ = ShowAppHelp(cCtx) - return nil - }, } -var helpSubcommand = &Command{ +var helpCommand = &Command{ Name: helpName, Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(cCtx *Context) error { args := cCtx.Args() - if args.Present() { - return ShowCommandHelp(cCtx, args.First()) + argsPresent := args.First() != "" + firstArg := args.First() + + // This action can be triggered by a "default" action of a command + // or via cmd.Run when cmd == helpCmd. So we have following possibilities + // + // 1 $ app + // 2 $ app help + // 3 $ app foo + // 4 $ app help foo + // 5 $ app foo help + + // Case 4. when executing a help command set the context to parent + // to allow resolution of subsequent args. This will transform + // $ app help foo + // to + // $ app foo + // which will then be handled as case 3 + if cCtx.Command.Name == helpName || cCtx.Command.Name == helpAlias { + cCtx = cCtx.parentContext } + // Case 4. $ app hello foo + // foo is the command for which help needs to be shown + if argsPresent { + return ShowCommandHelp(cCtx, firstArg) + } + + // Case 1 & 2 + // Special case when running help on main app itself as opposed to indivdual + // commands/subcommands + if cCtx.parentContext.App == nil { + _ = ShowAppHelp(cCtx) + return nil + } + + // Case 3, 5 return ShowSubcommandHelp(cCtx) }, } @@ -212,9 +238,19 @@ func ShowCommandHelp(ctx *Context, command string) error { for _, c := range ctx.App.Commands { if c.HasName(command) { + if !ctx.App.HideHelpCommand && !c.HasName(helpName) && len(c.Subcommands) != 0 { + c.Subcommands = append(c.Subcommands, helpCommandDontUse) + } + if !ctx.App.HideHelp && HelpFlag != nil { + c.appendFlag(HelpFlag) + } templ := c.CustomHelpTemplate if templ == "" { - templ = CommandHelpTemplate + if len(c.Subcommands) == 0 { + templ = CommandHelpTemplate + } else { + templ = SubcommandHelpTemplate + } } HelpPrinter(ctx.App.Writer, templ, c) diff --git a/help_test.go b/help_test.go index ade6f3d..5a50586 100644 --- a/help_test.go +++ b/help_test.go @@ -186,7 +186,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { c := NewContext(app, set, nil) - err := helpSubcommand.Action(c) + err := helpCommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") @@ -248,7 +248,7 @@ func TestShowCommandHelp_HelpPrinter(t *testing.T) { fmt.Fprint(w, "yo") }, command: "", - wantTemplate: SubcommandHelpTemplate, + wantTemplate: AppHelpTemplate, wantOutput: "yo", }, { @@ -333,7 +333,7 @@ func TestShowCommandHelp_HelpPrinterCustom(t *testing.T) { fmt.Fprint(w, "yo") }, command: "", - wantTemplate: SubcommandHelpTemplate, + wantTemplate: AppHelpTemplate, wantOutput: "yo", }, { @@ -1357,10 +1357,13 @@ DESCRIPTION: and a description long enough to wrap in this test case + +OPTIONS: + --help, -h show help (default: false) ` if output.String() != expected { - t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s", output.String(), expected) } } @@ -1426,7 +1429,6 @@ USAGE: OPTIONS: --help, -h show help (default: false) - ` if output.String() != expected { diff --git a/template.go b/template.go index 7ed2370..9e13604 100644 --- a/template.go +++ b/template.go @@ -54,12 +54,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. @@ -80,8 +78,7 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}} ` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} From 4c9651575ccbac60903dbdeddd4232388066ab63 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 21 Sep 2022 08:26:08 -0400 Subject: [PATCH 3/5] Approval for public API change --- testdata/godoc-v2.x.txt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index fbeeb5f..b29442f 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -82,12 +82,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{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 @@ -161,8 +159,7 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}} ` SubcommandHelpTemplate is the text template for the subcommand help topic. cli.go uses text/template to render templates. You can render custom help @@ -558,6 +555,7 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string + // Has unexported fields. } Command is a subcommand for a cli.App. @@ -576,6 +574,13 @@ 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) VisibleCategories() []CommandCategory + VisibleCategories returns a slice of categories and commands that are + Hidden=false + +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 From aecd7c159a4ad6f410794ca11b6e66327ed96fd2 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 21 Sep 2022 18:18:36 -0400 Subject: [PATCH 4/5] Remove un-needed func --- command.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/command.go b/command.go index ddc79b4..7213fd7 100644 --- a/command.go +++ b/command.go @@ -312,31 +312,6 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } -// VisibleCategories returns a slice of categories and commands that are -// Hidden=false -func (c *Command) VisibleCategories() []CommandCategory { - ret := []CommandCategory{} - if c.categories == nil { - c.categories = newCommandCategories() - for _, command := range c.Subcommands { - c.categories.AddCommand(command.Category, command) - } - sort.Sort(c.categories.(*commandCategories)) - } - for _, category := range c.categories.Categories() { - if visible := func() CommandCategory { - if len(category.VisibleCommands()) > 0 { - return category - } - return nil - }(); visible != nil { - ret = append(ret, visible) - } - } - - return ret -} - // VisibleCommands returns a slice of the Commands with Hidden=false func (c *Command) VisibleCommands() []*Command { var ret []*Command From d6395333f1a730ab61b35cc59f4c1d16eb96a585 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 21 Sep 2022 18:24:03 -0400 Subject: [PATCH 5/5] Remove un-needed func --- app.go | 1 - command.go | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/app.go b/app.go index 93ceba8..2ffacd5 100644 --- a/app.go +++ b/app.go @@ -133,7 +133,6 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), - HelpName: "", // setup will fill this later Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, diff --git a/command.go b/command.go index 7213fd7..1ead6e9 100644 --- a/command.go +++ b/command.go @@ -62,9 +62,6 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string - - // categories contains the categorized commands and is populated on app startup - categories CommandCategories } type Commands []*Command @@ -312,17 +309,6 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } -// 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 -} - func (c *Command) appendFlag(fl Flag) { if !hasFlag(c.Flags, fl) { c.Flags = append(c.Flags, fl)