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

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", ""))