diff --git a/inputters/djot.lua b/inputters/djot.lua
index 370cfbf..de6e8b1 100644
--- a/inputters/djot.lua
+++ b/inputters/djot.lua
@@ -6,6 +6,7 @@
-- @copyright License: MIT (c) 2023 Omikhleia
-- @module inputters.djot
--
+local utils = require("packages.markdown.utils")
local ast = require("silex.ast")
local createCommand, createStructuredCommand
= ast.createCommand, ast.createStructuredCommand
@@ -19,8 +20,8 @@ local Renderer = pl.class()
function Renderer:_init(options)
self.references = {}
self.footnotes = {}
- self.metadata = {}
self.shift_headings = SU.cast("integer", options.shift_headings or 0)
+ self.metadata = {}
for key, val in pairs(options) do
local meta = key:match("^meta:(.*)")
if meta then
@@ -60,7 +61,19 @@ function Renderer:render_children(node)
self.tight = node.tight
end
for i=1,#node.c do
- out[#out+1] = self[node.c[i].t](self, node.c[i])
+ local content = self[node.c[i].t](self, node.c[i])
+ -- Simplify outputs by collating strings
+ if type(content) == "string" and type(out[#out]) == "string" then
+ out[#out] = out[#out] .. content
+ else
+ -- Simplify out by removing empty elements
+ if type(content) ~= "table" or content.command or #content > 1 then
+ out[#out+1] = content
+ elseif #content == 1 then
+ -- Simplify out by removing useless grouping
+ out[#out+1] = content[1]
+ end
+ end
end
if node.tight ~= nil then
self.tight = oldtight
@@ -161,6 +174,19 @@ end
function Renderer:code_block (node)
local options = node.attr or {}
options.class = node.lang and ((options.class and (options.class.." ") or "") .. node.lang) or options.class
+ if utils.hasClass(options, "djot") or utils.hasClass(options, "markdown") then
+ options.shift_headings = self.shift_headings
+ self:resolveAllUserDefinedSymbols()
+ -- Parent metadata just have a label xxx
+ -- User-defined memoized metadata have the symbol (:xxx:) as key
+ -- So we sort the keys to get the user-defined metadata first as overrides.
+ for key, val in SU.sortedpairs(self.metadata) do
+ local name = key:match("^:([%w_+-]+):$") or key -- remove leading and trailing colons
+ if not options["meta:" .. name] then
+ options["meta:" .. name] = val
+ end
+ end
+ end
return createCommand("markdown:internal:codeblock", options, node.s, self:render_pos(node))
end
@@ -351,7 +377,7 @@ function Renderer.softbreak (_)
end
function Renderer.hardbreak (_)
- return createCommand("cr")
+ return createCommand("markdown:internal:hardbreak")
end
function Renderer:nbsp (node)
@@ -576,6 +602,31 @@ local predefinedSymbols = {
},
}
+function Renderer:getUserDefinedSymbol (label, node_fake_metadata)
+ local content
+ if self.metadata[label] then -- use memoized
+ content = self.metadata[label]
+ else
+ if #node_fake_metadata.c == 1 and node_fake_metadata.c[1].t == "para" then
+ -- Skip a single para node.
+ content = self:render_children(node_fake_metadata.c[1])
+ else
+ content = self:render_children(node_fake_metadata)
+ end
+ self.metadata[label] = content -- memoize
+ end
+ return content
+end
+
+function Renderer:resolveAllUserDefinedSymbols ()
+ -- Ensure all fake footnotes are rendered and memoized, even unused ones.
+ for label, node_fake_metadata in pairs(self.footnotes) do
+ if label:match("^:([%w_+-]+):$") then
+ self:getUserDefinedSymbol(label, node_fake_metadata)
+ end
+ end
+end
+
function Renderer:symbol (node)
-- Let's first look at fake footnotes to resolve the symbol.
-- We just added unforeseen templating and recursive variable substitution to Djot.
@@ -586,19 +637,7 @@ function Renderer:symbol (node)
if #node_fake_metadata.c > 1 and not node._standalone_ then
SU.error("Cannot use multi-paragraph metatada "..label.." as inline content")
end
-
- local content
- if self.metadata[label] then -- use memoized
- content = self.metadata[label]
- else
- if #node_fake_metadata.c == 1 and node_fake_metadata.c[1].t == "para" then
- -- Skip a single para node.
- content = self:render_children(node_fake_metadata.c[1])
- else
- content = self:render_children(node_fake_metadata)
- end
- self.metadata[label] = content -- memoize
- end
+ local content = self:getUserDefinedSymbol(label, node_fake_metadata)
if node.attr then
if not node._standalone_ then
-- Add a span for attributes on the inline variant.
diff --git a/inputters/markdown.lua b/inputters/markdown.lua
index 3328bbd..ad49e3b 100644
--- a/inputters/markdown.lua
+++ b/inputters/markdown.lua
@@ -72,6 +72,19 @@ local function SileAstWriter (writerOps, renderOps)
local generic = require("lunamark.writer.generic")
local writer = generic.new(writerOps or {})
local shift_headings = SU.cast("integer", renderOps.shift_headings or 0)
+ local parentmetadata = {}
+ for key, val in pairs(renderOps) do
+ local meta = key:match("^meta:(.*)")
+ if meta then
+ if meta:match("[%w_+-]+") then
+ -- We don't use them in this renderer, but we can pass them through
+ -- to embedded djot documents.
+ parentmetadata[key] = val
+ else
+ SU.warn("Invalid metadata key is skipped: "..meta)
+ end
+ end
+ end
-- Simple one-to-one mappings between lunamark AST and SILE
@@ -86,7 +99,7 @@ local function SileAstWriter (writerOps, renderOps)
writer.blockquote = simpleCommandWrapper("markdown:internal:blockquote")
writer.verbatim = simpleCommandWrapper("verbatim")
writer.listitem = simpleCommandWrapper("item")
- writer.linebreak = simpleCommandWrapper("cr")
+ writer.linebreak = simpleCommandWrapper("markdown:internal:hardbreak")
writer.singlequoted = simpleCommandWrapper("singlequoted")
writer.doublequoted = simpleCommandWrapper("doublequoted")
@@ -148,6 +161,10 @@ local function SileAstWriter (writerOps, renderOps)
writer.fenced_code = function (content, infostring, attr)
local opts = attr or { class = infostring }
+ if utils.hasClass(opts, "djot") or utils.hasClass(opts, "markdown") then
+ opts = pl.tablex.union(parentmetadata, opts)
+ opts.shift_headings = shift_headings
+ end
return createCommand("markdown:internal:codeblock", opts, content)
end
diff --git a/inputters/pandocast.lua b/inputters/pandocast.lua
index c195887..9d26b76 100644
--- a/inputters/pandocast.lua
+++ b/inputters/pandocast.lua
@@ -48,6 +48,19 @@ local Renderer = pl.class()
function Renderer:_init(options)
self.shift_headings = SU.cast("integer", options.shift_headings or 0)
+ self.parentmetadata = {}
+ for key, val in pairs(options) do
+ local meta = key:match("^meta:(.*)")
+ if meta then
+ if meta:match("[%w_+-]+") then
+ -- We don't use them in this renderer, but we can pass them through
+ -- to embedded djot documents.
+ self.parentmetadata[key] = val
+ else
+ SU.warn("Invalid metadata key is skipped: "..meta)
+ end
+ end
+ end
end
-- Allows unpacking tables on some Pandoc AST nodes so as to map them to methods
@@ -77,8 +90,8 @@ local function addNodes(out, elements)
out[#out] = out[#out] .. elements
else
-- Simplify out by removing empty elements
- if type(elements) ~= "table" or elements.command or #elements > -1 then
- out [#out+1] = elements
+ if type(elements) ~= "table" or elements.command or #elements > 0 then
+ out[#out+1] = elements
end
end
end
@@ -202,8 +215,12 @@ end
-- CodeBlock Attr Text
-- Code block (literal) with attributes
-function Renderer.CodeBlock (_, attributes, text)
+function Renderer:CodeBlock (attributes, text)
local options = pandocAttributes(attributes)
+ if utils.hasClass(options, "djot") or utils.hasClass(options, "markdown") then
+ options = pl.tablex.union(self.parentmetadata, options)
+ options.shift_headings = self.shift_headings
+ end
return createCommand("markdown:internal:codeblock", options, text)
end
@@ -481,7 +498,7 @@ end
-- LineBreak
-- Hard line break
function Renderer.LineBreak (_)
- return createCommand("cr")
+ return createCommand("markdown:internal:hardbreak")
end
-- Math MathType Text
diff --git a/packages/markdown/commands.lua b/packages/markdown/commands.lua
index 0c1a326..3c77213 100644
--- a/packages/markdown/commands.lua
+++ b/packages/markdown/commands.lua
@@ -9,6 +9,7 @@
--
require("silex.lang")
local utils = require("packages.markdown.utils")
+local hasClass = utils.hasClass
local ast = require("silex.ast")
local createCommand, createStructuredCommand,
extractFromTree, subContent
@@ -60,14 +61,6 @@ local function getSectioningCommand (level)
return "markdown:fallback:header"
end
-local function hasClass (options, classname)
- -- N.B. we want a true boolean here
- if options.class and string.match(' ' .. options.class .. ' ',' '..classname..' ') then
- return true
- end
- return false
-end
-
local function hasLinkContent(tree)
if type(tree) == "table" then
return #tree > 1 or hasLinkContent(tree[1])
@@ -242,18 +235,18 @@ function package:registerCommands ()
elseif hasClass(options, "bigrule") then
SILE.call("center", {}, function ()
SILE.call("raise", { height = "0.5ex" }, function ()
- SILE.call("hrule", { width = "33%lw" })
+ SILE.call("hrule", { width = "33%lw", height = "0.4pt" })
end)
end)
elseif hasClass(options, "fullrule") and self:hasCouyards() then
- SILE.call("fullrule")
+ SILE.call("fullrule", { thickness = "0.4pt" })
elseif hasClass(options, "pendant") and self:hasCouyards() then
SILE.call("smallskip")
SILE.call("couyard", { type = 6, width = "default" })
elseif not hasClass(options, "none") then
SILE.call("center", {}, function ()
SILE.call("raise", { height = "0.5ex" }, function ()
- SILE.call("hrule", { width = "20%lw" })
+ SILE.call("hrule", { width = "20%lw", height = "0.4pt" })
end)
end)
end
@@ -271,13 +264,13 @@ function package:registerCommands ()
elseif options.separator == "---" then
SILE.call("center", {}, function ()
SILE.call("raise", { height = "0.5ex" }, function ()
- SILE.call("hrule", { width = "20%lw" })
+ SILE.call("hrule", { width = "20%lw", height = "0.4pt" })
end)
end)
elseif options.separator == "----" then
SILE.call("center", {}, function ()
SILE.call("raise", { height = "0.5ex" }, function ()
- SILE.call("hrule", { width = "33%lw" })
+ SILE.call("hrule", { width = "33%lw", height = "0.4pt" })
end)
end)
elseif options.separator == "- - - -" and self:hasCouyards() then
@@ -286,7 +279,7 @@ function package:registerCommands ()
elseif options.separator == "--------------" then -- Page break
SILE.call("eject")
else
- SILE.call("fullrule")
+ SILE.call("fullrule", { thickness = "0.4pt" })
end
end, "Horizontal rule in Markdown (internal)")
@@ -512,13 +505,30 @@ Please consider using a resilient-compatible class!]])
SILE.processString(rawtext, "lua")
elseif format == "html" then
if rawtext:match("^
/%s]") then
- SILE.call("cr")
+ SILE.call("markdown:internal:hardbreak")
elseif rawtext:match("^/%s]") then
SILE.call("penalty", { penalty = 100 })
end
end
end, "Raw native inline content in Markdown (internal)")
+ self:registerCommand("markdown:internal:hardbreak", function (_, _)
+ -- We don't want to use a cr here, because it would affect parindents,
+ -- insert a parskip, and maybe other things.
+ -- It's a bit tricky to handle a hardbreak depending on depending on the
+ -- alignment of the paragraph:
+ -- justified = we can't use a break, a cr (hfill+break) would work
+ -- ragged left = we can't use a cr
+ -- centered = we can't use a cr
+ -- ragged right = we don't care, a break is sufficient
+ -- Knowning the alignment is not obvious, neither guessing it from the skips.
+ -- Using a parfillskip seems to do the trick, but it's maybe a bit hacky.
+ -- This is nevertheless what would have occurred with a par in the same
+ -- situation.
+ SILE.typesetter:pushGlue(SILE.settings:get("typesetter.parfillskip"))
+ SILE.call("break")
+ end, "Hard break in Markdown (internal)")
+
self:registerCommand("markdown:internal:rawblock", function (options, content)
local format = SU.required(options, "format", "rawcontent")
if format == "sile" or format == "sile-lua" then
diff --git a/packages/markdown/utils.lua b/packages/markdown/utils.lua
index 3f1ca61..0959cea 100644
--- a/packages/markdown/utils.lua
+++ b/packages/markdown/utils.lua
@@ -32,8 +32,17 @@ local function nbspFilter (str)
return #t == 1 and t[1] or t
end
+local function hasClass (options, classname)
+ -- N.B. we want a true boolean here
+ if options.class and string.match(' ' .. options.class .. ' ',' '..classname..' ') then
+ return true
+ end
+ return false
+end
+
--- @export
return {
getFileExtension = getFileExtension,
nbspFilter = nbspFilter,
+ hasClass = hasClass
}