Exploring Go Packages: Cobra
In this tutorial, we discuss how to code a help message using the Cobra package.
› Briefly about Cobra
Cobra is a powerful command line package for Golang. The full list of what projects use Cobra is here.
› Before You Begin
Install Cobra in your machine:
go get -u -v github.com/spf13/cobra
Yet, you do not need to create files of our app. There is a generator, which helps to create a working directory very easily.
To install the generator use this command:
go get -u -v github.com/spf13/cobra/cobra
Before we start coding, let’s review a help message we are going to create with the Cobra package.
› Writing Help Message
A help message (or usage message, or doc) is what users see when they
- invoke a program,
- type a flag like
--h
or-h
, - type invalid arguments.
This message includes the name of the program, its usage, and options. If you use the command line, you are already familiar with help messages or usage messages. In Linux, you can usually type program --help
to get a list of acceptable arguments of the program.
In this tutorial, we write a help message for a program that outputs:
- a list of random numbers of a specified count and range, or
- a list of random letters of a specific count and language.
This is our usage message:
Let’s analyze a command, numbers, from the preceding message:
randx numbers --count <count> [--range <range>...]
randx
— a name of the program,numbers
— a command,--count
— an option of the command,<count>
— a value of the option — count,[--range <range>…]
— an optional parameter which requires a value or several values,[--verbose]
— an optional flag.
We have two commands, numbers
and letters
(bool), to enable the numbers or letters mode, respectively. If one of them is true, another is false.
The — count
(int) option is required for both the numbers
andletters
commands. The program outputs a list of numbers or letters of the specified count.
$ randx numbers --count 1
$ randx letters --count 1
To declare an optional argument --range
([]string), we use square brackets. Three dots in the square brackets mean that you can pass several arguments to the command prompt. For example,
--range 1,10 --range 1,11
or--range 1-10 --range 1-11
or--range 1:10 --range 1:11
This option sets a range of random numbers. If there are two arguments, the program outputs two lists with the specified ranges.
The optional argument, --lang
(string), is also in square brackets. The option accepts a language. Therefore the program outputs letters of the specified language.
The program must return a help message when the required option is not set and/or the arguments are invalid. Both --range
and --lang
options can be omitted.
Also, we have three options (or just flags) without operands in the usage message:
-h / --help
— output the usage message,--verbose
— output the details of what a program does; the flag is used to inform a user what the program actually does, including errors while performing a required action. This can be helpful for troubleshooting problems.--version
— output the version of the program which you use.
› Generating Project Structure
- Create and go to a directory:
mkdir -p ~/go/src/github.com/your_username/app &&
cd ~/go/src/github.com/your_username/app
2. Generate a project structure with cobra init
:
cobra init --pkg-name ~/go/src/github.com/your_username/app
After that, we have the following project structure:
— — cmd
— — — root.go
— — LICENSE
— — main.go
The root.go file contains the rootCmd
command, which initializes all the commands and flags (or options).
› Creating Commands
To add a new command simply use cobra add commandName
in the project directory. For our help message, we need to create two commands numbers
and letters
:
$ cobra add numbers
$ cobra add letters
After that, the cobra package generates two files namely numbers.go and letters.go in the cmd directory. Now we have the following project structure:
— — cmd
— — — root.go
— — — numbers.go
— — — letters.go
— — LICENSE
— — main.go
› What main.go looks like
In the main.go file we execute our commands with cmd.Execute()
. The full code looks like this:
package mainimport “github.com/your_user_name/app/cmd”func main() {
cmd.Execute()
}
There is no need to add additional code here. Let’s create subcommands and options in the numbers.go and letters.go files.
› Numbers Command
Cobra generated some of the following code. We declared countFlagNumbers
and rangeFlagNumbers
variables to store values from--count
and --range
flags respectively. Also, we initialized required flags (--count
and --range
) in the init()
function.
// beginning of numbers.gopackage cmdimport (
"fmt"
"github.com/spf13/cobra"
)var (
countFlagNumbers int
rangeFlagNumbers []string
)var numbersCmd = &cobra.Command{
Use: "numbers",
Short: "Returns random numbers",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("numbers mode")
fmt.Println("--count:", countFlagNumbers)
fmt.Println("--range:", rangeFlagNumbers)
fmt.Println("--verbose:", verbose)
},
}// here is init()
We call arootCmd.AddCommand()
method to initialize the numbers
command.
//beginning of init()func init() {
rootCmd.AddCommand(numbersCmd)...
The declaration of every flag has the following pattern:
commandName.Flags().TypeOfValuesP(
where to store, long flag name,
short flag name, default value,
explanation,
)
Declare the --count
flag:
... numbersCmd.Flags().IntVarP(
&countFlag, "count", "c", 0,
"A count of random numbers",
)...
And make this flag required:
... numbersCmd.MarkFlagRequired("count")...
When we make a flag required, it means that a user cannot execute the program without this flag set. There will be an error.
Declare the --range
flag:
...
numbersCmd.Flags().StringSliceVarP(
&rangeFlag, "range", "r", []string{"1:100"},
"Range of numbers. Optional",
)
}// end of init()
// end of numbers.go
› Letters Command
The same as in numbers.go, some of the following code was generated. Here we declared countFlagLetters
and langFlagLetters
variables to store the received values from the --count
and --lang
flags respectively.
// beginning of letters.gopackage cmdimport (
“fmt”
“github.com/spf13/cobra”
)var (
countFlagLetters int
langFlagLetters string
)
The lettersCmd
command has the same structure as numbersCmd
. All the flags are initialized the same way as in the numbers.go file.
var lettersCmd = &cobra.Command{
Use: "letters",
Short: "Returns random letters",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("letters mode")
fmt.Println("--count:", countFlagLetters)
fmt.Println("--lang:", langFlagLetters)
fmt.Println("--verbose:", verbose)
},
}func init() {
rootCmd.AddCommand(lettersCmd) lettersCmd.Flags().IntVarP(
&Count, "count", "c", 0,
"A count of random letters",
) lettersCmd.MarkFlagRequired("count")
lettersCmd.Flags().StringVarP(
&Lang, "lang", "l", "en",
"A language. Optional",
)
}// end of letters.go
› Adding Version and Verbose flags
In root.go, we update the usage of our program, add the --version
and --verbose
flags.
package cmdimport (
"fmt"
"os" "github.com/spf13/cobra"
)
Declare a verbose variable. It has a bool type.
var (
verbose bool
)
In order to show the version of our program, add a Version
parameter and specify a current version in quotes.
var rootCmd = &cobra.Command{
Use: "randx",
Version: "1.0.1",
Short: "Returns random numbers or letters.",
}func Execute() {
if err := rootCmd.Execute();
err != nil {
fmt.Println(err)
os.Exit(1)
}
}
The--verbose
flag is persistent and global. Which means you can tell the program to show you details of the program execution with any command and flag set.
func init() {
rootCmd.PersistentFlags().BoolVarP(
&verbose, "verbose", "v", false,
"Verbose output",
)
}
› Usage
In this section, you can find several cases of how to use Cobra in the command line.
›› Letters Mode
$ go run main.go letters --count 9
$ go run main.go letters -c 9
letters mode
--count: 9
--lang: en // this is a default value
--verbose: false$ go run main.go letters --count 9 --lang en
$ go run main.go letters -c 9 -l en
letters mode
--count: 9
--lang: en
--verbose: false
›› Numbers Mode
$ go run main.go numbers --count 9
$ go run main.go numbers -c 9
numbers mode
--count: 9
--range: [1:100] // this is a default value
--verbose: false$ go run main.go numbers --count 10 --range 9,10
$ go run main.go numbers -c 10 -r 9,10
numbers mode
--count: 10
--range: [9 10]
--verbose: false$ go run main.go numbers --count 10 --range 9,10 --range 0,15
numbers: true
--count: 10
--range: [9 10 0 15]
--verbose: false
Some comments about delimiters. If you you use the comma as a delimiter in--range
flag, you get only a list of numbers. As discussed earlier, you can use the colon:
to separate these numbers. For example:
$ go run main.go numbers --count 10 --range 9:10 --range 10:11
numbers mode
--count: 10
--range: [9:10 10:11]
--verbose: false
›› Verbose
$ go run main.go numbers --count 18 --range 9,19 --verbose
$ go run main.go numbers -c 18 -r 9,19 --verbose
numbers mode
--count: 18
--range: [9 19]
--verbose: true
› About Author
Jane is a Go programmer and technical writer in software engineering. She has been writing technical material for 5 years in both English and Russian. She completed her specialist degree in Information Security from Novosibirsk State Technical University, and specializes in the information security of automated systems. You can follow her on Twitter and see her other written work at publications.enthusiastic.io.