// ingestion/internal/frontmatter/scan.go package frontmatter import ( "fmt" "io/fs" "os" "path/filepath" "strings" "unicode/utf8" ) // Finding is one markdown file whose frontmatter does not parse. type Finding struct { Path string // relative to the scanned root Err error // why it does not parse, or why the repair was refused Repaired bool // true only when fix was requested and the file was rewritten } // Scan walks root for *.md files whose frontmatter fails to parse. // // With fix set, each finding that quoting can rescue is rewritten in place; // anything quoting cannot rescue is reported and left byte-identical. A file // that cannot be read or decoded is reported as a finding rather than aborting // the walk — one poisoned note must not hide the other findings. func Scan(root string, fix bool) ([]Finding, error) { var found []Finding err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() || !strings.EqualFold(filepath.Ext(path), ".md") { return nil } rel, relErr := filepath.Rel(root, path) if relErr != nil { rel = path } doc, readErr := os.ReadFile(path) if readErr != nil { found = append(found, Finding{Path: rel, Err: readErr}) return nil } if !utf8.Valid(doc) { found = append(found, Finding{Path: rel, Err: fmt.Errorf("not valid UTF-8")}) return nil } if lintErr := Lint(doc); lintErr == nil { return nil } if !fix { found = append(found, Finding{Path: rel, Err: Lint(doc)}) return nil } repaired, changed, fixErr := Fix(doc) if fixErr != nil || !changed { found = append(found, Finding{Path: rel, Err: fixErr}) return nil } if writeErr := writeFilePreservingMode(path, repaired); writeErr != nil { found = append(found, Finding{Path: rel, Err: writeErr}) return nil } found = append(found, Finding{Path: rel, Repaired: true}) return nil }) if err != nil { return nil, fmt.Errorf("scan %s: %w", root, err) } return found, nil } func writeFilePreservingMode(path string, content []byte) error { mode := fs.FileMode(0o644) if info, err := os.Stat(path); err == nil { mode = info.Mode().Perm() } return os.WriteFile(path, content, mode) }