Add frontmatter, markdown, path, xml, rss, and web packages

Sync local packages into the registry repo and update index.json
and README.md to include all 9 packages.
This commit is contained in:
2026-02-24 21:04:20 -05:00
parent c5a2276f6e
commit cbb66fbb73
42 changed files with 3844 additions and 0 deletions

3
packages/frontmatter/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
_site/
.lux_packages/
*.bak

View File

@@ -0,0 +1,168 @@
// frontmatter - YAML-like frontmatter parser for Lux
//
// Parses documents with --- delimited frontmatter:
// ---
// title: My Post
// date: 2025-01-29
// tags: web blog
// ---
// Body content here...
// A single key-value pair from the frontmatter
type Entry =
| Entry(String, String)
// Parse result: list of key-value entries and the body text
type Document =
| Document(List<Entry>, String)
// Internal parser state
type PState =
| PState(Bool, Bool, List<Entry>, String)
fn psInFront(s: PState): Bool =
match s {
PState(f, _, _, _) => f,
}
fn psPastFront(s: PState): Bool =
match s {
PState(_, p, _, _) => p,
}
fn psEntries(s: PState): List<Entry> =
match s {
PState(_, _, e, _) => e,
}
fn psBody(s: PState): String =
match s {
PState(_, _, _, b) => b,
}
// Strip surrounding quotes from a value if present
fn stripQuotes(s: String): String =
if String.length(s) >= 2 then
if String.startsWith(s, "\"") then
if String.endsWith(s, "\"") then
String.substring(s, 1, String.length(s) - 1)
else s
else if String.startsWith(s, "'") then
if String.endsWith(s, "'") then
String.substring(s, 1, String.length(s) - 1)
else s
else s
else s
// Parse a single line within the frontmatter block
fn parseFrontLine(entries: List<Entry>, line: String): List<Entry> =
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 = stripQuotes(rawVal)
List.concat(entries, [Entry(key, val)])
},
None => {
// Handle "key:" with no value (treat as empty string)
let trimmed = String.trim(line)
if String.endsWith(trimmed, ":") then {
let key = String.substring(trimmed, 0, String.length(trimmed) - 1)
List.concat(entries, [Entry(key, "")])
} else
entries
},
}
// Fold function that processes one line at a time
fn foldLine(acc: PState, line: String): PState = {
let inFront = psInFront(acc)
let pastFront = psPastFront(acc)
let entries = psEntries(acc)
let body = psBody(acc)
if pastFront then
PState(inFront, pastFront, entries, body + line + "\n")
else if String.trim(line) == "---" then
if inFront then
PState(false, true, entries, body)
else
PState(true, false, entries, body)
else if inFront then
PState(inFront, pastFront, parseFrontLine(entries, line), body)
else
acc
}
// Parse a document string into frontmatter entries and body content.
//
// Returns a Document with an empty entry list if no frontmatter is found.
pub fn parse(content: String): Document = {
let lines = String.lines(content)
let init = PState(false, false, [], "")
let result = List.fold(lines, init, foldLine)
Document(psEntries(result), psBody(result))
}
// Get the list of key-value entries from a Document
pub fn entries(doc: Document): List<Entry> =
match doc {
Document(e, _) => e,
}
// Get the body text from a Document
pub fn body(doc: Document): String =
match doc {
Document(_, b) => b,
}
// Look up a value by key. Returns the first matching entry.
pub fn get(doc: Document, key: String): Option<String> = {
let es = entries(doc)
getFromEntries(es, key)
}
fn getFromEntries(es: List<Entry>, key: String): Option<String> =
match List.head(es) {
Some(entry) => match entry {
Entry(k, v) => if k == key then Some(v)
else match List.tail(es) {
Some(rest) => getFromEntries(rest, key),
None => None,
},
},
None => None,
}
// Get a value by key, returning a default if not found
pub fn getOrDefault(doc: Document, key: String, default: String): String =
match get(doc, key) {
Some(v) => v,
None => default,
}
// Get the entry key
pub fn entryKey(e: Entry): String =
match e {
Entry(k, _) => k,
}
// Get the entry value
pub fn entryValue(e: Entry): String =
match e {
Entry(_, v) => v,
}
// Check if a document has frontmatter (at least one entry)
pub fn hasFrontmatter(doc: Document): Bool =
List.length(entries(doc)) > 0
// Split a tags string into a list of tags (space-separated)
pub fn parseTags(tagsStr: String): List<String> =
if tagsStr == "" then []
else String.split(tagsStr, " ")
// Convenience: parse and get common blog fields
pub fn title(doc: Document): String = getOrDefault(doc, "title", "")
pub fn date(doc: Document): String = getOrDefault(doc, "date", "")
pub fn description(doc: Document): String = getOrDefault(doc, "description", "")
pub fn tags(doc: Document): List<String> = parseTags(getOrDefault(doc, "tags", ""))

