Skip to content

Commit 9d8d9f7

Browse files
Allow a node with a Run() method to run without a subcommand (#614)
A node that declares subcommands but also defines a Run() method can now be invoked on its own. checkMissingChildren no longer requires one of the subcommands to be selected when the node is runnable, and ctx.Run() already runs such a node.
1 parent ff16446 commit 9d8d9f7

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

context.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,10 @@ func checkMissingChildren(node *Node) error {
972972
missing = append(missing, strconv.Quote(strings.Join(missingArgs, " ")))
973973
}
974974

975+
// A node with a Run() method may run on its own, so it does not require one
976+
// of its subcommands to be selected.
977+
runnable := node.Target.IsValid() && getMethod(node.Target, "Run").IsValid()
978+
975979
for _, child := range node.Children {
976980
if child.Hidden {
977981
continue
@@ -981,7 +985,7 @@ func checkMissingChildren(node *Node) error {
981985
continue
982986
}
983987
missing = append(missing, strconv.Quote(child.Summary()))
984-
} else {
988+
} else if !runnable {
985989
missing = append(missing, strconv.Quote(child.Name))
986990
}
987991
}

kong_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,45 @@ func TestRun(t *testing.T) {
806806
assert.Equal(t, "argping", cli.Three.SubCommand.Arg)
807807
}
808808

809+
type rootWithRun struct {
810+
ran bool
811+
SubCmd1 struct{} `cmd:""`
812+
SubCmd2 struct{} `cmd:""`
813+
}
814+
815+
func (r *rootWithRun) Run() error {
816+
r.ran = true
817+
return nil
818+
}
819+
820+
func TestRunnableNodeDoesNotRequireSubcommand(t *testing.T) {
821+
t.Run("runs the node when no subcommand is given", func(t *testing.T) {
822+
cli := &rootWithRun{}
823+
p := mustNew(t, cli)
824+
ctx, err := p.Parse([]string{})
825+
assert.NoError(t, err)
826+
assert.NoError(t, ctx.Run())
827+
assert.True(t, cli.ran)
828+
})
829+
830+
t.Run("still allows selecting a subcommand", func(t *testing.T) {
831+
cli := &rootWithRun{}
832+
p := mustNew(t, cli)
833+
_, err := p.Parse([]string{"sub-cmd-1"})
834+
assert.NoError(t, err)
835+
})
836+
837+
t.Run("still requires a subcommand without a Run method", func(t *testing.T) {
838+
var cli struct {
839+
SubCmd1 struct{} `cmd:""`
840+
SubCmd2 struct{} `cmd:""`
841+
}
842+
p := mustNew(t, &cli)
843+
_, err := p.Parse([]string{})
844+
assert.Error(t, err)
845+
})
846+
}
847+
809848
type failCmd struct{}
810849

811850
func (f failCmd) Run() error {

0 commit comments

Comments
 (0)