From 32dec1ddaac72dad1d412745dbe8df15570e0c6b Mon Sep 17 00:00:00 2001 From: James Alavosus Date: Thu, 5 May 2022 23:50:22 -0400 Subject: [PATCH] feature: add DefaultCommand field to App See issue #1307 for context. --- app.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++--- app_test.go | 36 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 333bd57..a9ad131 100644 --- a/app.go +++ b/app.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "reflect" "sort" "time" ) @@ -43,6 +44,9 @@ type App struct { Version string // Description of the program Description string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // List of commands to execute Commands []*Command // List of flags to parse @@ -333,13 +337,33 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } } + var c *Command args := cCtx.Args() if args.Present() { name := args.First() - c := a.Command(name) - if c != nil { - return c.Run(cCtx) + if a.validCommandName(name) { + c = a.Command(name) + } else { + isFlagName := false + for _, flagName := range cCtx.FlagNames() { + if name == flagName { + isFlagName = true + break + } + } + if isFlagName { + 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 { @@ -570,6 +594,41 @@ func (a *App) handleExitCoder(cCtx *Context, err error) { } } +func (a *App) commandNames() []string { + var cmdNames []string + + for _, cmd := range a.Commands { + cmdNames = append(cmdNames, cmd.Names()...) + } + + return cmdNames +} + +func (a *App) validCommandName(checkCmdName string) bool { + valid := false + allCommandNames := a.commandNames() + + for _, cmdName := range allCommandNames { + if checkCmdName == cmdName { + valid = true + break + } + } + + return valid +} + +func (a *App) argsWithDefaultCommand(oldArgs Args) Args { + if a.DefaultCommand != "" { + rawArgs := append([]string{a.DefaultCommand}, oldArgs.Slice()...) + newArgs := args(rawArgs) + + return &newArgs + } + + return oldArgs +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name diff --git a/app_test.go b/app_test.go index 3dd73ab..8c2d3a9 100644 --- a/app_test.go +++ b/app_test.go @@ -469,6 +469,42 @@ func TestApp_Command(t *testing.T) { } } +var defaultCommandAppTests = []struct { + cmdName string + defaultCmd string + expected bool +}{ + {"foobar", "foobar", true}, + {"batbaz", "foobar", true}, + {"b", "", true}, + {"f", "", true}, + {"", "foobar", true}, + {"", "", true}, + {" ", "", false}, + {"bat", "batbaz", false}, + {"nothing", "batbaz", false}, + {"nothing", "", false}, +} + +func TestApp_RunDefaultCommand(t *testing.T) { + for _, test := range defaultCommandAppTests { + testTitle := fmt.Sprintf("command=%[1]s-default=%[2]s", test.cmdName, test.defaultCmd) + t.Run(testTitle, func(t *testing.T) { + app := &App{ + DefaultCommand: test.defaultCmd, + Commands: []*Command{ + {Name: "foobar", Aliases: []string{"f"}}, + {Name: "batbaz", Aliases: []string{"b"}}, + }, + } + + err := app.Run([]string{"c", test.cmdName}) + expect(t, err == nil, test.expected) + }) + } + +} + func TestApp_Setup_defaultsReader(t *testing.T) { app := &App{} app.Setup()