Skip to content

Commit 4fb441f

Browse files
committed
First take
1 parent 9f40192 commit 4fb441f

File tree

4 files changed

+218
-5
lines changed

4 files changed

+218
-5
lines changed

‎cobrakai.go‎

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,112 @@
11
package cobrakai
22

3-
func Foo() string {
4-
return "foo"
3+
import (
4+
"context"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/spf13/pflag"
8+
)
9+
10+
type root struct {
11+
c *Commandeer
12+
}
13+
14+
// Execute executes the root command and returns the commandeer that was executed.
15+
func (r *root) Execute(ctx context.Context) (*Commandeer, error) {
16+
cobraCommand, err := r.c.CobraCommand.ExecuteContextC(ctx)
17+
if err != nil {
18+
return nil, err
19+
}
20+
// Find the commandeer that was executed.
21+
var find func(*cobra.Command, *Commandeer) *Commandeer
22+
find = func(what *cobra.Command, in *Commandeer) *Commandeer {
23+
if in.CobraCommand == what {
24+
return in
25+
}
26+
for _, in2 := range in.commandeers {
27+
if found := find(what, in2); found != nil {
28+
return found
29+
}
30+
}
31+
return nil
32+
}
33+
return find(cobraCommand, r.c), nil
34+
}
35+
36+
// Commandeer holds the state of a command and its subcommands.
37+
type Commandeer struct {
38+
Command Commander
39+
commandeers []*Commandeer
40+
41+
// compiled
42+
CobraCommand *cobra.Command
43+
}
44+
45+
func (c *Commandeer) compile() error {
46+
c.CobraCommand = &cobra.Command{
47+
Use: c.Command.Name(),
48+
Short: "TODO",
49+
Long: "TODO",
50+
RunE: func(cmd *cobra.Command, args []string) error {
51+
return c.Command.Run(cmd.Context(), args)
52+
},
53+
}
54+
// THere's a LocalFlags set in Cobra which one would beliee would be the right place to put these flags,
55+
// but theat doesn't work and there's several related open issues.
56+
// This is how the docs say to do it and also where Hugo puts local flags.
57+
c.Command.AddFlagsLocal(c.CobraCommand.Flags())
58+
c.Command.AddFlagsPersistent(c.CobraCommand.PersistentFlags())
59+
60+
// Add commands recursively.
61+
for _, cc := range c.commandeers {
62+
if err := cc.compile(); err != nil {
63+
return err
64+
}
65+
c.CobraCommand.AddCommand(cc.CobraCommand)
66+
}
67+
68+
return nil
69+
}
70+
71+
// Executer is the execution entry point.
72+
type Executer interface {
73+
Execute(ctx context.Context) (*Commandeer, error)
74+
}
75+
76+
// Commander is the interface that must be implemented by all commands.
77+
type Commander interface {
78+
Name() string
79+
Run(ctx context.Context, args []string) error
80+
AddFlagsLocal(*pflag.FlagSet)
81+
AddFlagsPersistent(*pflag.FlagSet)
82+
}
83+
84+
// WithCommandeer allows chaining of commandeers.
85+
type WithCommandeer func(*Commandeer)
86+
87+
// R creates the execution entry poing given a root command and a chain of nested commands.
88+
func R(command Commander, wcs ...WithCommandeer) (Executer, error) {
89+
c := &Commandeer{
90+
Command: command,
91+
}
92+
for _, wc := range wcs {
93+
wc(c)
94+
}
95+
if err := c.compile(); err != nil {
96+
return nil, err
97+
}
98+
return &root{c: c}, nil
99+
}
100+
101+
// C creates nested commands.
102+
func C(command Commander, wcs ...WithCommandeer) func(*Commandeer) {
103+
return func(parent *Commandeer) {
104+
cd := &Commandeer{
105+
Command: command,
106+
}
107+
parent.commandeers = append(parent.commandeers, cd)
108+
for _, wc := range wcs {
109+
wc(cd)
110+
}
111+
}
5112
}

‎cobrakai_test.go‎

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,105 @@
1-
package cobrakai
1+
package cobrakai_test
22

33
import (
4+
"context"
5+
"fmt"
6+
"os"
47
"testing"
58

9+
ck "github.com/bep/cobrakai"
610
qt "github.com/frankban/quicktest"
11+
"github.com/spf13/pflag"
712
)
813

9-
func TestFoo(t *testing.T) {
14+
func TestCobraKai(t *testing.T) {
15+
16+
var (
17+
fooCommand = &testComand1{name: "foo"}
18+
barCommand = &testComand1{name: "bar"}
19+
fooBazCommand = &testComand2{name: "foo_baz"}
20+
)
21+
22+
os.Args = []string{"hugo", "foo", "--localFlagName", "foo_local", "--persistentFlagName", "foo_persistent"}
1023
c := qt.New(t)
11-
c.Assert(Foo(), qt.Equals, "foo")
24+
r, err := ck.R(
25+
&testComand1{name: "hugo"},
26+
ck.C(fooCommand,
27+
ck.C(fooBazCommand),
28+
),
29+
ck.C(barCommand),
30+
)
31+
c.Assert(err, qt.IsNil)
32+
33+
// This can be anything, just used to make sure the same context is passed all the way.
34+
type key string
35+
ctx := context.WithValue(context.Background(), key("foo"), "bar")
36+
cdeer, err := r.Execute(ctx)
37+
c.Assert(err, qt.IsNil)
38+
c.Assert(cdeer.Command.Name(), qt.Equals, "foo")
39+
tc := cdeer.Command.(*testComand1)
40+
c.Assert(tc.ctx, qt.Equals, ctx)
41+
c.Assert(tc.localFlagName, qt.Equals, "foo_local")
42+
c.Assert(tc.persistentFlagName, qt.Equals, "foo_persistent")
43+
44+
os.Args = []string{"hugo", "foo", "foo_baz", "--localFlagName", "foo_local2", "--persistentFlagName", "foo_persistent2"}
45+
ctx = context.WithValue(context.Background(), key("bar"), "baz")
46+
cdeer2, err := r.Execute(ctx)
47+
c.Assert(err, qt.IsNil)
48+
c.Assert(cdeer2.Command.Name(), qt.Equals, "foo_baz")
49+
tc2 := cdeer2.Command.(*testComand2)
50+
c.Assert(tc2.ctx, qt.Equals, ctx)
51+
c.Assert(tc2.localFlagName, qt.Equals, "foo_local2")
52+
c.Assert(tc.persistentFlagName, qt.Equals, "foo_persistent2")
53+
54+
}
55+
56+
type testComand1 struct {
57+
persistentFlagName string
58+
localFlagName string
59+
60+
ctx context.Context
61+
name string
62+
}
63+
64+
func (c *testComand1) Run(ctx context.Context, args []string) error {
65+
c.ctx = ctx
66+
fmt.Println("testComand.Run", c.name, args)
67+
return nil
68+
}
69+
70+
func (c *testComand1) Name() string {
71+
return c.name
72+
}
73+
74+
func (c *testComand1) AddFlagsLocal(flags *pflag.FlagSet) {
75+
flags.StringVar(&c.localFlagName, "localFlagName", "", "set localFlagName")
76+
}
77+
78+
func (c *testComand1) AddFlagsPersistent(flags *pflag.FlagSet) {
79+
flags.StringVar(&c.persistentFlagName, "persistentFlagName", "", "set persistentFlagName")
80+
}
81+
82+
type testComand2 struct {
83+
localFlagName string
84+
85+
ctx context.Context
86+
name string
87+
}
88+
89+
func (c *testComand2) Run(ctx context.Context, args []string) error {
90+
c.ctx = ctx
91+
fmt.Println("testComand2.Run", c.name, args)
92+
return nil
93+
}
94+
95+
func (c *testComand2) Name() string {
96+
return c.name
97+
}
98+
99+
func (c *testComand2) AddFlagsLocal(flags *pflag.FlagSet) {
100+
flags.StringVar(&c.localFlagName, "localFlagName", "", "set localFlagName for testCommand2")
101+
}
102+
103+
func (c *testComand2) AddFlagsPersistent(flags *pflag.FlagSet) {
104+
12105
}

‎go.mod‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ require github.com/frankban/quicktest v1.14.2
66

77
require (
88
github.com/google/go-cmp v0.5.7 // indirect
9+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
910
github.com/kr/pretty v0.3.0 // indirect
1011
github.com/kr/text v0.2.0 // indirect
1112
github.com/rogpeppe/go-internal v1.6.1 // indirect
13+
github.com/spf13/cobra v1.7.0 // indirect
14+
github.com/spf13/pflag v1.0.5 // indirect
1215
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
1316
)

‎go.sum‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
12
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
23
github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns=
34
github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
45
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
56
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
7+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
8+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
69
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
710
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
811
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@@ -12,7 +15,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
1215
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
1316
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
1417
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
18+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
19+
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
20+
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
21+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
22+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1523
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
1624
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
25+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1726
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1827
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
28+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)