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

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

93
packages/path/lib.lux Normal file
View File

@@ -0,0 +1,93 @@
// path - File path utilities for Lux
// Get the last component of a path (filename)
// basename("/foo/bar/baz.txt") => "baz.txt"
// basename("file.txt") => "file.txt"
pub fn basename(path: String): String =
match String.lastIndexOf(path, "/") {
Some(idx) => String.substring(path, idx + 1, String.length(path)),
None => path,
}
// Get the directory portion of a path
// dirname("/foo/bar/baz.txt") => "/foo/bar"
// dirname("file.txt") => "."
pub fn dirname(path: String): String =
match String.lastIndexOf(path, "/") {
Some(idx) => if idx == 0 then "/" else String.substring(path, 0, idx),
None => ".",
}
// Get the file extension (without the dot)
// extension("file.txt") => Some("txt")
// extension("file") => None
// extension("file.tar.gz") => Some("gz")
pub fn extension(path: String): Option<String> = {
let name = basename(path)
match String.lastIndexOf(name, ".") {
Some(idx) => if idx == 0 then None
else Some(String.substring(name, idx + 1, String.length(name))),
None => None,
}
}
// Remove the file extension
// stripExtension("file.txt") => "file"
// stripExtension("file") => "file"
// stripExtension("/foo/bar.txt") => "/foo/bar"
pub fn stripExtension(path: String): String =
match String.lastIndexOf(path, ".") {
Some(dotIdx) => match String.lastIndexOf(path, "/") {
Some(slashIdx) => if dotIdx > slashIdx then String.substring(path, 0, dotIdx) else path,
None => String.substring(path, 0, dotIdx),
},
None => path,
}
// Join two path components with a separator
// join("/foo", "bar") => "/foo/bar"
// join("/foo/", "bar") => "/foo/bar"
// join("", "bar") => "bar"
pub fn join(a: String, b: String): String =
if a == "" then b
else if b == "" then a
else if String.endsWith(a, "/") then
if String.startsWith(b, "/") then a + String.substring(b, 1, String.length(b))
else a + b
else if String.startsWith(b, "/") then a + b
else a + "/" + b
// Check if a path has a given extension
// hasExtension("file.txt", "txt") => true
// hasExtension("file.md", "txt") => false
pub fn hasExtension(path: String, ext: String): Bool =
String.endsWith(path, "." + ext)
// Replace the file extension
// replaceExtension("file.txt", "md") => "file.md"
// replaceExtension("file", "md") => "file.md"
pub fn replaceExtension(path: String, newExt: String): String =
stripExtension(path) + "." + newExt
// Get the filename without extension (stem)
// stem("file.txt") => "file"
// stem("/foo/bar.txt") => "bar"
// stem("file") => "file"
pub fn stem(path: String): String = {
let name = basename(path)
match String.lastIndexOf(name, ".") {
Some(idx) => if idx == 0 then name
else String.substring(name, 0, idx),
None => name,
}
}
// Check if a path is absolute (starts with /)
pub fn isAbsolute(path: String): Bool =
String.startsWith(path, "/")
// Check if a path is relative (does not start with /)
pub fn isRelative(path: String): Bool =
if path == "" then true
else if String.startsWith(path, "/") then false
else true

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

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

View File

@@ -0,0 +1,88 @@
import lib
// Integration tests: realistic path manipulation workflows
// Build a full output path from components
fn test_build_output_path(): Unit with {Test} = {
let srcFile = "/home/user/project/src/pages/about.md"
let outDir = "/home/user/project/_site"
let name = lib.stem(srcFile)
let outFile = lib.join(outDir, lib.join(name, "index.html"))
Test.assertEqualMsg("/home/user/project/_site/about/index.html", outFile, "build output path from source")
}
// Convert all markdown files to HTML paths
fn test_extension_swap_workflow(): Unit with {Test} = {
let file = "/content/posts/hello-world.md"
let result = lib.replaceExtension(file, "html")
Test.assertEqualMsg("/content/posts/hello-world.html", result, "swap .md to .html")
Test.assertEqualMsg("html", match lib.extension(result) { Some(e) => e, None => "" }, "verify new extension")
}
// Decompose a path and reconstruct it
fn test_decompose_reconstruct(): Unit with {Test} = {
let original = "/var/log/app/error.log"
let dir = lib.dirname(original)
let name = lib.basename(original)
let reconstructed = lib.join(dir, name)
Test.assertEqualMsg(original, reconstructed, "decompose and reconstruct path")
}
// Process a list of file paths: extract stems
fn test_batch_stem_extraction(): Unit with {Test} = {
let s1 = lib.stem("/posts/hello.md")
let s2 = lib.stem("/posts/world.md")
let s3 = lib.stem("/posts/readme.txt")
Test.assertEqualMsg("hello", s1, "batch stem 1")
Test.assertEqualMsg("world", s2, "batch stem 2")
Test.assertEqualMsg("readme", s3, "batch stem 3")
}
// Create sibling file path (same dir, different name)
fn test_sibling_file(): Unit with {Test} = {
let original = "/assets/css/main.css"
let dir = lib.dirname(original)
let sibling = lib.join(dir, "reset.css")
Test.assertEqualMsg("/assets/css/reset.css", sibling, "sibling file in same directory")
}
// Multi-level path construction
fn test_multi_level_join(): Unit with {Test} = {
let base = "/var/www"
let path = lib.join(lib.join(lib.join(base, "html"), "blog"), "index.html")
Test.assertEqualMsg("/var/www/html/blog/index.html", path, "multi-level path join")
Test.assertEqualMsg("/var/www/html/blog", lib.dirname(path), "dirname of multi-level")
Test.assertEqualMsg("index.html", lib.basename(path), "basename of multi-level")
Test.assert(lib.isAbsolute(path), "multi-level path is absolute")
}
// Verify path properties are consistent
fn test_path_property_consistency(): Unit with {Test} = {
let path = "src/components/Button.tsx"
Test.assertEqualMsg(true, lib.isRelative(path), "is relative")
Test.assertEqualMsg(false, lib.isAbsolute(path), "is not absolute")
Test.assertEqualMsg(true, lib.hasExtension(path, "tsx"), "has .tsx extension")
Test.assertEqualMsg(false, lib.hasExtension(path, "ts"), "does not have .ts extension")
Test.assertEqualMsg("Button", lib.stem(path), "stem is Button")
Test.assertEqualMsg("src/components", lib.dirname(path), "dirname is src/components")
}
// Swap extension and verify roundtrip
fn test_extension_roundtrip(): Unit with {Test} = {
let original = "report.csv"
let swapped = lib.replaceExtension(original, "json")
Test.assertEqualMsg("report.json", swapped, "csv -> json")
let back = lib.replaceExtension(swapped, "csv")
Test.assertEqualMsg("report.csv", back, "json -> csv roundtrip")
}
// Build a path for a static site generator output
fn test_ssg_path_pipeline(): Unit with {Test} = {
let inputFile = "content/blog/my-first-post.md"
let slug = lib.stem(inputFile)
let outputDir = lib.join("_site", slug)
let outputFile = lib.join(outputDir, "index.html")
Test.assertEqualMsg("my-first-post", slug, "extract slug")
Test.assertEqualMsg("_site/my-first-post", outputDir, "output directory")
Test.assertEqualMsg("_site/my-first-post/index.html", outputFile, "full output path")
}

View File

