Initial commit
This commit is contained in:
381
skills/cobra-patterns/examples/simple-cli-basic.md
Normal file
381
skills/cobra-patterns/examples/simple-cli-basic.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# 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
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```go
|
||||
// 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
|
||||
|
||||
```go
|
||||
// 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
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```bash
|
||||
$ ./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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```bash
|
||||
go build -o mytool
|
||||
```
|
||||
|
||||
### Cross-Platform Build
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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.
|
||||
Reference in New Issue
Block a user