Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:04:14 +08:00
commit 70c36b5eff
248 changed files with 47482 additions and 0 deletions

View File

@@ -0,0 +1,693 @@
---
name: cobra-patterns
description: Production-ready Cobra CLI patterns including command structure, flags (local and persistent), nested commands, PreRun/PostRun hooks, argument validation, and initialization patterns used by kubectl and hugo. Use when building Go CLIs, implementing Cobra commands, creating nested command structures, managing flags, validating arguments, or when user mentions Cobra, CLI development, command-line tools, kubectl patterns, or Go CLI frameworks.
allowed-tools: Bash, Read, Write, Edit
---
# Cobra Patterns Skill
Production-ready patterns for building powerful CLI applications with Cobra, following best practices from kubectl, hugo, and other production CLIs.
## Instructions
### 1. Choose CLI Structure Pattern
Select the appropriate CLI structure based on your use case:
- **simple**: Single command with flags (quick utilities)
- **flat**: Root command with subcommands at one level
- **nested**: Hierarchical command structure (kubectl-style)
- **plugin**: Extensible CLI with plugin support
- **hybrid**: Mix of built-in and dynamic commands
### 2. Generate Cobra CLI Structure
Use the setup script to scaffold a new Cobra CLI:
```bash
cd /home/gotime2022/.claude/plugins/repos/cli-builder/skills/cobra-patterns
./scripts/setup-cobra-cli.sh <cli-name> <structure-type>
```
**Structure types:** `simple`, `flat`, `nested`, `plugin`, `hybrid`
**Example:**
```bash
./scripts/setup-cobra-cli.sh myctl nested
```
**What This Creates:**
- Complete directory structure with cmd/ package
- Root command with initialization
- Example subcommands
- Flag definitions (local and persistent)
- Cobra initialization (cobra init pattern)
- Go module configuration
- Main entry point
### 3. Command Structure Patterns
#### Basic Command Structure
```go
var exampleCmd = &cobra.Command{
Use: "example [flags]",
Short: "Brief description",
Long: `Detailed description with examples`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// Command logic
},
}
```
#### Command with Lifecycle Hooks
```go
var advancedCmd = &cobra.Command{
Use: "advanced",
Short: "Advanced command with hooks",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Runs before command execution (inherited by children)
},
PreRun: func(cmd *cobra.Command, args []string) {
// Runs before command execution (local only)
},
Run: func(cmd *cobra.Command, args []string) {
// Main command logic
},
PostRun: func(cmd *cobra.Command, args []string) {
// Runs after command execution (local only)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
// Runs after command execution (inherited by children)
},
}
```
#### Command with Error Handling
```go
var robustCmd = &cobra.Command{
Use: "robust",
Short: "Command with proper error handling",
RunE: func(cmd *cobra.Command, args []string) error {
// Return errors instead of os.Exit
if err := validateInput(args); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
if err := executeOperation(); err != nil {
return fmt.Errorf("operation failed: %w", err)
}
return nil
},
}
```
### 4. Flag Management Patterns
#### Persistent Flags (Global Options)
```go
func init() {
// Available to this command and all subcommands
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "log level")
}
```
#### Local Flags (Command-Specific)
```go
func init() {
// Only available to this specific command
createCmd.Flags().StringVarP(&name, "name", "n", "", "resource name")
createCmd.Flags().IntVar(&replicas, "replicas", 1, "number of replicas")
createCmd.Flags().BoolVar(&dryRun, "dry-run", false, "simulate operation")
// Mark required flags
createCmd.MarkFlagRequired("name")
}
```
#### Flag Groups and Validation
```go
func init() {
// Mutually exclusive flags (only one allowed)
createCmd.MarkFlagsMutuallyExclusive("json", "yaml", "text")
// Required together (all or none)
createCmd.MarkFlagsRequiredTogether("username", "password")
// At least one required
createCmd.MarkFlagsOneRequired("file", "stdin", "url")
// Custom flag completion
createCmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"json", "yaml", "text"}, cobra.ShellCompDirectiveNoFileComp
})
}
```
### 5. Nested Command Patterns
#### Root Command Setup
```go
// cmd/root.go
var rootCmd = &cobra.Command{
Use: "myctl",
Short: "A production-grade CLI tool",
Long: `A complete CLI application built with Cobra.
This application demonstrates production patterns including
nested commands, flag management, and proper error handling.`,
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
cobra.OnInitialize(initConfig)
// Global flags
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
// Add subcommands
rootCmd.AddCommand(getCmd)
rootCmd.AddCommand(createCmd)
rootCmd.AddCommand(deleteCmd)
}
func initConfig() {
// Initialize configuration, logging, etc.
}
```
#### Subcommand with Children (kubectl-style)
```go
// cmd/create/create.go
var createCmd = &cobra.Command{
Use: "create",
Short: "Create resources",
Long: `Create various types of resources`,
}
func init() {
// Add nested subcommands
createCmd.AddCommand(createDeploymentCmd)
createCmd.AddCommand(createServiceCmd)
createCmd.AddCommand(createConfigMapCmd)
}
// cmd/create/deployment.go
var createDeploymentCmd = &cobra.Command{
Use: "deployment [name]",
Short: "Create a deployment",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return createDeployment(args[0])
},
}
```
#### Command Groups (Organized Help)
```go
func init() {
// Define command groups
rootCmd.AddGroup(&cobra.Group{
ID: "basic",
Title: "Basic Commands:",
})
rootCmd.AddGroup(&cobra.Group{
ID: "management",
Title: "Management Commands:",
})
// Assign commands to groups
getCmd.GroupID = "basic"
createCmd.GroupID = "management"
}
```
### 6. Argument Validation Patterns
```go
// No arguments allowed
var noArgsCmd = &cobra.Command{
Use: "list",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return listResources()
},
}
// Exactly n arguments
var exactArgsCmd = &cobra.Command{
Use: "get <name>",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return getResource(args[0])
},
}
// Range of arguments
var rangeArgsCmd = &cobra.Command{
Use: "delete <name> [names...]",
Args: cobra.RangeArgs(1, 5),
RunE: func(cmd *cobra.Command, args []string) error {
return deleteResources(args)
},
}
// Custom validation
var customValidationCmd = &cobra.Command{
Use: "custom",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("requires at least 1 argument")
}
for _, arg := range args {
if !isValid(arg) {
return fmt.Errorf("invalid argument: %s", arg)
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return processArgs(args)
},
}
// Valid args with completion
var validArgsCmd = &cobra.Command{
Use: "select <resource>",
ValidArgs: []string{"pod", "service", "deployment", "configmap"},
Args: cobra.OnlyValidArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return selectResource(args[0])
},
}
```
### 7. Initialization and Configuration Patterns
#### cobra.OnInitialize Pattern
```go
var (
cfgFile string
config Config
)
func init() {
// Register initialization functions
cobra.OnInitialize(initConfig, initLogging, initClient)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".myctl")
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
func initLogging() {
// Setup logging based on flags
}
func initClient() {
// Initialize API clients, connections, etc.
}
```
#### Viper Integration
```go
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func init() {
// Bind flags to viper
rootCmd.PersistentFlags().String("output", "json", "output format")
viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output"))
// Set defaults
viper.SetDefault("output", "json")
viper.SetDefault("timeout", 30)
}
func Execute() error {
// Access config via viper
output := viper.GetString("output")
timeout := viper.GetInt("timeout")
return rootCmd.Execute()
}
```
### 8. Production Patterns
#### Kubectl-Style Command Structure
```go
// Organize commands by resource type
// myctl get pods
// myctl create deployment
// myctl delete service
var getCmd = &cobra.Command{
Use: "get",
Short: "Display resources",
}
var createCmd = &cobra.Command{
Use: "create",
Short: "Create resources",
}
func init() {
// Resource-specific subcommands
getCmd.AddCommand(getPodsCmd)
getCmd.AddCommand(getServicesCmd)
createCmd.AddCommand(createDeploymentCmd)
createCmd.AddCommand(createServiceCmd)
}
```
#### Hugo-Style Plugin Commands
```go
// Support external commands (hugo server, hugo new, etc.)
func init() {
rootCmd.AddCommand(serverCmd)
rootCmd.AddCommand(newCmd)
// Auto-discover plugin commands
discoverPluginCommands(rootCmd)
}
func discoverPluginCommands(root *cobra.Command) {
// Look for executables like "myctl-plugin-*"
// Add them as dynamic commands
}
```
#### Context and Cancellation
```go
var longRunningCmd = &cobra.Command{
Use: "process",
Short: "Long-running operation",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// Respect context cancellation (Ctrl+C)
return processWithContext(ctx)
},
}
func processWithContext(ctx context.Context) error {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
// Do work
}
}
}
```
### 9. Validation and Testing
Use validation scripts to ensure CLI compliance:
```bash
# Validate command structure
./scripts/validate-cobra-cli.sh <cli-directory>
# Test command execution
./scripts/test-cobra-commands.sh <cli-binary>
# Generate shell completions
./scripts/generate-completions.sh <cli-binary>
```
**Validation Checks:**
- All commands have Use, Short, and Long descriptions
- Flags are properly defined and documented
- Required flags are marked
- Argument validation is implemented
- RunE is used for error handling
- Commands are organized in logical groups
### 10. Shell Completion Support
```go
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: `Generate shell completion script.
Example usage:
# Bash
source <(myctl completion bash)
# Zsh
source <(myctl completion zsh)
# Fish
myctl completion fish | source
# PowerShell
myctl completion powershell | Out-String | Invoke-Expression
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default:
return fmt.Errorf("unsupported shell: %s", args[0])
}
},
}
```
## Available Scripts
- **setup-cobra-cli.sh**: Scaffold new Cobra CLI with chosen structure
- **validate-cobra-cli.sh**: Validate CLI structure and patterns
- **test-cobra-commands.sh**: Test all commands and flags
- **generate-completions.sh**: Generate shell completion scripts
- **add-command.sh**: Add new command to existing CLI
- **refactor-flags.sh**: Reorganize flags (local to persistent, etc.)
## Templates
### Core Templates
- **root.go**: Root command with initialization
- **command.go**: Basic command template
- **nested-command.go**: Subcommand with children
- **main.go**: CLI entry point
- **config.go**: Configuration management with Viper
### Command Templates
- **get-command.go**: Read/retrieve operation
- **create-command.go**: Create operation with validation
- **delete-command.go**: Delete with confirmation
- **list-command.go**: List resources with filtering
- **update-command.go**: Update with partial modifications
### Advanced Templates
- **plugin-command.go**: Extensible plugin support
- **completion-command.go**: Shell completion generation
- **version-command.go**: Version information display
- **middleware.go**: Command middleware pattern
- **context-command.go**: Context-aware command
### Flag Templates
- **persistent-flags.go**: Global flag definitions
- **flag-groups.go**: Flag validation groups
- **custom-flags.go**: Custom flag types
- **viper-flags.go**: Viper-integrated flags
### Testing Templates
- **command_test.go**: Command unit test
- **integration_test.go**: CLI integration test
- **mock_test.go**: Mock dependencies for testing
## Examples
See `examples/` directory for production patterns:
- `kubectl-style/`: Kubectl command organization pattern
- `hugo-style/`: Hugo plugin architecture pattern
- `simple-cli/`: Basic single-level CLI
- `nested-cli/`: Multi-level command hierarchy
- `production-cli/`: Full production CLI with all features
Each example includes:
- Complete working CLI
- Command structure documentation
- Flag management examples
- Test suite
- Shell completion setup
## Best Practices
### Command Organization
1. One command per file for maintainability
2. Group related commands in subdirectories
3. Use command groups for organized help output
4. Keep root command focused on initialization
### Flag Management
1. Use persistent flags for truly global options
2. Mark required flags explicitly
3. Provide sensible defaults
4. Use flag groups for related options
5. Implement custom completion for better UX
### Error Handling
1. Always use RunE instead of Run
2. Return wrapped errors with context
3. Use cobra.CheckErr() for fatal errors
4. Provide helpful error messages with suggestions
### Code Organization
1. Separate command definition from logic
2. Keep business logic in separate packages
3. Use dependency injection for testability
4. Avoid global state where possible
### Documentation
1. Provide both Short and Long descriptions
2. Include usage examples in Long description
3. Document all flags with clear help text
4. Generate and maintain shell completions
### Testing
1. Unit test command functions separately
2. Integration test full command execution
3. Mock external dependencies
4. Test flag validation and argument parsing
5. Verify error messages and exit codes
### Performance
1. Use cobra.OnInitialize for lazy loading
2. Avoid expensive operations in init()
3. Implement context cancellation
4. Profile and optimize hot paths
## Common Workflows
### Creating a New Nested CLI
```bash
# 1. Generate CLI structure
./scripts/setup-cobra-cli.sh myctl nested
# 2. Add commands
cd myctl
../scripts/add-command.sh get
../scripts/add-command.sh create --parent get
# 3. Validate structure
../scripts/validate-cobra-cli.sh .
# 4. Build and test
go build -o myctl
./myctl --help
```
### Adding Authentication to CLI
```bash
# Use authentication template
cp templates/auth-command.go cmd/login.go
# Add persistent auth flags
cp templates/auth-flags.go cmd/root.go
# Implement token management
# Edit cmd/root.go to add initAuth() to cobra.OnInitialize
```
### Implementing kubectl-Style Resource Commands
```bash
# Generate resource-based structure
./scripts/setup-cobra-cli.sh myctl nested
# Add resource commands (get, create, delete, update)
./scripts/add-command.sh get --style kubectl
./scripts/add-command.sh create --style kubectl
# Add resource types as subcommands
./scripts/add-command.sh pods --parent get
./scripts/add-command.sh services --parent get
```
## Troubleshooting
**Commands not showing in help**: Ensure AddCommand() is called in init()
**Flags not recognized**: Check if flag is registered before command execution
**PersistentFlags not inherited**: Verify parent command has PersistentFlags defined
**Completion not working**: Run completion command and source output, check ValidArgs
**Context cancellation ignored**: Ensure you're checking ctx.Done() in long-running operations
## Integration
This skill is used by:
- CLI generation commands - Scaffolding new CLIs
- Code generation agents - Implementing CLI patterns
- Testing commands - Validating CLI structure
- All Go CLI development workflows
---
**Plugin:** cli-builder
**Version:** 1.0.0
**Category:** Go CLI Development
**Skill Type:** Patterns & Templates

View File

@@ -0,0 +1,366 @@
# Kubectl-Style CLI Example
This example demonstrates how to build a kubectl-style CLI with nested resource commands and consistent flag handling.
## Structure
```
myctl/
├── cmd/
│ ├── root.go # Root command with global flags
│ ├── get/
│ │ ├── get.go # Parent "get" command
│ │ ├── pods.go # Get pods subcommand
│ │ ├── services.go # Get services subcommand
│ │ └── deployments.go # Get deployments subcommand
│ ├── create/
│ │ ├── create.go # Parent "create" command
│ │ ├── deployment.go # Create deployment subcommand
│ │ └── service.go # Create service subcommand
│ ├── delete/
│ │ └── delete.go # Delete command (accepts any resource)
│ ├── apply.go # Apply from file/stdin
│ └── completion.go # Shell completion
└── main.go
```
## Usage Pattern
```bash
# Get resources
myctl get pods
myctl get pods my-pod
myctl get pods --namespace production
myctl get services --all-namespaces
# Create resources
myctl create deployment my-app --image nginx:latest --replicas 3
myctl create service my-svc --port 80 --target-port 8080
# Delete resources
myctl delete pod my-pod
myctl delete deployment my-app --force
# Apply configuration
myctl apply -f deployment.yaml
myctl apply -f config.yaml --dry-run
```
## Key Features
### 1. Resource-Based Organization
Commands are organized by resource type:
- `get <resource>` - Retrieve resources
- `create <resource>` - Create resources
- `delete <resource>` - Delete resources
### 2. Consistent Flag Handling
Global flags available to all commands:
- `--namespace, -n` - Target namespace
- `--all-namespaces, -A` - Query all namespaces
- `--output, -o` - Output format (json|yaml|text)
- `--verbose, -v` - Verbose logging
### 3. Command Groups
Organized help output:
```
Basic Commands:
get Display resources
describe Show detailed information
Management Commands:
create Create resources
delete Delete resources
apply Apply configuration
```
## Implementation Example
### Root Command (cmd/root.go)
```go
package cmd
import (
"github.com/spf13/cobra"
"myctl/cmd/get"
"myctl/cmd/create"
"myctl/cmd/delete"
)
var (
namespace string
allNamespaces bool
output string
)
var rootCmd = &cobra.Command{
Use: "myctl",
Short: "Kubernetes-style resource management CLI",
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
// Global flags
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "target namespace")
rootCmd.PersistentFlags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, "query all namespaces")
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "text", "output format (text|json|yaml)")
// Command groups
rootCmd.AddGroup(&cobra.Group{ID: "basic", Title: "Basic Commands:"})
rootCmd.AddGroup(&cobra.Group{ID: "management", Title: "Management Commands:"})
// Register commands
rootCmd.AddCommand(get.GetCmd)
rootCmd.AddCommand(create.CreateCmd)
rootCmd.AddCommand(delete.DeleteCmd)
}
// Helper to get global flags
func GetNamespace() string {
return namespace
}
func GetAllNamespaces() bool {
return allNamespaces
}
func GetOutput() string {
return output
}
```
### Get Command Parent (cmd/get/get.go)
```go
package get
import (
"github.com/spf13/cobra"
)
var GetCmd = &cobra.Command{
Use: "get",
Short: "Display resources",
Long: `Display one or many resources`,
GroupID: "basic",
}
func init() {
// Add resource subcommands
GetCmd.AddCommand(podsCmd)
GetCmd.AddCommand(servicesCmd)
GetCmd.AddCommand(deploymentsCmd)
}
```
### Get Pods Subcommand (cmd/get/pods.go)
```go
package get
import (
"fmt"
"github.com/spf13/cobra"
"myctl/cmd"
"myctl/internal/client"
)
var (
selector string
watch bool
)
var podsCmd = &cobra.Command{
Use: "pods [NAME]",
Short: "Display pods",
Long: `Display one or many pods`,
Args: cobra.MaximumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Dynamic completion: fetch pod names
return client.ListPodNames(cmd.GetNamespace()), cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
namespace := cmd.GetNamespace()
allNamespaces := cmd.GetAllNamespaces()
output := cmd.GetOutput()
if len(args) == 0 {
// List pods
return listPods(namespace, allNamespaces, selector, output)
}
// Get specific pod
podName := args[0]
return getPod(namespace, podName, output)
},
}
func init() {
// Command-specific flags
podsCmd.Flags().StringVarP(&selector, "selector", "l", "", "label selector")
podsCmd.Flags().BoolVarP(&watch, "watch", "w", false, "watch for changes")
}
func listPods(namespace string, allNamespaces bool, selector string, output string) error {
// Implementation
fmt.Printf("Listing pods (namespace: %s, all: %v, selector: %s, format: %s)\n",
namespace, allNamespaces, selector, output)
return nil
}
func getPod(namespace, name, output string) error {
// Implementation
fmt.Printf("Getting pod: %s (namespace: %s, format: %s)\n", name, namespace, output)
return nil
}
```
### Create Deployment (cmd/create/deployment.go)
```go
package create
import (
"fmt"
"github.com/spf13/cobra"
)
var (
image string
replicas int
port int
)
var deploymentCmd = &cobra.Command{
Use: "deployment NAME",
Short: "Create a deployment",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
if image == "" {
return fmt.Errorf("--image is required")
}
fmt.Printf("Creating deployment: %s\n", name)
fmt.Printf(" Image: %s\n", image)
fmt.Printf(" Replicas: %d\n", replicas)
if port > 0 {
fmt.Printf(" Container Port: %d\n", port)
}
return createDeployment(name, image, replicas, port)
},
}
func init() {
deploymentCmd.Flags().StringVar(&image, "image", "", "container image (required)")
deploymentCmd.Flags().IntVar(&replicas, "replicas", 1, "number of replicas")
deploymentCmd.Flags().IntVar(&port, "port", 0, "container port")
deploymentCmd.MarkFlagRequired("image")
}
func createDeployment(name, image string, replicas, port int) error {
// Implementation
return nil
}
```
## Best Practices
### 1. Consistent Flag Naming
- Use single-letter shortcuts for common flags (`-n`, `-o`, `-v`)
- Use descriptive long names (`--namespace`, `--output`, `--verbose`)
- Keep flag behavior consistent across commands
### 2. Dynamic Completion
Provide shell completion for resource names:
```go
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return client.ListResourceNames(), cobra.ShellCompDirectiveNoFileComp
}
```
### 3. Error Messages
Provide helpful error messages with suggestions:
```go
if image == "" {
return fmt.Errorf("--image is required. Example: --image nginx:latest")
}
```
### 4. Dry Run Support
Support `--dry-run` for preview:
```go
if dryRun {
fmt.Printf("Would create deployment: %s\n", name)
return nil
}
```
### 5. Output Formats
Support multiple output formats:
```go
switch output {
case "json":
return printJSON(pods)
case "yaml":
return printYAML(pods)
default:
return printTable(pods)
}
```
## Testing
```go
func TestGetPodsCommand(t *testing.T) {
cmd := get.GetCmd
cmd.SetArgs([]string{"pods", "--namespace", "production"})
err := cmd.Execute()
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
```
## Advanced Features
### 1. Watch Mode
```go
if watch {
return watchPods(namespace, selector)
}
```
### 2. Label Selectors
```go
podsCmd.Flags().StringVarP(&selector, "selector", "l", "", "label selector (e.g., app=nginx)")
```
### 3. Field Selectors
```go
podsCmd.Flags().StringVar(&fieldSelector, "field-selector", "", "field selector (e.g., status.phase=Running)")
```
### 4. Multiple Output Formats
```go
podsCmd.Flags().StringVarP(&output, "output", "o", "text", "output format (text|json|yaml|wide)")
```
This example provides a complete kubectl-style CLI structure that you can adapt for your resource management needs.

View File

@@ -0,0 +1,538 @@
# Complete Production CLI Example
A complete example demonstrating all production features: configuration management, error handling, logging, context support, and testing.
## Features
- ✅ Viper configuration management
- ✅ Structured logging (with levels)
- ✅ Context-aware commands (cancellation support)
- ✅ Proper error handling with wrapped errors
- ✅ Shell completion
- ✅ Unit and integration tests
- ✅ Dry-run support
- ✅ Multiple output formats
- ✅ Version information
- ✅ Configuration file support
## Complete Implementation
### main.go
```go
package main
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/example/myapp/cmd"
)
func main() {
// Setup context with cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Handle interrupt signals gracefully
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
cancel()
}()
// Execute with context
if err := cmd.ExecuteContext(ctx); err != nil {
os.Exit(1)
}
}
```
### cmd/root.go
```go
package cmd
import (
"context"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
cfgFile string
verbose bool
logLevel string
logger *zap.Logger
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "A production-grade CLI application",
Long: `A complete production CLI with proper error handling,
configuration management, logging, and context support.`,
Version: "1.0.0",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Initialize logger based on flags
return initLogger()
},
}
func ExecuteContext(ctx context.Context) error {
rootCmd.SetContext(ctx)
return rootCmd.Execute()
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
cobra.OnInitialize(initConfig)
// Global flags
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.myapp.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "log level (debug|info|warn|error)")
// Bind to viper
viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level"))
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(home)
viper.AddConfigPath(".")
viper.SetConfigType("yaml")
viper.SetConfigName(".myapp")
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil && verbose {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
func initLogger() error {
// Parse log level
level := zapcore.InfoLevel
if err := level.UnmarshalText([]byte(logLevel)); err != nil {
return fmt.Errorf("invalid log level: %w", err)
}
// Create logger config
config := zap.NewProductionConfig()
config.Level = zap.NewAtomicLevelAt(level)
if verbose {
config = zap.NewDevelopmentConfig()
}
// Build logger
var err error
logger, err = config.Build()
if err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}
return nil
}
func GetLogger() *zap.Logger {
if logger == nil {
// Fallback logger
logger, _ = zap.NewProduction()
}
return logger
}
```
### cmd/process.go (Context-Aware Command)
```go
package cmd
import (
"context"
"fmt"
"time"
"github.com/spf13/cobra"
)
var (
processTimeout time.Duration
processDryRun bool
processWorkers int
)
var processCmd = &cobra.Command{
Use: "process [files...]",
Short: "Process files with context support",
Long: `Process files with proper context handling,
graceful cancellation, and timeout support.`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
logger := GetLogger()
// Apply timeout if specified
if processTimeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, processTimeout)
defer cancel()
}
logger.Info("Starting process",
zap.Strings("files", args),
zap.Int("workers", processWorkers),
zap.Bool("dry-run", processDryRun))
if processDryRun {
logger.Info("Dry run mode - no changes will be made")
return nil
}
// Process with context
if err := processFiles(ctx, args, processWorkers); err != nil {
logger.Error("Processing failed", zap.Error(err))
return fmt.Errorf("process failed: %w", err)
}
logger.Info("Processing completed successfully")
return nil
},
}
func init() {
rootCmd.AddCommand(processCmd)
processCmd.Flags().DurationVar(&processTimeout, "timeout", 0, "processing timeout")
processCmd.Flags().BoolVar(&processDryRun, "dry-run", false, "simulate without changes")
processCmd.Flags().IntVarP(&processWorkers, "workers", "w", 4, "number of workers")
}
func processFiles(ctx context.Context, files []string, workers int) error {
logger := GetLogger()
for _, file := range files {
// Check context cancellation
select {
case <-ctx.Done():
return ctx.Err()
default:
}
logger.Debug("Processing file", zap.String("file", file))
// Simulate work
if err := processFile(ctx, file); err != nil {
return fmt.Errorf("failed to process %s: %w", file, err)
}
}
return nil
}
func processFile(ctx context.Context, file string) error {
// Simulate processing with context awareness
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for i := 0; i < 10; i++ {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
// Do work
}
}
return nil
}
```
### cmd/config.go (Configuration Management)
```go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var configCmd = &cobra.Command{
Use: "config",
Short: "Manage configuration",
}
var configViewCmd = &cobra.Command{
Use: "view",
Short: "View current configuration",
RunE: func(cmd *cobra.Command, args []string) error {
settings := viper.AllSettings()
fmt.Println("Current Configuration:")
fmt.Println("=====================")
for key, value := range settings {
fmt.Printf("%s: %v\n", key, value)
}
return nil
},
}
var configSetCmd = &cobra.Command{
Use: "set KEY VALUE",
Short: "Set configuration value",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
key := args[0]
value := args[1]
viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
if err := viper.SafeWriteConfig(); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
}
fmt.Printf("Set %s = %s\n", key, value)
return nil
},
}
func init() {
rootCmd.AddCommand(configCmd)
configCmd.AddCommand(configViewCmd)
configCmd.AddCommand(configSetCmd)
}
```
### cmd/version.go
```go
package cmd
import (
"fmt"
"runtime"
"github.com/spf13/cobra"
)
var (
Version = "dev"
Commit = "none"
BuildTime = "unknown"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("myapp version %s\n", Version)
fmt.Printf(" Commit: %s\n", Commit)
fmt.Printf(" Built: %s\n", BuildTime)
fmt.Printf(" Go version: %s\n", runtime.Version())
fmt.Printf(" OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
```
### Testing (cmd/root_test.go)
```go
package cmd
import (
"bytes"
"context"
"testing"
"time"
)
func TestProcessCommand(t *testing.T) {
// Reset command for testing
processCmd.SetArgs([]string{"file1.txt", "file2.txt"})
// Capture output
buf := new(bytes.Buffer)
processCmd.SetOut(buf)
processCmd.SetErr(buf)
// Execute
err := processCmd.Execute()
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestProcessCommandWithContext(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
processCmd.SetContext(ctx)
processCmd.SetArgs([]string{"file1.txt"})
err := processCmd.Execute()
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestProcessCommandCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
processCmd.SetContext(ctx)
processCmd.SetArgs([]string{"file1.txt", "file2.txt"})
// Cancel context immediately
cancel()
err := processCmd.Execute()
if err == nil {
t.Error("Expected context cancellation error")
}
}
func TestConfigViewCommand(t *testing.T) {
configViewCmd.SetArgs([]string{})
buf := new(bytes.Buffer)
configViewCmd.SetOut(buf)
err := configViewCmd.Execute()
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
output := buf.String()
if output == "" {
t.Error("Expected output, got empty string")
}
}
```
### Configuration File (.myapp.yaml)
```yaml
# Application configuration
verbose: false
log-level: info
timeout: 30s
# Custom settings
api:
endpoint: https://api.example.com
timeout: 10s
retries: 3
database:
host: localhost
port: 5432
name: myapp
features:
experimental: false
beta: true
```
### Makefile
```makefile
VERSION := $(shell git describe --tags --always --dirty)
COMMIT := $(shell git rev-parse HEAD)
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
LDFLAGS := -X github.com/example/myapp/cmd.Version=$(VERSION) \
-X github.com/example/myapp/cmd.Commit=$(COMMIT) \
-X github.com/example/myapp/cmd.BuildTime=$(BUILD_TIME)
.PHONY: build
build:
go build -ldflags "$(LDFLAGS)" -o myapp
.PHONY: test
test:
go test -v ./...
.PHONY: coverage
coverage:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
.PHONY: lint
lint:
golangci-lint run
.PHONY: install
install:
go install -ldflags "$(LDFLAGS)"
.PHONY: clean
clean:
rm -f myapp coverage.out
```
## Usage Examples
```bash
# Basic usage with verbose logging
myapp process file.txt -v
# With timeout and workers
myapp process *.txt --timeout 30s --workers 8
# Dry run
myapp process file.txt --dry-run
# Custom config file
myapp --config prod.yaml process file.txt
# View configuration
myapp config view
# Set configuration
myapp config set api.timeout 15s
# Version information
myapp version
# Shell completion
myapp completion bash > /etc/bash_completion.d/myapp
```
## Key Patterns
1. **Context Awareness**: All long-running operations respect context cancellation
2. **Structured Logging**: Use zap for performance and structure
3. **Configuration Management**: Viper for flexible config handling
4. **Error Wrapping**: Use fmt.Errorf with %w for error chains
5. **Testing**: Comprehensive unit and integration tests
6. **Build Info**: Version, commit, and build time injection
This example provides a complete production-ready CLI that you can use as a foundation for your own applications.

View 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.

View File

@@ -0,0 +1,113 @@
#!/bin/bash
# Add a new command to existing Cobra CLI
# Usage: ./add-command.sh <command-name> [--parent parent-command]
set -euo pipefail
COMMAND_NAME="${1:-}"
PARENT_CMD=""
# Parse arguments
shift || true
while [ $# -gt 0 ]; do
case "$1" in
--parent)
PARENT_CMD="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
if [ -z "$COMMAND_NAME" ]; then
echo "Error: Command name required"
echo "Usage: $0 <command-name> [--parent parent-command]"
exit 1
fi
if [ ! -d "cmd" ]; then
echo "Error: cmd/ directory not found. Run from CLI root directory."
exit 1
fi
# Determine file location
if [ -n "$PARENT_CMD" ]; then
CMD_DIR="cmd/$PARENT_CMD"
mkdir -p "$CMD_DIR"
CMD_FILE="$CMD_DIR/$COMMAND_NAME.go"
PACKAGE_NAME="$PARENT_CMD"
else
CMD_FILE="cmd/$COMMAND_NAME.go"
PACKAGE_NAME="cmd"
fi
if [ -f "$CMD_FILE" ]; then
echo "Error: Command file already exists: $CMD_FILE"
exit 1
fi
# Create command file
cat > "$CMD_FILE" << EOF
package $PACKAGE_NAME
import (
"fmt"
"github.com/spf13/cobra"
)
var (
// Add command-specific flags here
${COMMAND_NAME}Example string
)
var ${COMMAND_NAME}Cmd = &cobra.Command{
Use: "$COMMAND_NAME",
Short: "Short description of $COMMAND_NAME",
Long: \`Detailed description of the $COMMAND_NAME command.
This command does something useful. Add more details here.
Examples:
mycli $COMMAND_NAME --example value\`,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Executing $COMMAND_NAME command\n")
// Add command logic here
return nil
},
}
func init() {
// Define flags
${COMMAND_NAME}Cmd.Flags().StringVar(&${COMMAND_NAME}Example, "example", "", "example flag")
// Register command
EOF
if [ -n "$PARENT_CMD" ]; then
cat >> "$CMD_FILE" << EOF
${PARENT_CMD}Cmd.AddCommand(${COMMAND_NAME}Cmd)
EOF
else
cat >> "$CMD_FILE" << EOF
rootCmd.AddCommand(${COMMAND_NAME}Cmd)
EOF
fi
cat >> "$CMD_FILE" << EOF
}
EOF
echo "✓ Created command file: $CMD_FILE"
echo ""
echo "Next steps:"
echo "1. Update the command logic in $CMD_FILE"
echo "2. Add any required flags"
echo "3. Build and test: go build"
echo ""

View File

@@ -0,0 +1,82 @@
#!/bin/bash
# Generate shell completion scripts for Cobra CLI
# Usage: ./generate-completions.sh <cli-binary> [output-dir]
set -euo pipefail
CLI_BINARY="${1:-}"
OUTPUT_DIR="${2:-./completions}"
if [ -z "$CLI_BINARY" ]; then
echo "Error: CLI binary path required"
echo "Usage: $0 <cli-binary> [output-dir]"
exit 1
fi
if [ ! -f "$CLI_BINARY" ]; then
echo "Error: Binary not found: $CLI_BINARY"
exit 1
fi
if [ ! -x "$CLI_BINARY" ]; then
echo "Error: Binary is not executable: $CLI_BINARY"
exit 1
fi
mkdir -p "$OUTPUT_DIR"
CLI_NAME=$(basename "$CLI_BINARY")
echo "Generating shell completions for $CLI_NAME..."
echo ""
# Generate Bash completion
if "$CLI_BINARY" completion bash > "$OUTPUT_DIR/$CLI_NAME.bash" 2>/dev/null; then
echo "✓ Bash completion: $OUTPUT_DIR/$CLI_NAME.bash"
echo " Install: source $OUTPUT_DIR/$CLI_NAME.bash"
else
echo "⚠ Bash completion not available"
fi
# Generate Zsh completion
if "$CLI_BINARY" completion zsh > "$OUTPUT_DIR/_$CLI_NAME" 2>/dev/null; then
echo "✓ Zsh completion: $OUTPUT_DIR/_$CLI_NAME"
echo " Install: Place in \$fpath directory"
else
echo "⚠ Zsh completion not available"
fi
# Generate Fish completion
if "$CLI_BINARY" completion fish > "$OUTPUT_DIR/$CLI_NAME.fish" 2>/dev/null; then
echo "✓ Fish completion: $OUTPUT_DIR/$CLI_NAME.fish"
echo " Install: source $OUTPUT_DIR/$CLI_NAME.fish"
else
echo "⚠ Fish completion not available"
fi
# Generate PowerShell completion
if "$CLI_BINARY" completion powershell > "$OUTPUT_DIR/$CLI_NAME.ps1" 2>/dev/null; then
echo "✓ PowerShell completion: $OUTPUT_DIR/$CLI_NAME.ps1"
echo " Install: & $OUTPUT_DIR/$CLI_NAME.ps1"
else
echo "⚠ PowerShell completion not available"
fi
echo ""
echo "Completions generated in: $OUTPUT_DIR"
echo ""
echo "Installation instructions:"
echo ""
echo "Bash:"
echo " echo 'source $OUTPUT_DIR/$CLI_NAME.bash' >> ~/.bashrc"
echo ""
echo "Zsh:"
echo " mkdir -p ~/.zsh/completions"
echo " cp $OUTPUT_DIR/_$CLI_NAME ~/.zsh/completions/"
echo " Add to ~/.zshrc: fpath=(~/.zsh/completions \$fpath)"
echo ""
echo "Fish:"
echo " mkdir -p ~/.config/fish/completions"
echo " cp $OUTPUT_DIR/$CLI_NAME.fish ~/.config/fish/completions/"
echo ""

View File

@@ -0,0 +1,566 @@
#!/bin/bash
# Setup Cobra CLI with chosen structure pattern
# Usage: ./setup-cobra-cli.sh <cli-name> <structure-type>
set -euo pipefail
CLI_NAME="${1:-}"
STRUCTURE_TYPE="${2:-flat}"
if [ -z "$CLI_NAME" ]; then
echo "Error: CLI name required"
echo "Usage: $0 <cli-name> <structure-type>"
echo "Structure types: simple, flat, nested, plugin, hybrid"
exit 1
fi
# Validate structure type
case "$STRUCTURE_TYPE" in
simple|flat|nested|plugin|hybrid)
;;
*)
echo "Error: Invalid structure type: $STRUCTURE_TYPE"
echo "Valid types: simple, flat, nested, plugin, hybrid"
exit 1
;;
esac
echo "Creating Cobra CLI: $CLI_NAME with $STRUCTURE_TYPE structure..."
# Create directory structure
mkdir -p "$CLI_NAME"
cd "$CLI_NAME"
# Initialize Go module
go mod init "$CLI_NAME" 2>/dev/null || echo "Go module already initialized"
# Create base directories
mkdir -p cmd
mkdir -p internal
# Install Cobra
echo "Installing Cobra dependency..."
go get -u github.com/spf13/cobra@latest
case "$STRUCTURE_TYPE" in
simple)
# Single command CLI
cat > main.go << 'EOF'
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var (
verbose bool
)
var rootCmd = &cobra.Command{
Use: "CLI_NAME",
Short: "A simple CLI tool",
Long: `A simple command-line tool built with Cobra.`,
RunE: func(cmd *cobra.Command, args []string) error {
if verbose {
fmt.Println("Running in verbose mode")
}
fmt.Println("Hello from CLI_NAME!")
return nil
},
}
func init() {
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
}
func main() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
EOF
sed -i "s/CLI_NAME/$CLI_NAME/g" main.go
;;
flat)
# Root with subcommands at one level
cat > cmd/root.go << 'EOF'
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var (
cfgFile string
verbose bool
)
var rootCmd = &cobra.Command{
Use: "CLI_NAME",
Short: "A CLI tool with flat command structure",
Long: `A command-line tool with subcommands at a single level.`,
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
}
EOF
cat > cmd/get.go << 'EOF'
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var getCmd = &cobra.Command{
Use: "get [resource]",
Short: "Get resources",
Long: `Retrieve and display resources`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Getting resource: %s\n", args[0])
return nil
},
}
func init() {
rootCmd.AddCommand(getCmd)
}
EOF
cat > cmd/create.go << 'EOF'
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
createName string
)
var createCmd = &cobra.Command{
Use: "create",
Short: "Create resources",
Long: `Create new resources`,
RunE: func(cmd *cobra.Command, args []string) error {
if createName == "" {
return fmt.Errorf("name is required")
}
fmt.Printf("Creating resource: %s\n", createName)
return nil
},
}
func init() {
createCmd.Flags().StringVarP(&createName, "name", "n", "", "resource name (required)")
createCmd.MarkFlagRequired("name")
rootCmd.AddCommand(createCmd)
}
EOF
cat > main.go << 'EOF'
package main
import (
"os"
"CLI_NAME/cmd"
)
func main() {
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}
EOF
sed -i "s/CLI_NAME/$CLI_NAME/g" main.go cmd/root.go
;;
nested)
# kubectl-style nested commands
mkdir -p cmd/get cmd/create cmd/delete
cat > cmd/root.go << 'EOF'
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var (
cfgFile string
verbose bool
output string
)
var rootCmd = &cobra.Command{
Use: "CLI_NAME",
Short: "A production-grade CLI tool",
Long: `A complete CLI application with nested command structure.
This CLI demonstrates kubectl-style command organization with
hierarchical commands and consistent flag handling.`,
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
cobra.OnInitialize(initConfig)
// Global flags
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "text", "output format (text|json|yaml)")
// Command groups
rootCmd.AddGroup(&cobra.Group{
ID: "basic",
Title: "Basic Commands:",
})
rootCmd.AddGroup(&cobra.Group{
ID: "management",
Title: "Management Commands:",
})
}
func initConfig() {
if verbose {
fmt.Fprintln(os.Stderr, "Verbose mode enabled")
}
if cfgFile != "" {
fmt.Fprintf(os.Stderr, "Using config file: %s\n", cfgFile)
}
}
EOF
cat > cmd/get/get.go << 'EOF'
package get
import (
"github.com/spf13/cobra"
)
var GetCmd = &cobra.Command{
Use: "get",
Short: "Display resources",
Long: `Display one or many resources`,
GroupID: "basic",
}
func init() {
GetCmd.AddCommand(podsCmd)
GetCmd.AddCommand(servicesCmd)
}
EOF
cat > cmd/get/pods.go << 'EOF'
package get
import (
"fmt"
"github.com/spf13/cobra"
)
var (
namespace string
allNamespaces bool
)
var podsCmd = &cobra.Command{
Use: "pods [NAME]",
Short: "Display pods",
Long: `Display one or many pods`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if allNamespaces {
fmt.Println("Listing pods in all namespaces")
} else {
fmt.Printf("Listing pods in namespace: %s\n", namespace)
}
if len(args) > 0 {
fmt.Printf("Showing pod: %s\n", args[0])
}
return nil
},
}
func init() {
podsCmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "namespace")
podsCmd.Flags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, "list across all namespaces")
}
EOF
cat > cmd/get/services.go << 'EOF'
package get
import (
"fmt"
"github.com/spf13/cobra"
)
var servicesCmd = &cobra.Command{
Use: "services [NAME]",
Short: "Display services",
Long: `Display one or many services`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Listing services")
if len(args) > 0 {
fmt.Printf("Showing service: %s\n", args[0])
}
return nil
},
}
EOF
cat > cmd/create/create.go << 'EOF'
package create
import (
"github.com/spf13/cobra"
)
var CreateCmd = &cobra.Command{
Use: "create",
Short: "Create resources",
Long: `Create resources from files or stdin`,
GroupID: "management",
}
func init() {
CreateCmd.AddCommand(deploymentCmd)
}
EOF
cat > cmd/create/deployment.go << 'EOF'
package create
import (
"fmt"
"github.com/spf13/cobra"
)
var (
image string
replicas int
)
var deploymentCmd = &cobra.Command{
Use: "deployment NAME",
Short: "Create a deployment",
Long: `Create a deployment with the specified name`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
fmt.Printf("Creating deployment: %s\n", name)
fmt.Printf(" Image: %s\n", image)
fmt.Printf(" Replicas: %d\n", replicas)
return nil
},
}
func init() {
deploymentCmd.Flags().StringVar(&image, "image", "", "container image (required)")
deploymentCmd.Flags().IntVar(&replicas, "replicas", 1, "number of replicas")
deploymentCmd.MarkFlagRequired("image")
}
EOF
cat > cmd/delete/delete.go << 'EOF'
package delete
import (
"fmt"
"github.com/spf13/cobra"
)
var (
force bool
)
var DeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete resources",
Long: `Delete resources by names, stdin, or resources`,
GroupID: "management",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
for _, resource := range args {
if force {
fmt.Printf("Force deleting: %s\n", resource)
} else {
fmt.Printf("Deleting: %s\n", resource)
}
}
return nil
},
}
func init() {
DeleteCmd.Flags().BoolVarP(&force, "force", "f", false, "force deletion")
}
EOF
# Update root to add nested commands
cat >> cmd/root.go << 'EOF'
func init() {
// Add command imports at the top of your root.go:
// import (
// "CLI_NAME/cmd/get"
// "CLI_NAME/cmd/create"
// "CLI_NAME/cmd/delete"
// )
// Uncomment after fixing imports:
// rootCmd.AddCommand(get.GetCmd)
// rootCmd.AddCommand(create.CreateCmd)
// rootCmd.AddCommand(delete.DeleteCmd)
}
EOF
cat > main.go << 'EOF'
package main
import (
"os"
"CLI_NAME/cmd"
_ "CLI_NAME/cmd/get"
_ "CLI_NAME/cmd/create"
_ "CLI_NAME/cmd/delete"
)
func main() {
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}
EOF
sed -i "s/CLI_NAME/$CLI_NAME/g" main.go cmd/root.go
;;
plugin)
echo "Plugin structure not yet implemented"
exit 1
;;
hybrid)
echo "Hybrid structure not yet implemented"
exit 1
;;
esac
# Create .gitignore
cat > .gitignore << 'EOF'
# Binaries
*.exe
*.exe~
*.dll
*.so
*.dylib
/CLI_NAME
# Test binary
*.test
# Coverage
*.out
*.prof
# Go workspace
go.work
go.work.sum
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
EOF
sed -i "s/CLI_NAME/$CLI_NAME/g" .gitignore
# Create README
cat > README.md << 'EOF'
# CLI_NAME
A CLI tool built with Cobra.
## Installation
```bash
go install
```
## Usage
```bash
CLI_NAME --help
```
## Development
Build:
```bash
go build -o CLI_NAME
```
Run:
```bash
./CLI_NAME
```
Test:
```bash
go test ./...
```
## Structure
This CLI uses STRUCTURE_TYPE command structure.
EOF
sed -i "s/CLI_NAME/$CLI_NAME/g" README.md
sed -i "s/STRUCTURE_TYPE/$STRUCTURE_TYPE/g" README.md
# Initialize dependencies
echo "Downloading dependencies..."
go mod tidy
echo ""
echo "✓ CLI created successfully: $CLI_NAME"
echo ""
echo "Next steps:"
echo " cd $CLI_NAME"
echo " go build -o $CLI_NAME"
echo " ./$CLI_NAME --help"
echo ""

View File

@@ -0,0 +1,181 @@
#!/bin/bash
# Validate Cobra CLI structure and patterns
# Usage: ./validate-cobra-cli.sh <cli-directory>
set -euo pipefail
CLI_DIR="${1:-.}"
if [ ! -d "$CLI_DIR" ]; then
echo "Error: Directory not found: $CLI_DIR"
exit 1
fi
echo "Validating Cobra CLI structure in: $CLI_DIR"
echo ""
ERRORS=0
WARNINGS=0
# Check Go module
if [ ! -f "$CLI_DIR/go.mod" ]; then
echo "❌ ERROR: go.mod not found"
((ERRORS++))
else
echo "✓ go.mod found"
fi
# Check main.go
if [ ! -f "$CLI_DIR/main.go" ]; then
echo "❌ ERROR: main.go not found"
((ERRORS++))
else
echo "✓ main.go found"
# Check if main.go has proper structure
if ! grep -q "func main()" "$CLI_DIR/main.go"; then
echo "❌ ERROR: main() function not found in main.go"
((ERRORS++))
fi
fi
# Check cmd directory
if [ ! -d "$CLI_DIR/cmd" ]; then
echo "⚠ WARNING: cmd/ directory not found (acceptable for simple CLIs)"
((WARNINGS++))
else
echo "✓ cmd/ directory found"
# Check root command
if [ -f "$CLI_DIR/cmd/root.go" ]; then
echo "✓ cmd/root.go found"
# Validate root command structure
if ! grep -q "var rootCmd" "$CLI_DIR/cmd/root.go"; then
echo "❌ ERROR: rootCmd variable not found in root.go"
((ERRORS++))
fi
if ! grep -q "func Execute()" "$CLI_DIR/cmd/root.go"; then
echo "❌ ERROR: Execute() function not found in root.go"
((ERRORS++))
fi
else
echo "⚠ WARNING: cmd/root.go not found"
((WARNINGS++))
fi
fi
# Check for Cobra dependency
if [ -f "$CLI_DIR/go.mod" ]; then
if ! grep -q "github.com/spf13/cobra" "$CLI_DIR/go.mod"; then
echo "❌ ERROR: Cobra dependency not found in go.mod"
((ERRORS++))
else
echo "✓ Cobra dependency found"
fi
fi
# Validate command files have proper structure
if [ -d "$CLI_DIR/cmd" ]; then
for cmd_file in "$CLI_DIR/cmd"/*.go; do
if [ -f "$cmd_file" ]; then
filename=$(basename "$cmd_file")
# Check for command variable
if grep -q "var.*Cmd = &cobra.Command" "$cmd_file"; then
echo "✓ Command structure found in $filename"
# Check for Use field
if ! grep -A5 "var.*Cmd = &cobra.Command" "$cmd_file" | grep -q "Use:"; then
echo "⚠ WARNING: Use field missing in $filename"
((WARNINGS++))
fi
# Check for Short description
if ! grep -A10 "var.*Cmd = &cobra.Command" "$cmd_file" | grep -q "Short:"; then
echo "⚠ WARNING: Short description missing in $filename"
((WARNINGS++))
fi
# Check for Run or RunE
if ! grep -A15 "var.*Cmd = &cobra.Command" "$cmd_file" | grep -qE "Run:|RunE:"; then
echo "⚠ WARNING: Run/RunE function missing in $filename"
((WARNINGS++))
fi
# Prefer RunE over Run for error handling
if grep -A15 "var.*Cmd = &cobra.Command" "$cmd_file" | grep -q "Run:" && \
! grep -A15 "var.*Cmd = &cobra.Command" "$cmd_file" | grep -q "RunE:"; then
echo "⚠ WARNING: Consider using RunE instead of Run in $filename for better error handling"
((WARNINGS++))
fi
fi
fi
done
fi
# Check for .gitignore
if [ ! -f "$CLI_DIR/.gitignore" ]; then
echo "⚠ WARNING: .gitignore not found"
((WARNINGS++))
else
echo "✓ .gitignore found"
fi
# Check for README
if [ ! -f "$CLI_DIR/README.md" ]; then
echo "⚠ WARNING: README.md not found"
((WARNINGS++))
else
echo "✓ README.md found"
fi
# Check if Go code compiles
echo ""
echo "Checking if code compiles..."
cd "$CLI_DIR"
if go build -o /tmp/cobra-cli-test 2>&1 | head -20; then
echo "✓ Code compiles successfully"
rm -f /tmp/cobra-cli-test
else
echo "❌ ERROR: Code does not compile"
((ERRORS++))
fi
# Check for common anti-patterns
echo ""
echo "Checking for anti-patterns..."
# Check for os.Exit in command handlers
if grep -r "os.Exit" "$CLI_DIR/cmd" 2>/dev/null | grep -v "import" | grep -v "//"; then
echo "⚠ WARNING: Found os.Exit() in command handlers - prefer returning errors"
((WARNINGS++))
fi
# Check for panic in command handlers
if grep -r "panic(" "$CLI_DIR/cmd" 2>/dev/null | grep -v "import" | grep -v "//"; then
echo "⚠ WARNING: Found panic() in command handlers - prefer returning errors"
((WARNINGS++))
fi
# Summary
echo ""
echo "================================"
echo "Validation Summary"
echo "================================"
echo "Errors: $ERRORS"
echo "Warnings: $WARNINGS"
echo ""
if [ $ERRORS -eq 0 ]; then
echo "✓ Validation passed!"
if [ $WARNINGS -gt 0 ]; then
echo " ($WARNINGS warnings to review)"
fi
exit 0
else
echo "❌ Validation failed with $ERRORS errors"
exit 1
fi

View File

@@ -0,0 +1,71 @@
package {{.PackageName}}
import (
"fmt"
"github.com/spf13/cobra"
)
var (
// Command-specific flags
{{.CommandName}}Name string
{{.CommandName}}Force bool
{{.CommandName}}DryRun bool
)
// {{.CommandName}}Cmd represents the {{.CommandName}} command
var {{.CommandName}}Cmd = &cobra.Command{
Use: "{{.CommandName}} [flags]",
Short: "{{.ShortDescription}}",
Long: `{{.LongDescription}}
This command provides {{.CommandName}} functionality with proper
error handling and validation.
Examples:
{{.CLIName}} {{.CommandName}} --name example
{{.CLIName}} {{.CommandName}} --force
{{.CLIName}} {{.CommandName}} --dry-run`,
Args: cobra.NoArgs,
GroupID: "{{.GroupID}}",
RunE: func(cmd *cobra.Command, args []string) error {
// Validate required flags
if {{.CommandName}}Name == "" {
return fmt.Errorf("--name is required")
}
// Check dry-run mode
if {{.CommandName}}DryRun {
fmt.Printf("DRY RUN: Would execute {{.CommandName}} with name: %s\n", {{.CommandName}}Name)
return nil
}
// Execute command logic
if cmd.Root().PersistentFlags().Lookup("verbose").Changed {
fmt.Printf("Executing {{.CommandName}} in verbose mode...\n")
}
if err := execute{{.CommandName}}({{.CommandName}}Name, {{.CommandName}}Force); err != nil {
return fmt.Errorf("{{.CommandName}} failed: %w", err)
}
fmt.Printf("Successfully executed {{.CommandName}}: %s\n", {{.CommandName}}Name)
return nil
},
}
func init() {
// Define flags
{{.CommandName}}Cmd.Flags().StringVarP(&{{.CommandName}}Name, "name", "n", "", "resource name (required)")
{{.CommandName}}Cmd.Flags().BoolVarP(&{{.CommandName}}Force, "force", "f", false, "force operation")
{{.CommandName}}Cmd.Flags().BoolVar(&{{.CommandName}}DryRun, "dry-run", false, "simulate operation without making changes")
// Mark required flags
{{.CommandName}}Cmd.MarkFlagRequired("name")
}
// execute{{.CommandName}} performs the actual operation
func execute{{.CommandName}}(name string, force bool) error {
// Implementation goes here
return nil
}

View File

@@ -0,0 +1,71 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: `Generate shell completion script for {{.CLIName}}.
The completion script must be evaluated to provide interactive
completion. This can be done by sourcing it from your shell profile.
Bash:
source <({{.CLIName}} completion bash)
# To load completions for each session, execute once:
# Linux:
{{.CLIName}} completion bash > /etc/bash_completion.d/{{.CLIName}}
# macOS:
{{.CLIName}} completion bash > /usr/local/etc/bash_completion.d/{{.CLIName}}
Zsh:
# If shell completion is not already enabled, enable it:
echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
{{.CLIName}} completion zsh > "${fpath[1]}/_{{.CLIName}}"
# You will need to start a new shell for this setup to take effect.
Fish:
{{.CLIName}} completion fish | source
# To load completions for each session, execute once:
{{.CLIName}} completion fish > ~/.config/fish/completions/{{.CLIName}}.fish
PowerShell:
{{.CLIName}} completion powershell | Out-String | Invoke-Expression
# To load completions for every new session:
{{.CLIName}} completion powershell > {{.CLIName}}.ps1
# and source this file from your PowerShell profile.
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
default:
return fmt.Errorf("unsupported shell type: %s", args[0])
}
},
}
func init() {
rootCmd.AddCommand(completionCmd)
}