@@ -0,0 +1,72 @@
import lib
fn isSome(opt: Option<String>, expected: String): Bool =
match opt {
Some(v) => v == expected,
None => false,
}
fn isNone(opt: Option<String>): Bool =
match opt {
Some(_) => false,
None => true,
}
fn test_basename(): Unit with {Test} = {
Test.assertEqualMsg("baz.txt", lib.basename("/foo/bar/baz.txt"), "basename with dirs")
Test.assertEqualMsg("file.txt", lib.basename("file.txt"), "basename no dir")
Test.assertEqualMsg("", lib.basename("/foo/bar/"), "basename trailing slash")
Test.assertEqualMsg("", lib.basename(""), "basename empty")
}
fn test_dirname(): Unit with {Test} = {
Test.assertEqualMsg("/foo/bar", lib.dirname("/foo/bar/baz.txt"), "dirname with dirs")
Test.assertEqualMsg(".", lib.dirname("file.txt"), "dirname no dir")
Test.assertEqualMsg("/", lib.dirname("/file.txt"), "dirname root")
}
fn test_extension(): Unit with {Test} = {
Test.assert(isSome(lib.extension("file.txt"), "txt"), "extension simple")
Test.assert(isSome(lib.extension("file.tar.gz"), "gz"), "extension double")
Test.assert(isNone(lib.extension("file")), "extension none")
Test.assert(isSome(lib.extension("/foo/bar.txt"), "txt"), "extension with dir")
Test.assert(isNone(lib.extension(".hidden")), "extension dotfile")
}
fn test_strip_extension(): Unit with {Test} = {
Test.assertEqualMsg("file", lib.stripExtension("file.txt"), "stripExtension simple")
Test.assertEqualMsg("file", lib.stripExtension("file"), "stripExtension none")
Test.assertEqualMsg("/foo/bar", lib.stripExtension("/foo/bar.txt"), "stripExtension with dir")
}
fn test_join(): Unit with {Test} = {
Test.assertEqualMsg("/foo/bar", lib.join("/foo", "bar"), "join basic")
Test.assertEqualMsg("/foo/bar", lib.join("/foo/", "bar"), "join trailing slash")
Test.assertEqualMsg("/foo/bar", lib.join("/foo", "/bar"), "join leading slash")
Test.assertEqualMsg("/foo/bar", lib.join("/foo/", "/bar"), "join both slashes")
Test.assertEqualMsg("bar", lib.join("", "bar"), "join empty first")
Test.assertEqualMsg("foo", lib.join("foo", ""), "join empty second")
}
fn test_has_extension(): Unit with {Test} = {
Test.assertEqualMsg(true, lib.hasExtension("file.txt", "txt"), "hasExtension true")
Test.assertEqualMsg(false, lib.hasExtension("file.md", "txt"), "hasExtension false")
}
fn test_replace_extension(): Unit with {Test} = {
Test.assertEqualMsg("file.md", lib.replaceExtension("file.txt", "md"), "replaceExtension simple")
Test.assertEqualMsg("file.md", lib.replaceExtension("file", "md"), "replaceExtension no ext")
}
fn test_stem(): Unit with {Test} = {
Test.assertEqualMsg("file", lib.stem("file.txt"), "stem simple")
Test.assertEqualMsg("bar", lib.stem("/foo/bar.txt"), "stem with dir")
Test.assertEqualMsg("file", lib.stem("file"), "stem no ext")
}
fn test_absolute_relative(): Unit with {Test} = {
Test.assertEqualMsg(true, lib.isAbsolute("/foo/bar"), "isAbsolute true")
Test.assertEqualMsg(false, lib.isAbsolute("foo/bar"), "isAbsolute false")
Test.assertEqualMsg(true, lib.isRelative("foo/bar"), "isRelative true")
Test.assertEqualMsg(false, lib.isRelative("/foo/bar"), "isRelative false")
}

View File

