Files
hyperguild/ingestion/internal/brain/index.go
T
mathiasandClaude Opus 5.5 1c9c218dbf
CI / Lint / Test / Vet (pull_request) Successful in 59s
CI / Mirror to GitHub (pull_request) Skipped
test(tunnel): pin the raced read, the walk error and the _index.md skip (#174)
Review round 1 on PR #186:

- The swap test's static note had the same title as the raced note, so
  the harness's "inside was read" floor passed without reading r.md
  (skipping r.md stayed green). The static note now has its own title.
- An unreadable directory under wiki/ reaches the walk-error path, and
  swallowing that error stayed green. New test asserts the error.
- The FIFO test now runs with and without a writer held open.
- _index.md skip had no test; new test.
- index.go: the double blank line left by removing readTitleAndCreated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:03:31 +02:00

175 lines
4.3 KiB
Go

package brain
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/mathiasbq/hyperguild/ingestion/internal/brainfs"
)
// noteEntry is one row in a Wing _index.md.
type noteEntry struct {
Hall string
Slug string
Title string
Created string
}
// BuildWingIndex regenerates brain/wiki/<wing>/_index.md as a Map of
// Content listing every note in that wing with its Hall and creation
// date. Returns nil if the wing directory does not exist.
func BuildWingIndex(brainDir, wing string) error {
w := Sanitise(wing)
if w == "" {
return fmt.Errorf("invalid wing %q", wing)
}
wingDir := filepath.Join(brainDir, "wiki", w)
// Listed and read through the brain root (hyperguild#157): a wing or
// hall that is a symlink is not followed, and notes are regular files.
bfs, err := brainfs.Open(brainDir)
if err != nil {
return fmt.Errorf("open brain: %w", err)
}
defer func() { _ = bfs.Close() }()
if _, err := bfs.Lstat("wiki/" + w); errors.Is(err, fs.ErrNotExist) {
return nil
}
entries, err := collectWingEntries(bfs, "wiki/"+w)
if err != nil {
return err
}
sort.Slice(entries, func(i, j int) bool {
if entries[i].Hall != entries[j].Hall {
return entries[i].Hall < entries[j].Hall
}
return entries[i].Slug < entries[j].Slug
})
var b strings.Builder
fmt.Fprintf(&b, "# %s\n\n", w)
b.WriteString("| Hall | Note | Created |\n")
b.WriteString("|------|------|---------|\n")
for _, e := range entries {
fmt.Fprintf(&b, "| %s | [%s](%s/%s.md) | %s |\n", e.Hall, e.Title, e.Hall, e.Slug, e.Created)
}
dest := filepath.Join(wingDir, "_index.md")
return os.WriteFile(dest, []byte(b.String()), 0o644)
}
// BuildAllWingIndexes regenerates _index.md for every wing under brain/wiki/.
func BuildAllWingIndexes(brainDir string) error {
bfs, err := brainfs.Open(brainDir)
if err != nil {
return fmt.Errorf("open brain: %w", err)
}
wings, err := bfs.ReadDirDirs("wiki")
_ = bfs.Close()
if errors.Is(err, fs.ErrNotExist) {
return nil
}
if err != nil {
return fmt.Errorf("read wiki: %w", err)
}
for _, w := range wings {
if err := BuildWingIndex(brainDir, w); err != nil {
return fmt.Errorf("index %s: %w", w, err)
}
}
return nil
}
func collectWingEntries(bfs *brainfs.Brain, wingRel string) ([]noteEntry, error) {
var out []noteEntry
halls, err := bfs.ReadDirDirs(wingRel)
if err != nil {
return nil, fmt.Errorf("read wing: %w", err)
}
for _, hall := range halls {
if !IsValidHall(hall) {
continue
}
hallRel := wingRel + "/" + hall
names, err := bfs.ReadDirRegular(hallRel)
if err != nil {
return nil, fmt.Errorf("read hall %s: %w", hall, err)
}
for _, name := range names {
if !strings.HasSuffix(name, ".md") || name == "_index.md" {
continue
}
slug := strings.TrimSuffix(name, ".md")
title, created := readTitleAndCreatedIn(bfs, hallRel+"/"+name, slug)
out = append(out, noteEntry{Hall: hall, Slug: slug, Title: title, Created: created})
}
}
return out, nil
}
// readTitleAndCreatedIn is readTitleAndCreated through the brain root,
// regular files only (hyperguild#157).
func readTitleAndCreatedIn(bfs *brainfs.Brain, rel, slug string) (string, string) {
content, err := bfs.ReadRegular(rel)
if err != nil {
return slug, ""
}
title, created := parseTitleAndCreated(bytes.NewReader(content))
if created == "" {
if info, err := bfs.Lstat(rel); err == nil {
created = info.ModTime().UTC().Format("2006-01-02")
}
}
return titleOrSlug(title, slug), created
}
func titleOrSlug(title, slug string) string {
if title == "" {
return strings.ReplaceAll(slug, "-", " ")
}
return title
}
func parseTitleAndCreated(r io.Reader) (title, created string) {
scanner := bufio.NewScanner(r)
inFrontmatter := false
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == "---" {
if !inFrontmatter {
inFrontmatter = true
continue
}
break
}
if !inFrontmatter {
continue
}
key, val, ok := strings.Cut(line, ":")
if !ok {
continue
}
v := strings.Trim(strings.TrimSpace(val), `"'`)
switch strings.TrimSpace(key) {
case "title":
title = v
case "created_at":
if t, err := time.Parse(time.RFC3339, v); err == nil {
created = t.UTC().Format("2006-01-02")
} else {
created = v
}
}
}
return title, created
}