refactor: replace inline frontmatter code with frontmatter package

Removed ~60 lines of inline frontmatter parsing (FMState, Frontmatter,
ParseResult types, fmFoldLine, parseFrontmatter, and accessor functions).
Now uses the frontmatter package via import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 14:54:45 -05:00
parent e835b952a5
commit 42e9676956
4 changed files with 14 additions and 143 deletions

View File

@@ -1,79 +0,0 @@
// YAML-like frontmatter parser
// Parses the --- delimited frontmatter block from markdown files
pub type Frontmatter =
| Frontmatter(String, String, String, String)
// title date desc tagsRaw
pub type ParseResult =
| ParseResult(Frontmatter, String)
// front body
pub fn parse(content: String): ParseResult = {
let lines = String.lines(content);
// Skip first --- line, collect key: value pairs until next ---
let result = List.fold(lines, (false, false, "", "", "", "", ""), fn(acc: (Bool, Bool, String, String, String, String, String), line: String): (Bool, Bool, String, String, String, String, String) => {
let inFront = acc.0;
let pastFront = acc.1;
let title = acc.2;
let date = acc.3;
let desc = acc.4;
let tags = acc.5;
let body = acc.6;
if pastFront then
(inFront, pastFront, title, date, desc, tags, body + line + "\n")
else if String.trim(line) == "---" then
if inFront then
// End of frontmatter
(false, true, title, date, desc, tags, body)
else
// Start of frontmatter
(true, false, title, date, desc, tags, body)
else if inFront then {
// Parse key: value
match String.indexOf(line, ": ") {
Some(idx) => {
let key = String.trim(String.substring(line, 0, idx));
let rawVal = String.trim(String.substring(line, idx + 2, String.length(line)));
// Strip surrounding quotes if present
let val = if String.startsWith(rawVal, "\"") then
String.substring(rawVal, 1, String.length(rawVal) - 1)
else
rawVal;
if key == "title" then
(inFront, pastFront, val, date, desc, tags, body)
else if key == "date" then
(inFront, pastFront, title, val, desc, tags, body)
else if key == "description" then
(inFront, pastFront, title, date, val, tags, body)
else if key == "tags" then
(inFront, pastFront, title, date, desc, val, body)
else
(inFront, pastFront, title, date, desc, tags, body)
},
None => (inFront, pastFront, title, date, desc, tags, body)
}
} else
(inFront, pastFront, title, date, desc, tags, body)
});
let front = Frontmatter(result.2, result.3, result.4, result.5);
ParseResult(front, result.6)
}
pub fn getTitle(f: Frontmatter): String =
match f { Frontmatter(t, _, _, _) => t }
pub fn getDate(f: Frontmatter): String =
match f { Frontmatter(_, d, _, _) => d }
pub fn getDesc(f: Frontmatter): String =
match f { Frontmatter(_, _, d, _) => d }
pub fn getTagsRaw(f: Frontmatter): String =
match f { Frontmatter(_, _, _, t) => t }
pub fn getTags(f: Frontmatter): List<String> =
match f { Frontmatter(_, _, _, t) =>
if t == "" then []
else String.split(t, " ")
}

View File

@@ -1,5 +1,10 @@
# This file is auto-generated by lux pkg. Do not edit manually. # This file is auto-generated by lux pkg. Do not edit manually.
[[package]]
name = "frontmatter"
version = "0.1.0"
source = "path:../../packages/frontmatter"
[[package]] [[package]]
name = "markdown" name = "markdown"
version = "0.1.0" version = "0.1.0"

View File

@@ -5,3 +5,4 @@ description = "A Lux project"
[dependencies] [dependencies]
markdown = { version = "0.1.0", path = "../../packages/markdown" } markdown = { version = "0.1.0", path = "../../packages/markdown" }
frontmatter = { version = "0.1.0", path = "../../packages/frontmatter" }

View File

