From 3cb7c82cf7c4e050148f69be23590a7fbe587a27 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 10:11:18 +0000 Subject: Add static site builder: SQLite-backed MD→HTML pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cmd/nebbet: CLI with build [--watch] and user add/passwd/delete/list - internal/builder: markdown→HTML, component injection via HTML comments, auto importmap from lib/, fsnotify watch with 150ms debounce - internal/db: meta.db (page index, tag queries) + search.db (FTS5) - internal/sqlitedrv: minimal CGO database/sql driver for system libsqlite3 - internal/auth: htpasswd-compatible bcrypt password file management - templates/base.html + admin.html, styles/main.css + admin.css - nginx.conf with auth_basic for /admin, clean URLs, gzip - nebbet.service systemd unit for watch daemon - Example content/index.md and components/site-greeting.js https://claude.ai/code/session_01HTc1BCBCiMTEB54XQP1Wz9 --- internal/builder/frontmatter.go | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 internal/builder/frontmatter.go (limited to 'internal/builder/frontmatter.go') diff --git a/internal/builder/frontmatter.go b/internal/builder/frontmatter.go new file mode 100644 index 0000000..34de484 --- /dev/null +++ b/internal/builder/frontmatter.go @@ -0,0 +1,64 @@ +package builder + +import ( + "strings" +) + +// Frontmatter holds parsed page metadata from YAML-style front matter. +type Frontmatter struct { + Title string + Date string + Tags []string + Layout string // template name without extension, default "base" + Draft bool +} + +// ParseFrontmatter splits the optional ---...--- block from the markdown body. +// Supports: title, date, tags (comma-list or [a, b]), layout, draft. +func ParseFrontmatter(content string) (Frontmatter, string) { + fm := Frontmatter{Layout: "base"} + if !strings.HasPrefix(content, "---") { + return fm, content + } + // Find closing --- + rest := content[3:] + end := strings.Index(rest, "\n---") + if end == -1 { + return fm, content + } + block := strings.TrimSpace(rest[:end]) + body := strings.TrimSpace(rest[end+4:]) // skip \n--- + + for _, line := range strings.Split(block, "\n") { + k, v, ok := strings.Cut(strings.TrimSpace(line), ":") + if !ok { + continue + } + k = strings.TrimSpace(k) + v = strings.TrimSpace(v) + switch k { + case "title": + fm.Title = strings.Trim(v, `"'`) + case "date": + fm.Date = v + case "layout": + fm.Layout = strings.Trim(v, `"'`) + case "draft": + fm.Draft = v == "true" + case "tags": + fm.Tags = parseTags(v) + } + } + return fm, body +} + +func parseTags(v string) []string { + v = strings.Trim(v, "[] ") + var tags []string + for _, p := range strings.Split(v, ",") { + if t := strings.Trim(strings.TrimSpace(p), `"'`); t != "" { + tags = append(tags, t) + } + } + return tags +} -- cgit v1.3