Files
gh-vanman2024-cli-builder-p…/skills/cobra-patterns/examples/simple-cli-basic.md
2025-11-30 09:04:14 +08:00

7.7 KiB

Simple CLI - Basic Example

A minimal example for building a simple single-command CLI with Cobra.

Use Case

Perfect for:

  • Quick utility tools
  • Single-purpose commands
  • Personal automation scripts
  • Simple wrappers around existing tools

Complete Example

main.go

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var (
    // Flags
    input   string
    output  string
    verbose bool
    force   bool
)

var rootCmd = &cobra.Command{
    Use:   "mytool [file]",
    Short: "A simple utility tool",
    Long: `A simple command-line utility that processes files.

This tool demonstrates a basic Cobra CLI with:
- Flag management
- Argument validation
- Error handling
- Help generation`,
    Args: cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        filename := args[0]

        if verbose {
            fmt.Printf("Processing file: %s\n", filename)
            fmt.Printf("  Input format: %s\n", input)
            fmt.Printf("  Output format: %s\n", output)
            fmt.Printf("  Force mode: %v\n", force)
        }

        // Process the file
        if err := processFile(filename, input, output, force); err != nil {
            return fmt.Errorf("failed to process file: %w", err)
        }

        fmt.Printf("Successfully processed: %s\n", filename)
        return nil
    },
}

func init() {
    // Define flags
    rootCmd.Flags().StringVarP(&input, "input", "i", "text", "input format (text|json|yaml)")
    rootCmd.Flags().StringVarP(&output, "output", "o", "text", "output format (text|json|yaml)")
    rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
    rootCmd.Flags().BoolVarP(&force, "force", "f", false, "force overwrite")

    // Set version
    rootCmd.Version = "1.0.0"
}

func processFile(filename, input, output string, force bool) error {
    // Your processing logic here
    if verbose {
        fmt.Printf("Processing %s: %s -> %s\n", filename, input, output)
    }
    return nil
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

Usage

# Build
go build -o mytool

# Show help
./mytool --help

# Process file
./mytool data.txt

# With options
./mytool data.txt --input json --output yaml --verbose

# Force mode
./mytool data.txt --force

# Show version
./mytool --version

Key Features

1. Single Command Structure

Everything in one file - perfect for simple tools:

  • Command definition
  • Flag management
  • Business logic
  • Main function

2. Flag Types

// String flags with shorthand
rootCmd.Flags().StringVarP(&input, "input", "i", "text", "input format")

// Boolean flags
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")

// Integer flags
var count int
rootCmd.Flags().IntVar(&count, "count", 1, "number of iterations")

// String slice flags
var tags []string
rootCmd.Flags().StringSliceVar(&tags, "tags", []string{}, "list of tags")

3. Argument Validation

// Exactly one argument
Args: cobra.ExactArgs(1)

// No arguments
Args: cobra.NoArgs

// At least one argument
Args: cobra.MinimumNArgs(1)

// Between 1 and 3 arguments
Args: cobra.RangeArgs(1, 3)

// Any number of arguments
Args: cobra.ArbitraryArgs

4. Error Handling

RunE: func(cmd *cobra.Command, args []string) error {
    // Return errors instead of os.Exit
    if err := validate(args); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }

    if err := process(); err != nil {
        return fmt.Errorf("processing failed: %w", err)
    }

    return nil
}

5. Auto-Generated Help

Cobra automatically generates help from your command definition:

$ ./mytool --help
A simple command-line utility that processes files.

This tool demonstrates a basic Cobra CLI with:
- Flag management
- Argument validation
- Error handling
- Help generation

Usage:
  mytool [file] [flags]

Flags:
  -f, --force           force overwrite
  -h, --help            help for mytool
  -i, --input string    input format (text|json|yaml) (default "text")
  -o, --output string   output format (text|json|yaml) (default "text")
  -v, --verbose         verbose output
      --version         version for mytool

Enhancements

Add Configuration File Support

import "github.com/spf13/viper"

func init() {
    cobra.OnInitialize(initConfig)
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
}

func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        home, _ := os.UserHomeDir()
        viper.AddConfigPath(home)
        viper.SetConfigName(".mytool")
        viper.SetConfigType("yaml")
    }

    viper.AutomaticEnv()
    viper.ReadInConfig()
}

Add Dry Run Mode

var dryRun bool

func init() {
    rootCmd.Flags().BoolVar(&dryRun, "dry-run", false, "simulate without making changes")
}

func processFile(filename string) error {
    if dryRun {
        fmt.Printf("DRY RUN: Would process %s\n", filename)
        return nil
    }

    // Actual processing
    return nil
}

Add Progress Indication

import "github.com/schollz/progressbar/v3"

func processFile(filename string) error {
    bar := progressbar.Default(100)

    for i := 0; i < 100; i++ {
        // Do work
        bar.Add(1)
        time.Sleep(10 * time.Millisecond)
    }

    return nil
}

Testing

package main

import (
    "bytes"
    "testing"
)

func TestRootCommand(t *testing.T) {
    // Reset command for testing
    rootCmd.SetArgs([]string{"test.txt", "--verbose"})

    // Capture output
    buf := new(bytes.Buffer)
    rootCmd.SetOut(buf)
    rootCmd.SetErr(buf)

    // Execute
    err := rootCmd.Execute()
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }

    // Check output
    output := buf.String()
    if !bytes.Contains([]byte(output), []byte("Processing file")) {
        t.Errorf("Expected verbose output, got: %s", output)
    }
}

func TestRootCommandRequiresArgument(t *testing.T) {
    rootCmd.SetArgs([]string{})

    err := rootCmd.Execute()
    if err == nil {
        t.Error("Expected error when no argument provided")
    }
}

func TestFlagParsing(t *testing.T) {
    rootCmd.SetArgs([]string{"test.txt", "--input", "json", "--output", "yaml"})

    err := rootCmd.Execute()
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }

    // Verify flags were parsed
    if input != "json" {
        t.Errorf("Expected input=json, got %s", input)
    }
    if output != "yaml" {
        t.Errorf("Expected output=yaml, got %s", output)
    }
}

go.mod

module github.com/example/mytool

go 1.21

require github.com/spf13/cobra v1.8.0

require (
    github.com/inconshreveable/mousetrap v1.1.0 // indirect
    github.com/spf13/pflag v1.0.5 // indirect
)

Build and Distribution

Simple Build

go build -o mytool

Cross-Platform Build

# Linux
GOOS=linux GOARCH=amd64 go build -o mytool-linux

# macOS
GOOS=darwin GOARCH=amd64 go build -o mytool-macos

# Windows
GOOS=windows GOARCH=amd64 go build -o mytool.exe

With Version Info

VERSION=$(git describe --tags --always)
go build -ldflags "-X main.version=$VERSION" -o mytool

Best Practices

  1. Keep It Simple: Single file is fine for simple tools
  2. Use RunE: Always return errors instead of os.Exit
  3. Provide Defaults: Set sensible default flag values
  4. Add Examples: Include usage examples in Long description
  5. Version Info: Always set a version
  6. Test Thoroughly: Write tests for command execution and flags
  7. Document Flags: Provide clear flag descriptions

This example provides a solid foundation for building simple, production-ready CLI tools with Cobra.