diff --git a/command.go b/command.go index 54748fc67..94fba6ea7 100644 --- a/command.go +++ b/command.go @@ -469,9 +469,10 @@ func (c *Command) HelpFunc() func(*Command, []string) { } } -// Help puts out the help for the command. -// Used when a user calls help [command]. -// Can be defined by user by overriding HelpFunc. +// Help invokes the HelpFunc without arguments. +// Kept for backwards compatibility. +// Can be a simple way to trigger the help +// if arguments are not needed. func (c *Command) Help() error { c.HelpFunc()(c, []string{}) return nil @@ -1119,7 +1120,16 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { // Always show help if requested, even if SilenceErrors is in // effect if errors.Is(err, flag.ErrHelp) { - cmd.HelpFunc()(cmd, args) + // The call to execute() above has parsed the flags. + // We therefore only pass the remaining arguments to the help function. + remainingArgs := cmd.Flags().Args() + if cmd.DisableFlagParsing { + // For commands that have DisableFlagParsing == true, the flag parsing + // was not done and cmd.Flags().Args() is not filled. We therefore + // use the full set of arguments, which include flags. + remainingArgs = flags + } + cmd.HelpFunc()(cmd, remainingArgs) return cmd, nil } @@ -1260,14 +1270,14 @@ Simply type ` + c.displayName() + ` help [path to command] for full details.`, return completions, ShellCompDirectiveNoFileComp }, Run: func(c *Command, args []string) { - cmd, _, e := c.Root().Find(args) + cmd, remainingArgs, e := c.Root().Find(args) if cmd == nil || e != nil { c.Printf("Unknown help topic %#q\n", args) CheckErr(c.Root().Usage()) } else { cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown cmd.InitDefaultVersionFlag() // make possible 'version' flag to be shown - CheckErr(cmd.Help()) + cmd.HelpFunc()(cmd, remainingArgs) } }, GroupID: c.helpCommandGroupID, diff --git a/command_test.go b/command_test.go index 9ce7a529b..dd975778c 100644 --- a/command_test.go +++ b/command_test.go @@ -1079,6 +1079,197 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) { checkStringContains(t, output, childCmd.Long) } +type overridingHelp struct { + expectedCmdName string + expectedArgs []string + + helpCalled bool + err error +} + +func (o *overridingHelp) helpFunc() func(c *Command, args []string) { + return func(c *Command, args []string) { + o.helpCalled = true + if c.Name() != o.expectedCmdName { + o.err = fmt.Errorf("Expected command name: %q, got %q", o.expectedCmdName, c.Name()) + return + } + + if len(args) != len(o.expectedArgs) { + o.err = fmt.Errorf("Expected args %v, got %v", o.expectedArgs, args) + return + } + + for i, arg := range o.expectedArgs { + if args[i] != arg { + o.err = fmt.Errorf("Expected args %v, got %v", o.expectedArgs, args) + return + } + } + } +} + +func (o *overridingHelp) checkError() error { + if o.err != nil { + return o.err + } + if !o.helpCalled { + return fmt.Errorf("Overridden help function not called") + } + return nil +} + +func TestHelpOverrideOnRoot(t *testing.T) { + rootCmd := &Command{Use: "root"} + + override := overridingHelp{ + expectedCmdName: rootCmd.Name(), + expectedArgs: []string{"arg1", "arg2"}, + } + rootCmd.SetHelpFunc(override.helpFunc()) + + _, err := executeCommand(rootCmd, "arg1", "arg2", "--help") + if err != nil { + t.Errorf("Unexpected error executing command: %v", err) + } + + if err = override.checkError(); err != nil { + t.Errorf("Unexpected error from help function: %v", err) + } +} + +func TestHelpOverrideOnChild(t *testing.T) { + rootCmd := &Command{Use: "root"} + subCmd := &Command{Use: "child"} + rootCmd.AddCommand(subCmd) + + override := overridingHelp{ + expectedCmdName: subCmd.Name(), + expectedArgs: []string{"arg1", "arg2"}, + } + subCmd.SetHelpFunc(override.helpFunc()) + + _, err := executeCommand(rootCmd, "child", "arg1", "arg2", "--help") + if err != nil { + t.Errorf("Unexpected error executing command: %v", err) + } + + if err = override.checkError(); err != nil { + t.Errorf("Unexpected error from help function: %v", err) + } +} + +func TestHelpOverrideOnRootWithChild(t *testing.T) { + rootCmd := &Command{Use: "root"} + subCmd := &Command{Use: "child"} + rootCmd.AddCommand(subCmd) + + override := overridingHelp{ + expectedCmdName: subCmd.Name(), + expectedArgs: []string{"arg1", "arg2"}, + } + rootCmd.SetHelpFunc(override.helpFunc()) + + _, err := executeCommand(rootCmd, "child", "arg1", "arg2", "--help") + if err != nil { + t.Errorf("Unexpected error executing command: %v", err) + } + + if err = override.checkError(); err != nil { + t.Errorf("Unexpected error from help function: %v", err) + } +} + +func TestHelpOverrideOnRootWithChildAndFlags(t *testing.T) { + rootCmd := &Command{Use: "root"} + subCmd := &Command{Use: "child"} + rootCmd.AddCommand(subCmd) + + var myFlag bool + subCmd.Flags().BoolVar(&myFlag, "myflag", false, "") + + override := overridingHelp{ + expectedCmdName: subCmd.Name(), + expectedArgs: []string{"arg1", "arg2"}, + } + rootCmd.SetHelpFunc(override.helpFunc()) + + _, err := executeCommand(rootCmd, "child", "arg1", "--myflag", "arg2", "--help") + if err != nil { + t.Errorf("Unexpected error executing command: %v", err) + } + + if err = override.checkError(); err != nil { + t.Errorf("Unexpected error from help function: %v", err) + } +} + +func TestHelpOverrideOnRootWithChildAndFlagsButParsingDisabled(t *testing.T) { + rootCmd := &Command{Use: "root"} + subCmd := &Command{Use: "child", DisableFlagParsing: true} + rootCmd.AddCommand(subCmd) + + var myFlag bool + subCmd.Flags().BoolVar(&myFlag, "myflag", false, "") + + override := overridingHelp{ + expectedCmdName: subCmd.Name(), + expectedArgs: []string{"arg1", "--myflag", "arg2", "--help"}, + } + rootCmd.SetHelpFunc(override.helpFunc()) + + _, err := executeCommand(rootCmd, "child", "arg1", "--myflag", "arg2", "--help") + if err != nil { + t.Errorf("Unexpected error executing command: %v", err) + } + + if err = override.checkError(); err != nil { + t.Errorf("Unexpected error from help function: %v", err) + } +} + +func TestHelpCommandOverrideOnChild(t *testing.T) { + rootCmd := &Command{Use: "root"} + subCmd := &Command{Use: "child"} + rootCmd.AddCommand(subCmd) + + override := overridingHelp{ + expectedCmdName: subCmd.Name(), + expectedArgs: []string{"arg1", "arg2"}, + } + subCmd.SetHelpFunc(override.helpFunc()) + + _, err := executeCommand(rootCmd, "help", "child", "arg1", "arg2") + if err != nil { + t.Errorf("Unexpected error executing command: %v", err) + } + + if err = override.checkError(); err != nil { + t.Errorf("Unexpected error from help function: %v", err) + } +} + +func TestHelpCommandOverrideOnRootWithChild(t *testing.T) { + rootCmd := &Command{Use: "root"} + subCmd := &Command{Use: "child"} + rootCmd.AddCommand(subCmd) + + override := overridingHelp{ + expectedCmdName: subCmd.Name(), + expectedArgs: []string{"arg1", "arg2"}, + } + rootCmd.SetHelpFunc(override.helpFunc()) + + _, err := executeCommand(rootCmd, "help", "child", "arg1", "arg2") + if err != nil { + t.Errorf("Unexpected error executing command: %v", err) + } + + if err = override.checkError(); err != nil { + t.Errorf("Unexpected error from help function: %v", err) + } +} + func TestVersionFlagExecuted(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun}