feat: allow non-structs to be used as commands (#428)
* feat: allow non-structs to be used as commands This small MR allows using the func-to-interface trick to implement a command (see commandFunc in kong_test.go). This is useful e.g. for commands that have no flags or arguments of their own, but instead receive all required information via bound parameters. * fix: check DynamicCommand is runnable when adding
This commit is contained in:
@@ -51,6 +51,9 @@ type flattenedField struct {
|
|||||||
|
|
||||||
func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) {
|
func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) {
|
||||||
v = reflect.Indirect(v)
|
v = reflect.Indirect(v)
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
for i := 0; i < v.NumField(); i++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
ft := v.Type().Field(i)
|
ft := v.Type().Field(i)
|
||||||
fv := v.Field(i)
|
fv := v.Field(i)
|
||||||
|
|||||||
+1
-1
@@ -903,7 +903,7 @@ func checkMissingChildren(node *Node) error {
|
|||||||
if len(missing) == 1 {
|
if len(missing) == 1 {
|
||||||
return fmt.Errorf("expected %s", missing[0])
|
return fmt.Errorf("expected %s", missing[0])
|
||||||
}
|
}
|
||||||
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're missing any positionals and they're required, return an error.
|
// If we're missing any positionals and they're required, return an error.
|
||||||
|
|||||||
+17
-1
@@ -1297,6 +1297,12 @@ func (d *dynamicCommand) Run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type commandFunc func() error
|
||||||
|
|
||||||
|
func (cf commandFunc) Run() error {
|
||||||
|
return cf()
|
||||||
|
}
|
||||||
|
|
||||||
func TestDynamicCommands(t *testing.T) {
|
func TestDynamicCommands(t *testing.T) {
|
||||||
cli := struct {
|
cli := struct {
|
||||||
One struct{} `cmd:"one"`
|
One struct{} `cmd:"one"`
|
||||||
@@ -1304,9 +1310,12 @@ func TestDynamicCommands(t *testing.T) {
|
|||||||
help := &strings.Builder{}
|
help := &strings.Builder{}
|
||||||
two := &dynamicCommand{}
|
two := &dynamicCommand{}
|
||||||
three := &dynamicCommand{}
|
three := &dynamicCommand{}
|
||||||
|
fourRan := false
|
||||||
|
four := commandFunc(func() error { fourRan = true; return nil })
|
||||||
p := mustNew(t, &cli,
|
p := mustNew(t, &cli,
|
||||||
kong.DynamicCommand("two", "", "", &two),
|
kong.DynamicCommand("two", "", "", &two),
|
||||||
kong.DynamicCommand("three", "", "", three, "hidden"),
|
kong.DynamicCommand("three", "", "", three, "hidden"),
|
||||||
|
kong.DynamicCommand("four", "", "", &four),
|
||||||
kong.Writers(help, help),
|
kong.Writers(help, help),
|
||||||
kong.Exit(func(int) {}))
|
kong.Exit(func(int) {}))
|
||||||
kctx, err := p.Parse([]string{"two", "--flag=flag"})
|
kctx, err := p.Parse([]string{"two", "--flag=flag"})
|
||||||
@@ -1317,8 +1326,15 @@ func TestDynamicCommands(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, two.ran)
|
assert.True(t, two.ran)
|
||||||
|
|
||||||
|
kctx, err = p.Parse([]string{"four"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, fourRan)
|
||||||
|
err = kctx.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, fourRan)
|
||||||
|
|
||||||
_, err = p.Parse([]string{"--help"})
|
_, err = p.Parse([]string{"--help"})
|
||||||
assert.EqualError(t, err, `expected one of "one", "two"`)
|
assert.EqualError(t, err, `expected one of "one", "two", "four"`)
|
||||||
assert.NotContains(t, help.String(), "three", help.String())
|
assert.NotContains(t, help.String(), "three", help.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ type dynamicCommand struct {
|
|||||||
// "tags" is a list of extra tag strings to parse, in the form <key>:"<value>".
|
// "tags" is a list of extra tag strings to parse, in the form <key>:"<value>".
|
||||||
func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option {
|
func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option {
|
||||||
return OptionFunc(func(k *Kong) error {
|
return OptionFunc(func(k *Kong) error {
|
||||||
|
if run := getMethod(reflect.Indirect(reflect.ValueOf(cmd)), "Run"); !run.IsValid() {
|
||||||
|
return fmt.Errorf("kong: DynamicCommand %q must be a type with a 'Run' method; got %T", name, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
|
k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
|
||||||
name: name,
|
name: name,
|
||||||
help: help,
|
help: help,
|
||||||
|
|||||||
Reference in New Issue
Block a user