diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/markdown/markdown.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/markdown/markdown.lua | 384 |
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 |