View File

@@ -0,0 +1,8 @@
[project]
name = "frontmatter"
version = "0.1.0"
description = "YAML-like frontmatter parser for Lux"
authors = ["Brandon Lucas"]
license = "MIT"
[dependencies]

View File

@@ -0,0 +1,90 @@
import lib
fn test_basic_parsing(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Hello World\ndate: 2025-01-29\n---\nBody here.")
Test.assertEqualMsg("Hello World", lib.title(doc), "basic title")
Test.assertEqualMsg("2025-01-29", lib.date(doc), "basic date")
Test.assert(String.startsWith(lib.body(doc), "Body here."), "body starts with expected")
}
fn test_quoted_values(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: \"Quoted Title\"\n---\nBody")
Test.assertEqualMsg("Quoted Title", lib.title(doc), "double-quoted values")
let doc2 = lib.parse("---\ntitle: 'Single Quoted'\n---\nBody")
Test.assertEqualMsg("Single Quoted", lib.title(doc2), "single-quoted values")
}
fn test_tags(): Unit with {Test} = {
let doc = lib.parse("---\ntags: web blog lux\n---\nBody")
let tagList = lib.tags(doc)
Test.assertEqualMsg(3, List.length(tagList), "tag count")
let doc2 = lib.parse("---\ntitle: No Tags\n---\nBody")
Test.assertEqualMsg(0, List.length(lib.tags(doc2)), "empty tags")
let singleTags = lib.parseTags("solo")
Test.assertEqualMsg(1, List.length(singleTags), "parseTags single")
let noTags = lib.parseTags("")
Test.assertEqualMsg(0, List.length(noTags), "parseTags empty")
}
fn test_description(): Unit with {Test} = {
let doc = lib.parse("---\ndescription: A great post about things\n---\nBody")
Test.assertEqualMsg("A great post about things", lib.description(doc), "description field")
}
fn test_no_frontmatter(): Unit with {Test} = {
let doc = lib.parse("Just some text\nwith no frontmatter")
Test.assertEqualMsg(false, lib.hasFrontmatter(doc), "no frontmatter flag")
Test.assertEqualMsg("", lib.title(doc), "no frontmatter title")
}
fn test_get(): Unit with {Test} = {
let doc = lib.parse("---\nauthor: Brandon\nlicense: MIT\n---\nBody")
Test.assertEqualMsg("Brandon", lib.getOrDefault(doc, "author", ""), "get author")
Test.assertEqualMsg("MIT", lib.getOrDefault(doc, "license", ""), "get license")
Test.assert(match lib.get(doc, "missing") { Some(_) => false, None => true }, "get missing returns None")
}
fn test_get_or_default(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Present\n---\nBody")
Test.assertEqualMsg("Present", lib.getOrDefault(doc, "title", "default"), "getOrDefault present")
Test.assertEqualMsg("fallback", lib.getOrDefault(doc, "missing", "fallback"), "getOrDefault missing")
}
fn test_multiline_body(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Post\n---\nLine 1\nLine 2\nLine 3")
Test.assert(String.startsWith(lib.body(doc), "Line 1\n"), "multiline body starts correctly")
}
fn test_empty_value(): Unit with {Test} = {
let doc = lib.parse("---\ntitle:\n---\nBody")
Test.assertEqualMsg("", lib.title(doc), "empty value key")
}
fn test_entries(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: T\ndate: D\ndescription: Desc\ntags: a b\n---\nBody")
Test.assertEqualMsg(4, List.length(lib.entries(doc)), "four entries")
}
fn test_entry_accessors(): Unit with {Test} = {
let doc = lib.parse("---\nfoo: bar\n---\nBody")
let es = lib.entries(doc)
match List.head(es) {
Some(e) => {
Test.assertEqualMsg("foo", lib.entryKey(e), "entry key")
Test.assertEqualMsg("bar", lib.entryValue(e), "entry value")
},
None => Test.assert(false, "should have entry"),
}
}
fn test_has_frontmatter(): Unit with {Test} = {
let docWith = lib.parse("---\ntitle: Yes\n---\nBody")
let docWithout = lib.parse("No frontmatter here")
Test.assertEqualMsg(true, lib.hasFrontmatter(docWith), "has frontmatter true")
Test.assertEqualMsg(false, lib.hasFrontmatter(docWithout), "has frontmatter false")
}
fn test_value_with_colon(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Hello: World\n---\nBody")
Test.assertEqualMsg("Hello: World", lib.title(doc), "value with colon")
}

