457 lines
14 KiB
Go
457 lines
14 KiB
Go
// 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)
|
||
}
|
||
}
|