Files
ai-resume/internal/content/content.go

457 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package content loads the Markdown source of truth in content/ into documents
// that both renderers (Mode A static pages, Mode B corpus) read.
//
// The schema is frozen; see docs/content-schema.md. This package does not
// re-implement the validator (tools/validate_content.py is the gate). It does
// enforce the invariants the renderer needs, and it fails loudly rather than
// rendering a broken page.
package content
import (
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"gitea.lab.audasmedia.com.au/sam/ai-resume/internal/stage"
"gopkg.in/yaml.v3"
)
// Confidence values. These are displayed on the page and are never promoted.
const (
ConfidenceDelivered = "delivered"
ConfidenceRunning = "running"
ConfidenceInProgress = "in-progress"
ConfidenceAbandoned = "abandoned"
ConfidenceHistoric = "historic"
)
// Area is one of the six top-level navigation areas. Structure lives in code
// (it comes from reference/content-map.md, not from the content tree); the
// documents that fill it live in content/.
type Area struct {
Key string
Title string
Role string
Blurb string
Gutter string // two-letter mark used by the nav and the stage panel
}
// Areas is the fixed navigation order from reference/content-map.md.
var Areas = []Area{
{Key: "ai", Title: "AI & Agentic Systems", Role: "AI / LLM Engineer", Gutter: "AI",
Blurb: "Harnesses, subagents and skills, memory, model routing and cost, retrieval, voice, guardrails, agent observability."},
{Key: "platform", Title: "Platform & Infrastructure", Role: "Platform / DevOps Engineer", Gutter: "PL",
Blurb: "Declarative hosts, packaging, a fifty-container Docker estate, networking, storage and restore-tested backups, observability."},
{Key: "edge", Title: "Edge & Devices", Role: "Edge / IoT Engineer", Gutter: "ED",
Blurb: "Twenty-node fleet on constrained devices, custom radio, solar power, deep sleep, sensors and actuation — and the failures."},
{Key: "products", Title: "Products", Role: "", Gutter: "PR",
Blurb: "Three active businesses, built and run rather than advised on."},
{Key: "history", Title: "History", Role: "", Gutter: "HI",
Blurb: "2000–2014: agencies, clients, engagements, and the code that survives from them."},
{Key: "about", Title: "About", Role: "", Gutter: "AB",
Blurb: "The narrative, the non-IT work that came first, education, and how to get in touch."},
}
// Positioning is the site's single line of introduction.
const (
Positioning = "Twenty-five years shipping software. Now building agent systems, the platforms under them, and the hardware at the edge."
SupportLine = "…and it all runs unattended."
Location = "Melbourne"
)
// AreaByKey returns the area with the given key.
func AreaByKey(key string) (Area, bool) {
for _, a := range Areas {
if a.Key == key {
return a, true
}
}
return Area{}, false
}
// Metric is one approved chip on a page, e.g. {label: "edge nodes", value: 20}.
type Metric struct {
Label string `yaml:"label"`
Value any `yaml:"value"`
}
// String renders the chip value with its label: "20 edge nodes".
func (m Metric) String() string {
return fmt.Sprintf("%v %s", m.Value, m.Label)
}
// Artefact is a drill-down link. Every URL must resolve to a file in this
// repository; nothing external is allowed.
type Artefact struct {
Type string `yaml:"type"` // repo | image | video | doc
Label string `yaml:"label"`
URL string `yaml:"url"`
}
// Document is one Markdown file plus its front-matter.
type Document struct {
ID string `yaml:"id"`
Area string `yaml:"area"`
Level int `yaml:"level"`
Title string `yaml:"title"`
Claim string `yaml:"claim"`
Confidence string `yaml:"confidence"`
Period string `yaml:"period"`
Tech []string `yaml:"tech"`
Metrics []Metric `yaml:"metrics"`
Artefacts []Artefact `yaml:"artefacts"`
Public bool `yaml:"public"`
LastVerified string `yaml:"last_verified"`
Tags []string `yaml:"tags"`
// Derived by Load.
Path string `yaml:"-"` // path relative to the content root, e.g. edge/edge-fleet.md
URL string `yaml:"-"` // canonical page URL, e.g. /edge/edge-fleet/
HTML string `yaml:"-"` // rendered body
Plain string `yaml:"-"` // body reduced to text, for the search index
bodyRaw string // Markdown body, rendered by the markdown renderer
// Backlinks are the documents that reference this one as an artefact.
Backlinks []*Document `yaml:"-"`
}
// ResolvedArtefact is an Artefact with its target resolved.
type ResolvedArtefact struct {
Artefact
Target *Document // non-nil when the artefact is an L3/L2 document
File string // absolute path on disk
Rel string // path relative to the content root, e.g. assets/x.svg
Href string // site URL: a page URL for documents, /assets/... for files
Kind string // Fragment kind: text | code | diagram | image | video | links
}
// Set is every loaded document, indexed.
type Set struct {
Root string
Docs []*Document
byID map[string]*Document
byRel map[string]*Document
}
// ByID returns the document with the given id.
func (s *Set) ByID(id string) (*Document, bool) {
d, ok := s.byID[id]
return d, ok
}
// ByRel returns the document at the given content-relative path.
func (s *Set) ByRel(rel string) (*Document, bool) {
d, ok := s.byRel[rel]
return d, ok
}
// InArea returns the public documents for an area, ordered L1, L2, L3.
func (s *Set) InArea(key string) []*Document {
var out []*Document
for _, d := range s.Docs {
if d.Area == key {
out = append(out, d)
}
}
sort.SliceStable(out, func(i, j int) bool {
if out[i].Level != out[j].Level {
return out[i].Level < out[j].Level
}
return out[i].Title < out[j].Title
})
return out
}
// AreaLanding returns the L1 document for an area.
func (s *Set) AreaLanding(key string) (*Document, bool) {
for _, d := range s.InArea(key) {
if d.Level == 1 {
return d, true
}
}
return nil, false
}
// Load reads every Markdown file under root. Documents with public: false are
// loaded (so links to them still resolve) but are not published.
func Load(root string) (*Set, error) {
info, err := os.Stat(root)
if err != nil {
return nil, fmt.Errorf("content root %q: %w", root, err)
}
if !info.IsDir() {
return nil, fmt.Errorf("content root %q is not a directory", root)
}
s := &Set{Root: root, byID: map[string]*Document{}, byRel: map[string]*Document{}}
err = filepath.WalkDir(root, func(p string, e os.DirEntry, err error) error {
if err != nil {
return err
}
name := e.Name()
if e.IsDir() {
if p != root && (strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_")) {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(name, ".md") || strings.HasPrefix(name, ".") {
return nil
}
rel, rerr := filepath.Rel(root, p)
if rerr != nil {
return rerr
}
rel = filepath.ToSlash(rel)
raw, rerr := os.ReadFile(p)
if rerr != nil {
return rerr
}
doc, derr := parse(rel, p, raw)
if derr != nil {
return derr
}
if prev, dup := s.byID[doc.ID]; dup {
return fmt.Errorf("%s: duplicate id %q (already used by %s)", rel, doc.ID, prev.Path)
}
doc.URL = PageURL(doc)
s.Docs = append(s.Docs, doc)
s.byID[doc.ID] = doc
s.byRel[rel] = doc
return nil
})
if err != nil {
return nil, err
}
if len(s.Docs) == 0 {
return nil, fmt.Errorf("content root %q contains no documents", root)
}
sort.SliceStable(s.Docs, func(i, j int) bool {
if s.Docs[i].Area != s.Docs[j].Area {
return areaOrder(s.Docs[i].Area) < areaOrder(s.Docs[j].Area)
}
if s.Docs[i].Level != s.Docs[j].Level {
return s.Docs[i].Level < s.Docs[j].Level
}
return s.Docs[i].ID < s.Docs[j].ID
})
for _, d := range s.Docs {
for _, a := range d.Artefacts {
ra, rerr := s.Resolve(d, a)
if rerr != nil {
return nil, rerr
}
if ra.Target != nil && ra.Target.ID != d.ID {
ra.Target.Backlinks = append(ra.Target.Backlinks, d)
}
}
}
return s, nil
}
func areaOrder(key string) int {
for i, a := range Areas {
if a.Key == key {
return i
}
}
return len(Areas)
}
func parse(rel, abs string, raw []byte) (*Document, error) {
fm, body, err := splitFrontMatter(string(raw))
if err != nil {
return nil, fmt.Errorf("%s: %w", rel, err)
}
var d Document
dec := yaml.NewDecoder(strings.NewReader(fm))
dec.KnownFields(true) // unknown front-matter fields are a schema violation
if err := dec.Decode(&d); err != nil {
return nil, fmt.Errorf("%s: front-matter: %w", rel, err)
}
d.Path = rel
d.bodyRaw = body
if err := d.check(rel); err != nil {
return nil, err
}
return &d, nil
}
// BodyMarkdown returns the raw Markdown body (everything after the front-matter).
func (d *Document) BodyMarkdown() string { return d.bodyRaw }
func (d *Document) check(rel string) error {
switch {
case d.ID == "":
return fmt.Errorf("%s: id is required", rel)
case d.Title == "":
return fmt.Errorf("%s: title is required", rel)
case d.Claim == "":
return fmt.Errorf("%s: claim is required", rel)
}
if _, ok := AreaByKey(d.Area); !ok {
return fmt.Errorf("%s: unknown area %q", rel, d.Area)
}
if d.Level < 1 || d.Level > 3 {
return fmt.Errorf("%s: level must be 1, 2 or 3, got %d", rel, d.Level)
}
switch d.Confidence {
case ConfidenceDelivered, ConfidenceRunning, ConfidenceInProgress, ConfidenceAbandoned, ConfidenceHistoric:
default:
return fmt.Errorf("%s: unknown confidence %q", rel, d.Confidence)
}
if d.LastVerified == "" {
return fmt.Errorf("%s: last_verified is required", rel)
}
stem := strings.TrimSuffix(path.Base(rel), ".md")
if stem != d.ID {
return fmt.Errorf("%s: id %q must equal the file name %q", rel, d.ID, stem)
}
for _, a := range d.Artefacts {
if a.URL == "" || a.Label == "" {
return fmt.Errorf("%s: artefact %q needs both label and url", rel, a.Label)
}
if strings.Contains(a.URL, "://") {
return fmt.Errorf("%s: artefact %q uses an external URL; create an in-repo stub instead", rel, a.URL)
}
}
return nil
}
func splitFrontMatter(src string) (fm, body string, err error) {
src = strings.ReplaceAll(src, "\r\n", "\n")
if !strings.HasPrefix(src, "---\n") {
return "", "", fmt.Errorf("missing front-matter block on line 1")
}
rest := src[len("---\n"):]
for _, delim := range []string{"\n---\n", "\n...\n"} {
if i := strings.Index(rest, delim); i >= 0 {
return rest[:i], rest[i+len(delim):], nil
}
}
return "", "", fmt.Errorf("unterminated front-matter block")
}
// PageURL is the canonical URL for a document. Depth maps onto the URL path so
// the drill-down is legible: /edge/ · /edge/edge-fleet/ · /edge/a/edge-zigbee/.
func PageURL(d *Document) string {
switch d.Level {
case 1:
return "/" + d.Area + "/"
case 2:
return "/" + d.Area + "/" + d.ID + "/"
default:
return "/" + d.Area + "/a/" + d.ID + "/"
}
}
// Resolve maps an artefact reference onto a file, a site URL and a stage
// fragment kind. Paths may be repository-relative ("content/edge/l3/x.md") or
// document-relative ("./l3/x.md", "l3/x.md").
func (s *Set) Resolve(from *Document, a Artefact) (ResolvedArtefact, error) {
rel := a.URL
var target string
switch {
case strings.HasPrefix(rel, "content/"):
target = strings.TrimPrefix(rel, "content/")
case strings.HasPrefix(rel, "/") || strings.HasPrefix(rel, "../"):
return ResolvedArtefact{}, fmt.Errorf("%s: artefact %q must not be root-absolute or escape the repo", from.Path, rel)
default:
target = path.Join(path.Dir(from.Path), rel)
}
target = path.Clean(target)
out := ResolvedArtefact{Artefact: a, Rel: target}
abs := filepath.Join(s.Root, filepath.FromSlash(target))
st, err := os.Stat(abs)
if err != nil {
return out, fmt.Errorf("%s: artefact %q does not resolve to a file (%s)", from.Path, a.URL, target)
}
if st.IsDir() {
return out, fmt.Errorf("%s: artefact %q resolves to a directory", from.Path, a.URL)
}
out.File = abs
if strings.HasSuffix(target, ".md") {
doc, ok := s.byRel[target]
if !ok {
return out, fmt.Errorf("%s: artefact %q is not a loaded document", from.Path, a.URL)
}
out.Target = doc
out.Href = doc.URL
out.Kind = stage.KindText
return out, nil
}
out.Href = "/assets/" + strings.TrimPrefix(target, "assets/")
out.Kind = KindForFile(target)
return out, nil
}
var codeExtensions = map[string]string{
".go": "go", ".js": "javascript", ".mjs": "javascript", ".ts": "typescript",
".nix": "nix", ".php": "php", ".as": "actionscript", ".hpp": "cpp", ".h": "cpp",
".c": "c", ".cpp": "cpp", ".py": "python", ".sh": "bash", ".yaml": "yaml",
".yml": "yaml", ".json": "json", ".css": "css", ".sql": "sql", ".templ": "templ",
}
// KindForFile maps a file extension onto a stage fragment kind.
func KindForFile(name string) string {
ext := strings.ToLower(path.Ext(name))
if lang, ok := codeExtensions[ext]; ok && lang != "" {
return stage.KindCode
}
switch ext {
case ".svg":
return stage.KindDiagram
case ".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif":
return stage.KindImage
case ".mp4", ".webm", ".mov":
return stage.KindVideo
case ".md":
return stage.KindText
}
return stage.KindLinks
}
// LanguageFor returns the chroma language for a file, if it is code.
func LanguageFor(name string) string {
return codeExtensions[strings.ToLower(path.Ext(name))]
}
// Truncate shortens s to at most n runes, on a word boundary where possible.
func Truncate(s string, n int) string {
s = strings.TrimSpace(s)
if len([]rune(s)) <= n {
return s
}
r := []rune(s)[:n]
if i := strings.LastIndex(string(r), " "); i > n/2 {
return strings.TrimSpace(string(r)[:i]) + "…"
}
return strings.TrimSpace(string(r)) + "…"
}
// MetricValue renders a metric value for display.
func MetricValue(v any) string {
switch t := v.(type) {
case nil:
return ""
case string:
return t
case int:
return strconv.Itoa(t)
case bool:
return strconv.FormatBool(t)
default:
return fmt.Sprint(t)
}
}