@@ -1,20 +1,12 @@
import markdown import markdown
import frontmatter
type SiteConfig = type SiteConfig =
| SiteConfig(String, String, String, String, String, String, String) | SiteConfig(String, String, String, String, String, String, String)
type Frontmatter =
| Frontmatter(String, String, String, String)
type ParseResult =
| ParseResult(Frontmatter, String)
type Page = type Page =
| Page(String, String, String, String, String) | Page(String, String, String, String, String)
type FMState =
| FMState(Bool, Bool, String, String, String, String, String)
type TagEntry = type TagEntry =
| TagEntry(String, String, String, String, String) | TagEntry(String, String, String, String, String)
@@ -103,49 +95,6 @@ fn cfgStaticDir(c: SiteConfig): String =
SiteConfig(_, _, _, _, _, _, sd) => sd, SiteConfig(_, _, _, _, _, _, sd) => sd,
} }
fn fmFoldLine(acc: FMState, line: String): FMState =
match acc {
FMState(inFront, pastFront, title, date, desc, tags, body) => if pastFront then FMState(inFront, pastFront, title, date, desc, tags, body + line + "
") else if String.trim(line) == "---" then if inFront then FMState(false, true, title, date, desc, tags, body) else FMState(true, false, title, date, desc, tags, body) else if inFront then match String.indexOf(line, ": ") {
Some(idx) => {
let key = String.trim(String.substring(line, 0, idx))
let rawVal = String.trim(String.substring(line, idx + 2, String.length(line)))
let val = if String.startsWith(rawVal, "\"") then String.substring(rawVal, 1, String.length(rawVal) - 1) else rawVal
if key == "title" then FMState(inFront, pastFront, val, date, desc, tags, body) else if key == "date" then FMState(inFront, pastFront, title, val, desc, tags, body) else if key == "description" then FMState(inFront, pastFront, title, date, val, tags, body) else if key == "tags" then FMState(inFront, pastFront, title, date, desc, val, body) else acc
},
None => acc,
} else acc,
}
fn parseFrontmatter(content: String): ParseResult = {
let lines = String.lines(content)
let init = FMState(false, false, "", "", "", "", "")
let result = List.fold(lines, init, fmFoldLine)
match result {
FMState(_, _, title, date, desc, tags, body) => ParseResult(Frontmatter(title, date, desc, tags), body),
}
}
fn fmTitle(f: Frontmatter): String =
match f {
Frontmatter(t, _, _, _) => t,
}
fn fmDate(f: Frontmatter): String =
match f {
Frontmatter(_, d, _, _) => d,
}
fn fmTagsRaw(f: Frontmatter): String =
match f {
Frontmatter(_, _, _, t) => t,
}
fn fmTags(f: Frontmatter): List<String> =
match f {
Frontmatter(_, _, _, t) => if t == "" then [] else String.split(t, " "),
}
fn pgDate(p: Page): String = fn pgDate(p: Page): String =
match p { match p {
Page(d, _, _, _, _) => d, Page(d, _, _, _, _) => d,
@@ -262,18 +211,15 @@ fn htmlTagPage(tagName: String, postsHtml: String): String = "<div class=\"page
fn parseFile(path: String): Page with {File} = { fn parseFile(path: String): Page with {File} = {
let raw = File.read(path) let raw = File.read(path)
let parsed = parseFrontmatter(raw) let doc = frontmatter.parse(raw)
match parsed { let title = frontmatter.title(doc)
ParseResult(front, body) => { let date = frontmatter.date(doc)
let title = fmTitle(front) let tags = frontmatter.getOrDefault(doc, "tags", "")
let date = fmDate(front) let body = frontmatter.body(doc)
let tags = fmTagsRaw(front)
let htmlContent = convertMd(body) let htmlContent = convertMd(body)
let filename = basename(path) let filename = basename(path)
let slug = slugFromFilename(filename) let slug = slugFromFilename(filename)
Page(date, title, slug, tags, htmlContent) Page(date, title, slug, tags, htmlContent)
},
}
} }
fn mapParseFiles(dir: String, files: List<String>): List<Page> with {File} = fn mapParseFiles(dir: String, files: List<String>): List<Page> with {File} =
@@ -384,10 +330,8 @@ fn writeTagPages(outputDir: String, allTagEntries: List<TagEntry>, siteTitle: St
fn renderSnippetFile(snippetDir: String, filename: String): String with {File} = { fn renderSnippetFile(snippetDir: String, filename: String): String with {File} = {
let raw = File.read(snippetDir + "/" + filename) let raw = File.read(snippetDir + "/" + filename)
let parsed = parseFrontmatter(raw) let doc = frontmatter.parse(raw)
match parsed { htmlSnippetCard(convertMd(frontmatter.body(doc)))
ParseResult(_, body) => htmlSnippetCard(convertMd(body)),
}
} }
fn renderSnippets(snippetDir: String, files: List<String>): List<String> with {File} = fn renderSnippets(snippetDir: String, files: List<String>): List<String> with {File} =