From bf6f497d9d09af7c404a3b714afce7b6fb55fb06 Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Thu, 23 Apr 2026 15:57:16 +0200 Subject: [PATCH] feat(wiki): add Aliases to Entry and read from YAML frontmatter --- ingestion/internal/wiki/inventory.go | 43 +++++++++++++++++------ ingestion/internal/wiki/inventory_test.go | 21 +++++++++++ ingestion/internal/wiki/slug.go | 2 +- ingestion/internal/wiki/types.go | 7 ++-- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/ingestion/internal/wiki/inventory.go b/ingestion/internal/wiki/inventory.go index 43a4ad1..d887d48 100644 --- a/ingestion/internal/wiki/inventory.go +++ b/ingestion/internal/wiki/inventory.go @@ -32,23 +32,26 @@ func LoadInventory(brainDir string) (map[PageType][]Entry, error) { } slug := strings.TrimSuffix(e.Name(), ".md") path := filepath.Join(dir, e.Name()) - title := readTitle(path, slug) - result[pt] = append(result[pt], Entry{Slug: slug, Title: title, Type: pt}) + title, aliases := readFrontmatter(path, slug) + result[pt] = append(result[pt], Entry{Slug: slug, Title: title, Aliases: aliases, Type: pt}) } } return result, nil } -// readTitle extracts the title from YAML frontmatter, falling back to slug. -func readTitle(path, fallback string) string { +// readFrontmatter extracts title and aliases from YAML frontmatter. +// Falls back to slug for title and empty aliases on any error. +func readFrontmatter(path, fallbackSlug string) (title string, aliases []string) { + title = fallbackSlug f, err := os.Open(path) if err != nil { - return fallback + return } defer f.Close() scanner := bufio.NewScanner(f) inFM := false + inAliases := false for scanner.Scan() { line := scanner.Text() if strings.TrimSpace(line) == "---" { @@ -56,14 +59,32 @@ func readTitle(path, fallback string) string { inFM = true continue } - break + break // end of frontmatter } - if inFM { - key, val, ok := strings.Cut(line, ":") - if ok && strings.TrimSpace(key) == "title" { - return strings.Trim(strings.TrimSpace(val), `"'`) + if !inFM { + continue + } + + // Detect alias list items (lines starting with " - "). + if inAliases { + trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, "- ") { + aliases = append(aliases, strings.TrimPrefix(trimmed, "- ")) + continue } + inAliases = false // end of alias block + } + + key, val, ok := strings.Cut(line, ":") + if !ok { + continue + } + switch strings.TrimSpace(key) { + case "title": + title = strings.Trim(strings.TrimSpace(val), `"'`) + case "aliases": + inAliases = true } } - return fallback + return } diff --git a/ingestion/internal/wiki/inventory_test.go b/ingestion/internal/wiki/inventory_test.go index 7a485dc..7068fdb 100644 --- a/ingestion/internal/wiki/inventory_test.go +++ b/ingestion/internal/wiki/inventory_test.go @@ -60,3 +60,24 @@ func TestLoadInventory_MissingDirsOk(t *testing.T) { require.NoError(t, err) assert.NotNil(t, inv) } + +func TestLoadInventory_ReadsAliases(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki", "entities"), 0o755)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki", "concepts"), 0o755)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki", "sources"), 0o755)) + + require.NoError(t, os.WriteFile( + filepath.Join(dir, "wiki", "entities", "ryan-singer.md"), + []byte("---\ntitle: Ryan Singer\naliases:\n - Singer\n - R. Singer\n---\n\n## Description\n\nDesigner.\n"), + 0o644, + )) + + inv, err := LoadInventory(dir) + require.NoError(t, err) + + require.Len(t, inv[PageTypeEntity], 1) + e := inv[PageTypeEntity][0] + assert.Equal(t, "Ryan Singer", e.Title) + assert.Equal(t, []string{"Singer", "R. Singer"}, e.Aliases) +} diff --git a/ingestion/internal/wiki/slug.go b/ingestion/internal/wiki/slug.go index ab39b46..feb1681 100644 --- a/ingestion/internal/wiki/slug.go +++ b/ingestion/internal/wiki/slug.go @@ -21,7 +21,7 @@ func Slug(title string) string { case unicode.IsLetter(r) || unicode.IsDigit(r): b.WriteRune(r) prevHyphen = false - // all other characters (apostrophes, colons, dots, etc.) are dropped + // all other characters (apostrophes, colons, dots, etc.) are dropped } } return strings.TrimRight(b.String(), "-") diff --git a/ingestion/internal/wiki/types.go b/ingestion/internal/wiki/types.go index b555d91..62e74aa 100644 --- a/ingestion/internal/wiki/types.go +++ b/ingestion/internal/wiki/types.go @@ -18,7 +18,8 @@ type Page struct { // Entry is a summary of an existing wiki page used to build the inventory. type Entry struct { - Slug string - Title string - Type PageType + Slug string + Title string + Aliases []string + Type PageType }