Return an error in the case of unrunnable subcommand

* credit to @chriswhelix for initial commit
This commit is contained in:
Bruce Downs 2019-07-29 23:36:50 -07:00 committed by Albert Nigmatzianov
parent 9552679939
commit 9334a46bd6
2 changed files with 16 additions and 4 deletions

View file

@ -17,6 +17,7 @@ package cobra
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -27,6 +28,9 @@ import (
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
// NotRunnable defines subcommand error
var NotRunnable = errors.New("subcommand is required")
// FParseErrWhitelist configures Flag parse errors to be ignored // FParseErrWhitelist configures Flag parse errors to be ignored
type FParseErrWhitelist flag.ParseErrorsWhitelist type FParseErrWhitelist flag.ParseErrorsWhitelist
@ -369,7 +373,7 @@ func (c *Command) HelpFunc() func(*Command, []string) {
} }
return func(c *Command, a []string) { return func(c *Command, a []string) {
c.mergePersistentFlags() c.mergePersistentFlags()
err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c) err := tmpl(c.OutOrStderr(), c.HelpTemplate(), c)
if err != nil { if err != nil {
c.Println(err) c.Println(err)
} }
@ -786,7 +790,7 @@ func (c *Command) execute(a []string) (err error) {
} }
if !c.Runnable() { if !c.Runnable() {
return flag.ErrHelp return NotRunnable
} }
c.preRun() c.preRun()
@ -920,6 +924,14 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
return cmd, nil return cmd, nil
} }
// If command wasn't runnable, show full help, but do return the error.
// This will result in apps by default returning a non-success exit code, but also gives them the option to
// handle specially.
if err == NotRunnable {
cmd.HelpFunc()(cmd, args)
return cmd, err
}
// If root command has SilentErrors flagged, // If root command has SilentErrors flagged,
// all subcommands should respect it // all subcommands should respect it
if !cmd.SilenceErrors && !c.SilenceErrors { if !cmd.SilenceErrors && !c.SilenceErrors {

View file

@ -836,8 +836,8 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) {
rootCmd.AddCommand(childCmd) rootCmd.AddCommand(childCmd)
output, err := executeCommand(rootCmd, "child") output, err := executeCommand(rootCmd, "child")
if err != nil { if err != NotRunnable {
t.Errorf("Unexpected error: %v", err) t.Errorf("Expected error")
} }
checkStringContains(t, output, childCmd.Long) checkStringContains(t, output, childCmd.Long)