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:
3
packages/frontmatter/.gitignore
vendored
Normal file
3
packages/frontmatter/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
_site/
|
||||
.lux_packages/
|
||||
*.bak
|
||||
168
packages/frontmatter/lib.lux
Normal file
168
packages/frontmatter/lib.lux
Normal 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", ""))
|
||||
8
packages/frontmatter/lux.toml
Normal file
8
packages/frontmatter/lux.toml
Normal 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]
|
||||
90
packages/frontmatter/test_frontmatter.lux
Normal file
90
packages/frontmatter/test_frontmatter.lux
Normal 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")
|
||||
}
|
||||
87
packages/frontmatter/test_integration.lux
Normal file
87
packages/frontmatter/test_integration.lux
Normal 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")
|
||||
}
|
||||
79
packages/frontmatter/test_snapshot.lux
Normal file
79
packages/frontmatter/test_snapshot.lux
Normal 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"),
|
||||
}
|
||||
}
|
||||
126
packages/frontmatter/test_unit.lux
Normal file
126
packages/frontmatter/test_unit.lux
Normal 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")
|
||||
}
|
||||
Reference in New Issue
Block a user