Wrap Cobra Commands In Functions
I like using Cobra for CLI-based apps with subcommands. But in my opinion, their documented approach for defining these commands is not a good one. Their examples and generated code produces a global variable for each one, with the need for an init function if something beyond the basics is required:
var rootCmd = &cobra.Command{
Use: "eg",
Short: "Example",
Long: "Just some sample code",
Run: func(cmd *cobra.Command, args []string) { },
}
var subCmd = &cobra.Command{
Use: "point",
Short: "Gets the point across",
Long: "Provide some evidence",
Run: func(cmd *cobra.Command, args []string) { },
}
func init() {
subCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.AddCommand(subCmd)
}
func main() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
While this works, it messy and fragile. You’re spreading logic around the file, doing things like separating the flag definitions and parent-child registration from the command itself. You need to get the value of --toggle via a method of cmd, rather than a pointer variable like the builtin flags module. And there’s no good way to define private types and variables for a specific command.
A much better approach would be to wrap every command in it’s own function, with each one returning a *cobra.Command:
func rootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "eg",
Short: "Example",
Long: "Just some sample code",
Run: func(cmd *cobra.Command, args []string) { },
}
cmd.AddCommand(subCmd())
return cmd
}
func subCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "point",
Short: "Gets the point across",
Long: "Provide some evidence",
}
flagToggle := subCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
cmd.Run = func(cmd *cobra.Command, args []string) {
doThing := *flagToggle
}
return cmd
}
func main() {
err := rootCmd().Execute()
if err != nil {
os.Exit(1)
}
}
Compare this listing with the first one. All the commands are self contained: rootCmd with its reference to subCmd, which itself references a variable pointer bound to a flag. Everything you need to worry about for any particular command is local to its function. And it makes it easier to move things around. You can move the commands to different files or packages without worrying about updating all the various init functions.
I’ve done a few projects like this and it’s a huge improvement on what Cobra’s documentation recommends. If you’ve got a project you’re intending to use Cobra for, I’d recommend trying it.