local function ensure_html_deps()
quarto.doc.add_html_dependency({
name = "pseudocode",
version = "2.4.1",
scripts = { "pseudocode.min.js" },
stylesheets = { "pseudocode.min.css" }
})
quarto.doc.include_text("after-body", [[
]])
end
local function ensure_latex_deps()
quarto.doc.use_latex_package("algorithm")
quarto.doc.use_latex_package("algpseudocode")
quarto.doc.use_latex_package("caption")
end
local function extract_source_code_options(source_code, render_type)
local options = {}
local source_codes = {}
local found_source_code = false
for str in string.gmatch(source_code, "([^\n]*)\n?") do
if (string.match(str, "^%s*#|.*") or string.gsub(str, "%s", "") == "") and not found_source_code then
if string.match(str, "^%s*#|%s+[" .. render_type .. "|label].*") then
str = string.gsub(str, "^%s*#|%s+", "")
local idx_start, idx_end = string.find(str, ":%s*")
if idx_start and idx_end and idx_end + 1 < #str then
k = string.sub(str, 1, idx_start - 1)
v = string.sub(str, idx_end + 1)
v = string.gsub(v, "^\"%s*", "")
v = string.gsub(v, "%s*\"$", "")
options[k] = v
else
quarto.log.warning("Invalid pseducode option: " .. str)
end
end
else
found_source_code = true
table.insert(source_codes, str)
end
end
return options, table.concat(source_codes, "\n")
end
local function render_pseudocode_block_html(global_options)
ensure_html_deps()
local filter = {
CodeBlock = function(el)
if not el.attr.classes:includes("pseudocode") then
return el
end
local options, source_code = extract_source_code_options(el.text, "html")
source_code = string.gsub(source_code, "%s*\\begin{algorithm}[^\n]+", "\\begin{algorithm}")
source_code = string.gsub(source_code, "%s*\\begin{algorithmic}[^\n]+", "\\begin{algorithmic}")
local alg_id = options["label"]
options["label"] = nil
options["html-caption-prefix"] = global_options.caption_prefix
if global_options.number_with_in_chapter and global_options.html_chapter_level then
options["html-chapter-level"] = global_options.html_chapter_level
end
if global_options.caption_number then
options["html-pseudocode-number"] = global_options.html_current_number
end
local data_options = {}
for k, v in pairs(options) do
if string.match(k, "^html-") then
data_k = string.gsub(k, "^html", "data")
data_options[data_k] = v
end
end
local inner_el = pandoc.Div(source_code)
inner_el.attr.classes = pandoc.List()
inner_el.attr.classes:insert("pseudocode")
local outer_el = pandoc.Div(inner_el)
outer_el.attr.classes = pandoc.List()
outer_el.attr.classes:insert("pseudocode-container")
outer_el.attr.classes:insert("quarto-float")
outer_el.attr.attributes = data_options
if alg_id then
outer_el.attr.identifier = alg_id
global_options.html_identifier_number_mapping[alg_id] = global_options.html_current_number
global_options.html_current_number = global_options.html_current_number + 1
end
return outer_el
end
}
return filter
end
local function render_pseudocode_block_latex(global_options)
ensure_latex_deps()
if global_options.caption_number then
quarto.doc.include_text("before-body", "\\floatname{algorithm}{" .. global_options.caption_prefix .. "}")
else
quarto.doc.include_text("in-header", "\\DeclareCaptionLabelFormat{algnonumber}{" .. global_options.caption_prefix .. "}")
quarto.doc.include_text("before-body", "\\captionsetup[algorithm]{labelformat=algnonumber}")
end
if global_options.number_with_in_chapter then
quarto.doc.include_text("before-body", "\\numberwithin{algorithm}{chapter}")
end
local filter = {
CodeBlock = function(el)
if not el.attr.classes:includes("pseudocode") then
return el
end
local options, source_code = extract_source_code_options(el.text, "pdf")
local pdf_placement = "H"
if options["pdf-placement"] then
pdf_placement = options["pdf-placement"]
end
source_code = string.gsub(source_code, "\\begin{algorithm}%s*\n", "\\begin{algorithm}[" .. pdf_placement .. "]\n")
if not options["pdf-line-number"] or options["pdf-line-number"] == "true" then
source_code = string.gsub(source_code, "\\begin{algorithmic}%s*\n", "\\begin{algorithmic}[1]\n")
end
if options["label"] then
source_code = string.gsub(source_code, "\\begin{algorithmic}", "\\label{" .. options["label"] .. "}\n\\begin{algorithmic}")
end
return pandoc.RawInline("latex", source_code)
end
}
return filter
end
local function render_pseudocode_block(global_options)
local filter = {
CodeBlock = function(el)
return el
end
}
if quarto.doc.is_format("html") then
filter = render_pseudocode_block_html(global_options)
elseif quarto.doc.is_format("latex") then
filter = render_pseudocode_block_latex(global_options)
end
return filter
end
local function render_pseudocode_ref_html(global_options)
local filter = {
Cite = function(el)
local cite_text = pandoc.utils.stringify(el.content)
for k, v in pairs(global_options.html_identifier_number_mapping) do
if cite_text == "@" .. k then
local link_src = "#" .. k
local alg_id = v
if global_options.html_chapter_level then
alg_id = global_options.html_chapter_level .. "." .. alg_id
end
local link_text = global_options.reference_prefix .. " " .. alg_id
local link = pandoc.Link(link_text, link_src)
link.attr.classes = pandoc.List()
link.attr.classes:insert("quarto-xref")
return link
end
end
end
}
return filter
end
local function render_pseudocode_ref_latex(global_options)
local filter = {
Cite = function(el)
local cite_text = pandoc.utils.stringify(el.content)
if string.match(cite_text, "^@alg-") then
return pandoc.RawInline("latex", global_options.reference_prefix .. "~\\ref{" .. string.gsub(cite_text, "^@", "") .. "}" )
end
end
}
return filter
end
local function render_pseudocode_ref(global_options)
local filter = {
Cite = function(el)
return el
end
}
if quarto.doc.is_format("html") then
filter = render_pseudocode_ref_html(global_options)
elseif quarto.doc.is_format("latex") then
filter = render_pseudocode_ref_latex(global_options)
end
return filter
end
function Pandoc(doc)
local global_options = {
caption_prefix = "Algorithm",
reference_prefix = "Algorithm",
caption_number = true,
number_with_in_chapter = false,
html_chapter_level = nil,
html_current_number = 1,
html_identifier_number_mapping = {}
}
if doc.meta["pseudocode"] then
global_options.caption_prefix = pandoc.utils.stringify(doc.meta["pseudocode"]["caption-prefix"]) or global_options.caption_prefix
global_options.reference_prefix = pandoc.utils.stringify(doc.meta["pseudocode"]["reference-prefix"]) or global_options.reference_prefix
global_options.caption_number = doc.meta["pseudocode"]["caption-number"] or global_options.caption_number
end
if doc.meta["book"] then
global_options.number_with_in_chapter = true
if quarto.doc.is_format("html") then
local _, input_qmd_filename = string.match(quarto.doc["input_file"], "^(.-)([^\\/]-%.([^\\/%.]-))$")
local renders = doc.meta["book"]["render"]
for _, render in pairs(renders) do
if render["file"] and render["number"] and pandoc.utils.stringify(render["file"]) == input_qmd_filename then
global_options.html_chapter_level = pandoc.utils.stringify(render["number"])
end
end
end
end
doc = doc:walk(render_pseudocode_block(global_options))
return doc:walk(render_pseudocode_ref(global_options))
end