View File

@@ -0,0 +1,87 @@
import lib
// Integration tests: realistic document processing workflows
// Parse a complete blog post and extract all metadata
fn test_complete_blog_post(): Unit with {Test} = {
let content = "---\ntitle: My First Post\ndate: 2025-01-29\ndescription: A great introduction\ntags: web lux programming\nauthor: Brandon\n---\nThis is the body of my post.\n\nIt has multiple paragraphs."
let doc = lib.parse(content)
Test.assertEqualMsg(true, lib.hasFrontmatter(doc), "has frontmatter")
Test.assertEqualMsg("My First Post", lib.title(doc), "title")
Test.assertEqualMsg("2025-01-29", lib.date(doc), "date")
Test.assertEqualMsg("A great introduction", lib.description(doc), "description")
Test.assertEqualMsg(3, List.length(lib.tags(doc)), "tag count")
Test.assertEqualMsg("Brandon", lib.getOrDefault(doc, "author", ""), "author field")
Test.assert(String.startsWith(lib.body(doc), "This is the body"), "body starts correctly")
}
// Parse multiple documents and compare metadata
fn test_parse_multiple_documents(): Unit with {Test} = {
let doc1 = lib.parse("---\ntitle: Post A\ndate: 2025-01-01\n---\nBody A")
let doc2 = lib.parse("---\ntitle: Post B\ndate: 2025-02-01\n---\nBody B")
let doc3 = lib.parse("---\ntitle: Post C\ndate: 2025-03-01\n---\nBody C")
Test.assertEqualMsg("Post A", lib.title(doc1), "doc1 title")
Test.assertEqualMsg("Post B", lib.title(doc2), "doc2 title")
Test.assertEqualMsg("Post C", lib.title(doc3), "doc3 title")
Test.assertEqualMsg(true, lib.hasFrontmatter(doc1), "doc1 has frontmatter")
Test.assertEqualMsg(true, lib.hasFrontmatter(doc2), "doc2 has frontmatter")
}
// Simulate a static site generator reading a page
fn test_ssg_page_workflow(): Unit with {Test} = {
let page = "---\ntitle: About Me\ndescription: Learn about the author\nlayout: page\n---\n# About\n\nI write software."
let doc = lib.parse(page)
let pageTitle = lib.title(doc)
let pageDesc = lib.description(doc)
let layout = lib.getOrDefault(doc, "layout", "default")
let body = lib.body(doc)
Test.assertEqualMsg("About Me", pageTitle, "page title")
Test.assertEqualMsg("Learn about the author", pageDesc, "page description")
Test.assertEqualMsg("page", layout, "layout field")
Test.assert(String.startsWith(body, "# About"), "body starts with heading")
}
// Extract custom fields beyond the standard ones
fn test_custom_fields(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Post\nslug: custom-slug\ndraft: true\nweight: 10\n---\nContent")
Test.assertEqualMsg("custom-slug", lib.getOrDefault(doc, "slug", ""), "custom slug field")
Test.assertEqualMsg("true", lib.getOrDefault(doc, "draft", "false"), "custom draft field")
Test.assertEqualMsg("10", lib.getOrDefault(doc, "weight", "0"), "custom weight field")
}
// Iterate over entries and collect all keys
fn test_iterate_entries(): Unit with {Test} = {
let doc = lib.parse("---\na: 1\nb: 2\nc: 3\n---\nBody")
let es = lib.entries(doc)
Test.assertEqualMsg(3, List.length(es), "three entries")
match List.head(es) {
Some(e) => Test.assertEqualMsg("a", lib.entryKey(e), "first entry key"),
None => Test.assert(false, "should have entries"),
}
}
// Document with frontmatter but no body
fn test_frontmatter_only_document(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Metadata Only\ntags: meta\n---")
Test.assertEqualMsg("Metadata Only", lib.title(doc), "title from frontmatter-only doc")
Test.assertEqualMsg(1, List.length(lib.tags(doc)), "one tag")
Test.assertEqualMsg("", lib.body(doc), "empty body")
}
// Document with all convenience fields
fn test_all_convenience_fields(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Full Post\ndate: 2025-06-15\ndescription: Complete metadata\ntags: a b c d\n---\nBody text")
Test.assertEqualMsg("Full Post", lib.title(doc), "title convenience")
Test.assertEqualMsg("2025-06-15", lib.date(doc), "date convenience")
Test.assertEqualMsg("Complete metadata", lib.description(doc), "description convenience")
Test.assertEqualMsg(4, List.length(lib.tags(doc)), "tags convenience")
}
// Fallback behavior when fields are missing
fn test_missing_field_defaults(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Minimal\n---\nBody")
Test.assertEqualMsg("Minimal", lib.title(doc), "title present")
Test.assertEqualMsg("", lib.date(doc), "date defaults to empty")
Test.assertEqualMsg("", lib.description(doc), "description defaults to empty")
Test.assertEqualMsg(0, List.length(lib.tags(doc)), "tags defaults to empty")
}

