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/xml/.gitignore vendored Normal file
View File

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

109
packages/xml/lib.lux Normal file
View File

@@ -0,0 +1,109 @@
// xml - XML builder for Lux
//
// Provides a simple API for constructing well-formed XML documents.
// Designed for generating RSS feeds, sitemaps, and other XML output.
// An XML attribute (key-value pair)
type Attr =
| Attr(String, String)
// An XML node: element with tag, attributes, and children, or text content
type Node =
| Element(String, List<Attr>, List<Node>)
| Text(String)
| CData(String)
| Raw(String)
// Escape special XML characters in text content
pub fn escape(s: String): String =
String.replace(
String.replace(
String.replace(
String.replace(
String.replace(s, "&", "&amp;"),
"<", "&lt;"),
">", "&gt;"),
"\"", "&quot;"),
"'", "&apos;")
// Create a text node
pub fn text(content: String): Node = Text(content)
// Create a CDATA section
pub fn cdata(content: String): Node = CData(content)
// Create a raw (unescaped) node
pub fn raw(content: String): Node = Raw(content)
// Create an element with tag, attributes, and children
pub fn element(tag: String, attrs: List<Attr>, children: List<Node>): Node =
Element(tag, attrs, children)
// Create an element with no attributes
pub fn el(tag: String, children: List<Node>): Node =
Element(tag, [], children)
// Create a self-closing element with attributes and no children
pub fn selfClosing(tag: String, attrs: List<Attr>): Node =
Element(tag, attrs, [])
// Create an attribute
pub fn attr(key: String, value: String): Attr =
Attr(key, value)
// Create an element with text content
pub fn textEl(tag: String, content: String): Node =
Element(tag, [], [Text(content)])
// Create an element with attributes and text content
pub fn textElAttr(tag: String, attrs: List<Attr>, content: String): Node =
Element(tag, attrs, [Text(content)])
// Render a single attribute to string
fn renderAttr(a: Attr): String =
match a {
Attr(k, v) => " " + k + "=\"" + escape(v) + "\"",
}
// Render a list of attributes to string
fn renderAttrs(attrs: List<Attr>): String =
match List.head(attrs) {
None => "",
Some(a) => renderAttr(a) + match List.tail(attrs) {
Some(rest) => renderAttrs(rest),
None => "",
},
}
// Render child nodes to string
fn renderChildren(nodes: List<Node>): String =
match List.head(nodes) {
None => "",
Some(node) => render(node) + match List.tail(nodes) {
Some(rest) => renderChildren(rest),
None => "",
},
}
// Render a node to an XML string
pub fn render(node: Node): String =
match node {
Element(tag, attrs, children) => {
let attrStr = renderAttrs(attrs)
if List.length(children) == 0 then
"<" + tag + attrStr + "/>"
else
"<" + tag + attrStr + ">" + renderChildren(children) + "</" + tag + ">"
},
Text(content) => escape(content),
CData(content) => "<![CDATA[" + content + "]]>",
Raw(content) => content,
}
// Render a complete XML document with declaration
pub fn document(root: Node): String =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + render(root)
// Render with a custom declaration (e.g., for RSS stylesheets)
pub fn documentWithDecl(decl: String, root: Node): String =
decl + "\n" + render(root)

8
packages/xml/lux.toml Normal file
View File

@@ -0,0 +1,8 @@
[project]
name = "xml"
version = "0.1.0"
description = "XML builder for Lux"
authors = ["Brandon Lucas"]
license = "MIT"
[dependencies]

View File

