9.5 KiB
9.5 KiB
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
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
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
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
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
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
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
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
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
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
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
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
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
- Always handle errors explicitly - Never ignore errors
- Close resources - Use defer for cleanup
- Validate inputs early - Fail fast with clear messages
- Use buffered I/O - For file operations
- Progress feedback - For long operations
- Graceful degradation - Separate recoverable from fatal errors
- Configurable behavior - Use flags for common parameters
- Stream large data - Don't load entire files into memory
- Parallel processing - Use goroutines for independent operations
- Test thoroughly - Use table-driven tests