Merge pull request #1388 from jalavosus/feature/default-command
feature: add DefaultCommand field to App
This commit is contained in:
commit
d29120f08b
89
app.go
89
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,45 @@ 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 {
|
||||
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 {
|
||||
@ -570,6 +606,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
|
||||
@ -602,3 +673,15 @@ func HandleAction(action interface{}, cCtx *Context) (err error) {
|
||||
|
||||
return errInvalidActionType
|
||||
}
|
||||
|
||||
func checkStringSliceIncludes(want string, sSlice []string) bool {
|
||||
found := false
|
||||
for _, s := range sSlice {
|
||||
if want == s {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
169
app_test.go
169
app_test.go
@ -469,6 +469,175 @@ 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var defaultCommandSubCmdAppTests = []struct {
|
||||
cmdName string
|
||||
subCmd string
|
||||
defaultCmd string
|
||||
expected bool
|
||||
}{
|
||||
{"foobar", "", "foobar", true},
|
||||
{"foobar", "carly", "foobar", true},
|
||||
{"batbaz", "", "foobar", true},
|
||||
{"b", "", "", true},
|
||||
{"f", "", "", true},
|
||||
{"", "", "foobar", true},
|
||||
{"", "", "", true},
|
||||
{"", "jimbob", "foobar", true},
|
||||
{"", "j", "foobar", true},
|
||||
{"", "carly", "foobar", true},
|
||||
{"", "jimmers", "foobar", true},
|
||||
{"", "jimmers", "", true},
|
||||
{" ", "jimmers", "foobar", false},
|
||||
{"", "", "", true},
|
||||
{" ", "", "", false},
|
||||
{" ", "j", "", false},
|
||||
{"bat", "", "batbaz", false},
|
||||
{"nothing", "", "batbaz", false},
|
||||
{"nothing", "", "", false},
|
||||
{"nothing", "j", "batbaz", false},
|
||||
{"nothing", "carly", "", false},
|
||||
}
|
||||
|
||||
func TestApp_RunDefaultCommandWithSubCommand(t *testing.T) {
|
||||
for _, test := range defaultCommandSubCmdAppTests {
|
||||
testTitle := fmt.Sprintf("command=%[1]s-subcmd=%[2]s-default=%[3]s", test.cmdName, test.subCmd, test.defaultCmd)
|
||||
t.Run(testTitle, func(t *testing.T) {
|
||||
app := &App{
|
||||
DefaultCommand: test.defaultCmd,
|
||||
Commands: []*Command{
|
||||
{
|
||||
Name: "foobar",
|
||||
Aliases: []string{"f"},
|
||||
Subcommands: []*Command{
|
||||
{Name: "jimbob", Aliases: []string{"j"}},
|
||||
{Name: "carly"},
|
||||
},
|
||||
},
|
||||
{Name: "batbaz", Aliases: []string{"b"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run([]string{"c", test.cmdName, test.subCmd})
|
||||
expect(t, err == nil, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var defaultCommandFlagAppTests = []struct {
|
||||
cmdName string
|
||||
flag string
|
||||
defaultCmd string
|
||||
expected bool
|
||||
}{
|
||||
{"foobar", "", "foobar", true},
|
||||
{"foobar", "-c derp", "foobar", true},
|
||||
{"batbaz", "", "foobar", true},
|
||||
{"b", "", "", true},
|
||||
{"f", "", "", true},
|
||||
{"", "", "foobar", true},
|
||||
{"", "", "", true},
|
||||
{"", "-j", "foobar", true},
|
||||
{"", "-j", "foobar", true},
|
||||
{"", "-c derp", "foobar", true},
|
||||
{"", "--carly=derp", "foobar", true},
|
||||
{"", "-j", "foobar", true},
|
||||
{"", "-j", "", true},
|
||||
{" ", "-j", "foobar", false},
|
||||
{"", "", "", true},
|
||||
{" ", "", "", false},
|
||||
{" ", "-j", "", false},
|
||||
{"bat", "", "batbaz", false},
|
||||
{"nothing", "", "batbaz", false},
|
||||
{"nothing", "", "", false},
|
||||
{"nothing", "--jimbob", "batbaz", false},
|
||||
{"nothing", "--carly", "", false},
|
||||
}
|
||||
|
||||
func TestApp_RunDefaultCommandWithFlags(t *testing.T) {
|
||||
for _, test := range defaultCommandFlagAppTests {
|
||||
testTitle := fmt.Sprintf("command=%[1]s-flag=%[2]s-default=%[3]s", test.cmdName, test.flag, test.defaultCmd)
|
||||
t.Run(testTitle, func(t *testing.T) {
|
||||
app := &App{
|
||||
DefaultCommand: test.defaultCmd,
|
||||
Flags: []Flag{
|
||||
&StringFlag{
|
||||
Name: "carly",
|
||||
Aliases: []string{"c"},
|
||||
Required: false,
|
||||
},
|
||||
&BoolFlag{
|
||||
Name: "jimbob",
|
||||
Aliases: []string{"j"},
|
||||
Required: false,
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
Commands: []*Command{
|
||||
{
|
||||
Name: "foobar",
|
||||
Aliases: []string{"f"},
|
||||
},
|
||||
{Name: "batbaz", Aliases: []string{"b"}},
|
||||
},
|
||||
}
|
||||
|
||||
appArgs := []string{"c"}
|
||||
|
||||
if test.flag != "" {
|
||||
flags := strings.Split(test.flag, " ")
|
||||
if len(flags) > 1 {
|
||||
appArgs = append(appArgs, flags...)
|
||||
}
|
||||
|
||||
flags = strings.Split(test.flag, "=")
|
||||
if len(flags) > 1 {
|
||||
appArgs = append(appArgs, flags...)
|
||||
}
|
||||
}
|
||||
|
||||
appArgs = append(appArgs, test.cmdName)
|
||||
|
||||
err := app.Run(appArgs)
|
||||
expect(t, err == nil, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_Setup_defaultsReader(t *testing.T) {
|
||||
app := &App{}
|
||||
app.Setup()
|
||||
|
Loading…
Reference in New Issue
Block a user