View File

@@ -0,0 +1,79 @@
import lib
// Snapshot tests: verify complete parse output against golden values
// Snapshot: full blog post parse
fn test_snapshot_blog_post(): Unit with {Test} = {
let input = "---\ntitle: Understanding Algebraic Effects\ndate: 2025-03-15\ndescription: A deep dive into effect systems\ntags: lux effects programming\nauthor: Brandon\nlayout: post\n---\n# Understanding Algebraic Effects\n\nAlgebraic effects are a powerful way to handle side effects.\n\n## What are they?\n\nThey let you declare what effects your code uses."
let doc = lib.parse(input)
Test.assertEqualMsg("Understanding Algebraic Effects", lib.title(doc), "snap: title")
Test.assertEqualMsg("2025-03-15", lib.date(doc), "snap: date")
Test.assertEqualMsg("A deep dive into effect systems", lib.description(doc), "snap: description")
Test.assertEqualMsg(3, List.length(lib.tags(doc)), "snap: tag count")
Test.assertEqualMsg("Brandon", lib.getOrDefault(doc, "author", ""), "snap: author")
Test.assertEqualMsg("post", lib.getOrDefault(doc, "layout", ""), "snap: layout")
Test.assertEqualMsg(6, List.length(lib.entries(doc)), "snap: entry count")
Test.assert(String.startsWith(lib.body(doc), "# Understanding"), "snap: body starts with heading")
Test.assert(String.contains(lib.body(doc), "## What are they?"), "snap: body contains subheading")
}
// Snapshot: minimal frontmatter
fn test_snapshot_minimal(): Unit with {Test} = {
let input = "---\ntitle: Hello\n---\nWorld"
let doc = lib.parse(input)
Test.assertEqualMsg("Hello", lib.title(doc), "snap: minimal title")
Test.assertEqualMsg("", lib.date(doc), "snap: minimal no date")
Test.assertEqualMsg("", lib.description(doc), "snap: minimal no description")
Test.assertEqualMsg(0, List.length(lib.tags(doc)), "snap: minimal no tags")
Test.assertEqualMsg(1, List.length(lib.entries(doc)), "snap: minimal one entry")
Test.assert(String.startsWith(lib.body(doc), "World"), "snap: minimal body")
}
// Snapshot: no frontmatter at all
fn test_snapshot_no_frontmatter(): Unit with {Test} = {
let input = "This is just plain text.\nNo frontmatter here."
let doc = lib.parse(input)
Test.assertEqualMsg(false, lib.hasFrontmatter(doc), "snap: no frontmatter")
Test.assertEqualMsg("", lib.title(doc), "snap: no fm title")
Test.assertEqualMsg("", lib.date(doc), "snap: no fm date")
Test.assertEqualMsg(0, List.length(lib.entries(doc)), "snap: no fm entries")
}
// Snapshot: quoted values with various quote styles
fn test_snapshot_quoted_values(): Unit with {Test} = {
let input = "---\ntitle: \"Double Quoted: Title\"\nsubtitle: 'Single Quoted: Subtitle'\nplain: Just a plain value\n---\nBody"
let doc = lib.parse(input)
Test.assertEqualMsg("Double Quoted: Title", lib.title(doc), "snap: double-quoted")
Test.assertEqualMsg("Single Quoted: Subtitle", lib.getOrDefault(doc, "subtitle", ""), "snap: single-quoted")
Test.assertEqualMsg("Just a plain value", lib.getOrDefault(doc, "plain", ""), "snap: unquoted")
}
// Snapshot: document with many entries
fn test_snapshot_many_entries(): Unit with {Test} = {
let input = "---\ntitle: Complex Post\ndate: 2025-06-01\ndescription: A complex post\ntags: a b c d e\nauthor: Alice\nlayout: page\ncategory: tech\nslug: complex-post\ndraft: false\nweight: 42\n---\nBody content here."
let doc = lib.parse(input)
Test.assertEqualMsg(10, List.length(lib.entries(doc)), "snap: ten entries")
Test.assertEqualMsg("Complex Post", lib.title(doc), "snap: title")
Test.assertEqualMsg("2025-06-01", lib.date(doc), "snap: date")
Test.assertEqualMsg(5, List.length(lib.tags(doc)), "snap: five tags")
Test.assertEqualMsg("Alice", lib.getOrDefault(doc, "author", ""), "snap: author")
Test.assertEqualMsg("page", lib.getOrDefault(doc, "layout", ""), "snap: layout")
Test.assertEqualMsg("tech", lib.getOrDefault(doc, "category", ""), "snap: category")
Test.assertEqualMsg("complex-post", lib.getOrDefault(doc, "slug", ""), "snap: slug")
Test.assertEqualMsg("false", lib.getOrDefault(doc, "draft", ""), "snap: draft")
Test.assertEqualMsg("42", lib.getOrDefault(doc, "weight", ""), "snap: weight")
}
// Snapshot: entry iteration order
fn test_snapshot_entry_order(): Unit with {Test} = {
let input = "---\nfirst: 1\nsecond: 2\nthird: 3\n---\n"
let doc = lib.parse(input)
let es = lib.entries(doc)
match List.head(es) {
Some(e) => {
Test.assertEqualMsg("first", lib.entryKey(e), "snap: first entry key")
Test.assertEqualMsg("1", lib.entryValue(e), "snap: first entry value")
},
None => Test.assert(false, "snap: should have entries"),
}
}

