From e691e08c5b64d45f76ddfd36eaa939aeb1ff8e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:47:27 +0100 Subject: [PATCH 1/4] feat: return path and fix reasons of get_project_metadata Callers compare the reason strings, so they are now documented as a stable API. The unreadable case also returns the QUARTO_EXECUTE_INFO path, so a caller can name the file without reading the variable again. --- CHANGELOG.md | 2 ++ modules/metadata.lua | 12 +++++++++--- tests/run.lua | 19 ++++++++++++------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87a47ac..06bf27e 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 when the file is unreadable, 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. + ## 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..816fa1b 100644 --- a/modules/metadata.lua +++ b/modules/metadata.lua @@ -195,8 +195,14 @@ 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, only with the +--- "file is unreadable" 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,7 +210,7 @@ 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() diff --git a/tests/run.lua b/tests/run.lua index 098d51f..1f8b914 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -390,15 +390,17 @@ do if content ~= nil then write(path, content) end - with_env(path, fn) + with_env(path, function() fn(path) end) 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 @@ -423,7 +425,10 @@ do with_env('', function() reports('get_project_metadata is silent when QUARTO_EXECUTE_INFO is empty', nil) end) - reports_for(nil, 'get_project_metadata reports a file that cannot be opened', 'file is unreadable') + with_execute_info(nil, function(path) + reports('get_project_metadata reports a file that cannot be opened, with its path', + 'file is unreadable', path) + end) reports_for('', 'get_project_metadata reports an empty file', 'file is empty') reports_for('{bad', 'get_project_metadata reports invalid JSON', 'invalid JSON') reports_for(' \n', 'get_project_metadata reports whitespace-only content as invalid JSON', From 4d8babaa90678efa2b5d137a7dc6d169c4a1396d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:47:45 +0100 Subject: [PATCH 2/4] test: pass the env value to the with_env callback --- tests/run.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run.lua b/tests/run.lua index 1f8b914..fb21a04 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 @@ -390,7 +390,7 @@ do if content ~= nil then write(path, content) end - with_env(path, function() fn(path) end) + with_env(path, fn) end) end From 73ba1890345f76a8937307e4c9877b88875d40fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:49:02 +0100 Subject: [PATCH 3/4] feat: return the path with every metadata reason --- CHANGELOG.md | 2 +- modules/metadata.lua | 9 ++++----- tests/run.lua | 7 ++----- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06bf27e..1d4365b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- feat: `get_project_metadata` returns the `QUARTO_EXECUTE_INFO` path as a third value when the file is unreadable, 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. +- 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. ## 2.7.0 (2026-10-29) diff --git a/modules/metadata.lua b/modules/metadata.lua index 816fa1b..fc5b9ac 100644 --- a/modules/metadata.lua +++ b/modules/metadata.lua @@ -200,8 +200,7 @@ end --- 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, only with the ---- "file is unreadable" reason +--- @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. @@ -215,14 +214,14 @@ function M.get_project_metadata() 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 fb21a04..21c9c6c 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -405,7 +405,7 @@ do 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) @@ -425,10 +425,7 @@ do with_env('', function() reports('get_project_metadata is silent when QUARTO_EXECUTE_INFO is empty', nil) end) - with_execute_info(nil, function(path) - reports('get_project_metadata reports a file that cannot be opened, with its path', - 'file is unreadable', path) - end) + reports_for(nil, 'get_project_metadata reports a file that cannot be opened', 'file is unreadable') reports_for('', 'get_project_metadata reports an empty file', 'file is empty') reports_for('{bad', 'get_project_metadata reports invalid JSON', 'invalid JSON') reports_for(' \n', 'get_project_metadata reports whitespace-only content as invalid JSON', From 01ac455a0bfcfd57f22b73bd02be65757e14f064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:50:20 +0100 Subject: [PATCH 4/4] docs: add PR reference to changelog [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d4365b..bb12829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 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. +- 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)