diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a47ac..bb12829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- feat: `get_project_metadata` returns the `QUARTO_EXECUTE_INFO` path as a third value with each reason, so a caller can name the file without reading the variable again. Its four reasons are now a stable API that callers can compare against. (#21) + ## 2.7.0 (2026-10-29) - feat: Add `get_project_metadata`, which returns the project's `format.metadata` table from the `QUARTO_EXECUTE_INFO` file. When the file is set but can't be used, it returns `nil` and a short reason, so each extension can stay silent or warn in its own words. (#19) diff --git a/modules/metadata.lua b/modules/metadata.lua index cedfa95..fc5b9ac 100644 --- a/modules/metadata.lua +++ b/modules/metadata.lua @@ -195,8 +195,13 @@ end --- The file is read on every call; pass the result on rather than calling twice. --- @return table|nil format_meta The format.metadata table, or nil if unavailable --- @return string|nil reason Why the file could not be used when ---- QUARTO_EXECUTE_INFO is set; nil when it is unset or empty ---- @usage local format_meta, reason = M.get_project_metadata() +--- QUARTO_EXECUTE_INFO is set; nil when it is unset or empty. +--- The reasons are a stable API: callers can compare against them, and a +--- change to any of them is a breaking change. They are exactly +--- "file is unreadable", "file is empty", "invalid JSON" and +--- "no format.metadata table". +--- @return string|nil path The QUARTO_EXECUTE_INFO path, with every reason +--- @usage local format_meta, reason, path = M.get_project_metadata() --- @note quarto-cli's decoder raises on bad JSON, while q2's (pandoc.json) --- returns nil, so a nil result is also reported as invalid JSON. function M.get_project_metadata() @@ -204,19 +209,19 @@ function M.get_project_metadata() if str.is_empty(path) then return nil end local file = io.open(path, "r") - if not file then return nil, "file is unreadable" end + if not file then return nil, "file is unreadable", path end local content = file:read("*a") file:close() - if str.is_empty(content) then return nil, "file is empty" end + if str.is_empty(content) then return nil, "file is empty", path end local ok, info = pcall(quarto.json.decode, content) - if not ok or info == nil then return nil, "invalid JSON" end + if not ok or info == nil then return nil, "invalid JSON", path end if type(info) ~= "table" or type(info["format"]) ~= "table" or type(info["format"]["metadata"]) ~= "table" then - return nil, "no format.metadata table" + return nil, "no format.metadata table", path end return info["format"]["metadata"] diff --git a/tests/run.lua b/tests/run.lua index 098d51f..21c9c6c 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -379,7 +379,7 @@ do end return previous_getenv(name) end - fn() + fn(value) end --- Point QUARTO_EXECUTE_INFO at a file holding `content`, or at a path that @@ -394,16 +394,18 @@ do end) end - local function reports(description, expected_reason) - local ok, format_meta, reason = pcall(metadata.get_project_metadata) - check(ok and format_meta == nil and reason == expected_reason, description, - ok and string.format('expected nil, %s; got %s, %s', - tostring(expected_reason), tostring(format_meta), tostring(reason)) + local function reports(description, expected_reason, expected_path) + local ok, format_meta, reason, path = pcall(metadata.get_project_metadata) + check(ok and format_meta == nil and reason == expected_reason and path == expected_path, + description, + ok and string.format('expected nil, %s, %s; got %s, %s, %s', + tostring(expected_reason), tostring(expected_path), + tostring(format_meta), tostring(reason), tostring(path)) or ('raised: ' .. tostring(format_meta))) end local function reports_for(content, description, expected_reason) - with_execute_info(content, function() reports(description, expected_reason) end) + with_execute_info(content, function(path) reports(description, expected_reason, path) end) end local function returns_blocks(content, description, predicate)