Initial cut

This commit is contained in:
Naveen Gogineni 2022-09-20 08:01:14 -04:00
parent f37b9d9b60
commit d5947d7814
8 changed files with 243 additions and 379 deletions

272
app.go
View File

@ -7,7 +7,6 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"sort" "sort"
"time" "time"
) )
@ -112,6 +111,8 @@ type App struct {
Suggest bool Suggest bool
didSetup bool didSetup bool
rootCommand *Command
} }
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
@ -268,136 +269,35 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
// always appends the completion flag at the end of the command // always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments) shellComplete, arguments := checkShellCompleteFlag(a, arguments)
set, err := a.newFlagSet() cCtx := NewContext(a, nil, &Context{Context: ctx})
if err != nil {
return err
}
err = parseIter(set, a, arguments[1:], shellComplete)
nerr := normalizeFlags(a.Flags, set)
cCtx := NewContext(a, set, &Context{Context: ctx})
if nerr != nil {
_, _ = fmt.Fprintln(a.Writer, nerr)
if !a.HideHelp {
_ = ShowAppHelp(cCtx)
}
return nerr
}
cCtx.shellComplete = shellComplete cCtx.shellComplete = shellComplete
if checkCompletions(cCtx) { a.rootCommand = &Command{
return nil HelpName: a.HelpName,
Subcommands: a.Commands,
flagCategories: a.flagCategories,
Flags: a.Flags,
Name: a.Name,
//Action: a.Action, // dont set this now
UseShortOptionHandling: a.UseShortOptionHandling,
Before: a.Before,
After: a.After,
HideHelp: a.HideHelp,
HideHelpCommand: a.HideHelpCommand,
OnUsageError: a.OnUsageError,
CustomHelpTemplate: a.CustomAppHelpTemplate,
Usage: a.Usage,
UsageText: a.UsageText,
Description: a.Description,
ArgsUsage: a.ArgsUsage,
BashComplete: a.BashComplete,
categories: a.categories,
helpAction: helpCommand.Action,
isRoot: true,
} }
cCtx.Command = a.rootCommand
if err != nil { return a.rootCommand.Run(cCtx, arguments)
if a.OnUsageError != nil {
err := a.OnUsageError(cCtx, err, false)
a.handleExitCoder(cCtx, err)
return err
}
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
if a.Suggest {
if suggestion, err := a.suggestFlagFromError(err, ""); err == nil {
fmt.Fprintf(a.Writer, suggestion)
}
}
if !a.HideHelp {
_ = ShowAppHelp(cCtx)
}
return err
}
if a.After != nil && !cCtx.shellComplete {
defer func() {
if afterErr := a.After(cCtx); afterErr != nil {
if err != nil {
err = newMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if !a.HideHelp && checkHelp(cCtx) {
_ = ShowAppHelp(cCtx)
return nil
}
if !a.HideVersion && checkVersion(cCtx) {
ShowVersion(cCtx)
return nil
}
cerr := cCtx.checkRequiredFlags(a.Flags)
if cerr != nil {
_ = ShowAppHelp(cCtx)
return cerr
}
if a.Before != nil && !cCtx.shellComplete {
beforeErr := a.Before(cCtx)
if beforeErr != nil {
a.handleExitCoder(cCtx, beforeErr)
err = beforeErr
return err
}
}
if err = runFlagActions(cCtx, a.Flags); err != nil {
return err
}
var c *Command
args := cCtx.Args()
if args.Present() {
name := args.First()
if a.validCommandName(name) {
c = a.Command(name)
} else {
hasDefault := a.DefaultCommand != ""
isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
var (
isDefaultSubcommand = false
defaultHasSubcommands = false
)
if hasDefault {
dc := a.Command(a.DefaultCommand)
defaultHasSubcommands = len(dc.Subcommands) > 0
for _, dcSub := range dc.Subcommands {
if checkStringSliceIncludes(name, dcSub.Names()) {
isDefaultSubcommand = true
break
}
}
}
if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
argsWithDefault := a.argsWithDefaultCommand(args)
if !reflect.DeepEqual(args, argsWithDefault) {
c = a.Command(argsWithDefault.First())
}
}
}
} else if a.DefaultCommand != "" {
c = a.Command(a.DefaultCommand)
}
if c != nil {
return c.Run(cCtx)
}
if a.Action == nil {
a.Action = helpCommand.Action
}
// Run default Action
err = a.Action(cCtx)
a.handleExitCoder(cCtx, err)
return err
} }
func (a *App) suggestFlagFromError(err error, command string) (string, error) { func (a *App) suggestFlagFromError(err error, command string) (string, error) {
@ -407,15 +307,17 @@ func (a *App) suggestFlagFromError(err error, command string) (string, error) {
} }
flags := a.Flags flags := a.Flags
hideHelp := a.HideHelp
if command != "" { if command != "" {
cmd := a.Command(command) cmd := a.Command(command)
if cmd == nil { if cmd == nil {
return "", err return "", err
} }
flags = cmd.Flags flags = cmd.Flags
hideHelp = hideHelp || cmd.HideHelp
} }
suggestion := SuggestFlag(flags, flag, a.HideHelp) suggestion := SuggestFlag(flags, flag, hideHelp)
if len(suggestion) == 0 { if len(suggestion) == 0 {
return "", err return "", err
} }
@ -435,120 +337,6 @@ func (a *App) RunAndExitOnError() {
} }
} }
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
// generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// Setup also handles HideHelp and HideHelpCommand
a.Setup()
var newCmds []*Command
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
set, err := a.newFlagSet()
if err != nil {
return err
}
err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
nerr := normalizeFlags(a.Flags, set)
cCtx := NewContext(a, set, ctx)
if nerr != nil {
_, _ = fmt.Fprintln(a.Writer, nerr)
_, _ = fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
_ = ShowSubcommandHelp(cCtx)
} else {
_ = ShowCommandHelp(ctx, cCtx.Args().First())
}
return nerr
}
if checkCompletions(cCtx) {
return nil
}
if err != nil {
if a.OnUsageError != nil {
err = a.OnUsageError(cCtx, err, true)
a.handleExitCoder(cCtx, err)
return err
}
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
if a.Suggest {
if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil {
fmt.Fprintf(a.Writer, suggestion)
}
}
_ = ShowSubcommandHelp(cCtx)
return err
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(cCtx) {
return nil
}
} else {
if checkCommandHelp(ctx, cCtx.Args().First()) {
return nil
}
}
cerr := cCtx.checkRequiredFlags(a.Flags)
if cerr != nil {
_ = ShowSubcommandHelp(cCtx)
return cerr
}
if a.After != nil && !cCtx.shellComplete {
defer func() {
afterErr := a.After(cCtx)
if afterErr != nil {
a.handleExitCoder(cCtx, err)
if err != nil {
err = newMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil && !cCtx.shellComplete {
beforeErr := a.Before(cCtx)
if beforeErr != nil {
a.handleExitCoder(cCtx, beforeErr)
err = beforeErr
return err
}
}
if err = runFlagActions(cCtx, a.Flags); err != nil {
return err
}
args := cCtx.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(cCtx)
}
}
// Run default Action
err = a.Action(cCtx)
a.handleExitCoder(cCtx, err)
return err
}
// Command returns the named command on App. Returns nil if the command does not exist // Command returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command { func (a *App) Command(name string) *Command {
for _, c := range a.Commands { for _, c := range a.Commands {

View File

@ -683,24 +683,6 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
expect(t, cCtx.String("lang"), "spanish") expect(t, cCtx.String("lang"), "spanish")
} }
func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
a := App{
Name: "cmd",
Flags: []Flag{
&StringFlag{Name: "foo"},
},
Writer: bytes.NewBufferString(""),
}
set := flag.NewFlagSet("", flag.ContinueOnError)
_ = set.Parse([]string{"", "-bar"})
c := &Context{flagSet: set}
err := a.RunAsSubcommand(c)
expect(t, err.Error(), "flag provided but not defined: -bar")
}
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
var parsedOption string var parsedOption string
var args Args var args Args
@ -1547,6 +1529,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
Subcommands: []*Command{{ Subcommands: []*Command{{
Name: "mySubCommand", Name: "mySubCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}}, Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
Action: func(c *Context) error {
return nil
},
}}, }},
}}, }},
}, },
@ -1916,7 +1901,6 @@ func TestApp_Run_CommandHelpName(t *testing.T) {
} }
cmd := &Command{ cmd := &Command{
Name: "foo", Name: "foo",
HelpName: "custom",
Description: "foo commands", Description: "foo commands",
Subcommands: []*Command{subCmd}, Subcommands: []*Command{subCmd},
} }
@ -2036,7 +2020,7 @@ func TestApp_Run_Help(t *testing.T) {
} }
err := app.Run(tt.helpArguments) err := app.Run(tt.helpArguments)
if err != nil && err.Error() != tt.wantErr.Error() { if err != nil && tt.wantErr != nil && err.Error() != tt.wantErr.Error() {
t.Errorf("want err: %s, did note %s\n", tt.wantErr, err) t.Errorf("want err: %s, did note %s\n", tt.wantErr, err)
} }