@@ -0,0 +1,105 @@
import lib
// Integration tests: building real-world XML documents
// Build an HTML page structure
fn test_build_html_page(): Unit with {Test} = {
let head = lib.el("head", [
lib.textEl("title", "My Page"),
lib.selfClosing("meta", [lib.attr("charset", "utf-8")]),
lib.selfClosing("link", [lib.attr("rel", "stylesheet"), lib.attr("href", "style.css")])
])
let body = lib.el("body", [
lib.el("header", [lib.textEl("h1", "Welcome")]),
lib.el("main", [lib.textEl("p", "Hello world")]),
lib.el("footer", [lib.textEl("p", "Copyright 2025")])
])
let html = lib.el("html", [head, body])
let result = lib.render(html)
Test.assert(String.contains(result, "<title>My Page</title>"), "page has title")
Test.assert(String.contains(result, "<meta charset=\"utf-8\"/>"), "page has meta charset")
Test.assert(String.contains(result, "<h1>Welcome</h1>"), "page has h1")
Test.assert(String.contains(result, "<p>Hello world</p>"), "page has paragraph")
Test.assert(String.startsWith(result, "<html>"), "starts with html tag")
Test.assert(String.endsWith(result, "</html>"), "ends with html tag")
}
// Build a sitemap.xml
fn test_build_sitemap(): Unit with {Test} = {
let url1 = lib.el("url", [
lib.textEl("loc", "https://example.com/"),
lib.textEl("lastmod", "2025-01-29"),
lib.textEl("priority", "1.0")
])
let url2 = lib.el("url", [
lib.textEl("loc", "https://example.com/about"),
lib.textEl("lastmod", "2025-01-15"),
lib.textEl("priority", "0.8")
])
let urlset = lib.element("urlset", [lib.attr("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")], [url1, url2])
let sitemap = lib.document(urlset)
Test.assert(String.startsWith(sitemap, "<?xml version=\"1.0\""), "sitemap has XML declaration")
Test.assert(String.contains(sitemap, "xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\""), "sitemap has namespace")
Test.assert(String.contains(sitemap, "<loc>https://example.com/</loc>"), "sitemap has first URL")
Test.assert(String.contains(sitemap, "<loc>https://example.com/about</loc>"), "sitemap has second URL")
}
// Build a form with various input types
fn test_build_form(): Unit with {Test} = {
let form = lib.element("form", [lib.attr("action", "/submit"), lib.attr("method", "post")], [
lib.selfClosing("input", [lib.attr("type", "text"), lib.attr("name", "username")]),
lib.selfClosing("input", [lib.attr("type", "password"), lib.attr("name", "pass")]),
lib.textElAttr("button", [lib.attr("type", "submit")], "Login")
])
let result = lib.render(form)
Test.assert(String.contains(result, "action=\"/submit\""), "form has action")
Test.assert(String.contains(result, "type=\"text\""), "has text input")
Test.assert(String.contains(result, "type=\"password\""), "has password input")
Test.assert(String.contains(result, "<button type=\"submit\">Login</button>"), "has submit button")
}
// Build nested table structure
fn test_build_table(): Unit with {Test} = {
let headerRow = lib.el("tr", [lib.textEl("th", "Name"), lib.textEl("th", "Age")])
let row1 = lib.el("tr", [lib.textEl("td", "Alice"), lib.textEl("td", "30")])
let row2 = lib.el("tr", [lib.textEl("td", "Bob"), lib.textEl("td", "25")])
let table = lib.el("table", [lib.el("thead", [headerRow]), lib.el("tbody", [row1, row2])])
let result = lib.render(table)
Test.assert(String.contains(result, "<th>Name</th>"), "has header Name")
Test.assert(String.contains(result, "<td>Alice</td>"), "has cell Alice")
Test.assert(String.contains(result, "<td>25</td>"), "has cell 25")
}
// documentWithDecl for custom processing instructions
fn test_custom_declaration(): Unit with {Test} = {
let decl = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?xml-stylesheet type=\"text/xsl\" href=\"transform.xsl\"?>"
let root = lib.textEl("data", "value")
let result = lib.documentWithDecl(decl, root)
Test.assert(String.contains(result, "xml-stylesheet"), "has stylesheet PI")
Test.assert(String.contains(result, "<data>value</data>"), "has root element")
}
// Build SVG-like structure
fn test_build_svg(): Unit with {Test} = {
let svg = lib.element("svg", [lib.attr("xmlns", "http://www.w3.org/2000/svg"), lib.attr("width", "100"), lib.attr("height", "100")], [
lib.selfClosing("circle", [lib.attr("cx", "50"), lib.attr("cy", "50"), lib.attr("r", "40")]),
lib.selfClosing("rect", [lib.attr("x", "10"), lib.attr("y", "10"), lib.attr("width", "80"), lib.attr("height", "80")])
])
let result = lib.render(svg)
Test.assert(String.contains(result, "<svg xmlns=\"http://www.w3.org/2000/svg\""), "svg has namespace")
Test.assert(String.contains(result, "<circle"), "has circle")
Test.assert(String.contains(result, "<rect"), "has rect")
}
// Content with special characters throughout
fn test_special_chars_in_content(): Unit with {Test} = {
let node = lib.el("message", [
lib.textEl("from", "Alice & Bob"),
lib.textEl("subject", "Re: <Important>"),
lib.el("body", [lib.cdata("Use <html> & \"xml\" freely")])
])
let result = lib.render(node)
Test.assert(String.contains(result, "Alice &amp; Bob"), "from is escaped")
Test.assert(String.contains(result, "&lt;Important&gt;"), "subject is escaped")
Test.assert(String.contains(result, "<![CDATA[Use <html> & \"xml\" freely]]>"), "cdata preserves content")
}