@@ -0,0 +1,65 @@
import lib
// Snapshot tests: verify complete path decomposition output against golden values
// Snapshot: decompose a typical web project file
fn test_snapshot_web_project_file(): Unit with {Test} = {
let path = "/home/user/project/src/components/Button.tsx"
Test.assertEqualMsg("Button.tsx", lib.basename(path), "snap: basename")
Test.assertEqualMsg("/home/user/project/src/components", lib.dirname(path), "snap: dirname")
Test.assertEqualMsg("tsx", match lib.extension(path) { Some(e) => e, None => "" }, "snap: extension")
Test.assertEqualMsg("Button", lib.stem(path), "snap: stem")
Test.assertEqualMsg("/home/user/project/src/components/Button", lib.stripExtension(path), "snap: stripExtension")
Test.assertEqualMsg(true, lib.isAbsolute(path), "snap: isAbsolute")
Test.assertEqualMsg(false, lib.isRelative(path), "snap: isRelative")
Test.assertEqualMsg(true, lib.hasExtension(path, "tsx"), "snap: hasExtension tsx")
Test.assertEqualMsg(false, lib.hasExtension(path, "ts"), "snap: hasExtension ts")
Test.assertEqualMsg("/home/user/project/src/components/Button.jsx", lib.replaceExtension(path, "jsx"), "snap: replaceExtension")
}
// Snapshot: decompose a markdown blog post path
fn test_snapshot_blog_post(): Unit with {Test} = {
let path = "content/posts/2025/my-first-post.md"
Test.assertEqualMsg("my-first-post.md", lib.basename(path), "snap: basename")
Test.assertEqualMsg("content/posts/2025", lib.dirname(path), "snap: dirname")
Test.assertEqualMsg("md", match lib.extension(path) { Some(e) => e, None => "" }, "snap: extension")
Test.assertEqualMsg("my-first-post", lib.stem(path), "snap: stem")
Test.assertEqualMsg("content/posts/2025/my-first-post", lib.stripExtension(path), "snap: stripExtension")
Test.assertEqualMsg(false, lib.isAbsolute(path), "snap: isAbsolute")
Test.assertEqualMsg(true, lib.isRelative(path), "snap: isRelative")
Test.assertEqualMsg("content/posts/2025/my-first-post.html", lib.replaceExtension(path, "html"), "snap: replaceExtension")
}
// Snapshot: SSG path transformation pipeline
fn test_snapshot_ssg_pipeline(): Unit with {Test} = {
let input = "pages/about.md"
let slug = lib.stem(input)
let outputDir = lib.join("_site", slug)
let indexFile = lib.join(outputDir, "index.html")
Test.assertEqualMsg("about", slug, "snap: slug")
Test.assertEqualMsg("_site/about", outputDir, "snap: output dir")
Test.assertEqualMsg("_site/about/index.html", indexFile, "snap: index file")
Test.assertEqualMsg("_site/about", lib.dirname(indexFile), "snap: dirname of index")
Test.assertEqualMsg("index.html", lib.basename(indexFile), "snap: basename of index")
Test.assertEqualMsg("index", lib.stem(indexFile), "snap: stem of index")
}
// Snapshot: dotfile handling
fn test_snapshot_dotfiles(): Unit with {Test} = {
Test.assertEqualMsg(".gitignore", lib.basename("/project/.gitignore"), "snap: dotfile basename")
Test.assertEqualMsg("/project", lib.dirname("/project/.gitignore"), "snap: dotfile dirname")
Test.assertEqualMsg(".gitignore", lib.stem(".gitignore"), "snap: dotfile stem")
Test.assertEqualMsg("json", match lib.extension(".eslintrc.json") { Some(e) => e, None => "" }, "snap: dotconfig extension")
Test.assertEqualMsg(".eslintrc", lib.stem(".eslintrc.json"), "snap: dotconfig stem")
}
// Snapshot: path join normalization
fn test_snapshot_join_normalization(): Unit with {Test} = {
Test.assertEqualMsg("/a/b", lib.join("/a", "b"), "snap: normal join")
Test.assertEqualMsg("/a/b", lib.join("/a/", "b"), "snap: trailing slash join")
Test.assertEqualMsg("/a/b", lib.join("/a", "/b"), "snap: leading slash join")
Test.assertEqualMsg("/a/b", lib.join("/a/", "/b"), "snap: both slash join")
Test.assertEqualMsg("b", lib.join("", "b"), "snap: empty first join")
Test.assertEqualMsg("a", lib.join("a", ""), "snap: empty second join")
Test.assertEqualMsg("", lib.join("", ""), "snap: both empty join")
}

144
packages/path/test_unit.lux Normal file
View File