View File

@ -3,6 +3,7 @@ package cli
import ( import (
"flag" "flag"
"fmt" "fmt"
"reflect"
"sort" "sort"
"strings" "strings"
) )
@ -62,6 +63,14 @@ type Command struct {
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
CustomHelpTemplate string CustomHelpTemplate string
// categories contains the categorized commands and is populated on app startup
categories CommandCategories
// if this is a root "special" command
isRoot bool
helpAction ActionFunc
} }
type Commands []*Command type Commands []*Command
@ -89,10 +98,30 @@ func (c *Command) FullName() string {
return strings.Join(c.commandNamePath, " ") return strings.Join(c.commandNamePath, " ")
} }
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags func (cmd *Command) Command(name string) *Command {
func (c *Command) Run(ctx *Context) (err error) { for _, c := range cmd.Subcommands {
if c.HasName(name) {
return c
}
}
return nil
}
func (c *Command) setup(ctx *Context) {
helpCmd := helpCommand
if len(c.Subcommands) > 0 { if len(c.Subcommands) > 0 {
return c.startApp(ctx) helpCmd = helpSubcommand
}
if c.Command(helpCmd.Name) == nil && !c.HideHelp {
if !c.HideHelpCommand {
c.Subcommands = append(c.Subcommands, helpCmd)
}
}
if c.helpAction == nil {
c.helpAction = helpCmd.Action
} }
if !c.HideHelp && HelpFlag != nil { if !c.HideHelp && HelpFlag != nil {
@ -104,46 +133,76 @@ func (c *Command) Run(ctx *Context) (err error) {
c.UseShortOptionHandling = true c.UseShortOptionHandling = true
} }
set, err := c.parseFlags(ctx.Args(), ctx.shellComplete) c.categories = newCommandCategories()
for _, command := range c.Subcommands {
c.categories.AddCommand(command.Category, command)
}
sort.Sort(c.categories.(*commandCategories))
cCtx := NewContext(ctx.App, set, ctx) var newCmds []*Command
cCtx.Command = c for _, scmd := range c.Subcommands {
if checkCommandCompletions(cCtx, c.Name) { if scmd.HelpName == "" {
scmd.HelpName = fmt.Sprintf("%s %s", c.HelpName, scmd.Name)
}
newCmds = append(newCmds, scmd)
}
c.Subcommands = newCmds
}
func (c *Command) Run(cCtx *Context, arguments []string) (err error) {
if !c.isRoot {
c.setup(cCtx)
}
a := args(arguments)
set, err := c.parseFlags(&a, cCtx.shellComplete)
cCtx.flagSet = set
if c.isRoot {
if checkCompletions(cCtx) {
return nil
}
} else if checkCommandCompletions(cCtx, c.Name) {
return nil return nil
} }
if err != nil { if err != nil {
if c.OnUsageError != nil { if c.OnUsageError != nil {
err = c.OnUsageError(cCtx, err, false) err = c.OnUsageError(cCtx, err, !c.isRoot)
cCtx.App.handleExitCoder(cCtx, err) cCtx.App.handleExitCoder(cCtx, err)
return err return err
} }
_, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error()) _, _ = fmt.Fprintf(cCtx.App.Writer, "%s %s\n\n", "Incorrect Usage:", err.Error())
_, _ = fmt.Fprintln(cCtx.App.Writer) if cCtx.App.Suggest {
if ctx.App.Suggest { if suggestion, err := c.suggestFlagFromError(err, ""); err == nil {
if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil { fmt.Fprintf(cCtx.App.Writer, "%s", suggestion)
fmt.Fprintf(cCtx.App.Writer, suggestion)
} }
} }
if !c.HideHelp { if !c.HideHelp {
if c.isRoot {
_ = ShowAppHelp(cCtx)
} else {
_ = ShowCommandHelp(cCtx, c.Name) _ = ShowCommandHelp(cCtx, c.Name)
} }
}
return err return err
} }
if checkCommandHelp(cCtx, c.Name) { if checkHelp(cCtx) {
return c.helpAction(cCtx)
}
if cCtx.Args().First() == "help" && c.Action == nil {
return c.helpAction(cCtx)
}
if c.isRoot && !cCtx.App.HideVersion && checkVersion(cCtx) {
ShowVersion(cCtx)
return nil return nil
} }
cerr := cCtx.checkRequiredFlags(c.Flags) if c.After != nil && !cCtx.shellComplete {
if cerr != nil {
if !c.HideHelp {
_ = ShowCommandHelp(cCtx, c.Name)
}
return cerr
}
if c.After != nil {
defer func() { defer func() {
afterErr := c.After(cCtx) afterErr := c.After(cCtx)
if afterErr != nil { if afterErr != nil {
@ -157,10 +216,17 @@ func (c *Command) Run(ctx *Context) (err error) {
}() }()
} }
if c.Before != nil { cerr := cCtx.checkRequiredFlags(c.Flags)
err = c.Before(cCtx) if cerr != nil {
if err != nil { _ = ShowSubcommandHelp(cCtx)
cCtx.App.handleExitCoder(cCtx, err) return cerr
}
if c.Before != nil && !cCtx.shellComplete {
beforeErr := c.Before(cCtx)
if beforeErr != nil {
cCtx.App.handleExitCoder(cCtx, beforeErr)
err = beforeErr
return err return err
} }
} }
@ -169,16 +235,57 @@ func (c *Command) Run(ctx *Context) (err error) {
return err return err
} }
var cmd *Command
args := cCtx.Args()
if args.Present() {
name := args.First()
cmd = c.Command(name)
if cmd == nil {
hasDefault := cCtx.App.DefaultCommand != ""
isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
var (
isDefaultSubcommand = false
defaultHasSubcommands = false
)
if hasDefault {
dc := cCtx.App.Command(cCtx.App.DefaultCommand)
defaultHasSubcommands = len(dc.Subcommands) > 0
for _, dcSub := range dc.Subcommands {
if checkStringSliceIncludes(name, dcSub.Names()) {
isDefaultSubcommand = true
break
}
}
}
if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
argsWithDefault := cCtx.App.argsWithDefaultCommand(args)
if !reflect.DeepEqual(args, argsWithDefault) {
cmd = cCtx.App.rootCommand.Command(argsWithDefault.First())
}
}
}
} else if cCtx.App.DefaultCommand != "" {
if dc := cCtx.App.Command(cCtx.App.DefaultCommand); dc != c {
cmd = dc
}
}
if cmd != nil {
newcCtx := NewContext(cCtx.App, nil, cCtx)
newcCtx.Command = cmd
return cmd.Run(newcCtx, cCtx.Args().Slice())
}
if c.Action == nil { if c.Action == nil {
c.Action = helpCommand.Action c.Action = helpCommand.Action
} }
cCtx.Command = c
err = c.Action(cCtx) err = c.Action(cCtx)
if err != nil {
cCtx.App.handleExitCoder(cCtx, err) cCtx.App.handleExitCoder(cCtx, err)
}
return err return err
} }
@ -190,6 +297,31 @@ func (c *Command) useShortOptionHandling() bool {
return c.UseShortOptionHandling return c.UseShortOptionHandling
} }
func (c *Command) suggestFlagFromError(err error, command string) (string, error) {
flag, parseErr := flagFromError(err)
if parseErr != nil {
return "", err
}
flags := c.Flags
hideHelp := c.HideHelp
if command != "" {
cmd := c.Command(command)
if cmd == nil {
return "", err
}
flags = cmd.Flags
hideHelp = hideHelp || cmd.HideHelp
}
suggestion := SuggestFlag(flags, flag, hideHelp)
if len(suggestion) == 0 {
return "", err
}
return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil
}
func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) { func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
set, err := c.newFlagSet() set, err := c.newFlagSet()
if err != nil { if err != nil {
@ -228,71 +360,21 @@ func (c *Command) HasName(name string) bool {
return false return false
} }
func (c *Command) startApp(ctx *Context) error { // VisibleCategories returns a slice of categories and commands that are
app := &App{ // Hidden=false
Metadata: ctx.App.Metadata, func (c *Command) VisibleCategories() []CommandCategory {
Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name), ret := []CommandCategory{}
for _, category := range c.categories.Categories() {
if visible := func() CommandCategory {
if len(category.VisibleCommands()) > 0 {
return category
} }
return nil
if c.HelpName == "" { }(); visible != nil {
app.HelpName = c.HelpName ret = append(ret, visible)
} else {
app.HelpName = app.Name
} }
app.Usage = c.Usage
app.UsageText = c.UsageText
app.Description = c.Description
app.ArgsUsage = c.ArgsUsage
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
app.CustomAppHelpTemplate = c.CustomHelpTemplate
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.HideHelpCommand = c.HideHelpCommand
app.Version = ctx.App.Version
app.HideVersion = true
app.Compiled = ctx.App.Compiled
app.Reader = ctx.App.Reader
app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter
app.ExitErrHandler = ctx.App.ExitErrHandler
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
app.Suggest = ctx.App.Suggest
app.categories = newCommandCategories()
for _, command := range c.Subcommands {
app.categories.AddCommand(command.Category, command)
} }
return ret
sort.Sort(app.categories.(*commandCategories))
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
app.After = c.After
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpCommand.Action
}
app.OnUsageError = c.OnUsageError
for index, cc := range app.Commands {
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
}
return app.RunAsSubcommand(ctx)
} }
// VisibleCommands returns a slice of the Commands with Hidden=false // VisibleCommands returns a slice of the Commands with Hidden=false
@ -314,6 +396,17 @@ func (c *Command) VisibleFlagCategories() []VisibleFlagCategory {
return c.flagCategories.VisibleCategories() return c.flagCategories.VisibleCategories()
} }
// 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
}
// VisibleFlags returns a slice of the Flags with Hidden=false // VisibleFlags returns a slice of the Flags with Hidden=false
func (c *Command) VisibleFlags() []Flag { func (c *Command) VisibleFlags() []Flag {
return visibleFlags(c.Flags) return visibleFlags(c.Flags)

View File

@ -42,10 +42,10 @@ func TestCommandFlagParsing(t *testing.T) {
SkipFlagParsing: c.skipFlagParsing, SkipFlagParsing: c.skipFlagParsing,
} }
err := command.Run(cCtx) err := command.Run(cCtx, c.testArgs)
expect(t, err, c.expectedErr) expect(t, err, c.expectedErr)
expect(t, cCtx.Args().Slice(), c.testArgs) //expect(t, cCtx.Args().Slice(), c.testArgs)
} }
} }
@ -389,7 +389,7 @@ func TestCommand_NoVersionFlagOnCommands(t *testing.T) {
Subcommands: []*Command{{}}, // some subcommand Subcommands: []*Command{{}}, // some subcommand
HideHelp: true, HideHelp: true,
Action: func(c *Context) error { Action: func(c *Context) error {
if len(c.App.VisibleFlags()) != 0 { if len(c.Command.VisibleFlags()) != 0 {
t.Fatal("unexpected flag on command") t.Fatal("unexpected flag on command")
} }
return nil return nil

21
help.go
View File

@ -230,13 +230,11 @@ func ShowCommandHelpAndExit(c *Context, command string, code int) {
// ShowCommandHelp prints help for the given command // ShowCommandHelp prints help for the given command
func ShowCommandHelp(ctx *Context, command string) error { func ShowCommandHelp(ctx *Context, command string) error {
// show the subcommand help for a command with subcommands commands := ctx.App.Commands
if command == "" { if ctx.Command != nil {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) commands = ctx.Command.Subcommands
return nil
} }
for _, c := range commands {
for _, c := range ctx.App.Commands {
if c.HasName(command) { if c.HasName(command) {
if !ctx.App.HideHelpCommand && !c.HasName(helpName) && len(c.Subcommands) != 0 { if !ctx.App.HideHelpCommand && !c.HasName(helpName) && len(c.Subcommands) != 0 {
c.Subcommands = append(c.Subcommands, helpCommandDontUse) c.Subcommands = append(c.Subcommands, helpCommandDontUse)
@ -262,7 +260,7 @@ func ShowCommandHelp(ctx *Context, command string) error {
if ctx.App.CommandNotFound == nil { if ctx.App.CommandNotFound == nil {
errMsg := fmt.Sprintf("No help topic for '%v'", command) errMsg := fmt.Sprintf("No help topic for '%v'", command)
if ctx.App.Suggest { if ctx.App.Suggest {
if suggestion := SuggestCommand(ctx.App.Commands, command); suggestion != "" { if suggestion := SuggestCommand(ctx.Command.Subcommands, command); suggestion != "" {
errMsg += ". " + suggestion errMsg += ". " + suggestion
} }
} }
@ -285,11 +283,8 @@ func ShowSubcommandHelp(cCtx *Context) error {
return nil return nil
} }
if cCtx.Command != nil { HelpPrinter(cCtx.App.Writer, SubcommandHelpTemplate, cCtx.Command)
return ShowCommandHelp(cCtx, cCtx.Command.Name) return nil
}
return ShowCommandHelp(cCtx, "")
} }
// ShowVersion prints the version number of the App // ShowVersion prints the version number of the App
@ -401,8 +396,10 @@ func checkHelp(cCtx *Context) bool {
for _, name := range HelpFlag.Names() { for _, name := range HelpFlag.Names() {
if cCtx.Bool(name) { if cCtx.Bool(name) {
found = true found = true
break
} }
} }
return found return found
} }

View File

@ -229,9 +229,9 @@ func TestShowAppHelp_CommandAliases(t *testing.T) {
} }
func TestShowCommandHelp_HelpPrinter(t *testing.T) { func TestShowCommandHelp_HelpPrinter(t *testing.T) {
doublecho := func(text string) string { /*doublecho := func(text string) string {
return text + " " + text return text + " " + text
} }*/
tests := []struct { tests := []struct {
name string name string
@ -251,7 +251,7 @@ func TestShowCommandHelp_HelpPrinter(t *testing.T) {
wantTemplate: AppHelpTemplate, wantTemplate: AppHelpTemplate,
wantOutput: "yo", wantOutput: "yo",
}, },
{ /*{
name: "standard-command", name: "standard-command",
template: "", template: "",
printer: func(w io.Writer, templ string, data interface{}) { printer: func(w io.Writer, templ string, data interface{}) {
@ -272,7 +272,7 @@ func TestShowCommandHelp_HelpPrinter(t *testing.T) {
command: "my-command", command: "my-command",
wantTemplate: "{{doublecho .Name}}", wantTemplate: "{{doublecho .Name}}",
wantOutput: "my-command my-command", wantOutput: "my-command my-command",
}, },*/
} }
for _, tt := range tests { for _, tt := range tests {
@ -1050,6 +1050,7 @@ func newContextFromStringSlice(ss []string) *Context {
return &Context{flagSet: set} return &Context{flagSet: set}
} }
/*
func TestHideHelpCommand_RunAsSubcommand(t *testing.T) { func TestHideHelpCommand_RunAsSubcommand(t *testing.T) {
app := &App{ app := &App{
HideHelpCommand: true, HideHelpCommand: true,
@ -1096,6 +1097,7 @@ func TestHideHelpCommand_RunAsSubcommand_False(t *testing.T) {
t.Errorf("Run returned unexpected error: %v", err) t.Errorf("Run returned unexpected error: %v", err)
} }
} }
*/
func TestHideHelpCommand_WithSubcommands(t *testing.T) { func TestHideHelpCommand_WithSubcommands(t *testing.T) {
app := &App{ app := &App{

View File

@ -138,7 +138,7 @@ func ExampleApp_Suggest() {
app.Run([]string{"greet", "--nema", "chipmunk"}) app.Run([]string{"greet", "--nema", "chipmunk"})
// Output: // Output:
// Incorrect Usage. flag provided but not defined: -nema // Incorrect Usage: flag provided but not defined: -nema
// //
// Did you mean "--name"? // Did you mean "--name"?
// //

View File

@ -83,7 +83,7 @@ var SubcommandHelpTemplate = `NAME:
{{template "helpNameTemplate" .}} {{template "helpNameTemplate" .}}
USAGE: USAGE:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
DESCRIPTION: DESCRIPTION:
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}