Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General lua improvements #11526

Merged
merged 5 commits into from
Dec 9, 2024
Merged
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
8 changes: 8 additions & 0 deletions news/changelog-1.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ All changes included in 1.7:
## `quarto check`

- ([#11608](https://github.com/quarto-dev/quarto-cli/pull/11608)): Do not issue error message when calling `quarto check info`.

## Lua Filters and extensions

- ([#11526](https://github.com/quarto-dev/quarto-cli/pull/11526)):
General improvements to the style and robustness of Quarto's Lua code.
This also provides a new public function `quarto.utils.is_empty_node`
that allows to check whether a node is empty, i.e., whether it's an
empty list, has no child nodes, and contains no text.
8 changes: 1 addition & 7 deletions src/resources/filters/ast/customnodes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -337,13 +337,7 @@ _quarto.ast = {
end
local node = node_accessor(table)
local t = pandoc.utils.type(value)
-- FIXME this is broken; that can only be "Block", "Inline", etc
if t == "Div" or t == "Span" then
local custom_data, t, kind = _quarto.ast.resolve_custom_data(value)
if custom_data ~= nil then
value = custom_data
end
end
quarto_assert(t ~= 'Div' and t ~= 'Span', "")
if index > #node.content then
_quarto.ast.grow_scaffold(node, index)
end
Expand Down
11 changes: 9 additions & 2 deletions src/resources/filters/common/error.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ function fail(message, level)
end
end

function internal_error()
fail("This is an internal error. Please file a bug report at https://github.com/quarto-dev/quarto-cli/", 5)
function internal_error(msg, level)
fail((msg and (msg .. '\n') or '') ..
"This is an internal error. Please file a bug report at https://github.com/quarto-dev/quarto-cli/", level or 5)
end

function quarto_assert (test, msg, level)
if not test then
internal_error(msg, level or 6)
end
end

function currentFile()
Expand Down
8 changes: 6 additions & 2 deletions src/resources/filters/common/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
-- could write to named filed (e.g. <docname>.filter.log) and client could read warnings and delete (also delete before run)
-- always append b/c multiple filters

--- The default, built-in error function.
-- The `error` global is redefined below.
local builtin_error_function = error

-- luacov: disable
local function caller_info(offset)
offset = offset or 3
Expand All @@ -27,6 +31,6 @@ end
function fatal(message, offset)
io.stderr:write(lunacolors.red("FATAL (" .. caller_info(offset) .. ") " ..message .. "\n"))
-- TODO write stack trace into log, and then exit.
crash_with_stack_trace()
builtin_error_function('FATAL QUARTO ERROR', offset)
end
-- luacov: enable
-- luacov: enable
23 changes: 11 additions & 12 deletions src/resources/filters/common/pandoc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,14 @@ function inlinesToString(inlines)
return pandoc.utils.stringify(pandoc.Span(inlines))
end

local InlinesMT = getmetatable(pandoc.Inlines{})

-- lua string to pandoc inlines
function stringToInlines(str)
if str then
return pandoc.Inlines({pandoc.Str(str)})
return setmetatable({pandoc.Str(str)}, InlinesMT)
else
return pandoc.Inlines({})
return setmetatable({}, InlinesMT)
end
end

Expand All @@ -98,27 +100,24 @@ end
function markdownToInlines(str)
if str then
local doc = pandoc.read(str)
if #doc.blocks == 0 then
return pandoc.List({})
else
return doc.blocks[1].content
end
return pandoc.utils.blocks_to_inlines(doc.blocks)
else
return pandoc.List()
return setmetatable({}, InlinesMT)
end
end


function stripTrailingSpace(inlines)
-- we always convert to pandoc.List to ensure a uniform
-- we always convert to pandoc.Inlines to ensure a uniform
-- return type (and its associated methods)
if #inlines > 0 then
if inlines[#inlines].t == "Space" then
return pandoc.List(tslice(inlines, 1, #inlines - 1))
return setmetatable(tslice(inlines, 1, #inlines - 1), InlinesMT)
else
return pandoc.List(inlines)
return setmetatable(inlines, InlinesMT)
end
else
return pandoc.List(inlines)
return setmetatable(inlines, InlinesMT)
end
end

Expand Down
137 changes: 90 additions & 47 deletions src/resources/pandoc/datadir/_utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -265,59 +265,75 @@ local function get_type(v)
return pandoc_type
end

local function as_inlines(v)
if v == nil then
return pandoc.Inlines({})
end
local t = pandoc.utils.type(v)
if t == "Inlines" then
---@cast v pandoc.Inlines
return v
elseif t == "Blocks" then
return pandoc.utils.blocks_to_inlines(v)
elseif t == "Inline" then
return pandoc.Inlines({v})
elseif t == "Block" then
return pandoc.utils.blocks_to_inlines({v})
end
--- Blocks metatable
local BlocksMT = getmetatable(pandoc.Blocks{})
--- Inlines metatable
local InlinesMT = getmetatable(pandoc.Inlines{})

if type(v) == "table" then
local result = pandoc.Inlines({})
for i, v in ipairs(v) do
tappend(result, as_inlines(v))
--- Turns the given object into a `Inlines` list.
--
-- Works mostly like `pandoc.Inlines`, but doesn't a do a full
-- unmarshal/marshal roundtrip. This buys performance, at the cost of
-- less thorough type checks.
--
-- NOTE: The input object might be modified *destructively*!
local function as_inlines(obj)
local pt = pandoc.utils.type(obj)
if pt == 'Inlines' then
return obj
elseif pt == "Inline" then
-- Faster than calling pandoc.Inlines
return setmetatable({obj}, InlinesMT)
elseif pt == 'List' or pt == 'table' then
if obj[1] and pandoc.utils.type(obj[1]) == 'Block' then
return pandoc.utils.blocks_to_inlines(obj)
end
return result
-- Faster than calling pandoc.Inlines
return setmetatable(obj, InlinesMT)
elseif pt == "Block" then
return pandoc.utils.blocks_to_inlines({obj})
elseif pt == "Blocks" then
return pandoc.utils.blocks_to_inlines(obj)
else
return pandoc.Inlines(obj or {})
end

-- luacov: disable
fatal("as_inlines: invalid type " .. t)
return pandoc.Inlines({})
-- luacov: enable
end

local function as_blocks(v)
if v == nil then
return pandoc.Blocks({})
end
local t = pandoc.utils.type(v)
if t == "Blocks" then
return v
elseif t == "Inlines" then
return pandoc.Blocks({pandoc.Plain(v)})
elseif t == "Block" then
return pandoc.Blocks({v})
elseif t == "Inline" then
return pandoc.Blocks({pandoc.Plain(v)})
end

if type(v) == "table" then
return pandoc.Blocks(v)
--- Turns the given object into a `Blocks` list.
--
-- Works mostly like `pandoc.Blocks`, but doesn't a do a full
-- unmarshal/marshal roundtrip. This buys performance, at the cost of
-- less thorough type checks.
--
-- NOTE: The input object might be modified *destructively*!
--
-- This might need some benchmarking.
local function as_blocks(obj)
local pt = pandoc.utils.type(obj)
if pt == 'Blocks' then
return obj
elseif pt == 'Block' then
-- Assigning a metatable directly is faster than calling
-- `pandoc.Blocks`.
return setmetatable({obj}, BlocksMT)
elseif pt == 'Inline' then
return setmetatable({pandoc.Plain{obj}}, BlocksMT)
elseif pt == 'Inlines' then
if next(obj) then
return setmetatable({pandoc.Plain(obj)}, BlocksMT)
end
return setmetatable({}, BlocksMT)
elseif pt == 'List' or (pt == 'table' and obj[1]) then
if pandoc.utils.type(obj[1]) == 'Inline' then
obj = {pandoc.Plain(obj)}
end
return setmetatable(obj, BlocksMT)
elseif (pt == 'table' and obj.long) or pt == 'Caption' then
-- Looks like a Caption
return as_blocks(obj.long)
else
return pandoc.Blocks(obj or {})
end

-- luacov: disable
fatal("as_blocks: invalid type " .. t)
return pandoc.Blocks({})
-- luacov: enable
end

local function match_fun(reset, ...)
Expand Down Expand Up @@ -557,6 +573,32 @@ local function match(...)
return match_fun(reset, table.unpack(result))
end

--- Returns `true` iff the given AST node is empty.
-- A node is considered "empty" if it's an empty list, table, or a node
-- without any text or nested AST nodes.
local function is_empty_node (node)
if not node then
return true
elseif type(node) == 'table' then
-- tables are considered empty if they don't have any fields.
return not next(node)
elseif node.content then
return not next(node.content)
elseif node.caption then
-- looks like an image, figure, or table
if node.caption.long then
return not next(node.caption.long)
end
return not next(node.caption)
elseif node.text then
-- looks like a code node or text node
return node.text ~= ''
else
-- Not sure what this is, but it's probably not empty.
return false
end
end

return {
dump = dump,
type = get_type,
Expand All @@ -567,6 +609,7 @@ return {
},
as_inlines = as_inlines,
as_blocks = as_blocks,
is_empty_node = is_empty_node,
match = match,
add_to_blocks = function(blocks, block)
if pandoc.utils.type(blocks) ~= "Blocks" then
Expand Down
1 change: 1 addition & 0 deletions src/resources/pandoc/datadir/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2085,6 +2085,7 @@ quarto = {
resolve_path_relative_to_document = resolvePath,
as_inlines = utils.as_inlines,
as_blocks = utils.as_blocks,
is_empty_node = utils.is_empty_node,
string_to_blocks = utils.string_to_blocks,
string_to_inlines = utils.string_to_inlines,
render = utils.render,
Expand Down
Loading