@@ -0,0 +1,144 @@
import lib
// --- basename edge cases ---
fn test_basename_root(): Unit with {Test} =
Test.assertEqualMsg("", lib.basename("/"), "basename of root")
fn test_basename_multiple_slashes(): Unit with {Test} =
Test.assertEqualMsg("", lib.basename("///"), "basename of ///")
fn test_basename_dotfile(): Unit with {Test} =
Test.assertEqualMsg(".hidden", lib.basename("/home/.hidden"), "basename of dotfile")
fn test_basename_dots_in_name(): Unit with {Test} =
Test.assertEqualMsg("file.tar.gz", lib.basename("/path/to/file.tar.gz"), "basename with multiple dots")
fn test_basename_single_char(): Unit with {Test} =
Test.assertEqualMsg("x", lib.basename("x"), "basename of single char")
fn test_basename_deep_path(): Unit with {Test} =
Test.assertEqualMsg("deep.txt", lib.basename("/a/b/c/d/e/deep.txt"), "basename of deep path")
fn test_basename_space_in_name(): Unit with {Test} =
Test.assertEqualMsg("my file.txt", lib.basename("/path/my file.txt"), "basename with space")
// --- dirname edge cases ---
fn test_dirname_root(): Unit with {Test} =
Test.assertEqualMsg("/", lib.dirname("/"), "dirname of root")
fn test_dirname_root_file(): Unit with {Test} =
Test.assertEqualMsg("/", lib.dirname("/a"), "dirname of root-level file")
fn test_dirname_deep(): Unit with {Test} =
Test.assertEqualMsg("/a/b/c", lib.dirname("/a/b/c/d"), "dirname of deep path")
fn test_dirname_empty(): Unit with {Test} =
Test.assertEqualMsg(".", lib.dirname(""), "dirname of empty")
fn test_dirname_relative(): Unit with {Test} =
Test.assertEqualMsg("a/b", lib.dirname("a/b/c"), "dirname of relative path")
// --- extension edge cases ---
fn isSome(opt: Option<String>, expected: String): Bool =
match opt {
Some(v) => v == expected,
None => false,
}
fn isNone(opt: Option<String>): Bool =
match opt {
Some(_) => false,
None => true,
}
fn test_extension_dotfile(): Unit with {Test} =
Test.assert(isNone(lib.extension(".bashrc")), "extension of .bashrc is None")
fn test_extension_trailing_dot(): Unit with {Test} =
Test.assert(isSome(lib.extension("file."), ""), "extension of file. is empty string")
fn test_extension_multiple_dots(): Unit with {Test} =
Test.assert(isSome(lib.extension("archive.tar.gz"), "gz"), "extension of .tar.gz is gz")
fn test_extension_empty(): Unit with {Test} =
Test.assert(isNone(lib.extension("")), "extension of empty is None")
fn test_extension_no_dot(): Unit with {Test} =
Test.assert(isNone(lib.extension("Makefile")), "extension of Makefile is None")
fn test_extension_hidden_with_ext(): Unit with {Test} =
Test.assert(isSome(lib.extension(".config.json"), "json"), "extension of .config.json is json")
fn test_extension_dot_in_dir(): Unit with {Test} =
Test.assert(isSome(lib.extension("/path.d/file.txt"), "txt"), "extension through dotted dir")
// --- stripExtension edge cases ---
fn test_strip_extension_dotfile(): Unit with {Test} =
Test.assertEqualMsg("", lib.stripExtension(".bashrc"), "stripExtension of dotfile")
fn test_strip_extension_trailing_dot(): Unit with {Test} =
Test.assertEqualMsg("file", lib.stripExtension("file."), "stripExtension trailing dot")
fn test_strip_extension_double(): Unit with {Test} =
Test.assertEqualMsg("file.tar", lib.stripExtension("file.tar.gz"), "stripExtension only strips last")
fn test_strip_extension_dot_in_dir(): Unit with {Test} =
Test.assertEqualMsg("/usr/local.d/config", lib.stripExtension("/usr/local.d/config.ini"), "stripExtension with dot in dir")
// --- join edge cases ---
fn test_join_both_empty(): Unit with {Test} =
Test.assertEqualMsg("", lib.join("", ""), "join empty + empty")
fn test_join_double_slash(): Unit with {Test} =
Test.assertEqualMsg("/foo/bar", lib.join("/foo/", "/bar"), "join normalizes double slash")
fn test_join_root(): Unit with {Test} =
Test.assertEqualMsg("/bar", lib.join("/", "bar"), "join root + relative")
fn test_join_three_parts(): Unit with {Test} = {
let result = lib.join(lib.join("/a", "b"), "c")
Test.assertEqualMsg("/a/b/c", result, "three-part join")
}
// --- stem edge cases ---
fn test_stem_dotfile(): Unit with {Test} =
Test.assertEqualMsg(".hidden", lib.stem(".hidden"), "stem of dotfile preserved")
fn test_stem_multiple_dots(): Unit with {Test} =
Test.assertEqualMsg("file.tar", lib.stem("file.tar.gz"), "stem of .tar.gz")
fn test_stem_no_dir_no_ext(): Unit with {Test} =
Test.assertEqualMsg("README", lib.stem("README"), "stem of extensionless file")
// --- isAbsolute / isRelative edge cases ---
fn test_is_absolute_empty(): Unit with {Test} =
Test.assertEqualMsg(false, lib.isAbsolute(""), "empty is not absolute")
fn test_is_relative_empty(): Unit with {Test} =
Test.assertEqualMsg(true, lib.isRelative(""), "empty is relative")
fn test_is_absolute_just_slash(): Unit with {Test} =
Test.assertEqualMsg(true, lib.isAbsolute("/"), "/ is absolute")
// --- hasExtension / replaceExtension edge cases ---
fn test_has_extension_case_sensitive(): Unit with {Test} =
Test.assertEqualMsg(false, lib.hasExtension("file.TXT", "txt"), "hasExtension is case sensitive")
fn test_has_extension_partial(): Unit with {Test} =
Test.assertEqualMsg(false, lib.hasExtension("file.txta", "txt"), "hasExtension no partial match")
fn test_replace_extension_dotfile(): Unit with {Test} =
Test.assertEqualMsg(".md", lib.replaceExtension(".bashrc", "md"), "replaceExtension on dotfile")
fn test_replace_extension_chain(): Unit with {Test} = {
let result = lib.replaceExtension(lib.replaceExtension("file.txt", "md"), "html")
Test.assertEqualMsg("file.html", result, "chained replaceExtension")
}