View File

@@ -0,0 +1,95 @@
import lib
// Snapshot tests: compare full XML output against golden strings
// Snapshot: simple HTML page
fn test_snapshot_html_page(): Unit with {Test} = {
let page = lib.el("html", [
lib.el("head", [lib.textEl("title", "Test")]),
lib.el("body", [lib.textEl("p", "Hello")])
])
let expected = "<html><head><title>Test</title></head><body><p>Hello</p></body></html>"
Test.assertEqualMsg(expected, lib.render(page), "snap: HTML page")
}
// Snapshot: self-closing tags
fn test_snapshot_self_closing(): Unit with {Test} = {
let node = lib.el("head", [
lib.selfClosing("meta", [lib.attr("charset", "utf-8")]),
lib.selfClosing("link", [lib.attr("rel", "stylesheet"), lib.attr("href", "/css/main.css")])
])
let expected = "<head><meta charset=\"utf-8\"/><link rel=\"stylesheet\" href=\"/css/main.css\"/></head>"
Test.assertEqualMsg(expected, lib.render(node), "snap: self-closing tags")
}
// Snapshot: XML document with declaration
fn test_snapshot_xml_document(): Unit with {Test} = {
let root = lib.el("config", [
lib.textEl("name", "MyApp"),
lib.textEl("version", "1.0")
])
let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<config><name>MyApp</name><version>1.0</version></config>"
Test.assertEqualMsg(expected, lib.document(root), "snap: XML document")
}
// Snapshot: element with escaped content
fn test_snapshot_escaped_content(): Unit with {Test} = {
let node = lib.textEl("message", "Hello <world> & \"everyone\"")
let expected = "<message>Hello &lt;world&gt; &amp; &quot;everyone&quot;</message>"
Test.assertEqualMsg(expected, lib.render(node), "snap: escaped content")
}
// Snapshot: CDATA section
fn test_snapshot_cdata(): Unit with {Test} = {
let node = lib.el("script", [lib.cdata("if (a < b && c > d) then run()")])
let expected = "<script><![CDATA[if (a < b && c > d) then run()]]></script>"
Test.assertEqualMsg(expected, lib.render(node), "snap: CDATA in script")
}
// Snapshot: mixed node types
fn test_snapshot_mixed_nodes(): Unit with {Test} = {
let node = lib.el("content", [
lib.text("Normal "),
lib.raw("<br/>"),
lib.text(" text & "),
lib.cdata("<special>")
])
let expected = "<content>Normal <br/> text &amp; <![CDATA[<special>]]></content>"
Test.assertEqualMsg(expected, lib.render(node), "snap: mixed node types")
}
// Snapshot: deeply nested structure
fn test_snapshot_deep_nesting(): Unit with {Test} = {
let node = lib.el("l1", [lib.el("l2", [lib.el("l3", [lib.el("l4", [lib.el("l5", [lib.text("deep")])])])])])
let expected = "<l1><l2><l3><l4><l5>deep</l5></l4></l3></l2></l1>"
Test.assertEqualMsg(expected, lib.render(node), "snap: 5-level nesting")
}
// Snapshot: attribute escaping
fn test_snapshot_attr_escaping(): Unit with {Test} = {
let node = lib.selfClosing("div", [
lib.attr("title", "a \"test\" & <more>"),
lib.attr("class", "a&b")
])
let expected = "<div title=\"a &quot;test&quot; &amp; &lt;more&gt;\" class=\"a&amp;b\"/>"
Test.assertEqualMsg(expected, lib.render(node), "snap: attribute escaping")
}
// Snapshot: empty elements
fn test_snapshot_empty_elements(): Unit with {Test} = {
Test.assertEqualMsg("<br/>", lib.render(lib.el("br", [])), "snap: empty el")
Test.assertEqualMsg("<hr/>", lib.render(lib.selfClosing("hr", [])), "snap: selfClosing")
Test.assertEqualMsg("<p></p>", lib.render(lib.textEl("p", "")), "snap: textEl with empty content")
}
// Snapshot: sitemap entry
fn test_snapshot_sitemap_entry(): Unit with {Test} = {
let url = lib.el("url", [
lib.textEl("loc", "https://example.com/page"),
lib.textEl("lastmod", "2025-01-29"),
lib.textEl("changefreq", "weekly"),
lib.textEl("priority", "0.8")
])
let expected = "<url><loc>https://example.com/page</loc><lastmod>2025-01-29</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>"
Test.assertEqualMsg(expected, lib.render(url), "snap: sitemap URL entry")
}

