summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/luatex/markdown/markdown.lua
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2023-02-28 20:57:36 +0000
committerKarl Berry <karl@freefriends.org>2023-02-28 20:57:36 +0000
commit5bb5421117595253f49df10c66cabffa61f8c4f2 (patch)
tree544320d0476d89ab4d2fbc340cc985c36bf5b9e4 /Master/texmf-dist/tex/luatex/markdown/markdown.lua
parentb67efc730c77919f1b008ff88f37afc6f8585458 (diff)
markdown (28feb23)
git-svn-id: svn://tug.org/texlive/trunk@66257 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/luatex/markdown/markdown.lua')
-rw-r--r--Master/texmf-dist/tex/luatex/markdown/markdown.lua384
1 files changed, 249 insertions, 135 deletions
diff --git a/Master/texmf-dist/tex/luatex/markdown/markdown.lua b/Master/texmf-dist/tex/luatex/markdown/markdown.lua
index 4d5a96e56cb..cd6aaf80c68 100644
--- a/Master/texmf-dist/tex/luatex/markdown/markdown.lua
+++ b/Master/texmf-dist/tex/luatex/markdown/markdown.lua
@@ -20,7 +20,7 @@
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--
--- Copyright (C) 2016-2022 Vít Novotný
+-- Copyright (C) 2016-2023 Vít Novotný
--
-- This work may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either version 1.3
@@ -58,11 +58,11 @@
-- those in the standard .ins files.
--
local metadata = {
- version = "2.20.0-0-gf64ade1",
+ version = "2.21.0-0-gee15b88",
comment = "A module for the conversion from markdown to plain TeX",
author = "John MacFarlane, Hans Hagen, Vít Novotný",
copyright = {"2009-2016 John MacFarlane, Hans Hagen",
- "2016-2022 Vít Novotný"},
+ "2016-2023 Vít Novotný"},
license = "LPPL 1.3c"
}
@@ -74,10 +74,29 @@ local unicode
local ran_ok
ran_ok, unicode = pcall(require, "unicode")
if not ran_ok then
- unicode = {["utf8"]={char=utf8.char}}
+ unicode = {utf8 = {char=utf8.char}}
end
end)()
local md5 = require("md5")
+local uni_case
+(function()
+ local ran_ok
+ -- TODO: Stop loading kpse module to a global kpse variable
+ -- after https://github.com/latex3/lua-uni-algos/issues/3 has been fixed.
+ -- Remove kpse global also from file .luacheckrc.
+ ran_ok, kpse = pcall(require, "kpse")
+ if ran_ok then
+ kpse.set_program_name("luatex")
+ ran_ok, uni_case = pcall(require, "lua-uni-case")
+ end
+ if not ran_ok then
+ if unicode.utf8.lower then
+ uni_case = {casefold = unicode.utf8.lower}
+ else
+ uni_case = {casefold = string.lower}
+ end
+ end
+end)()
local M = {metadata = metadata}
local walkable_syntax = {
Block = {
@@ -165,10 +184,11 @@ defaultOptions.superscripts = false
defaultOptions.tableCaptions = false
defaultOptions.taskLists = false
defaultOptions.texComments = false
+defaultOptions.texMathDollars = false
defaultOptions.tightLists = true
defaultOptions.underscores = true
-local upper, gsub, format, length =
- string.upper, string.gsub, string.format, string.len
+local upper, format, length =
+ string.upper, string.format, string.len
local P, R, S, V, C, Cg, Cb, Cmt, Cc, Ct, B, Cs, any =
lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.C, lpeg.Cg, lpeg.Cb,
lpeg.Cmt, lpeg.Cc, lpeg.Ct, lpeg.B, lpeg.Cs, lpeg.P(1)
@@ -2396,6 +2416,11 @@ function M.writer.new(options)
self.slice_end = "$" .. slice_specifiers[1]
end
+ self.slice_begin_type = self.slice_begin:sub(1, 1)
+ self.slice_begin_identifier = self.slice_begin:sub(2) or ""
+ self.slice_end_type = self.slice_end:sub(1, 1)
+ self.slice_end_identifier = self.slice_end:sub(2) or ""
+
if self.slice_begin == "^" and self.slice_end ~= "^" then
self.is_writing = true
else
@@ -2418,7 +2443,7 @@ function M.writer.new(options)
if not self.is_writing then return "" end
return "\\markdownRendererInterblockSeparator\n{}"
end
- self.linebreak = "\\markdownRendererLineBreak\n{}"
+ self.hard_line_break = "\\markdownRendererHardLineBreak\n{}"
self.ellipsis = "\\markdownRendererEllipsis{}"
function self.thematic_break()
if not self.is_writing then return "" end
@@ -2452,15 +2477,22 @@ function M.writer.new(options)
["|"] = "\\markdownRendererPipe{}",
[entities.hex_entity('0000')] = "\\markdownRendererReplacementCharacter{}",
}
- self.escape = util.escaper(self.escaped_chars, self.escaped_strings)
- self.escape_uri = util.escaper(self.escaped_uri_chars, self.escaped_minimal_strings)
- self.escape_minimal = util.escaper({}, self.escaped_minimal_strings)
+ local escape_typographic_text = util.escaper(
+ self.escaped_chars, self.escaped_strings)
+ local escape_programmatic_text = util.escaper(
+ self.escaped_uri_chars, self.escaped_minimal_strings)
+ local escape_minimal = util.escaper(
+ {}, self.escaped_minimal_strings)
+ self.escape = escape_typographic_text
+ self.math = escape_minimal
if options.hybrid then
- self.string = self.escape_minimal
- self.uri = self.escape_minimal
+ self.identifier = escape_minimal
+ self.string = escape_minimal
+ self.uri = escape_minimal
else
- self.string = self.escape
- self.uri = self.escape_uri
+ self.identifier = escape_programmatic_text
+ self.string = escape_typographic_text
+ self.uri = escape_programmatic_text
end
function self.code(s)
return {"\\markdownRendererCodeSpan{",self.escape(s),"}"}
@@ -2567,19 +2599,10 @@ function M.writer.new(options)
return {"\\markdownRendererInputVerbatim{",name,"}"}
end
function self.document(d)
- local active_attributes = self.active_attributes
local buf = {"\\markdownRendererDocumentBegin\n", d}
- -- pop attributes for sections that have ended
- if options.headerAttributes and self.is_writing then
- while #active_attributes > 0 do
- local attributes = active_attributes[#active_attributes]
- if #attributes > 0 then
- table.insert(buf, "\\markdownRendererHeaderAttributeContextEnd")
- end
- table.remove(active_attributes, #active_attributes)
- end
- end
+ -- pop all attributes
+ table.insert(buf, self.pop_attributes())
table.insert(buf, "\\markdownRendererDocumentEnd")
@@ -2607,86 +2630,130 @@ function M.writer.new(options)
return buf
end
self.active_attributes = {}
- function self.heading(s, level, attributes)
+ local function apply_attributes()
+ local buf = {}
+ for i = 1, #self.active_attributes do
+ local start_output = self.active_attributes[i][3]
+ if start_output ~= nil then
+ table.insert(buf, start_output)
+ end
+ end
+ return buf
+ end
+
+ local function tear_down_attributes()
+ local buf = {}
+ for i = #self.active_attributes, 1, -1 do
+ local end_output = self.active_attributes[i][4]
+ if end_output ~= nil then
+ table.insert(buf, end_output)
+ end
+ end
+ return buf
+ end
+ function self.push_attributes(attribute_type, attributes,
+ start_output, end_output)
+ -- index attributes in a hash table for easy lookup
attributes = attributes or {}
for i = 1, #attributes do
attributes[attributes[i]] = true
end
- local active_attributes = self.active_attributes
- local slice_begin_type = self.slice_begin:sub(1, 1)
- local slice_begin_identifier = self.slice_begin:sub(2) or ""
- local slice_end_type = self.slice_end:sub(1, 1)
- local slice_end_identifier = self.slice_end:sub(2) or ""
-
local buf = {}
-
- -- push empty attributes for implied sections
- while #active_attributes < level-1 do
- table.insert(active_attributes, {})
+ -- handle slicing
+ if attributes["#" .. self.slice_end_identifier] ~= nil and
+ self.slice_end_type == "^" then
+ if self.is_writing then
+ table.insert(buf, tear_down_attributes())
+ end
+ self.is_writing = false
+ end
+ if attributes["#" .. self.slice_begin_identifier] ~= nil and
+ self.slice_begin_type == "^" then
+ self.is_writing = true
+ table.insert(buf, apply_attributes())
+ self.is_writing = true
end
+ if self.is_writing and start_output ~= nil then
+ table.insert(buf, start_output)
+ end
+ table.insert(self.active_attributes,
+ {attribute_type, attributes,
+ start_output, end_output})
+ return buf
+ end
- -- pop attributes for sections that have ended
- while #active_attributes >= level do
- local active_identifiers = active_attributes[#active_attributes]
- -- tear down all active attributes at slice end
- if active_identifiers["#" .. slice_end_identifier] ~= nil
- and slice_end_type == "$" then
- for header_level = #active_attributes, 1, -1 do
- if options.headerAttributes and #active_attributes[header_level] > 0 then
- table.insert(buf, "\\markdownRendererHeaderAttributeContextEnd")
- end
+ function self.pop_attributes(attribute_type)
+ local buf = {}
+ -- pop attributes until we find attributes of correct type
+ -- or until no attributes remain
+ local current_attribute_type = false
+ while current_attribute_type ~= attribute_type and
+ #self.active_attributes > 0 do
+ local attributes, _, end_output
+ current_attribute_type, attributes, _, end_output = table.unpack(
+ self.active_attributes[#self.active_attributes])
+ if self.is_writing and end_output ~= nil then
+ table.insert(buf, end_output)
+ end
+ table.remove(self.active_attributes, #self.active_attributes)
+ -- handle slicing
+ if attributes["#" .. self.slice_end_identifier] ~= nil
+ and self.slice_end_type == "$" then
+ if self.is_writing then
+ table.insert(buf, tear_down_attributes())
end
self.is_writing = false
end
- table.remove(active_attributes, #active_attributes)
- if self.is_writing and options.headerAttributes and #active_identifiers > 0 then
- table.insert(buf, "\\markdownRendererHeaderAttributeContextEnd")
- end
- -- apply all active attributes at slice beginning
- if active_identifiers["#" .. slice_begin_identifier] ~= nil
- and slice_begin_type == "$" then
- for header_level = 1, #active_attributes do
- if options.headerAttributes and #active_attributes[header_level] > 0 then
- table.insert(buf, "\\markdownRendererHeaderAttributeContextBegin")
- end
- end
+ if attributes["#" .. self.slice_begin_identifier] ~= nil and
+ self.slice_begin_type == "$" then
self.is_writing = true
+ table.insert(buf, apply_attributes())
end
end
+ return buf
+ end
+ local current_heading_level = 0
+ function self.heading(s, level, attributes)
+ local buf = {}
- -- tear down all active attributes at slice end
- if attributes["#" .. slice_end_identifier] ~= nil
- and slice_end_type == "^" then
- for header_level = #active_attributes, 1, -1 do
- if options.headerAttributes and #active_attributes[header_level] > 0 then
- table.insert(buf, "\\markdownRendererHeaderAttributeContextEnd")
- end
- end
- self.is_writing = false
+ -- push empty attributes for implied sections
+ while current_heading_level < level - 1 do
+ table.insert(buf,
+ self.push_attributes("heading",
+ nil,
+ "\\markdownRendererSectionBegin\n",
+ "\n\\markdownRendererSectionEnd "))
+ current_heading_level = current_heading_level + 1
end
- -- push attributes for the new section
- table.insert(active_attributes, attributes)
- if self.is_writing and options.headerAttributes and #attributes > 0 then
- table.insert(buf, "\\markdownRendererHeaderAttributeContextBegin")
+ -- pop attributes for sections that have ended
+ while current_heading_level >= level do
+ table.insert(buf, self.pop_attributes("heading"))
+ current_heading_level = current_heading_level - 1
end
- -- apply all active attributes at slice beginning
- if attributes["#" .. slice_begin_identifier] ~= nil
- and slice_begin_type == "^" then
- for header_level = 1, #active_attributes do
- if options.headerAttributes and #active_attributes[header_level] > 0 then
- table.insert(buf, "\\markdownRendererHeaderAttributeContextBegin")
- end
- end
- self.is_writing = true
+ -- push attributes for the new section
+ local start_output = {}
+ local end_output = {}
+ table.insert(start_output, "\\markdownRendererSectionBegin\n")
+ if options.headerAttributes and attributes ~= nil and #attributes > 0 then
+ table.insert(start_output,
+ "\\markdownRendererHeaderAttributeContextBegin\n")
+ table.insert(start_output, self.attributes(attributes))
+ table.insert(end_output,
+ "\n\\markdownRendererHeaderAttributeContextEnd ")
end
+ table.insert(end_output, "\n\\markdownRendererSectionEnd ")
- if self.is_writing then
- table.insert(buf, self.attributes(attributes))
- end
+ table.insert(buf, self.push_attributes("heading",
+ attributes,
+ start_output,
+ end_output))
+ current_heading_level = current_heading_level + 1
+ assert(current_heading_level == level)
+ -- produce the renderer
local cmd
level = level + options.shiftHeadings
if level <= 1 then
@@ -2745,6 +2812,7 @@ parsers.plus = P("+")
parsers.underscore = P("_")
parsers.period = P(".")
parsers.hash = P("#")
+parsers.dollar = P("$")
parsers.ampersand = P("&")
parsers.backtick = P("`")
parsers.less = P("<")
@@ -3155,8 +3223,11 @@ function M.reader.new(writer, options)
end)(parsers)
local parsers = self.parsers
function self.normalize_tag(tag)
- return string.lower(
- gsub(util.rope_to_string(tag), "[ \n\r\t]+", " "))
+ tag = util.rope_to_string(tag)
+ tag = tag:gsub("[ \n\r\t]+", " ")
+ tag = tag:gsub("^ ", ""):gsub(" $", "")
+ tag = uni_case.casefold(tag, true, false)
+ return tag
end
local function iterlines(s, f)
local rope = lpeg.match(Ct((parsers.line / f)^1), s)
@@ -3369,18 +3440,18 @@ function M.reader.new(writer, options)
parsers.Endline = parsers.newline
* -V("EndlineExceptions")
* parsers.spacechar^0
- / (options.hardLineBreaks and writer.linebreak
+ / (options.hardLineBreaks and writer.hard_line_break
or writer.space)
parsers.OptionalIndent
= parsers.spacechar^1 / writer.space
- parsers.Space = parsers.spacechar^2 * parsers.Endline / writer.linebreak
+ parsers.Space = parsers.spacechar^2 * parsers.Endline / writer.hard_line_break
+ parsers.spacechar^1 * parsers.Endline^-1 * parsers.eof / ""
+ parsers.spacechar^1 * parsers.Endline
* parsers.optionalspace
/ (options.hardLineBreaks
- and writer.linebreak
+ and writer.hard_line_break
or writer.space)
+ parsers.spacechar^1 * parsers.optionalspace
/ writer.space
@@ -3389,16 +3460,16 @@ function M.reader.new(writer, options)
= parsers.newline
* -V("EndlineExceptions")
* parsers.spacechar^0
- / (options.hardLineBreaks and writer.linebreak
+ / (options.hardLineBreaks and writer.hard_line_break
or writer.nbsp)
parsers.NonbreakingSpace
- = parsers.spacechar^2 * parsers.Endline / writer.linebreak
+ = parsers.spacechar^2 * parsers.Endline / writer.hard_line_break
+ parsers.spacechar^1 * parsers.Endline^-1 * parsers.eof / ""
+ parsers.spacechar^1 * parsers.Endline
* parsers.optionalspace
/ (options.hardLineBreaks
- and writer.linebreak
+ and writer.hard_line_break
or writer.nbsp)
+ parsers.spacechar^1 * parsers.optionalspace
/ writer.nbsp
@@ -3520,12 +3591,10 @@ function M.reader.new(writer, options)
parsers.Paragraph = parsers.nonindentspace * Ct(parsers.Inline^1)
* ( parsers.newline
- * ( parsers.blankline^1
- + #parsers.hash
- + #(parsers.leader * parsers.more * parsers.space^-1)
- + parsers.eof
+ * ( parsers.blankline^1
+ + #V("EndlineExceptions")
)
- + parsers.eof )
+ + parsers.eof)
/ writer.paragraph
parsers.Plain = parsers.nonindentspace * Ct(parsers.Inline^1)
@@ -3969,26 +4038,9 @@ M.extensions.bracketed_spans = function()
}
end
M.extensions.citations = function(citation_nbsps)
- local escaped_citation_chars = {
- ["{"] = "\\markdownRendererLeftBrace{}",
- ["}"] = "\\markdownRendererRightBrace{}",
- ["%"] = "\\markdownRendererPercentSign{}",
- ["\\"] = "\\markdownRendererBackslash{}",
- ["#"] = "\\markdownRendererHash{}",
- }
return {
name = "built-in citations syntax extension",
extend_writer = function(self)
- local options = self.options
-
- local escape_citation = util.escaper(
- escaped_citation_chars,
- self.escaped_minimal_strings)
- if options.hybrid then
- self.citation = self.escape_minimal
- else
- self.citation = escape_citation
- end
function self.citations(text_cites, cites)
local buffer = {"\\markdownRenderer", text_cites and "TextCite" or "Cite",
"{", #cites, "}"}
@@ -4077,7 +4129,7 @@ M.extensions.citations = function(citation_nbsps)
cites[#cites+1] = {
prenote = normalize(raw_cites[i]),
suppress_author = raw_cites[i+1] == "-",
- name = writer.citation(raw_cites[i+2]),
+ name = writer.identifier(raw_cites[i+2]),
postnote = normalize(raw_cites[i+3]),
}
end
@@ -4633,11 +4685,14 @@ M.extensions.fenced_divs = function(blank_before_div_fence)
return {
name = "built-in fenced_divs syntax extension",
extend_writer = function(self)
- function self.div(c, attr)
- return {"\\markdownRendererFencedDivAttributeContextBegin",
- self.attributes(attr),
- c,
- "\\markdownRendererFencedDivAttributeContextEnd"}
+ function self.div_begin(attributes)
+ local start_output = {"\\markdownRendererFencedDivAttributeContextBegin\n",
+ self.attributes(attributes)}
+ local end_output = {"\n\\markdownRendererFencedDivAttributeContextEnd "}
+ return self.push_attributes("div", attributes, start_output, end_output)
+ end
+ function self.div_end()
+ return self.pop_attributes("div")
end
end, extend_reader = function(self)
local parsers = self.parsers
@@ -4673,7 +4728,16 @@ M.extensions.fenced_divs = function(blank_before_div_fence)
, "div_level")
end
- local FencedDiv = fenced_div_begin * increment_div_level(1)
+ local FencedDiv = fenced_div_begin
+ / function (infostring)
+ local attr = lpeg.match(Ct(parsers.attributes), infostring)
+ if attr == nil then
+ attr = {"." .. infostring}
+ end
+ return attr
+ end
+ / writer.div_begin
+ * increment_div_level(1)
* parsers.skipblanklines
* Ct( (V("Block") - fenced_div_end)^-1
* ( parsers.blanklines
@@ -4683,14 +4747,7 @@ M.extensions.fenced_divs = function(blank_before_div_fence)
* (V("Block") - fenced_div_end))^0)
* parsers.skipblanklines
* fenced_div_end * increment_div_level(-1)
- / function (infostring, div)
- local attr = lpeg.match(Ct(parsers.attributes), infostring)
- if attr == nil then
- attr = {"." .. infostring}
- end
- return div, attr
- end
- / writer.div
+ * (Cc("") / writer.div_end)
self.insert_pattern("Block after Verbatim",
FencedDiv, "FencedDiv")
@@ -4786,7 +4843,7 @@ M.extensions.line_blocks = function()
if not self.is_writing then return "" end
local buffer = {}
for i = 1, #lines - 1 do
- buffer[#buffer + 1] = { lines[i], self.linebreak }
+ buffer[#buffer + 1] = { lines[i], self.hard_line_break }
end
buffer[#buffer + 1] = lines[#lines]
@@ -5117,6 +5174,58 @@ M.extensions.superscripts = function()
end
}
end
+M.extensions.tex_math_dollars = function()
+ return {
+ name = "built-in tex_math_dollars syntax extension",
+ extend_writer = function(self)
+ function self.display_math(s)
+ if not self.is_writing then return "" end
+ return {"\\markdownRendererDisplayMath{",self.math(s),"}"}
+ end
+ function self.inline_math(s)
+ if not self.is_writing then return "" end
+ return {"\\markdownRendererInlineMath{",self.math(s),"}"}
+ end
+ end, extend_reader = function(self)
+ local parsers = self.parsers
+ local writer = self.writer
+
+ local function between(p, starter, ender)
+ return (starter * C(p * (p - ender)^0) * ender)
+ end
+
+ local inlinemathtail = B( parsers.any * parsers.nonspacechar
+ + parsers.backslash * parsers.any)
+ * parsers.dollar
+ * -#(parsers.digit)
+
+ local inlinemath = between(C( parsers.backslash^-1
+ * parsers.any
+ - parsers.blankline^2
+ - parsers.dollar),
+ parsers.dollar * #(parsers.nonspacechar),
+ inlinemathtail)
+
+ local displaymathdelim = parsers.dollar
+ * parsers.dollar
+
+ local displaymath = between(C( parsers.backslash^-1
+ * parsers.any
+ - parsers.blankline^2
+ - parsers.dollar),
+ displaymathdelim,
+ displaymathdelim)
+
+ local TexMathDollars = displaymath / writer.display_math
+ + inlinemath / writer.inline_math
+
+ self.insert_pattern("Inline after Emph",
+ TexMathDollars, "TexMathDollars")
+
+ self.add_special_character("$")
+ end
+ }
+end
M.extensions.jekyll_data = function(expect_jekyll_data)
return {
name = "built-in jekyll_data syntax extension",
@@ -5138,13 +5247,13 @@ M.extensions.jekyll_data = function(expect_jekyll_data)
if #d > 0 then
table.insert(buf, "\\markdownRendererJekyllDataSequenceBegin{")
- table.insert(buf, self.uri(p or "null"))
+ table.insert(buf, self.identifier(p or "null"))
table.insert(buf, "}{")
table.insert(buf, #keys)
table.insert(buf, "}")
else
table.insert(buf, "\\markdownRendererJekyllDataMappingBegin{")
- table.insert(buf, self.uri(p or "null"))
+ table.insert(buf, self.identifier(p or "null"))
table.insert(buf, "}{")
table.insert(buf, #keys)
table.insert(buf, "}")
@@ -5160,7 +5269,7 @@ M.extensions.jekyll_data = function(expect_jekyll_data)
self.jekyllData(v, t, k)
)
else
- k = self.uri(k)
+ k = self.identifier(k)
v = tostring(v)
if typ == "boolean" then
table.insert(buf, "\\markdownRendererJekyllDataBoolean{")
@@ -5296,6 +5405,11 @@ function M.new(options)
table.insert(extensions, jekyll_data_extension)
end
+ if options.lineBlocks then
+ local line_block_extension = M.extensions.line_blocks()
+ table.insert(extensions, line_block_extension)
+ end
+
if options.pipeTables then
local pipe_tables_extension = M.extensions.pipe_tables(
options.tableCaptions)
@@ -5322,9 +5436,9 @@ function M.new(options)
table.insert(extensions, superscript_extension)
end
- if options.lineBlocks then
- local line_block_extension = M.extensions.line_blocks()
- table.insert(extensions, line_block_extension)
+ if options.texMathDollars then
+ local tex_math_dollars_extension = M.extensions.tex_math_dollars()
+ table.insert(extensions, tex_math_dollars_extension)
end
if options.footnotes or options.inlineFootnotes or