feat/002_init-remote
Raw Download raw file

Functional Cobra CLI Pattern

This document describes a functional approach to structuring Cobra CLI applications that avoids global variables and init functions in favor of explicit, testable code.

Key Principles

  1. No Global Variables: All command state is encapsulated within functions
  2. No Init Functions: Explicit initialization through constructor functions
  3. Clear Control Flow: Easy to follow execution path from main() through command execution
  4. Testability: Each command can be instantiated and tested in isolation
  5. Composable: Each cobra.Command could be the root of it’s own cli, the root of this cli could be a subcommand of another cli application.

Project Structure

.
├── main.go           # Entry point - minimal, just calls Execute()
└── cmd/
    ├── root.go       # Root command and Execute() function
    ├── init.go       # Example subcommand
    └── ...           # Additional subcommands

Implementation Pattern

cmd/root.go

Root command constructor:

// New command sets up a Command and registers subcommands directly
func NewRootCmd() *cobra.Command {
    var cmd = &cobra.Command{
        RunE:  rootRunE, // Always RunE
        // ... 
    }

    // Add subcommands
    cmd.AddCommand(NewOtherCmd())

    return cmd
}

// rootRunE is the run function for the root command
func rootRunE(cmd *cobra.Command, args []string) error { ... }

cmd/subcommand.go

Pattern for subcommands:

// New command sets up a sub command the same way
func NewSubCmd() *cobra.Command {
    var cmd = &cobra.Command{
        RunE:  subRunE, // Always RunE
    }
    // ...
    return cmd
}

// subCmdRunE is the run function for the subcommand
func subRunE(cmd *cobra.Command, args []string) error { ... }

Advantages Over Standard Cobra Template

1. No Global State

The standard template often uses global variables for flags and configuration. Our pattern encapsulates everything in functions.

2. Explicit Initialization

No hidden init() functions that run automatically. Everything is explicitly called:

  • main() calls cmd.Execute()
  • Execute() calls NewRootCmd()
  • NewRootCmd() explicitly adds subcommands

3. Better Testing

Each command can be instantiated independently for testing:

func TestInitCommand(t *testing.T) {
    cmd := NewInitCmd()
    // Test the command in isolation
}