Skip to content

Commit

Permalink
closes #2130 new command flag ErrorOnUnknownSubcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
andremueller authored and andremueller-cosateq committed Jul 8, 2024
1 parent e94f6d0 commit 9892605
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 2 deletions.
22 changes: 20 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ type Group struct {
Title string
}

// ErrUnknownSubcommand is returned by Command.Execute() when the subcommand was not found.
// Hereto, the ErrorOnUnknownSubcommand flag must be set true.
var ErrUnknownSubcommand = errors.New("subcommand is unknown")

// Command is just that, a command for your application.
// E.g. 'go run ...' - 'run' is the command. Cobra requires
// you to define the usage and description as part of your command
Expand Down Expand Up @@ -254,6 +258,17 @@ type Command struct {
// SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions.
// Must be > 0.
SuggestionsMinimumDistance int

// ErrorOnUnknownSubcommand controls the behavior of subcommand handling.
// When the flag is set true the behavior of Command.Execute() will change:
// If a sub-subcommand is not found ErrUnknownSubcommand will be returned on calling
// Command.Exec() instead of the old behavior where a nil error was sent.
// If the flag is false (default) the old behavior is performed.
// For this behavior the child subcommand must be nil.
// Example: in root/service/run - there would be an existing subcommand `run`
// in root/service/unknown - there would be an unknown subcommand `unknown` therefore returning an error
// `service` must have a nil Command.Run() function for this.
ErrorOnUnknownSubcommand bool
}

// Context returns underlying command context. If command was executed
Expand Down Expand Up @@ -923,9 +938,12 @@ func (c *Command) execute(a []string) (err error) {
}

if !c.Runnable() {
return flag.ErrHelp
if c.ErrorOnUnknownSubcommand {
return ErrUnknownSubcommand
} else {
return flag.ErrHelp
}
}

c.preRun()

defer c.postRun()
Expand Down
89 changes: 89 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cobra
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -174,6 +175,94 @@ func TestSubcommandExecuteC(t *testing.T) {
}
}

func TestSubcommandExecuteMissingSubcommand(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{Use: "child", Run: nil, ErrorOnUnknownSubcommand: false}
child2Cmd := &Command{Use: "child2", Run: emptyRun}
rootCmd.AddCommand(childCmd)
childCmd.AddCommand(child2Cmd)

// test existing command
c, output, err := executeCommandC(rootCmd, "child")
if !strings.HasPrefix(output, "Usage:") {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if c.Name() != "child" {
t.Errorf(`invalid command returned from ExecuteC: expected "child"', got: %q`, c.Name())
}

// test existing sub command
c, output, err = executeCommandC(rootCmd, "child", "child2")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if c.Name() != "child2" {
t.Errorf(`invalid command returned from ExecuteC: expected "child"', got: %q`, c.Name())
}

// now test a command which does not exist, we will get no error, just "Usage:" is printed
c, output, err = executeCommandC(rootCmd, "child", "unknownChild")
if !strings.HasPrefix(output, "Usage:") {
t.Errorf("Expected: 'Usage: ...'\nGot:\n %q\n", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if c.Name() != "child" {
t.Errorf(`invalid command returned from ExecuteC: expected "child"', got: %q`, c.Name())
}
}

func TestSubcommandExecuteMissingSubcommandWithErrorOnUnknownSubcommand(t *testing.T) {
rootCmd := &Command{Use: "root", Run: emptyRun}
childCmd := &Command{Use: "child", Run: nil, ErrorOnUnknownSubcommand: true}
child2Cmd := &Command{Use: "child2", Run: emptyRun}
rootCmd.AddCommand(childCmd)
childCmd.AddCommand(child2Cmd)

// test existing command
c, output, err := executeCommandC(rootCmd, "child")
if !strings.HasPrefix(output, "Error:") {
t.Errorf("Unexpected output: %v", output)
}
if !errors.Is(err, ErrUnknownSubcommand) {
t.Errorf("Unexpected error: %v", err)
}
if c.Name() != "child" {
t.Errorf(`invalid command returned from ExecuteC: expected "child"', got: %q`, c.Name())
}

// test existing sub command
c, output, err = executeCommandC(rootCmd, "child", "child2")
if output != "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if c.Name() != "child2" {
t.Errorf(`invalid command returned from ExecuteC: expected "child"', got: %q`, c.Name())
}

// now test a command which does not exist, we expect an error because of the ErrorOnUnknownSubcommand flag
c, output, err = executeCommandC(rootCmd, "child", "unknownChild")
if !strings.HasPrefix(output, "Error:") {
t.Errorf("Unexpected output: %v", output)
}
if !errors.Is(err, ErrUnknownSubcommand) {
t.Errorf("Unexpected error: %v", err)
}
if c.Name() != "child" {
t.Errorf(`invalid command returned from ExecuteC: expected "child"', got: %q`, c.Name())
}
}

func TestExecuteContext(t *testing.T) {
ctx := context.TODO()

Expand Down

0 comments on commit 9892605

Please sign in to comment.