Skip to content

Commit

Permalink
feat: Generic rendered code blocks based on raw handlers and embedders
Browse files Browse the repository at this point in the history
This removes the hard-coded class specifiers allows any existing SILE
raw handler or any embedder to be used.
If the piecharts package is available (for instance), we can render
blocks marked as `piechart` since the package provides a row handler
by that name.
  • Loading branch information
Omikhleia authored and Didier Willis committed Feb 11, 2024
1 parent 6707e98 commit add84c1
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 47 deletions.
111 changes: 65 additions & 46 deletions packages/markdown/commands.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
--- Common commands for Markdown support in SILE, when there is no
--- Common commands for Markdown and Djot support in SILE, when there is no
-- direct mapping to existing commands or packages.
--
-- Split in a standalone package so that it can be reused and
Expand Down Expand Up @@ -149,6 +149,11 @@ function package:loadPackageAlt(resilientpack, legacypack)
end
end

function package:loadPackageOptional(pack)
local ok, _ = pcall(function () return self:loadPackage(pack) end)
SU.debug("markdown", "Optional package "..pack.. (ok and " loaded" or " not loaded"))
end

-- For feature detection.
-- NOTE: The previous implementation was clever;
-- local ok, ResilientBase = pcall(require, 'classes.resilient.base')
Expand Down Expand Up @@ -197,7 +202,8 @@ function package:_init (_)
self:loadPackageAlt("resilient.verbatim", "verbatim")

-- Optional packages
pcall(function () return self:loadPackage("couyards") end)
self:loadPackageOptional("couyards")
self:loadPackageOptional("piecharts")

-- Other conditional packages
if self.isResilient then
Expand Down Expand Up @@ -621,52 +627,65 @@ Please consider using a resilient-compatible class!]])
end
end, "Captioned figure in Markdown (internal)")

