feat: add blu-site static site generator and fix language issues
Build a complete static site generator in Lux that faithfully clones
blu.cx (elmstatic). Generates 14 post pages, section indexes, tag pages,
and a home page with snippets grid from markdown content.
Language fixes discovered during development:
- Add \{ and \} escape sequences in string literals (lexer)
- Register String.indexOf and String.lastIndexOf in type checker
- Fix formatter to preserve brace escapes in string literals
- Improve LSP hover to show documentation for let bindings and functions
ISSUES.md documents 15 Lux language limitations found during the project.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
79
projects/blu-site/frontmatter.lux
Normal file
79
projects/blu-site/frontmatter.lux
Normal file
@@ -0,0 +1,79 @@
|
||||
// YAML-like frontmatter parser
|
||||
// Parses the --- delimited frontmatter block from markdown files
|
||||
|
||||
pub type Frontmatter =
|
||||
| Frontmatter(String, String, String, String)
|
||||
// title date desc tagsRaw
|
||||
|
||||
pub type ParseResult =
|
||||
| ParseResult(Frontmatter, String)
|
||||
// front body
|
||||
|
||||
pub fn parse(content: String): ParseResult = {
|
||||
let lines = String.lines(content);
|
||||
// Skip first --- line, collect key: value pairs until next ---
|
||||
let result = List.fold(lines, (false, false, "", "", "", "", ""), fn(acc: (Bool, Bool, String, String, String, String, String), line: String): (Bool, Bool, String, String, String, String, String) => {
|
||||
let inFront = acc.0;
|
||||
let pastFront = acc.1;
|
||||
let title = acc.2;
|
||||
let date = acc.3;
|
||||
let desc = acc.4;
|
||||
let tags = acc.5;
|
||||
let body = acc.6;
|
||||
if pastFront then
|
||||
(inFront, pastFront, title, date, desc, tags, body + line + "\n")
|
||||
else if String.trim(line) == "---" then
|
||||
if inFront then
|
||||
// End of frontmatter
|
||||
(false, true, title, date, desc, tags, body)
|
||||
else
|
||||
// Start of frontmatter
|
||||
(true, false, title, date, desc, tags, body)
|
||||
else if inFront then {
|
||||
// Parse key: value
|
||||
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)));
|
||||
// Strip surrounding quotes if present
|
||||
let val = if String.startsWith(rawVal, "\"") then
|
||||
String.substring(rawVal, 1, String.length(rawVal) - 1)
|
||||
else
|
||||
rawVal;
|
||||
if key == "title" then
|
||||
(inFront, pastFront, val, date, desc, tags, body)
|
||||
else if key == "date" then
|
||||
(inFront, pastFront, title, val, desc, tags, body)
|
||||
else if key == "description" then
|
||||
(inFront, pastFront, title, date, val, tags, body)
|
||||
else if key == "tags" then
|
||||
(inFront, pastFront, title, date, desc, val, body)
|
||||
else
|
||||
(inFront, pastFront, title, date, desc, tags, body)
|
||||
},
|
||||
None => (inFront, pastFront, title, date, desc, tags, body)
|
||||
}
|
||||
} else
|
||||
(inFront, pastFront, title, date, desc, tags, body)
|
||||
});
|
||||
let front = Frontmatter(result.2, result.3, result.4, result.5);
|
||||
ParseResult(front, result.6)
|
||||
}
|
||||
|
||||
pub fn getTitle(f: Frontmatter): String =
|
||||
match f { Frontmatter(t, _, _, _) => t }
|
||||
|
||||
pub fn getDate(f: Frontmatter): String =
|
||||
match f { Frontmatter(_, d, _, _) => d }
|
||||
|
||||
pub fn getDesc(f: Frontmatter): String =
|
||||
match f { Frontmatter(_, _, d, _) => d }
|
||||
|
||||
pub fn getTagsRaw(f: Frontmatter): String =
|
||||
match f { Frontmatter(_, _, _, t) => t }
|
||||
|
||||
pub fn getTags(f: Frontmatter): List<String> =
|
||||
match f { Frontmatter(_, _, _, t) =>
|
||||
if t == "" then []
|
||||
else String.split(t, " ")
|
||||
}
|
||||
Reference in New Issue
Block a user