From 41edf2ed9ed278146f6aabd0e0f903c2558745d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sun, 30 Apr 2023 10:59:53 +0200 Subject: [PATCH] Simplify API --- cobrakai.go | 157 +++++++++++++++++++++++++---------------------- cobrakai_test.go | 139 ++++++++++++++++++++++++----------------- 2 files changed, 169 insertions(+), 127 deletions(-) diff --git a/cobrakai.go b/cobrakai.go index 221efeb..586ff57 100644 --- a/cobrakai.go +++ b/cobrakai.go @@ -6,50 +6,89 @@ import ( "github.com/spf13/cobra" ) -// Executer is the execution entry point. -// The args are usually filled with os.Args[1:]. -type Executer interface { - Execute(ctx context.Context, args []string) (*Commandeer, error) -} - // Commander is the interface that must be implemented by all commands. type Commander interface { + // The name of the command. Name() string + + // The command execution. Run(ctx context.Context, args []string) error + + // Init called on all commands in this tree, before execution, starting from the root. + // This is the place to evaluate flags and set up the command. + Init(*Commandeer) error + + // WithCobraCommand is called when the cobra command is created. + // This is where the flags, short and long description etc. are added. WithCobraCommand(*cobra.Command) error + + // Commands returns the sub commands, if any. + Commands() []Commander } -type root struct { - c *Commandeer +// Executer is the execution entry point. +// The args are usually filled with os.Args[1:]. +type Executer interface { + Execute(ctx context.Context, args []string) (*Commandeer, error) } -func (r *root) Execute(ctx context.Context, args []string) (*Commandeer, error) { - r.c.CobraCommand.SetArgs(args) - cobraCommand, err := r.c.CobraCommand.ExecuteContextC(ctx) - if err != nil { - return nil, err +// New creates a new Executer from the command tree in Commander. +func New(rootCmd Commander) (Executer, error) { + rootCd := &Commandeer{ + Command: rootCmd, } - // Find the commandeer that was executed. - var find func(*cobra.Command, *Commandeer) *Commandeer - find = func(what *cobra.Command, in *Commandeer) *Commandeer { - if in.CobraCommand == what { - return in + rootCd.root = rootCd + + // Add all commands recursively. + var addCommands func(cd *Commandeer, cmd Commander) + addCommands = func(cd *Commandeer, cmd Commander) { + cd2 := &Commandeer{ + root: rootCd, + Command: cmd, } - for _, in2 := range in.commandeers { - if found := find(what, in2); found != nil { - return found - } + cd.commandeers = append(cd.commandeers, cd2) + for _, c := range cmd.Commands() { + addCommands(cd2, c) } - return nil + } - return find(cobraCommand, r.c), nil + + addCommands(rootCd, rootCmd) + + if err := rootCd.compile(); err != nil { + return nil, err + } + + return &root{c: rootCd}, nil + } // Commandeer holds the state of a command and its subcommands. type Commandeer struct { Command Commander CobraCommand *cobra.Command - commandeers []*Commandeer + + root *Commandeer + commandeers []*Commandeer +} + +func (c *Commandeer) init() error { + // Start from the root and initialize all commands recursively. + // root is always set. + cd := c.root + var initc func(*Commandeer) error + initc = func(cd *Commandeer) error { + if err := cd.Command.Init(cd); err != nil { + return err + } + for _, cc := range cd.commandeers { + if err := initc(cc); err != nil { + return err + } + } + return nil + } + return initc(cd) } func (c *Commandeer) compile() error { @@ -58,6 +97,9 @@ func (c *Commandeer) compile() error { RunE: func(cmd *cobra.Command, args []string) error { return c.Command.Run(cmd.Context(), args) }, + PreRunE: func(cmd *cobra.Command, args []string) error { + return c.init() + }, } // This is where the flags, short and long description etc. are added @@ -74,57 +116,28 @@ func (c *Commandeer) compile() error { return nil } -// WithCommandeer allows chaining of commandeers. -type WithCommandeer func(*Commandeer) +type root struct { + c *Commandeer +} -// R creates the execution entry poing given a root command and a chain of nested commands. -func R(command Commander, wcs ...WithCommandeer) (Executer, error) { - c := &Commandeer{ - Command: command, - } - for _, wc := range wcs { - wc(c) - } - if err := c.compile(); err != nil { +func (r *root) Execute(ctx context.Context, args []string) (*Commandeer, error) { + r.c.CobraCommand.SetArgs(args) + cobraCommand, err := r.c.CobraCommand.ExecuteContextC(ctx) + if err != nil { return nil, err } - return &root{c: c}, nil -} - -// C creates nested commands. -func C(command Commander, wcs ...WithCommandeer) WithCommandeer { - return func(parent *Commandeer) { - cd := &Commandeer{ - Command: command, + // Find the commandeer that was executed. + var find func(*cobra.Command, *Commandeer) *Commandeer + find = func(what *cobra.Command, in *Commandeer) *Commandeer { + if in.CobraCommand == what { + return in } - parent.commandeers = append(parent.commandeers, cd) - for _, wc := range wcs { - wc(cd) + for _, in2 := range in.commandeers { + if found := find(what, in2); found != nil { + return found + } } + return nil } -} - -// SimpleCommand creates a simple command that does not take any flags. -func SimpleCommand(name string, run func(ctx context.Context, args []string) error) Commander { - return &simpleCommand{ - name: name, - run: run, - } -} - -type simpleCommand struct { - name string - run func(ctx context.Context, args []string) error -} - -func (c *simpleCommand) Name() string { - return c.name -} - -func (c *simpleCommand) Run(ctx context.Context, args []string) error { - return c.run(ctx, args) -} - -func (c *simpleCommand) WithCobraCommand(cmd *cobra.Command) error { - return nil + return find(cobraCommand, r.c), nil } diff --git a/cobrakai_test.go b/cobrakai_test.go index e67737d..e66cd20 100644 --- a/cobrakai_test.go +++ b/cobrakai_test.go @@ -12,90 +12,108 @@ import ( ) func TestCobraKai(t *testing.T) { + c := qt.New(t) - var ( - fooCommand = &testComand1{name: "foo"} - barCommand = &testComand1{name: "bar"} - fooBazCommand = &testComand2{name: "foo_baz"} - ) + rootCmd := &rootCommand{name: "root", + commands: []cobrakai.Commander{ + &testCommand{name: "foo"}, + &testCommand{name: "bar"}, + }, + } - c := qt.New(t) - r, err := cobrakai.R( - &testComand1{name: "hugo"}, // The root command. - cobrakai.C( - fooCommand, - cobrakai.C( - fooBazCommand), - ), - cobrakai.C(barCommand), - ) + x, err := cobrakai.New(rootCmd) c.Assert(err, qt.IsNil) - // This can be anything, just used to make sure the same context is passed all the way. type key string ctx := context.WithValue(context.Background(), key("foo"), "bar") - args := []string{"foo", "--localFlagName", "foo_local", "--persistentFlagName", "foo_persistent"} - cdeer, err := r.Execute(ctx, args) + args := []string{"root", "--localFlagName", "foo_local", "--persistentFlagName", "foo_persistent"} + cd, err := x.Execute(ctx, args) c.Assert(err, qt.IsNil) - c.Assert(cdeer.Command.Name(), qt.Equals, "foo") - tc := cdeer.Command.(*testComand1) + c.Assert(cd.Command.Name(), qt.Equals, "root") + tc := cd.Command.(*rootCommand) c.Assert(tc.ctx, qt.Equals, ctx) c.Assert(tc.localFlagName, qt.Equals, "foo_local") c.Assert(tc.persistentFlagName, qt.Equals, "foo_persistent") + c.Assert(tc.persistentFlagNameC, qt.Equals, "foo_persistent_rootCommand_compiled") + c.Assert(tc.localFlagNameC, qt.Equals, "foo_local_rootCommand_compiled") - args = []string{"foo", "foo_baz", "--localFlagName", "foo_local2", "--persistentFlagName", "foo_persistent2"} + // This may not be very realistic, but it works. The common use case for a CLI app is to run one command and then exit. + args = []string{"root", "bar", "--localFlagName", "foo_local2", "--persistentFlagName", "foo_persistent2"} ctx = context.WithValue(context.Background(), key("bar"), "baz") - cdeer2, err := r.Execute(ctx, args) + cd2, err := x.Execute(ctx, args) c.Assert(err, qt.IsNil) - c.Assert(cdeer2.Command.Name(), qt.Equals, "foo_baz") - tc2 := cdeer2.Command.(*testComand2) + c.Assert(cd2.Command.Name(), qt.Equals, "bar") + tc2 := cd2.Command.(*testCommand) c.Assert(tc2.ctx, qt.Equals, ctx) c.Assert(tc2.localFlagName, qt.Equals, "foo_local2") + c.Assert(tc2.localFlagNameC, qt.Equals, "foo_local2_testCommand_compiled") c.Assert(tc.persistentFlagName, qt.Equals, "foo_persistent2") - + c.Assert(tc.persistentFlagNameC, qt.Equals, "foo_persistent2_rootCommand_compiled") } -func ExampleSimpleCommand() { - r, err := cobrakai.R( - // If you need flags, implement cobrakai.Commander. - cobrakai.SimpleCommand("root", func(ctx context.Context, args []string) error { fmt.Print("run root "); return nil }), - cobrakai.C(cobrakai.SimpleCommand("sub1", func(ctx context.Context, args []string) error { fmt.Print("run sub1"); return nil })), - cobrakai.C(cobrakai.SimpleCommand("sub2", func(ctx context.Context, args []string) error { fmt.Print("run sub2"); return nil })), - ) - - if err != nil { - log.Fatal(err) +func Example() { + rootCmd := &rootCommand{name: "root", + commands: []cobrakai.Commander{ + &testCommand{name: "foo"}, + &testCommand{name: "bar"}, + }, } - if _, err := r.Execute(context.Background(), []string{""}); err != nil { + args := []string{"root", "bar", "--localFlagName", "bar_local", "--persistentFlagName", "bar_persistent"} + x, err := cobrakai.New(rootCmd) + if err != nil { log.Fatal(err) } - if _, err := r.Execute(context.Background(), []string{"sub1"}); err != nil { + cd, err := x.Execute(context.Background(), args) + if err != nil { log.Fatal(err) } - // Output: run root run sub1 + tc := cd.Command.(*testCommand) + + fmt.Println("Executed", tc.name, "with localFlagName", tc.localFlagName, "and persistentFlagName", rootCmd.persistentFlagName) + // Output: Executed bar with localFlagName bar_local and persistentFlagName bar_persistent } -type testComand1 struct { +type rootCommand struct { + name string + + // Flags persistentFlagName string localFlagName string - ctx context.Context - name string + // Compiled flags. + persistentFlagNameC string + localFlagNameC string + + // For testing. + ctx context.Context + + // Sub commands. + commands []cobrakai.Commander } -func (c *testComand1) Run(ctx context.Context, args []string) error { - c.ctx = ctx - fmt.Println("testComand.Run", c.name, args) +func (c *rootCommand) Commands() []cobrakai.Commander { + return c.commands +} + +func (c *rootCommand) Init(*cobrakai.Commandeer) error { + c.persistentFlagNameC = c.persistentFlagName + "_rootCommand_compiled" + c.localFlagNameC = c.localFlagName + "_rootCommand_compiled" return nil } -func (c *testComand1) Name() string { +func (c *rootCommand) Name() string { return c.name } -func (c *testComand1) WithCobraCommand(cmd *cobra.Command) error { +func (c *rootCommand) Run(ctx context.Context, args []string) error { + c.ctx = ctx + fmt.Println("rootCommand.Run", c.name, args) + return nil +} + +func (c *rootCommand) WithCobraCommand(cmd *cobra.Command) error { localFlags := cmd.Flags() persistentFlags := cmd.PersistentFlags() @@ -105,24 +123,35 @@ func (c *testComand1) WithCobraCommand(cmd *cobra.Command) error { return nil } -type testComand2 struct { - localFlagName string - - ctx context.Context +type testCommand struct { name string + + localFlagName string + localFlagNameC string + + ctx context.Context } -func (c *testComand2) Run(ctx context.Context, args []string) error { - c.ctx = ctx - fmt.Println("testComand2.Run", c.name, args) +func (c *testCommand) Commands() []cobrakai.Commander { + return nil +} + +func (c *testCommand) Init(*cobrakai.Commandeer) error { + c.localFlagNameC = c.localFlagName + "_testCommand_compiled" return nil } -func (c *testComand2) Name() string { +func (c *testCommand) Name() string { return c.name } -func (c *testComand2) WithCobraCommand(cmd *cobra.Command) error { +func (c *testCommand) Run(ctx context.Context, args []string) error { + c.ctx = ctx + fmt.Println("testCommand.Run", c.name, args) + return nil +} + +func (c *testCommand) WithCobraCommand(cmd *cobra.Command) error { localFlags := cmd.Flags() localFlags.StringVar(&c.localFlagName, "localFlagName", "", "set localFlagName for testCommand2") return nil