Skip to content

Commands

maxlandon edited this page Jan 8, 2023 · 5 revisions

This page first introduces you to declaring commands, either trees or groups of them.

Starting point: the root command

We start with the following struct:

type Root struct {}

This struct will be root parser (or cobra.Command) of our application. The name of the cobra command will thus be the os.Args[0] value. It has no execution method by default.

If we generate our CLI from this command, we get an empty -but working- cobra command, which we would use and run like this in a main package:

rootCmd := flags.Generate(&Root{})
rootCmd.Execute()

Declaring subcommands

We now declare another command type, which now includes an execution method. This subcommand does not do anything, as its runner is empty.

type Create struct {}

// Execute - The actual command implementation. The `args` parameter is not the args
// of the command itself, but any aditional arguments that `flags` will not have 
// parsed into the command positional arguments' struct fields.
func (c *Create) Execute(args []string) (err error) {
    return
}

We then add this subcommand to our root:

type Root struct {
    Create `command:"create" desc:"a subcommand that creates something"`    
}

Note: See the section Runners Interfaces for all command interfaces that are automatically searched for and mapped to the generated cobra.Command.

Grouping subcommands

Forwarding a little, now you have various command structs ready. You can either add them below the Create command, or you can group them together into an another struct, which will act as a group holder for these commands. This enables better help usage formatting, as well as completions formats for shell supporting it.

A group struct is actually identical to a root command: it's just a struct.

// Comms is a group of commands for communicating things.
type Comms struct {
    Dial `command:"dial" desc:"this command dials a host"`    
    Send `command:"create"  desc:"a subcommand that sends data"`    
    Recv `command:"receive" desc:"this command receives data"`    
}

We would then include it in our root parser like this:

type Root struct {
    Create `command:"create" alias:"make" desc:"a subcommand that creates something"`    
        
    Comms `commands:"comms"` 
}

Compile this program and run it with --help, and you will see a structured ouput. An invocation of our program would look like:

./program --help
./program create ...
./program dial ...

Runners Interfaces

The following functions can be implemented by your struct type and will be automatically bound to the generated cobra.Command:

func PreRun(args []string)
func PreRunE(args []string) error

// Runners
func Execute(args []string) (err error) // This has precedence
func RunE(args []string) error          // over this one.
func Run(args []string)                 // Cobra only executes this is both previous are nil

// Post runners
func PostRun(args []string)
func PostRunE(args []string) error

Accessing the cobra API

If we stayed only with native Go code to declare the commands' functionality and specify various behavior stuff, we would be quite stuck. Additionally, there would be no way for a child command to know about the state of any parent.

In order to solve this, you can still access the cobra API. And we might have multiple reasons to do so.

1) Specifying additional behavior (before running)

As said in the introduction page, declaring specs through tags has its limits. We might want to assing longer help strings, PreRun and PostRun functions of all sorts, etc. The following briefly shows how we would do this for our program dial subcommand:

// Our newly generated cobra tree.
rootCmd := flags.Generate(&Root{})

// Get the dial subcommand
dial, _, _ := rootCmd.Find([]string{"dial"})

// And specify/bind additional behavior.
dial.Long = `a very long and multiline string...`
dial.PreRunE = func(cmd *cobra.Command, args []string) error { return nil }

2) Querying state (when running)

When executing our command, and from within our Execute(args []string) method, we might need to query something in the cobra application. (Note, though, that one of the interests of this library is actually to use flags/args native types as is, not through the cobra API).

Now familiar with ways to avoid circular dependencies, you might declare your root command in a common package, and you would assign the generated command tree to it:

package common

var CLI *cobra.Command

...
package main 

func main() {
    common.CLI = flags.Generate(&Root{}) // Root is the struct of the beginning.
}

Then you can freely query your cobra CLI from within the Dial execution method, had you written one:

type Dial struct {}

func (c *Dial) Execute(args []string) (err error) {
    cmdOfInterest, _, _ := common.CLI.Find([]string{"send"})
    // ... do stuff here
        
    return
}

You might have understood, at this point, that you don't even have to write such an Execute method if you dont want to, and you just declare this naked struct, while binding a Run(cmd *cobra.Command) runner to it via the generated cobra command.

But this would have no interest if your command would declare positional arguments.