View File

@@ -0,0 +1,126 @@
import lib
// --- parse edge cases ---
fn test_parse_empty_string(): Unit with {Test} = {
let doc = lib.parse("")
Test.assertEqualMsg(false, lib.hasFrontmatter(doc), "empty string has no frontmatter")
Test.assertEqualMsg("", lib.body(doc), "empty string has empty body")
}
fn test_parse_only_body(): Unit with {Test} = {
let doc = lib.parse("Just text\nwith lines")
Test.assertEqualMsg(false, lib.hasFrontmatter(doc), "plain text has no frontmatter")
}
fn test_parse_only_frontmatter(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Hello\n---")
Test.assertEqualMsg(true, lib.hasFrontmatter(doc), "has frontmatter")
Test.assertEqualMsg("Hello", lib.title(doc), "title extracted")
Test.assertEqualMsg("", lib.body(doc), "no body")
}
fn test_parse_empty_frontmatter(): Unit with {Test} = {
let doc = lib.parse("---\n---\nBody here")
Test.assertEqualMsg(false, lib.hasFrontmatter(doc), "empty frontmatter has no entries")
Test.assert(String.startsWith(lib.body(doc), "Body here"), "body after empty frontmatter")
}
fn test_parse_unclosed_frontmatter(): Unit with {Test} = {
// Parser treats opening --- as starting frontmatter; entries are parsed even without closing ---
let doc = lib.parse("---\ntitle: Test\nNo closing marker")
Test.assertEqualMsg(true, lib.hasFrontmatter(doc), "unclosed frontmatter still parses entries")
Test.assertEqualMsg("Test", lib.title(doc), "unclosed frontmatter title still extracted")
}
// --- value formats ---
fn test_parse_double_quoted(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: \"Hello World\"\n---\n")
Test.assertEqualMsg("Hello World", lib.title(doc), "double-quoted value")
}
fn test_parse_single_quoted(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: 'Hello World'\n---\n")
Test.assertEqualMsg("Hello World", lib.title(doc), "single-quoted value")
}
fn test_parse_value_with_colon(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Hello: World: Again\n---\n")
Test.assertEqualMsg("Hello: World: Again", lib.title(doc), "value with multiple colons")
}
fn test_parse_value_empty(): Unit with {Test} = {
let doc = lib.parse("---\ntitle:\n---\n")
Test.assertEqualMsg("", lib.title(doc), "empty value after colon")
}
fn test_parse_value_with_special_chars(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Hello <world> & \"friends\"\n---\n")
Test.assertEqualMsg("Hello <world> & \"friends\"", lib.title(doc), "value with HTML special chars")
}
// --- get / getOrDefault edge cases ---
fn test_get_missing_key(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: Hello\n---\n")
Test.assert(match lib.get(doc, "nonexistent") { Some(_) => false, None => true }, "missing key returns None")
}
fn test_get_first_match(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: First\ntitle: Second\n---\n")
Test.assertEqualMsg("First", lib.getOrDefault(doc, "title", ""), "get returns first matching key")
}
fn test_get_or_default_present(): Unit with {Test} = {
let doc = lib.parse("---\nkey: value\n---\n")
Test.assertEqualMsg("value", lib.getOrDefault(doc, "key", "fallback"), "getOrDefault returns value when present")
}
fn test_get_or_default_missing(): Unit with {Test} = {
let doc = lib.parse("---\nkey: value\n---\n")
Test.assertEqualMsg("fallback", lib.getOrDefault(doc, "other", "fallback"), "getOrDefault returns default when missing")
}
// --- parseTags edge cases ---
fn test_parse_tags_single(): Unit with {Test} =
Test.assertEqualMsg(1, List.length(lib.parseTags("solo")), "single tag")
fn test_parse_tags_multiple(): Unit with {Test} =
Test.assertEqualMsg(3, List.length(lib.parseTags("a b c")), "three tags")
fn test_parse_tags_empty(): Unit with {Test} =
Test.assertEqualMsg(0, List.length(lib.parseTags("")), "empty tags")
// --- entries / entryKey / entryValue ---
fn test_entries_count(): Unit with {Test} = {
let doc = lib.parse("---\na: 1\nb: 2\nc: 3\n---\n")
Test.assertEqualMsg(3, List.length(lib.entries(doc)), "three entries")
}
fn test_entry_accessors(): Unit with {Test} = {
let doc = lib.parse("---\nfoo: bar\n---\n")
match List.head(lib.entries(doc)) {
Some(e) => {
Test.assertEqualMsg("foo", lib.entryKey(e), "entry key")
Test.assertEqualMsg("bar", lib.entryValue(e), "entry value")
},
None => Test.assert(false, "should have at least one entry"),
}
}
// --- body edge cases ---
fn test_body_multiline(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: T\n---\nLine 1\nLine 2\nLine 3")
let b = lib.body(doc)
Test.assert(String.startsWith(b, "Line 1\n"), "multiline body starts correctly")
Test.assert(String.contains(b, "Line 2"), "multiline body contains middle line")
}
fn test_body_preserves_blank_lines(): Unit with {Test} = {
let doc = lib.parse("---\ntitle: T\n---\nPara 1\n\nPara 2")
Test.assert(String.contains(lib.body(doc), "\n\n"), "body preserves blank lines")
}