feat(pipeline): write into wiki/<wing>/{sources,facts}/ when Config.Wing is set (#205 S2)
Sources -> wiki/<wing>/sources, concepts/entities -> wiki/<wing>/facts with kind: frontmatter; inventory, title resolution and back-references are confined to the target wing. Empty Wing keeps the retired layout unchanged. Mutation-checked: widening the inventory beyond the wing fails the cross-wing back-reference test; a non-plain wing name is rejected. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,3 +96,79 @@ func readFrontmatter(bfs *brainfs.Brain, rel, fallbackSlug string) (title string
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LoadWingInventory returns the pages of ONE wing, grouped by type. Sources
|
||||
// come from wiki/<wing>/sources/; concepts and entities live together in
|
||||
// wiki/<wing>/facts/ and are told apart by their `kind:` frontmatter. A facts
|
||||
// page without a kind (a hand-written note) is left out, so extraction can
|
||||
// never merge into something it did not create. Other wings are never read.
|
||||
func LoadWingInventory(brainDir, wing string) (map[PageType][]Entry, error) {
|
||||
result := map[PageType][]Entry{PageTypeConcept: {}, PageTypeEntity: {}, PageTypeSource: {}}
|
||||
bfs, err := brainfs.Open(brainDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open brain: %w", err)
|
||||
}
|
||||
defer func() { _ = bfs.Close() }()
|
||||
for _, hall := range []string{"sources", "facts"} {
|
||||
dir := "wiki/" + wing + "/" + hall
|
||||
names, err := bfs.ReadDirRegular(dir)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read dir %s: %w", dir, err)
|
||||
}
|
||||
for _, name := range names {
|
||||
if !strings.HasSuffix(name, ".md") {
|
||||
continue
|
||||
}
|
||||
rel := dir + "/" + name
|
||||
slug := strings.TrimSuffix(name, ".md")
|
||||
pt := PageTypeSource
|
||||
if hall == "facts" {
|
||||
var ok bool
|
||||
if pt, ok = kindOf(bfs, rel); !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
title, aliases := readFrontmatter(bfs, rel, slug)
|
||||
result[pt] = append(result[pt], Entry{Slug: slug, Title: title, Aliases: aliases, Type: pt, Path: rel})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// kindOf reads `kind:` from a facts page's frontmatter.
|
||||
func kindOf(bfs *brainfs.Brain, rel string) (PageType, bool) {
|
||||
content, err := bfs.ReadRegular(rel)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return KindFromContent(string(content))
|
||||
}
|
||||
|
||||
// KindFromContent maps frontmatter `kind: concept|entity` to a PageType.
|
||||
func KindFromContent(content string) (PageType, bool) {
|
||||
inFM := false
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
if strings.TrimSpace(line) == "---" {
|
||||
if inFM {
|
||||
break
|
||||
}
|
||||
inFM = true
|
||||
continue
|
||||
}
|
||||
if !inFM {
|
||||
continue
|
||||
}
|
||||
if key, val, ok := strings.Cut(line, ":"); ok && strings.TrimSpace(key) == "kind" {
|
||||
switch strings.Trim(strings.TrimSpace(val), `"'`) {
|
||||
case "concept":
|
||||
return PageTypeConcept, true
|
||||
case "entity":
|
||||
return PageTypeEntity, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
@@ -22,4 +22,5 @@ type Entry struct {
|
||||
Title string
|
||||
Aliases []string
|
||||
Type PageType
|
||||
Path string // brain-relative path; set for wing-layout entries
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user