Skip to content

Commit

Permalink
Add support for dynamically registered commands.
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed Apr 2, 2021
1 parent 57bfb05 commit 49417fe
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 0 deletions.
15 changes: 15 additions & 0 deletions kong.go
Expand Up @@ -66,6 +66,7 @@ type Kong struct {

// Set temporarily by Options. These are applied after build().
postBuildOptions []Option
dynamicCommands []*dynamicCommand
}

// New creates a new Kong parser on grammar.
Expand Down Expand Up @@ -106,6 +107,20 @@ func New(grammar interface{}, options ...Option) (*Kong, error) {
k.Model = model
k.Model.HelpFlag = k.helpFlag

// Synthesise command nodes.
for _, dcmd := range k.dynamicCommands {
tag := newEmptyTag()
tag.Name = dcmd.name
tag.Help = dcmd.help
tag.Group = dcmd.group
tag.Cmd = true
v := reflect.Indirect(reflect.ValueOf(dcmd.cmd))
buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{
Name: dcmd.name,
Type: v.Type(),
}, v, tag, dcmd.name, map[string]bool{})
}

for _, option := range k.postBuildOptions {
if err = option.Apply(k); err != nil {
return nil, err
Expand Down
27 changes: 27 additions & 0 deletions kong_test.go
Expand Up @@ -1002,3 +1002,30 @@ func TestPointers(t *testing.T) {
require.NotNil(t, cli.JSON)
require.Equal(t, "FOO", string(*cli.JSON))
}

type dynamicCommand struct {
Flag string

ran bool
}

func (d *dynamicCommand) Run() error {
d.ran = true
return nil
}

func TestDynamicCommands(t *testing.T) {
cli := struct {
One struct{} `cmd:"one"`
}{}
two := &dynamicCommand{}
var twoi interface{} = &two
p := mustNew(t, &cli, kong.DynamicCommand("two", "", "", twoi))
kctx, err := p.Parse([]string{"two", "--flag=flag"})
require.NoError(t, err)
require.Equal(t, "flag", two.Flag)
require.False(t, two.ran)
err = kctx.Run()
require.NoError(t, err)
require.True(t, two.ran)
}
22 changes: 22 additions & 0 deletions options.go
Expand Up @@ -54,6 +54,28 @@ func Exit(exit func(int)) Option {
})
}

type dynamicCommand struct {
name string
help string
group string
cmd interface{}
}

// DynamicCommand registers a dynamically constructed command with the root of the CLI.
//
// This is useful for command-line structures that are extensible via user-provided plugins.

This comment has been minimized.

Copy link
@josephschmitt

josephschmitt Apr 7, 2021

Contributor

🥳 🎉 🍾

func DynamicCommand(name, help, group string, cmd interface{}) Option {
return OptionFunc(func(k *Kong) error {
k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
name: name,
help: help,
group: group,
cmd: cmd,
})
return nil
})
}

// NoDefaultHelp disables the default help flags.
func NoDefaultHelp() Option {
return OptionFunc(func(k *Kong) error {
Expand Down

2 comments on commit 49417fe

@mickael-menu
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Pretty excited about this one

@alecthomas
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give it a whirl and let me know how it goes.

Please sign in to comment.