Working with Cobra - The Sub-commands

In this lesson, we’ll define two sub-commands for the calculator application: the `add` and `subtract` commands which look very similar and can serve as a pattern to define additional sub-commands. Then, we'll connect the subcommands to the root command.

The add subcommand

Cobra is all about defining sub-commands. Let’s start with the add subcommand. It will be defined in the cmd/add.go file:

package cmd

import (
	"fmt"
	"gigi/calc/pkg/calc"
	"github.com/spf13/cobra"
	"strconv"
)

var check bool

var addCmd = &cobra.Command{
	Use:   "add",
	Short: "Add two integers",
	Long:  `Add two integers a and b; result = a + b`,
	Args:  cobra.ExactArgs(2),
	Run: func(cmd *cobra.Command, args []string) {
		var a, b int
		var err error
		a, err = strconv.Atoi(args[0])
		if err != nil {
			panic("Arguments to `add` must be integers")
		}
		b, err = strconv.Atoi(args[1])
		if err != nil {
			panic("Arguments to `add` must be integers")
		}

		result := calc.Add(a, b, check)
		fmt.Println(result)
	},
}

Let’s review its Run function. The signature includes the command itself, which functions the same way as a method receiver and a slice of arguments. The implementation converts the arguments to integers, panicking if they can’t be converted. Then they call the calc’s Add() function package and prints the result:

Run: func(cmd *cobra.Command, args []string) {
		var a, b int
		var err error
		a, err = strconv.Atoi(args[0])
		if err != nil {
			panic("Arguments to `add` must be integers")
		}
		b, err = strconv.Atoi(args[1])
		if err != nil {
			panic("Arguments to `add` must be integers")
		}

		result := calc.Add(a, b, check)
		fmt.Println(result)
	},

This is very similar to the root command, except that it has a Run function. In fact, all Cobra commands are the same. The fact that one command is the root command and the other subcommands or sub-subcommands is determined when wiring the commands together.

The subtract sub-command

The subtract sub-command follows the exact same pattern:

package cmd

import (
	"fmt"
	"gigi/calc/pkg/calc"
	"github.com/spf13/cobra"
	"strconv"
)

var check bool

var subtractCmd = &cobra.Command{
	Use:   "subtract",
	Short: "Substract one integer from another",
	Long:  `Substract one integer a from another integer b; result = a -b`,
	Args:  cobra.ExactArgs(2),
	Run: func(cmd *cobra.Command, args []string) {
		var a, b int
		var err error
		a, err = strconv.Atoi(args[0])
		if err != nil {
			panic("Arguments to `subtract` must be integers")
		}
		b, err = strconv.Atoi(args[1])
		if err != nil {
			panic("Arguments to `subtract` must be integers")
		}

		result := calc.Subtract(a, b, check)
		fmt.Println(result)
	},
}

Now that we have subcommand, let’s wire them together.

Wiring commands

Wiring commands is very simple. To make command B and subcommand of command A, we just add B to A. The question is where to do this. One place to do this is in one central place to connect all the commands. Another approach, which I chose here, is to have each subcommand add itself to the root command. I defined for each subcommand an init() function that does the job. Here is the init() function of the Add command defined in cmd/add.go:

func init() {
	rootCmd.AddCommand(addCmd)
}

Don’t be confused by AddCommand() and addCmd. The former is the method Cobra provides to wire up sub-commands. The latter is our calculator’s command to add to integer, which is a Cobra command called addComd.

The init() function of the Subtract subcommand in cmd/subtract.go is virtually identical, except it connects the Subtract command to the root command:

func init() {
	rootCmd.AddCommand(subtractCmd)
}

But, who is calling this init() function and when? Well, init() functions are a special Go feature. All the init() functions in a package are executed by Go, itself after all the package variables have been initialized and most importantly before the main() function runs. This means that when main() runs all our subcommands are already wired up and ready.

At this point, our subcommands are connected to our root command.

Run the following application to see the effect of the added subcommands.

Get hands-on with 1200+ tech skills courses.