118
packages/xml/test_unit.lux Normal file
View File

@@ -0,0 +1,118 @@
import lib
// --- escape edge cases ---
fn test_escape_all_chars(): Unit with {Test} = {
Test.assertEqualMsg("&amp;", lib.escape("&"), "escape ampersand")
Test.assertEqualMsg("&lt;", lib.escape("<"), "escape less-than")
Test.assertEqualMsg("&gt;", lib.escape(">"), "escape greater-than")
Test.assertEqualMsg("&quot;", lib.escape("\""), "escape double quote")
Test.assertEqualMsg("&apos;", lib.escape("'"), "escape single quote")
}
fn test_escape_empty(): Unit with {Test} =
Test.assertEqualMsg("", lib.escape(""), "escape empty string")
fn test_escape_no_special(): Unit with {Test} =
Test.assertEqualMsg("hello world 123", lib.escape("hello world 123"), "escape plain text unchanged")
fn test_escape_all_special(): Unit with {Test} =
Test.assertEqualMsg("&lt;&amp;&gt;&quot;&apos;", lib.escape("<&>\"'"), "escape all special chars together")
fn test_escape_mixed(): Unit with {Test} =
Test.assertEqualMsg("a &amp; b &lt; c", lib.escape("a & b < c"), "escape mixed content")
// --- node constructors ---
fn test_text_node_simple(): Unit with {Test} =
Test.assertEqualMsg("hello", lib.render(lib.text("hello")), "text node renders content")
fn test_text_node_escapes(): Unit with {Test} =
Test.assertEqualMsg("&lt;script&gt;", lib.render(lib.text("<script>")), "text node escapes HTML")
fn test_text_node_empty(): Unit with {Test} =
Test.assertEqualMsg("", lib.render(lib.text("")), "empty text node")
fn test_cdata_preserves_special(): Unit with {Test} =
Test.assertEqualMsg("<![CDATA[<b>bold & stuff</b>]]>", lib.render(lib.cdata("<b>bold & stuff</b>")), "cdata preserves special chars")
fn test_raw_no_escape(): Unit with {Test} =
Test.assertEqualMsg("<br/>", lib.render(lib.raw("<br/>")), "raw passes through unchanged")
fn test_raw_empty(): Unit with {Test} =
Test.assertEqualMsg("", lib.render(lib.raw("")), "empty raw node")
// --- element rendering ---
fn test_self_closing_no_attrs(): Unit with {Test} =
Test.assertEqualMsg("<br/>", lib.render(lib.selfClosing("br", [])), "self-closing no attrs")
fn test_self_closing_with_attr(): Unit with {Test} =
Test.assertEqualMsg("<img src=\"photo.jpg\"/>", lib.render(lib.selfClosing("img", [lib.attr("src", "photo.jpg")])), "self-closing with attr")
fn test_self_closing_multiple_attrs(): Unit with {Test} = {
let node = lib.selfClosing("input", [lib.attr("type", "text"), lib.attr("name", "q"), lib.attr("value", "")])
Test.assertEqualMsg("<input type=\"text\" name=\"q\" value=\"\"/>", lib.render(node), "self-closing with multiple attrs")
}
fn test_el_empty_children(): Unit with {Test} =
Test.assertEqualMsg("<div/>", lib.render(lib.el("div", [])), "el with no children self-closes")
fn test_el_with_children(): Unit with {Test} = {
let node = lib.el("ul", [lib.textEl("li", "a"), lib.textEl("li", "b")])
Test.assertEqualMsg("<ul><li>a</li><li>b</li></ul>", lib.render(node), "el with children")
}
fn test_text_el(): Unit with {Test} =
Test.assertEqualMsg("<title>Hello</title>", lib.render(lib.textEl("title", "Hello")), "textEl renders correctly")
fn test_text_el_escapes(): Unit with {Test} =
Test.assertEqualMsg("<p>a &amp; b</p>", lib.render(lib.textEl("p", "a & b")), "textEl escapes content")
fn test_text_el_attr(): Unit with {Test} = {
let node = lib.textElAttr("a", [lib.attr("href", "/page")], "click")
Test.assertEqualMsg("<a href=\"/page\">click</a>", lib.render(node), "textElAttr renders correctly")
}
// --- attribute edge cases ---
fn test_attr_value_escaping(): Unit with {Test} = {
let node = lib.selfClosing("div", [lib.attr("data", "a&b<c\"d'e")])
Test.assertEqualMsg("<div data=\"a&amp;b&lt;c&quot;d&apos;e\"/>", lib.render(node), "attr value escapes all special chars")
}
fn test_attr_empty_value(): Unit with {Test} = {
let node = lib.selfClosing("input", [lib.attr("disabled", "")])
Test.assertEqualMsg("<input disabled=\"\"/>", lib.render(node), "attr with empty value")
}
// --- nesting depth ---
fn test_deeply_nested(): Unit with {Test} = {
let deep = lib.el("a", [lib.el("b", [lib.el("c", [lib.el("d", [lib.text("deep")])])])])
Test.assertEqualMsg("<a><b><c><d>deep</d></c></b></a>", lib.render(deep), "4 levels deep")
}
// --- document ---
fn test_document_declaration(): Unit with {Test} = {
let doc = lib.document(lib.textEl("root", ""))
Test.assert(String.startsWith(doc, "<?xml version=\"1.0\""), "document starts with XML declaration")
}
fn test_document_with_custom_decl(): Unit with {Test} = {
let doc = lib.documentWithDecl("<?xml version=\"1.1\"?>", lib.textEl("root", ""))
Test.assert(String.startsWith(doc, "<?xml version=\"1.1\"?>"), "custom declaration")
}
// --- mixed children ---
fn test_mixed_children_types(): Unit with {Test} = {
let node = lib.el("div", [
lib.text("Hello "),
lib.raw("<strong>world</strong>"),
lib.text(" & "),
lib.cdata("raw data")
])
Test.assertEqualMsg("<div>Hello <strong>world</strong> &amp; <![CDATA[raw data]]></div>", lib.render(node), "mixed children types")
}