self:registerCommand("markdown:internal:codeblock", function (options, content)
local render = SU.boolean(options.render, true)
if render and hasClass(options, "dot") then
local handler = SILE.rawHandlers["embed"]
if not handler then
-- Shouldn't occur since we loaded the embedders package
SU.error("No inline handler for image embedding")
end
options.format = options.class
handler(options, content)
elseif render and hasClass(options, "djot") then
SILE.processString(SU.contentToString(content), "djot", nil, options)
elseif render and hasClass(options, "markdown") then
SILE.processString(SU.contentToString(content), "markdown", nil, options)
elseif hasClass(options, "lua") then
-- Naive syntax highlighting for Lua, until we have a more general solution
local tree = {}
if options.id then
tree[#tree+1] = createCommand("label", { marker = options.id })
-- Code blocks

self:registerCommand("markdown:internal:lua-highlighter", function (options, content)
-- Naive syntax highlighting for Lua, until we have a more general solution
local tree = {}
if options.id then
tree[#tree+1] = createCommand("label", { marker = options.id })
end
local toks = pl.lexer.lua(content[1], {})
for tag, v in toks do
local out = tostring(v)
if tag == "string" then
-- rebuild string quoting...
out = out:match('"') and ("'"..out.."'") or ('"'..out..'"')
end
local toks = pl.lexer.lua(content[1], {})
for tag, v in toks do
local out = tostring(v)
if tag == "string" then
-- rebuild string quoting...
out = out:match('"') and ("'"..out.."'") or ('"'..out..'"')
if naiveLuaCodeTheme[tag] then
local cascade = CommandCascade()
if naiveLuaCodeTheme[tag].color then
cascade:call("color", { color = naiveLuaCodeTheme[tag].color })
end
if naiveLuaCodeTheme[tag] then
local cascade = CommandCascade()
if naiveLuaCodeTheme[tag].color then
cascade:call("color", { color = naiveLuaCodeTheme[tag].color })
end
if naiveLuaCodeTheme[tag].bold then
cascade:call("strong", {})
end
if naiveLuaCodeTheme[tag].italic then
cascade:call("em", {})
end
tree[#tree+1] = cascade:tree({ out })
else
tree[#tree+1] = SU.utf8charfromcodepoint("U+200B")..out -- HACK with ZWSP to trick the typesetter respecting standalone linebreaks
if naiveLuaCodeTheme[tag].bold then
cascade:call("strong", {})
end
if naiveLuaCodeTheme[tag].italic then
cascade:call("em", {})
end
tree[#tree+1] = cascade:tree({ out })
else
tree[#tree+1] = SU.utf8charfromcodepoint("U+200B")..out -- HACK with ZWSP to trick the typesetter respecting standalone linebreaks
end
SILE.call("verbatim", {}, tree)
else
-- Just raw unstyled verbatim
end
SILE.call("verbatim", {}, tree)
end, "Lua code block naive syntax highlighting in Markdown or Djot (internal)")

self:registerCommand("markdown:internal:codeblock", function (options, content)
local render = SU.boolean(options.render, true)
local processed = false
if hasClass(options, "lua") then
-- Comes first as we don't want SILE raw handler to take over here.
-- (There's no such raw handler in the standard SILE distribution currently,
-- but let's be cautious.)
SILE.call("markdown:internal:lua-highlighter", options, content)
processed = true
elseif render then
local handler = utils.hasRawHandler(options)
if handler then
handler(options, content)
processed = true
else
local format, embed = utils.hasEmbedHandler(options)
if format then
options.format = format
embed(options, content)
processed = true
end
end
end
if not processed then
-- Default case: just raw unstyled verbatim text
SILE.call("verbatim", {},
options.id and {
createCommand("label", { marker = options.id }),
Expand All @@ -676,7 +695,7 @@ Please consider using a resilient-compatible class!]])
}
)
end
SILE.call("smallskip")
SILE.call("par")
end, "(Fenced) code block in Markdown (internal)")

self:registerCommand("markdown:internal:lineblock", function (_, content)
Expand Down Expand Up @@ -893,7 +912,7 @@ Please consider using a resilient-compatible class!]])
end

package.documentation = [[\begin{document}
A helper package for Markdown processing, providing common hooks and fallback commands.
A helper package for Markdown and Djot processing, providing common hooks and fallback commands.
It is not intended to be used alone.
\end{document}]]
Expand Down
46 changes: 45 additions & 1 deletion packages/markdown/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ local function nbspFilter (str)
return #t == 1 and t[1] or t
end

--- Check if a given class is present in the options.
---@param options table Command options
---@param classname string Pseudo-class specifier
---@return boolean
local function hasClass (options, classname)
-- N.B. we want a true boolean here
if options.class and string.match(' ' .. options.class .. ' ',' '..classname..' ') then
Expand All @@ -40,9 +44,49 @@ local function hasClass (options, classname)
return false
end

--- Find the first raw handler suitable for the given pseudo-class attributes.
---@param options table Command options
---@return function|nil Handler function (if found)
local function hasRawHandler (options)
for name, handler in pairs(SILE.rawHandlers) do
if hasClass(options, name) then
SU.debug("markdown", "Found a raw handler for", name)
return handler
end
end
return nil
end

--- Find the first embedder suitable for the given pseudo-class attributes.
---@param options table Command options
---@return string|nil, function Embedder name and handler function (if found)
local function hasEmbedHandler (options)
local handler = SILE.rawHandlers["embed"]
if not handler then
-- Shouldn't occur since we loaded the embedders package
-- but let's play safe...
return nil
end
-- HACK TODO: Use of a scratch variable is ugly, tapping into the
-- internals of the embedders package.
local embedders = SILE.scratch.embedders
if not embedders then
return nil
end
for name, _ in pairs(SILE.scratch.embedders) do
if hasClass(options, name) then
SU.debug("markdown", "Found an embedder for", name)
return name, handler
end
end
return nil
end

--- @export
return {
getFileExtension = getFileExtension,
nbspFilter = nbspFilter,
hasClass = hasClass
hasClass = hasClass,
hasRawHandler = hasRawHandler,
hasEmbedHandler = hasEmbedHandler,
}

0 comments on commit add84c1

Please sign in to comment.