Skip to content

bep/simplecobra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tests on Linux, MacOS and Windows Go Report Card GoDoc

So, Cobra is a Go CLI library with a feature set that's hard to resist for bigger applications (autocomplete, docs auto generation etc.). But it's also rather complex to use beyond the simplest of applications. This package is built to aid rewriting Hugo's 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:

// 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, cd *Commandeer, args []string) error

	// Init 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 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
}

The Init method allows for flag compilation, referencing the parent and root etc. If needed, the full Cobra command is still available for configuration in WithCobraCommand.

There's a runnable example in the documentation, but the gist of it is:

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 imortant difference:

Cobra only treats the first level of misspelled commands as an unknown command with "Did you mean this?" suggestions, see see this issue for more context. The reason this is, is because of the ambiguity between sub command names and command arguments, but that is throwing away a very useful feature for not a very good reason. We recently rewrote Hugo's CLI using this poackage, and found only one sub command that needed to be adjusted to avoid this ambiguity.

About

A simpler API for the popular Cobra CLI.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages