ScanFileLines streaming reader with bufio.Scanner

Go
package files

import (
    "bufio"
    "context"
    "fmt"
    "os"
)

// ScanFileLines streams path line by line, calling fn with the 1-based line
// number. It never holds the whole file in memory and stops on the first error.
func ScanFileLines(ctx context.Context, path string, fn func(line int, text string) error) error {
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("open %s: %w", path, err)
    }
    defer f.Close()

    sc := bufio.NewScanner(f)
    sc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024) // allow long lines
    for n := 1; sc.Scan(); n++ {
        if err := ctx.Err(); err != nil {
            return err
        }
        if err := fn(n, sc.Text()); err != nil {
            return fmt.Errorf("line %d: %w", n, err)
        }
    }
    if err := sc.Err(); err != nil {
        return fmt.Errorf("scan %s: %w", path, err)
    }
    return nil
}