View File

@@ -0,0 +1,13 @@
package main
import (
"os"
"{{.ModulePath}}/cmd"
)
func main() {
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

View File

@@ -0,0 +1,20 @@
package {{.PackageName}}
import (
"github.com/spf13/cobra"
)
// {{.CommandName}}Cmd represents the {{.CommandName}} parent command
var {{.CommandName}}Cmd = &cobra.Command{
Use: "{{.CommandName}}",
Short: "{{.ShortDescription}}",
Long: `{{.LongDescription}}`,
GroupID: "{{.GroupID}}",
}
func init() {
// Add subcommands here
// {{.CommandName}}Cmd.AddCommand({{.CommandName}}ListCmd)
// {{.CommandName}}Cmd.AddCommand({{.CommandName}}CreateCmd)
// {{.CommandName}}Cmd.AddCommand({{.CommandName}}DeleteCmd)
}

View File

@@ -0,0 +1,95 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cfgFile string
verbose bool
output string
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "{{.CLIName}}",
Short: "{{.ShortDescription}}",
Long: `{{.LongDescription}}
This is a production-grade CLI application built with Cobra.
It provides a complete command-line interface with proper error
handling, configuration management, and extensibility.`,
Version: "{{.Version}}",
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() error {
return rootCmd.Execute()
}
func init() {
cobra.OnInitialize(initConfig)
// Global flags
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{.CLIName}}.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "text", "output format (text|json|yaml)")
// Bind flags to viper
viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output"))
// Command groups for organized help
rootCmd.AddGroup(&cobra.Group{
ID: "basic",
Title: "Basic Commands:",
})
rootCmd.AddGroup(&cobra.Group{
ID: "management",
Title: "Management Commands:",
})
rootCmd.AddGroup(&cobra.Group{
ID: "other",
Title: "Other Commands:",
})
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
// Search config in home directory with name ".{{.CLIName}}" (without extension).
viper.AddConfigPath(home)
viper.AddConfigPath(".")
viper.SetConfigType("yaml")
viper.SetConfigName(".{{.CLIName}}")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil && verbose {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
// GetVerbose returns whether verbose mode is enabled
func GetVerbose() bool {
return viper.GetBool("verbose")
}
// GetOutput returns the output format
func GetOutput() string {
return viper.GetString("output")
}