74
packages/xml/test_xml.lux Normal file
View File

@@ -0,0 +1,74 @@
import lib
fn test_escape(): Unit with {Test} = {
Test.assertEqualMsg("&lt;b&gt;hello&lt;/b&gt;", lib.escape("<b>hello</b>"), "escape angle brackets")
Test.assertEqualMsg("a &amp; b", lib.escape("a & b"), "escape ampersand")
Test.assertEqualMsg("&quot;quoted&quot;", lib.escape("\"quoted\""), "escape quotes")
}
fn test_text_node(): Unit with {Test} = {
Test.assertEqualMsg("hello", lib.render(lib.text("hello")), "text node simple")
Test.assertEqualMsg("&lt;b&gt;", lib.render(lib.text("<b>")), "text node escaped")
}
fn test_cdata(): Unit with {Test} = {
Test.assertEqualMsg("<![CDATA[hello <b>world</b>]]>", lib.render(lib.cdata("hello <b>world</b>")), "cdata node")
}
fn test_raw(): Unit with {Test} = {
Test.assertEqualMsg("<br/>", lib.render(lib.raw("<br/>")), "raw node")
}
fn test_text_element(): Unit with {Test} = {
Test.assertEqualMsg("<title>Hello</title>", lib.render(lib.textEl("title", "Hello")), "text element")
}
fn test_element_with_attrs(): Unit with {Test} = {
let linkEl = lib.element("link", [lib.attr("href", "http://example.com"), lib.attr("rel", "alternate")], [])
Test.assertEqualMsg("<link href=\"http://example.com\" rel=\"alternate\"/>", lib.render(linkEl), "element with attrs self-closing")
}
fn test_self_closing(): Unit with {Test} = {
Test.assertEqualMsg("<br/>", lib.render(lib.selfClosing("br", [])), "self-closing no attrs")
Test.assertEqualMsg("<img src=\"photo.jpg\"/>", lib.render(lib.selfClosing("img", [lib.attr("src", "photo.jpg")])), "self-closing with attr")
}
fn test_nested_elements(): Unit with {Test} = {
let nested = lib.el("root", [lib.textEl("child1", "a"), lib.textEl("child2", "b")])
Test.assertEqualMsg("<root><child1>a</child1><child2>b</child2></root>", lib.render(nested), "nested elements")
}
fn test_element_with_attrs_and_children(): Unit with {Test} = {
let withAttr = lib.element("item", [lib.attr("id", "1")], [lib.textEl("name", "Test")])
Test.assertEqualMsg("<item id=\"1\"><name>Test</name></item>", lib.render(withAttr), "element with attrs and children")
}
fn test_text_element_with_attrs(): Unit with {Test} = {
let attrText = lib.textElAttr("a", [lib.attr("href", "/page")], "Click")
Test.assertEqualMsg("<a href=\"/page\">Click</a>", lib.render(attrText), "text element with attrs")
}
fn test_xml_document(): Unit with {Test} = {
let doc = lib.document(lib.textEl("root", "content"))
let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>content</root>"
Test.assertEqualMsg(expected, doc, "xml document")
}
fn test_deeply_nested(): Unit with {Test} = {
let deep = lib.el("a", [lib.el("b", [lib.el("c", [lib.text("deep")])])])
Test.assertEqualMsg("<a><b><c>deep</c></b></a>", lib.render(deep), "deeply nested")
}
fn test_empty_element(): Unit with {Test} = {
Test.assertEqualMsg("<empty/>", lib.render(lib.el("empty", [])), "el empty children self-closes")
}
fn test_attribute_value_escaping(): Unit with {Test} = {
let escapedAttr = lib.selfClosing("div", [lib.attr("data", "a&b<c")])
Test.assertEqualMsg("<div data=\"a&amp;b&lt;c\"/>", lib.render(escapedAttr), "attribute value escaping")
}
fn test_mixed_children(): Unit with {Test} = {
let mixed = lib.el("p", [lib.text("Hello "), lib.raw("<b>world</b>")])
Test.assertEqualMsg("<p>Hello <b>world</b></p>", lib.render(mixed), "mixed text and raw children")
}