pax_global_header00006660000000000000000000000064145310623500014511gustar00rootroot0000000000000052 comment=38e3a117deadd9c6bcc3241e4180e7d9abdb05e2 simplecobra-0.4.0/000077500000000000000000000000001453106235000140125ustar00rootroot00000000000000simplecobra-0.4.0/.github/000077500000000000000000000000001453106235000153525ustar00rootroot00000000000000simplecobra-0.4.0/.github/FUNDING.yml000066400000000000000000000000151453106235000171630ustar00rootroot00000000000000github: [bep]simplecobra-0.4.0/.github/workflows/000077500000000000000000000000001453106235000174075ustar00rootroot00000000000000simplecobra-0.4.0/.github/workflows/test.yml000066400000000000000000000033621453106235000211150ustar00rootroot00000000000000on: push: branches: [main] pull_request: name: Test jobs: test: strategy: matrix: go-version: [1.20.x, 1.21.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} - name: Install staticcheck run: go install honnef.co/go/tools/cmd/staticcheck@latest shell: bash - name: Install golint run: go install golang.org/x/lint/golint@latest shell: bash - name: Update PATH run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH shell: bash - name: Checkout code uses: actions/checkout@v3 - name: Fmt if: matrix.platform != 'windows-latest' # :( run: "diff <(gofmt -d .) <(printf '')" shell: bash - name: Vet run: go vet ./... - name: Staticcheck run: staticcheck ./... - name: Lint run: golint ./... - name: Test run: go test -race ./... -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic - name: Upload coverage if: success() && matrix.platform == 'ubuntu-latest' run: | curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import # One-time step curl -Os https://uploader.codecov.io/latest/linux/codecov curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig gpgv codecov.SHA256SUM.sig codecov.SHA256SUM shasum -a 256 -c codecov.SHA256SUM chmod +x codecov ./codecov simplecobra-0.4.0/.gitignore000066400000000000000000000004151453106235000160020ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ simplecobra-0.4.0/LICENSE000066400000000000000000000020651453106235000150220ustar00rootroot00000000000000MIT License Copyright (c) 2022 Bjørn Erik Pedersen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. simplecobra-0.4.0/README.md000066400000000000000000000072511453106235000152760ustar00rootroot00000000000000[![Tests on Linux, MacOS and Windows](https://github.com/bep/simplecobra/workflows/Test/badge.svg)](https://github.com/bep/simplecobra/actions?query=workflow:Test) [![Go Report Card](https://goreportcard.com/badge/github.com/bep/simplecobra)](https://goreportcard.com/report/github.com/bep/simplecobra) [![codecov](https://codecov.io/gh/bep/simplecobra/branch/master/graph/badge.svg)](https://codecov.io/gh/bep/simplecobra) [![GoDoc](https://godoc.org/github.com/bep/simplecobra?status.svg)](https://godoc.org/github.com/bep/simplecobra) So, [Cobra](https://github.com/spf13/cobra) is a Go CLI library with a feature set that's hard to resist for bigger applications (autocompletion, docs and man pages auto generation etc.). But it's also complex to use beyond the simplest of applications. This package was built to help rewriting [Hugo's](https://github.com/gohugoio/hugo) commands package to something that's easier to understand and maintain. I welcome suggestions to improve/simplify this further, but the core idea is that the command graph gets built in one go with a tree of struct pointers implementing a simple `Commander` interface: ```go // Commander is the interface that must be implemented by all commands. type Commander interface { // The name of the command. Name() string // Init is called when the cobra command is created. // This is where the flags, short and long description etc. can be added. Init(*Commandeer) error // PreRun called on all ancestors and the executing command itself, before execution, starting from the root. // This is the place to evaluate flags and set up the this Commandeer. // The runner Commandeer holds the currently running command, which will be PreRun last. PreRun(this, runner *Commandeer) error // The command execution. Run(ctx context.Context, cd *Commandeer, args []string) error // Commands returns the sub commands, if any. Commands() []Commander } ``` The `Init` method allows for flag compilation, referencing the parent and root etc. If needed, the full Cobra command is still available. There's a runnable [example](https://pkg.go.dev/github.com/bep/simplecobra#example-package) in the documentation, but the gist of it is: ```go func main() { rootCmd := &rootCommand{name: "root", commands: []simplecobra.Commander{ &lvl1Command{name: "foo"}, &lvl1Command{name: "bar", commands: []simplecobra.Commander{ &lvl2Command{name: "baz"}, }, }, }, } x, err := simplecobra.New(rootCmd) if err != nil { log.Fatal(err) } cd, err := x.Execute(context.Background(), []string{"bar", "baz", "--localFlagName", "baz_local", "--persistentFlagName", "baz_persistent"}) if err != nil { log.Fatal(err) } // These are wired up in Init(). lvl2 := cd.Command.(*lvl2Command) lvl1 := lvl2.parentCmd root := lvl1.rootCmd fmt.Printf("Executed %s.%s.%s with localFlagName %s and and persistentFlagName %s.\n", root.name, lvl1.name, lvl2.name, lvl2.localFlagName, root.persistentFlagName) } ``` ## Differences to Cobra You have access to the `*cobra.Command` pointer so there's not much you cannot do with this project compared to the more low-level Cobra, but there's one small, but important difference: Cobra only treats the first level of misspelled commands as an `unknown command` with "Did you mean this?" suggestions, see [see this issue](https://github.com/spf13/cobra/pull/1500) for more context. The reason for this is the ambiguity between sub-command names and command arguments, but that is throwing away a very useful feature for a not very good reason. We recently rewrote [Hugo's CLI](https://github.com/gohugoio/hugo) using this package, and found only one sub command that needed to be adjusted to avoid this ambiguity. simplecobra-0.4.0/codecov.yml000066400000000000000000000002161453106235000161560ustar00rootroot00000000000000coverage: status: project: default: target: auto threshold: 0.5% patch: off comment: require_changes: true simplecobra-0.4.0/go.mod000066400000000000000000000006401453106235000151200ustar00rootroot00000000000000module github.com/bep/simplecobra go 1.20 require ( github.com/frankban/quicktest v1.14.6 github.com/spf13/cobra v1.8.0 ) require ( github.com/google/go-cmp v0.5.9 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect ) simplecobra-0.4.0/go.sum000066400000000000000000000036131453106235000151500ustar00rootroot00000000000000github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= simplecobra-0.4.0/simplecobra.go000066400000000000000000000135441453106235000166500ustar00rootroot00000000000000package simplecobra import ( "context" "errors" "fmt" "strings" "github.com/spf13/cobra" ) // Commander is the interface that must be implemented by all commands. type Commander interface { // The name of the command. Name() string // Init is called when the cobra command is created. // This is where the flags, short and long description etc. can be added. Init(*Commandeer) error // PreRun called on all ancestors and the executing command itself, before execution, starting from the root. // This is the place to evaluate flags and set up the this Commandeer. // The runner Commandeer holds the currently running command, which will be PreRun last. PreRun(this, runner *Commandeer) error // The command execution. Run(ctx context.Context, cd *Commandeer, args []string) error // Commands returns the sub commands, if any. Commands() []Commander } // New creates a new Executer from the command tree in Commander. func New(rootCmd Commander) (*Exec, error) { rootCd := &Commandeer{ Command: rootCmd, } rootCd.Root = rootCd // Add all commands recursively. var addCommands func(cd *Commandeer, cmd Commander) addCommands = func(cd *Commandeer, cmd Commander) { cd2 := &Commandeer{ Root: rootCd, Parent: cd, Command: cmd, } cd.commandeers = append(cd.commandeers, cd2) for _, c := range cmd.Commands() { addCommands(cd2, c) } } for _, cmd := range rootCmd.Commands() { addCommands(rootCd, cmd) } if err := rootCd.compile(); err != nil { return nil, err } return &Exec{c: rootCd}, nil } // Commandeer holds the state of a command and its subcommands. type Commandeer struct { Command Commander CobraCommand *cobra.Command Root *Commandeer Parent *Commandeer commandeers []*Commandeer } func (c *Commandeer) init() error { // Collect all ancestors including self. var ancestors []*Commandeer { cd := c for cd != nil { ancestors = append(ancestors, cd) cd = cd.Parent } } // Init all of them starting from the root. for i := len(ancestors) - 1; i >= 0; i-- { cd := ancestors[i] if err := cd.Command.PreRun(cd, c); err != nil { return err } } return nil } type runErr struct { error } func (c *Commandeer) compile() error { useCommandFlagsArgs := "[command] [flags]" if len(c.commandeers) == 0 { useCommandFlagsArgs = "[flags] [args]" } c.CobraCommand = &cobra.Command{ Use: fmt.Sprintf("%s %s", c.Command.Name(), useCommandFlagsArgs), RunE: func(cmd *cobra.Command, args []string) error { if err := c.Command.Run(cmd.Context(), c, args); err != nil { return &runErr{error: err} } return nil }, PreRunE: func(cmd *cobra.Command, args []string) error { return c.init() }, SilenceErrors: true, SilenceUsage: true, SuggestionsMinimumDistance: 2, } // This is where the flags, short and long description etc. are added if err := c.Command.Init(c); err != nil { return err } // Add commands recursively. for _, cc := range c.commandeers { if err := cc.compile(); err != nil { return err } c.CobraCommand.AddCommand(cc.CobraCommand) } return nil } // Exec provides methods to execute the command tree. type Exec struct { c *Commandeer } // Execute executes the command tree starting from the root command. // The args are usually filled with os.Args[1:]. func (r *Exec) Execute(ctx context.Context, args []string) (*Commandeer, error) { if args == nil { // Cobra falls back to os.Args[1:] if args is nil. args = []string{} } r.c.CobraCommand.SetArgs(args) cobraCommand, err := r.c.CobraCommand.ExecuteContextC(ctx) var cd *Commandeer if cobraCommand != nil { if err == nil { err = checkArgs(cobraCommand, args) } // 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 } for _, in2 := range in.commandeers { if found := find(what, in2); found != nil { return found } } return nil } cd = find(cobraCommand, r.c) } return cd, wrapErr(err) } // CommandError is returned when a command fails because of a user error (unknown command, invalid flag etc.). // All other errors comes from the execution of the command. type CommandError struct { Err error } // Error implements error. func (e *CommandError) Error() string { return fmt.Sprintf("command error: %v", e.Err) } // Is reports whether e is of type *CommandError. func (*CommandError) Is(e error) bool { _, ok := e.(*CommandError) return ok } // IsCommandError reports whether any error in err's tree matches CommandError. func IsCommandError(err error) bool { return errors.Is(err, &CommandError{}) } func wrapErr(err error) error { if err == nil { return nil } if rerr, ok := err.(*runErr); ok { return rerr.error } // All other errors are coming from Cobra. return &CommandError{Err: err} } // Cobra only does suggestions for the root command. // See https://github.com/spf13/cobra/pull/1500 func checkArgs(cmd *cobra.Command, args []string) error { // no subcommand, always take args. if !cmd.HasSubCommands() { return nil } var commandName string for _, arg := range args { if strings.HasPrefix(arg, "-") { break } commandName = arg } if commandName == "" || cmd.Name() == commandName { return nil } // Also check the aliases. if cmd.HasAlias(commandName) { return nil } return fmt.Errorf("unknown command %q for %q%s", args[1], cmd.CommandPath(), findSuggestions(cmd, commandName)) } func findSuggestions(cmd *cobra.Command, arg string) string { if cmd.DisableSuggestions { return "" } suggestionsString := "" if suggestions := cmd.SuggestionsFor(arg); len(suggestions) > 0 { suggestionsString += "\n\nDid you mean this?\n" for _, s := range suggestions { suggestionsString += fmt.Sprintf("\t%v\n", s) } } return suggestionsString } simplecobra-0.4.0/simplecobra_test.go000066400000000000000000000267461453106235000177170ustar00rootroot00000000000000package simplecobra_test import ( "context" "errors" "fmt" "log" "os" "testing" "github.com/bep/simplecobra" qt "github.com/frankban/quicktest" ) func testCommands() *rootCommand { return &rootCommand{name: "root", commands: []simplecobra.Commander{ &lvl1Command{name: "foo"}, &lvl1Command{name: "bar", commands: []simplecobra.Commander{ &lvl2Command{name: "baz"}, }, }, }, } } func TestSimpleCobra(t *testing.T) { c := qt.New(t) rootCmd := testCommands() x, err := simplecobra.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") // Execute the root command. args := []string{"--localFlagName", "root_local", "--persistentFlagName", "root_persistent"} cd, err := x.Execute(ctx, args) c.Assert(err, qt.IsNil) c.Assert(cd.Command.Name(), qt.Equals, "root") tc := cd.Command.(*rootCommand) c.Assert(tc, qt.Equals, rootCmd) c.Assert(tc.ctx, qt.Equals, ctx) c.Assert(tc.localFlagName, qt.Equals, "root_local") c.Assert(tc.persistentFlagName, qt.Equals, "root_persistent") c.Assert(tc.persistentFlagNameC, qt.Equals, "root_persistent_rootCommand_compiled") c.Assert(tc.localFlagNameC, qt.Equals, "root_local_rootCommand_compiled") c.Assert(tc.initRunner, qt.Equals, cd) c.Assert(tc.initThis, qt.Equals, cd) // Execute a level 1 command. // 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{"bar", "--localFlagName", "bar_local", "--persistentFlagName", "bar_persistent"} ctx = context.WithValue(context.Background(), key("bar"), "baz") cd2, err := x.Execute(ctx, args) c.Assert(err, qt.IsNil) c.Assert(cd2.Command.Name(), qt.Equals, "bar") tc2 := cd2.Command.(*lvl1Command) c.Assert(tc2.rootCmd, qt.Equals, rootCmd) c.Assert(tc2.ctx, qt.Equals, ctx) c.Assert(tc2.localFlagName, qt.Equals, "bar_local") c.Assert(tc2.localFlagNameC, qt.Equals, "bar_local_lvl1Command_compiled") c.Assert(tc.persistentFlagName, qt.Equals, "bar_persistent") c.Assert(tc.persistentFlagNameC, qt.Equals, "bar_persistent_rootCommand_compiled") c.Assert(tc2.rootCmd.initRunner, qt.Equals, cd2) c.Assert(tc2.rootCmd.initThis, qt.Equals, cd2.Root) // Execute a level 2 command. args = []string{"bar", "baz", "--persistentFlagName", "baz_persistent"} ctx = context.WithValue(context.Background(), key("baz"), "qux") cd3, err := x.Execute(ctx, args) c.Assert(err, qt.IsNil) c.Assert(cd3.Command.Name(), qt.Equals, "baz") tc3 := cd3.Command.(*lvl2Command) c.Assert(tc3.rootCmd, qt.Equals, rootCmd) c.Assert(tc3.parentCmd, qt.Equals, tc2) c.Assert(tc3.ctx, qt.Equals, ctx) c.Assert(tc3.rootCmd.initRunner, qt.Equals, cd3) c.Assert(tc3.rootCmd.initThis, qt.Equals, cd3.Root) } func TestAliases(t *testing.T) { c := qt.New(t) rootCmd := &rootCommand{name: "root", commands: []simplecobra.Commander{ &lvl1Command{name: "foo", aliases: []string{"f"}, commands: []simplecobra.Commander{ &lvl2Command{name: "bar"}, }, }, }, } x, err := simplecobra.New(rootCmd) c.Assert(err, qt.IsNil) args := []string{"f"} _, err = x.Execute(context.Background(), args) c.Assert(err, qt.IsNil) } func TestInitAncestorsOnly(t *testing.T) { c := qt.New(t) rootCmd := testCommands() x, err := simplecobra.New(rootCmd) c.Assert(err, qt.IsNil) args := []string{"bar", "baz", "--persistentFlagName", "baz_persistent"} cd3, err := x.Execute(context.Background(), args) c.Assert(err, qt.IsNil) c.Assert(cd3.Command.Name(), qt.Equals, "baz") c.Assert(rootCmd.isInit, qt.IsTrue) c.Assert(rootCmd.commands[0].(*lvl1Command).isInit, qt.IsFalse) c.Assert(rootCmd.commands[1].(*lvl1Command).isInit, qt.IsTrue) c.Assert(cd3.Command.(*lvl2Command).isInit, qt.IsTrue) } func TestErrors(t *testing.T) { c := qt.New(t) c.Run("unknown similar command", func(c *qt.C) { x, err := simplecobra.New(testCommands()) c.Assert(err, qt.IsNil) _, err = x.Execute(context.Background(), []string{"fooo"}) c.Assert(err, qt.Not(qt.IsNil)) c.Assert(err.Error(), qt.Contains, "unknown command \"fooo\"") c.Assert(err.Error(), qt.Contains, "Did you mean this?") c.Assert(simplecobra.IsCommandError(err), qt.Equals, true) }) c.Run("unknown similar sub command", func(c *qt.C) { x, err := simplecobra.New(testCommands()) c.Assert(err, qt.IsNil) _, err = x.Execute(context.Background(), []string{"bar", "bazz"}) c.Assert(err, qt.Not(qt.IsNil)) c.Assert(err.Error(), qt.Contains, "unknown") c.Assert(err.Error(), qt.Contains, "Did you mean this?") c.Assert(simplecobra.IsCommandError(err), qt.Equals, true) }) c.Run("disable suggestions", func(c *qt.C) { r := &rootCommand{name: "root", commands: []simplecobra.Commander{ &lvl1Command{name: "foo", disableSuggestions: true, commands: []simplecobra.Commander{ &lvl2Command{name: "bar"}, }, }, }, } x, err := simplecobra.New(r) c.Assert(err, qt.IsNil) _, err = x.Execute(context.Background(), []string{"foo", "bars"}) c.Assert(err, qt.Not(qt.IsNil)) c.Assert(err.Error(), qt.Contains, `command error: unknown command "bars" for "root foo"`) c.Assert(err.Error(), qt.Not(qt.Contains), "Did you mean this?") }) c.Run("unknown flag", func(c *qt.C) { x, err := simplecobra.New(testCommands()) c.Assert(err, qt.IsNil) _, err = x.Execute(context.Background(), []string{"bar", "--unknown"}) c.Assert(err, qt.Not(qt.IsNil)) c.Assert(err.Error(), qt.Contains, "unknown") c.Assert(simplecobra.IsCommandError(err), qt.Equals, true) }) c.Run("fail New in root command", func(c *qt.C) { r := &rootCommand{name: "root", failWithCobraCommand: true, commands: []simplecobra.Commander{ &lvl1Command{name: "foo"}, }, } _, err := simplecobra.New(r) c.Assert(err, qt.IsNotNil) }) c.Run("fail New in sub command", func(c *qt.C) { r := &rootCommand{name: "root", commands: []simplecobra.Commander{ &lvl1Command{name: "foo", failWithCobraCommand: true}, }, } _, err := simplecobra.New(r) c.Assert(err, qt.IsNotNil) }) c.Run("fail run root command", func(c *qt.C) { r := &rootCommand{name: "root", failRun: true, commands: []simplecobra.Commander{ &lvl1Command{name: "foo"}, }, } x, err := simplecobra.New(r) c.Assert(err, qt.IsNil) _, err = x.Execute(context.Background(), nil) c.Assert(err, qt.IsNotNil) c.Assert(err.Error(), qt.Equals, "failRun") }) c.Run("fail init sub command", func(c *qt.C) { r := &rootCommand{name: "root", commands: []simplecobra.Commander{ &lvl1Command{name: "foo", failInit: true}, }, } x, err := simplecobra.New(r) c.Assert(err, qt.IsNil) _, err = x.Execute(context.Background(), []string{"foo"}) c.Assert(err, qt.IsNotNil) }) } func TestIsCommandError(t *testing.T) { c := qt.New(t) cerr := &simplecobra.CommandError{Err: errors.New("foo")} c.Assert(simplecobra.IsCommandError(os.ErrNotExist), qt.Equals, false) c.Assert(simplecobra.IsCommandError(nil), qt.Equals, false) c.Assert(simplecobra.IsCommandError(errors.New("foo")), qt.Equals, false) c.Assert(simplecobra.IsCommandError(cerr), qt.Equals, true) c.Assert(simplecobra.IsCommandError(fmt.Errorf("foo: %w", cerr)), qt.Equals, true) } func Example() { rootCmd := &rootCommand{name: "root", commands: []simplecobra.Commander{ &lvl1Command{name: "foo"}, &lvl1Command{name: "bar", commands: []simplecobra.Commander{ &lvl2Command{name: "baz"}, }, }, }, } x, err := simplecobra.New(rootCmd) if err != nil { log.Fatal(err) } cd, err := x.Execute(context.Background(), []string{"bar", "baz", "--localFlagName", "baz_local", "--persistentFlagName", "baz_persistent"}) if err != nil { log.Fatal(err) } // These are wired up in Init(). lvl2 := cd.Command.(*lvl2Command) lvl1 := lvl2.parentCmd root := lvl1.rootCmd fmt.Printf("Executed %s.%s.%s with localFlagName %s and and persistentFlagName %s.\n", root.name, lvl1.name, lvl2.name, lvl2.localFlagName, root.persistentFlagName) // Output: Executed root.bar.baz with localFlagName baz_local and and persistentFlagName baz_persistent. } type rootCommand struct { name string isInit bool // Flags persistentFlagName string localFlagName string // Compiled flags. persistentFlagNameC string localFlagNameC string // For testing. ctx context.Context initThis *simplecobra.Commandeer initRunner *simplecobra.Commandeer failWithCobraCommand bool failRun bool // Sub commands. commands []simplecobra.Commander } func (c *rootCommand) Commands() []simplecobra.Commander { return c.commands } func (c *rootCommand) PreRun(this, runner *simplecobra.Commandeer) error { c.isInit = true c.persistentFlagNameC = c.persistentFlagName + "_rootCommand_compiled" c.localFlagNameC = c.localFlagName + "_rootCommand_compiled" c.initThis = this c.initRunner = runner return nil } func (c *rootCommand) Name() string { return c.name } func (c *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error { if c.failRun { return errors.New("failRun") } c.ctx = ctx return nil } func (c *rootCommand) Init(cd *simplecobra.Commandeer) error { if c.failWithCobraCommand { return errors.New("failWithCobraCommand") } cmd := cd.CobraCommand localFlags := cmd.Flags() persistentFlags := cmd.PersistentFlags() localFlags.StringVar(&c.localFlagName, "localFlagName", "", "set localFlagName") persistentFlags.StringVar(&c.persistentFlagName, "persistentFlagName", "", "set persistentFlagName") return nil } type lvl1Command struct { name string isInit bool aliases []string localFlagName string localFlagNameC string failInit bool failWithCobraCommand bool disableSuggestions bool rootCmd *rootCommand commands []simplecobra.Commander ctx context.Context } func (c *lvl1Command) Commands() []simplecobra.Commander { return c.commands } func (c *lvl1Command) PreRun(this, runner *simplecobra.Commandeer) error { if c.failInit { return fmt.Errorf("failInit") } c.isInit = true c.localFlagNameC = c.localFlagName + "_lvl1Command_compiled" c.rootCmd = this.Root.Command.(*rootCommand) return nil } func (c *lvl1Command) Name() string { return c.name } func (c *lvl1Command) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error { c.ctx = ctx return nil } func (c *lvl1Command) Init(cd *simplecobra.Commandeer) error { if c.failWithCobraCommand { return errors.New("failWithCobraCommand") } cmd := cd.CobraCommand cmd.DisableSuggestions = c.disableSuggestions cmd.Aliases = c.aliases localFlags := cmd.Flags() localFlags.StringVar(&c.localFlagName, "localFlagName", "", "set localFlagName for lvl1Command") return nil } type lvl2Command struct { name string isInit bool localFlagName string ctx context.Context rootCmd *rootCommand parentCmd *lvl1Command } func (c *lvl2Command) Commands() []simplecobra.Commander { return nil } func (c *lvl2Command) PreRun(this, runner *simplecobra.Commandeer) error { c.isInit = true c.rootCmd = this.Root.Command.(*rootCommand) c.parentCmd = this.Parent.Command.(*lvl1Command) return nil } func (c *lvl2Command) Name() string { return c.name } func (c *lvl2Command) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error { c.ctx = ctx return nil } func (c *lvl2Command) Init(cd *simplecobra.Commandeer) error { cmd := cd.CobraCommand localFlags := cmd.Flags() localFlags.StringVar(&c.localFlagName, "localFlagName", "", "set localFlagName for lvl2Command") return nil }