Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion src/resources/filters/ast/customnodes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,51 @@ function is_regular_node(node, name)
return node
end

function run_emulated_filter(doc, filter, traverser)
--- Checks if a filter follows the "nondestructive" property.
-- The nondestructive property is fulfilled if filter functions returns
-- an explicit object, or if it returns `nil` while leaving the passed
-- in object unmodified.
--
-- An error is raised if the property is violated.
--
-- Only filters with this property can use jog safely, without
-- unintended consequences.
local function check_nondestructive_property (filter, filtername)
for name, fn in pairs(filter or {}) do
if type(fn) == 'function' then
local copy = function (x)
local tp = type(x)
return tp ~= 'table' and x:clone() or
(pandoc.utils.type(x) == 'Meta' and pandoc.Meta(x) or tcopy(x, {}))
end
filter[name] = function (obj, context)
local orig = copy(obj)
local result, descend = fn(obj, context)
if result == nil then
if type(obj) ~= 'table' and not tequals(obj, orig) then
warn(
"\nFunction '" .. name ..
"' in filter '" .. (filtername or '<unknown>') ..
"' returned `nil`, but modified the input."
)
end
elseif result.t == obj.t and not rawequal(result, obj) then
warn(
"\nFunction '" .. name ..
"' in filter '" .. (filtername or '<unknown>') ..
"' returned a new object instead of passing the original one through."
)
end
return result, descend
end
end
end
return filter
end


function run_emulated_filter(doc, filter, traverser, name)
name = name or '<unnamed>'
if doc == nil then
return nil
end
Expand Down Expand Up @@ -79,6 +123,11 @@ function run_emulated_filter(doc, filter, traverser)
_quarto.traverser = _quarto.utils.walk
elseif traverser == 'jog' then
_quarto.traverser = _quarto.modules.jog
elseif traverser == 'checked-jog' then
_quarto.traverser = function(obj, filter_functions)
check_nondestructive_property(filter_functions, name)
return old_traverse(obj, filter_functions)
end
elseif type(traverser) == 'function' then
_quarto.traverser = traverser
else
Expand Down
13 changes: 12 additions & 1 deletion src/resources/filters/ast/runemulation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ end

local function run_emulated_filter_chain(doc, filters, afterFilterPass, profiling)
init_trace(doc)
local compare_jog_and_walk = os.getenv 'QUARTO_JOG_CHECK'
for i, v in ipairs(filters) do
local function callback()
if v.flags then
Expand Down Expand Up @@ -79,7 +80,17 @@ local function run_emulated_filter_chain(doc, filters, afterFilterPass, profilin
print(pandoc.write(doc, "native"))
else
_quarto.ast._current_doc = doc
doc = run_emulated_filter(doc, v.filter, v.traverser)

if compare_jog_and_walk and not v.force_pandoc_walk then
v.traverser = 'checked-jog'
end
doc = run_emulated_filter(doc, v.filter, v.traverser, v.name)

if compare_jog_and_walk and not v.force_pandoc_walk then
-- Types of meta values are only checked on assignment.
doc.meta = doc.meta
end

ensure_vault(doc)

add_trace(doc, v.name)
Expand Down
50 changes: 50 additions & 0 deletions src/resources/filters/common/table.lua
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,53 @@ function spairs(t, order)
end
end
end

--- Checks if two tables are equal
function tequals(o1, o2)
if o1 == o2 then
return true
end
local o1type = type(o1)
local o2type = type(o2)
if o1type ~= o2type or o1type ~= 'table' then
return false
end

local keys = {}

for key1, value1 in pairs(o1) do
local value2 = o2[key1]
if value2 == nil or tequals(value1, value2) == false then
return false
end
keys[key1] = true
end

for key2 in pairs(o2) do
if not keys[key2] then return false end
end
return true
end

--- Create a deep copy of a table.
function tcopy (tbl, seen)
local tp = type(tbl)
if tp == 'table' then
if seen[tbl] then
return seen[tbl]
end
local copy = {}
-- Iterate 'raw' pairs, i.e., without using metamethods
for key, value in next, tbl, nil do
copy[tcopy(key, seen)] = tcopy(value)
end
copy = setmetatable(copy, getmetatable(tbl))
seen[tbl] = copy
return copy
elseif tp == 'userdata' then
return tbl:clone()
else -- number, string, boolean, etc
return tbl
end
end

4 changes: 4 additions & 0 deletions src/resources/filters/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ import("./quarto-init/metainit.lua")

-- [/import]

if os.getenv 'QUARTO_JOG_CHECK' then
_quarto.modules.attribcheck.enable_attribute_checks()
end

initCrossrefIndex()

initShortcodeHandlers()
Expand Down
Loading
Loading