# Go Patterns for Skill Scripts This reference provides battle-tested Go patterns for common operations in Claude Code skills. ## File Processing Patterns ### Reading Files Line by Line ```go func processFileLineByLine(filename string) error { file, err := os.Open(filename) if err != nil { return fmt.Errorf("failed to open file: %w", err) } defer file.Close() scanner := bufio.NewScanner(file) lineNum := 0 for scanner.Scan() { lineNum++ line := scanner.Text() // Process line if err := processLine(line); err != nil { return fmt.Errorf("error on line %d: %w", lineNum, err) } } if err := scanner.Err(); err != nil { return fmt.Errorf("scanner error: %w", err) } return nil } ``` ### Streaming Large Files ```go func streamProcess(input, output string) error { inFile, err := os.Open(input) if err != nil { return err } defer inFile.Close() outFile, err := os.Create(output) if err != nil { return err } defer outFile.Close() reader := bufio.NewReader(inFile) writer := bufio.NewWriter(outFile) defer writer.Flush() buf := make([]byte, 4096) for { n, err := reader.Read(buf) if err != nil && err != io.EOF { return err } if n == 0 { break } // Process chunk processed := processChunk(buf[:n]) if _, err := writer.Write(processed); err != nil { return err } } return nil } ``` ## Concurrent Processing Patterns ### Worker Pool ```go func processParallel(items []string, workers int) error { jobs := make(chan string, len(items)) results := make(chan error, len(items)) // Start workers var wg sync.WaitGroup for i := 0; i < workers; i++ { wg.Add(1) go func() { defer wg.Done() for item := range jobs { if err := processItem(item); err != nil { results <- err } else { results <- nil } } }() } // Send jobs for _, item := range items { jobs <- item } close(jobs) // Wait for completion wg.Wait() close(results) // Check for errors for err := range results { if err != nil { return err } } return nil } ``` ### Rate Limited Processing ```go func processWithRateLimit(items []string, requestsPerSecond int) error { limiter := time.NewTicker(time.Second / time.Duration(requestsPerSecond)) defer limiter.Stop() for i, item := range items { <-limiter.C if *verbose { log.Printf("Processing %d/%d: %s", i+1, len(items), item) } if err := processItem(item); err != nil { return fmt.Errorf("failed to process %s: %w", item, err) } } return nil } ``` ## Progress Reporting ### Simple Progress Bar ```go func showProgress(current, total int) { if total == 0 { return } percent := float64(current) / float64(total) * 100 filled := int(percent / 2) // 50 chars max fmt.Fprintf(os.Stderr, "\r[") for i := 0; i < 50; i++ { if i < filled { fmt.Fprintf(os.Stderr, "=") } else { fmt.Fprintf(os.Stderr, " ") } } fmt.Fprintf(os.Stderr, "] %.1f%% (%d/%d)", percent, current, total) if current == total { fmt.Fprintln(os.Stderr) } } ``` ### Timed Progress Updates ```go type ProgressTracker struct { total int current int lastUpdate time.Time updateEvery time.Duration } func NewProgressTracker(total int) *ProgressTracker { return &ProgressTracker{ total: total, updateEvery: 500 * time.Millisecond, lastUpdate: time.Now(), } } func (p *ProgressTracker) Update(current int) { p.current = current if time.Since(p.lastUpdate) < p.updateEvery && current < p.total { return } p.lastUpdate = time.Now() showProgress(p.current, p.total) } ``` ## Error Handling Patterns ### Recoverable vs Fatal Errors ```go type ProcessResult struct { Processed int Failed int Errors []error } func processWithRecovery(items []string) (*ProcessResult, error) { result := &ProcessResult{} for _, item := range items { if err := processItem(item); err != nil { // Check if error is recoverable if isRecoverable(err) { result.Failed++ result.Errors = append(result.Errors, fmt.Errorf("%s: %w", item, err)) continue } else { // Fatal error return result, fmt.Errorf("fatal error processing %s: %w", item, err) } } result.Processed++ } return result, nil } func isRecoverable(err error) bool { // Define what errors are recoverable return errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) } ``` ## CLI Patterns ### Multiple Input Sources ```go func getInput() (io.Reader, func() error, error) { if flag.NArg() > 0 { // File input filename := flag.Arg(0) file, err := os.Open(filename) if err != nil { return nil, nil, err } return file, file.Close, nil } else { // Stdin input return os.Stdin, func() error { return nil }, nil } } func main() { // ... flag parsing ... input, cleanup, err := getInput() if err != nil { log.Fatal(err) } defer cleanup() if err := process(input); err != nil { log.Fatal(err) } } ``` ### Configuration with Defaults ```go type Config struct { Workers int BufferSize int Timeout time.Duration OutputDir string } func loadConfig() *Config { cfg := &Config{ Workers: 4, BufferSize: 4096, Timeout: 30 * time.Second, OutputDir: "output", } flag.IntVar(&cfg.Workers, "workers", cfg.Workers, "Number of worker goroutines") flag.IntVar(&cfg.BufferSize, "buffer", cfg.BufferSize, "Buffer size in bytes") flag.DurationVar(&cfg.Timeout, "timeout", cfg.Timeout, "Operation timeout") flag.StringVar(&cfg.OutputDir, "output", cfg.OutputDir, "Output directory") flag.Parse() return cfg } ``` ## Data Processing Patterns ### CSV Processing ```go func processCSV(filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() reader := csv.NewReader(file) reader.TrimLeadingSpace = true // Read header header, err := reader.Read() if err != nil { return fmt.Errorf("failed to read header: %w", err) } // Process rows for { record, err := reader.Read() if err == io.EOF { break } if err != nil { return fmt.Errorf("failed to read row: %w", err) } // Convert to map for easy access row := make(map[string]string) for i, value := range record { if i < len(header) { row[header[i]] = value } } if err := processRow(row); err != nil { return err } } return nil } ``` ### JSON Streaming ```go func processJSONStream(r io.Reader) error { decoder := json.NewDecoder(r) // Expect array of objects // Read opening bracket if _, err := decoder.Token(); err != nil { return err } // Process objects for decoder.More() { var obj YourType if err := decoder.Decode(&obj); err != nil { return err } if err := processObject(&obj); err != nil { return err } } // Read closing bracket if _, err := decoder.Token(); err != nil { return err } return nil } ``` ## Testing Patterns ### Table-Driven Tests ```go func TestProcess(t *testing.T) { tests := []struct { name string input string want string wantErr bool }{ { name: "valid input", input: "test.txt", want: "output.txt", }, { name: "invalid input", input: "nonexistent.txt", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := process(tt.input) if (err != nil) != tt.wantErr { t.Errorf("process() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("process() = %v, want %v", got, tt.want) } }) } } ``` ## Best Practices Summary 1. **Always handle errors explicitly** - Never ignore errors 2. **Close resources** - Use defer for cleanup 3. **Validate inputs early** - Fail fast with clear messages 4. **Use buffered I/O** - For file operations 5. **Progress feedback** - For long operations 6. **Graceful degradation** - Separate recoverable from fatal errors 7. **Configurable behavior** - Use flags for common parameters 8. **Stream large data** - Don't load entire files into memory 9. **Parallel processing** - Use goroutines for independent operations 10. **Test thoroughly** - Use table-driven tests