feat(launcher-test): cover frontmatter + markdown parsers
Extends run_tests from 12 -> ~46 asserts: 10 frontmatter cases (simple field, no-frontmatter passthrough, numeric field, multi-line body, non-string input, empty meta) and ~25 markdown cases (headers, bullets, ordered, images, bold/italic/code spans, paragraph joining, mixed mid-block). frontmatter.lua and markdown.lua are byte-identical copies of the launcher's helpers; smoke.sh enforces no drift via git hash-object.
This commit is contained in:
68
frontmatter.lua
Normal file
68
frontmatter.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
-- frontmatter.lua — JSON-Frontmatter parser for manifest.launcher.md
|
||||
-- Splits on the first standalone '---' line. Everything before goes to
|
||||
-- depf._json_decode; everything after (after a single LF) is body.
|
||||
-- Loud-Error semantics: parse failure returns nil + err-string.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- depf is required lazily so this file can be dofile'd into a test-lib
|
||||
-- context where lib-management.dep-fetcher is not on the dep list. The
|
||||
-- launcher main module does require it eagerly; the test-lib monkeys
|
||||
-- a stub decoder into _G before calling fm.parse.
|
||||
local function get_decoder()
|
||||
if _G.LAUNCHER_FRONTMATTER_DECODER then
|
||||
return _G.LAUNCHER_FRONTMATTER_DECODER
|
||||
end
|
||||
local ok, depf = pcall(require, "lib-management.dep-fetcher")
|
||||
if ok and depf and depf._json_decode then return depf._json_decode end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Splits the content at the first line equal to '---'. The first such
|
||||
-- line ends the JSON region; lines before it are JSON, lines after
|
||||
-- are body. Both regions are LF-joined back into strings.
|
||||
local function split_at_separator(content)
|
||||
local lines = {}
|
||||
for line in (content .. "\n"):gmatch("([^\n]*)\n") do
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
-- A valid file starts with '---' (no leading whitespace).
|
||||
if lines[1] ~= "---" then return nil end
|
||||
local sep_idx = nil
|
||||
for i = 2, #lines do
|
||||
if lines[i] == "---" then sep_idx = i; break end
|
||||
end
|
||||
if not sep_idx then return nil end
|
||||
local json_lines = {}
|
||||
for i = 2, sep_idx - 1 do json_lines[#json_lines + 1] = lines[i] end
|
||||
local body_lines = {}
|
||||
for i = sep_idx + 1, #lines do body_lines[#body_lines + 1] = lines[i] end
|
||||
-- Drop the trailing empty entry produced by the final LF appended above,
|
||||
-- so the body matches user expectation ('body text' rather than 'body text\n').
|
||||
if #body_lines > 0 and body_lines[#body_lines] == "" then
|
||||
body_lines[#body_lines] = nil
|
||||
end
|
||||
return table.concat(json_lines, "\n"), table.concat(body_lines, "\n")
|
||||
end
|
||||
|
||||
function M.parse(content)
|
||||
if type(content) ~= "string" then
|
||||
return nil, nil, "frontmatter.parse: content must be string"
|
||||
end
|
||||
local json_str, body = split_at_separator(content)
|
||||
if not json_str then
|
||||
-- No frontmatter — treat the whole content as body, empty meta.
|
||||
return {}, content
|
||||
end
|
||||
local decode = get_decoder()
|
||||
if not decode then
|
||||
return nil, nil, "frontmatter.parse: no JSON decoder available"
|
||||
end
|
||||
local meta, derr = decode(json_str)
|
||||
if not meta then
|
||||
return nil, nil, "frontmatter.parse: " .. tostring(derr)
|
||||
end
|
||||
return meta, body
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user