diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex')
-rw-r--r-- | Master/texmf-dist/tex/luatex/markdown/markdown.lua | 223 |
1 files changed, 167 insertions, 56 deletions
diff --git a/Master/texmf-dist/tex/luatex/markdown/markdown.lua b/Master/texmf-dist/tex/luatex/markdown/markdown.lua index e6127c8bf6d..8583a21f5a0 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-2020 Vít Novotný +-- Copyright (C) 2016-2021 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,18 +58,21 @@ -- those in the standard .ins files. -- local metadata = { - version = "2.9.0", + version = "2.10.0", 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-2020 Vít Novotný"}, + "2016-2021 Vít Novotný"}, license = "LPPL 1.3" } if not modules then modules = { } end modules['markdown'] = metadata local lpeg = require("lpeg") -local unicode = require("unicode") +local ran_ok, unicode = pcall(require, "unicode") +if not ran_ok then + unicode = {["utf8"]={char=utf8.char}} +end local md5 = require("md5") local M = {metadata = metadata} local defaultOptions = {} @@ -100,7 +103,9 @@ defaultOptions.shiftHeadings = 0 defaultOptions.slice = "^ $" defaultOptions.smartEllipses = false defaultOptions.startNumber = true +defaultOptions.stripIndent = false defaultOptions.tableCaptions = false +defaultOptions.texComments = false defaultOptions.tightLists = true defaultOptions.underscores = true local upper, gsub, format, length = @@ -119,7 +124,8 @@ function util.cache(dir, string, salt, transform, suffix) local name = util.pathname(dir, digest .. suffix) local file = io.open(name, "r") if file == nil then -- If no cache entry exists, then create a new one. - local file = assert(io.open(name, "w")) + local file = assert(io.open(name, "w"), + [[could not open file "]] .. name .. [[" for writing]]) local result = string if transform ~= nil then result = transform(result) @@ -2382,16 +2388,18 @@ function M.writer.new(options) self.citation = escape_citation self.uri = escape_uri end + self.escape = escape function self.code(s) - return {"\\markdownRendererCodeSpan{",escape(s),"}"} + return {"\\markdownRendererCodeSpan{",self.escape(s),"}"} end function self.link(lab,src,tit) return {"\\markdownRendererLink{",lab,"}", - "{",self.string(src),"}", + "{",self.escape(src),"}", "{",self.uri(src),"}", "{",self.string(tit or ""),"}"} end function self.table(rows, caption) + if not self.is_writing then return "" end local buffer = {"\\markdownRendererTable{", caption or "", "}{", #rows - 1, "}{", #rows[1], "}"} local temp = rows[2] -- put alignments on the first row @@ -2405,10 +2413,10 @@ function M.writer.new(options) end table.insert(buffer, column) if i > 1 then - table.insert(buffer, "}%\n") + table.insert(buffer, "}") end end - table.insert(buffer, "}%\n") + table.insert(buffer, "}") end return buffer end @@ -2419,13 +2427,18 @@ function M.writer.new(options) "{",self.string(tit or ""),"}"} end local languages_json = (function() - local kpse = require("kpse") - kpse.set_program_name("luatex") + local ran_ok, kpse = pcall(require, "kpse") + if ran_ok then + kpse.set_program_name("luatex") + else + kpse = {lookup=function(filename, options) return filename end} + end local base, prev, curr - for _, file in ipairs{kpse.lookup(options.contentBlocksLanguageMap, - { all=true })} do - json = io.open(file, "r"):read("*all") - :gsub('("[^\n]-"):','[%1]=') + for _, filename in ipairs{kpse.lookup(options.contentBlocksLanguageMap, + { all=true })} do + local file = io.open(filename, "r") + if not file then goto continue end + json = file:read("*all"):gsub('("[^\n]-"):','[%1]=') curr = (function() local _ENV={ json=json, load=load } -- run in sandbox return load("return "..json)() @@ -2438,6 +2451,7 @@ function M.writer.new(options) end prev = curr end + ::continue:: end return base or {} end)() @@ -2512,8 +2526,9 @@ function M.writer.new(options) "\n\\markdownRendererOlEnd "} end end - function self.inline_html(html) return "" end - function self.display_html(html) return "" end + function self.inline_html_comment(contents) + return {"\\markdownRendererInlineHtmlComment{",contents,"}"} + end local function dlitem(term, defs) local retVal = {"\\markdownRendererDlItem{",term,"}"} for _, def in ipairs(defs) do @@ -2638,6 +2653,28 @@ function M.writer.new(options) end return buffer end + function self.get_state() + return { + is_writing=self.is_writing, + active_headings={table.unpack(self.active_headings)}, + } + end + function self.set_state(s) + previous_state = self.get_state() + for key, value in pairs(state) do + self[key] = value + end + return previous_state + end + function self.defer_call(f) + state = self.get_state() + return function(...) + state = self.set_state(state) + local return_value = f(...) + self.set_state(state) + return return_value + end + end return self end @@ -2672,6 +2709,7 @@ parsers.semicolon = P(";") parsers.exclamation = P("!") parsers.pipe = P("|") parsers.tilde = P("~") +parsers.backslash = P("\\") parsers.tab = P("\t") parsers.newline = P("\n") parsers.tightblocksep = P("\001") @@ -2694,7 +2732,7 @@ parsers.any = P(1) parsers.fail = parsers.any - 1 parsers.escapable = S("\\`*_{}[]()+_.!<>#-~:^@;") -parsers.anyescaped = P("\\") / "" * parsers.escapable +parsers.anyescaped = parsers.backslash / "" * parsers.escapable + parsers.any parsers.spacechar = S("\t ") @@ -2727,6 +2765,30 @@ parsers.spnl = parsers.optionalspace parsers.line = parsers.linechar^0 * parsers.newline parsers.nonemptyline = parsers.line - parsers.blankline +parsers.commented_line = Cs(((parsers.linechar -- initial + - parsers.backslash + - parsers.percent)^1 + + (parsers.backslash -- even backslash + * parsers.backslash)^1 + + (parsers.backslash -- odd backslash + * parsers.backslash)^0 + * (parsers.backslash / "" + * parsers.percent + + parsers.backslash + * (parsers.linechar -- initial + + parsers.newline + - parsers.backslash + - parsers.percent)) + )^0) + * ((parsers.percent -- comment + * parsers.line + * #parsers.blankline) -- blank line + / "\n" + + parsers.percent -- comment + * parsers.line + * parsers.optionalspace -- leading tabs and spaces + + C(parsers.newline)) + parsers.chunk = parsers.line * (parsers.optionallyindentedline - parsers.blankline)^0 @@ -3131,9 +3193,13 @@ parsers.htmlattribute = parsers.spacing^1 * parsers.sp * parsers.equal * parsers.sp * parsers.htmlattributevalue -parsers.htmlcomment = P("<!--") * (parsers.any - P("-->"))^0 * P("-->") +parsers.htmlcomment = P("<!--") + * parsers.optionalspace + * Cs((parsers.any - parsers.optionalspace * P("-->"))^0) + * parsers.optionalspace + * P("-->") -parsers.htmlinstruction = P("<?") * (parsers.any - P("?>" ))^0 * P("?>" ) +parsers.htmlinstruction = P("<?") * (parsers.any - P("?>"))^0 * P("?>") parsers.openelt_any = parsers.less * parsers.keyword * parsers.htmlattribute^0 * parsers.sp * parsers.more @@ -3180,17 +3246,11 @@ end parsers.in_matched_block_tags = parsers.less * Cmt(#parsers.openelt_block, parse_matched_tags) -parsers.displayhtml = parsers.htmlcomment +parsers.displayhtml = parsers.htmlcomment / "" + parsers.emptyelt_block + parsers.openelt_exact("hr") + parsers.in_matched_block_tags + parsers.htmlinstruction - -parsers.inlinehtml = parsers.emptyelt_any - + parsers.htmlcomment - + parsers.htmlinstruction - + parsers.openelt_any - + parsers.closeelt_any parsers.hexentity = parsers.ampersand * parsers.hash * S("Xx") * C(parsers.hexdigit^1) * parsers.semicolon parsers.decentity = parsers.ampersand * parsers.hash @@ -3280,24 +3340,51 @@ function M.reader.new(writer, options) setmetatable(options, { __index = function (_, key) return defaultOptions[key] end }) local function normalize_tag(tag) - return unicode.utf8.lower( + return string.lower( gsub(util.rope_to_string(tag), "[ \n\r\t]+", " ")) end + local function iterlines(s, f) + rope = lpeg.match(Ct((parsers.line / f)^1), s) + return util.rope_to_string(rope) + end local expandtabs if options.preserveTabs then expandtabs = function(s) return s end else expandtabs = function(s) if s:find("\t") then - return s:gsub("[^\n]*", util.expand_tabs_in_line) + return iterlines(s, util.expand_tabs_in_line) else return s end end end local larsers = {} - local function create_parser(name, grammar) + local function create_parser(name, grammar, toplevel) return function(str) + if toplevel and options.stripIndent then + local min_prefix_length, min_prefix = nil, '' + str = iterlines(str, function(line) + if lpeg.match(parsers.nonemptyline, line) == nil then + return line + end + line = util.expand_tabs_in_line(line) + prefix = lpeg.match(C(parsers.optionalspace), line) + local prefix_length = #prefix + local is_shorter = min_prefix_length == nil + is_shorter = is_shorter or prefix_length < min_prefix_length + if is_shorter then + min_prefix_length, min_prefix = prefix_length, prefix + end + return line + end) + str = str:gsub('^' .. min_prefix, '') + end + if toplevel and options.texComments then + str = lpeg.match(Ct(parsers.commented_line^1), str) + str = util.rope_to_string(str) + print(str) + end local res = lpeg.match(grammar(), str) if res == nil then error(format("%s failed on:\n%s", name, str:sub(1,20))) @@ -3311,37 +3398,43 @@ function M.reader.new(writer, options) = create_parser("parse_blocks", function() return larsers.blocks - end) + end, false) local parse_blocks_toplevel = create_parser("parse_blocks_toplevel", function() return larsers.blocks_toplevel - end) + end, true) local parse_inlines = create_parser("parse_inlines", function() return larsers.inlines - end) + end, false) local parse_inlines_no_link = create_parser("parse_inlines_no_link", function() return larsers.inlines_no_link - end) + end, false) local parse_inlines_no_inline_note = create_parser("parse_inlines_no_inline_note", function() return larsers.inlines_no_inline_note - end) + end, false) + + local parse_inlines_no_html + = create_parser("parse_inlines_no_html", + function() + return larsers.inlines_no_html + end, false) local parse_inlines_nbsp = create_parser("parse_inlines_nbsp", function() return larsers.inlines_nbsp - end) + end, false) if options.hashEnumerators then larsers.dig = parsers.digit + parsers.hash else @@ -3397,14 +3490,14 @@ function M.reader.new(writer, options) -- like indirect_link local function lookup_note(ref) - return function() + return writer.defer_call(function() local found = rawnotes[normalize_tag(ref)] if found then return writer.note(parse_blocks_toplevel(found)) else return {"[", parse_inlines("^" .. ref), "]"} end - end + end) end local function register_note(ref,rawnote) @@ -3479,27 +3572,27 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline -- lookup link reference and return a link, if the reference is found, -- or a bracketed label otherwise. local function indirect_link(label,sps,tag) - return function() + return writer.defer_call(function() local r,fallback = lookup_reference(label,sps,tag) if r then return writer.link(parse_inlines_no_link(label), r.url, r.title) else return fallback end - end + end) end -- lookup image reference and return an image, if the reference is found, -- or a bracketed label otherwise. local function indirect_image(label,sps,tag) - return function() + return writer.defer_call(function() local r,fallback = lookup_reference(label,sps,tag) if r then return writer.image(writer.string(label), r.url, r.title) else return {"!", fallback} end - end + end) end larsers.Str = (parsers.normalchar * (parsers.normalchar + parsers.at)^0) / writer.string @@ -3593,7 +3686,7 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline * C(parsers.alphanumeric^1 * P("://") * parsers.urlchar^1) * parsers.more / function(url) - return writer.link(writer.string(url), url) + return writer.link(writer.escape(url), url) end larsers.AutoLinkEmail = parsers.less @@ -3601,7 +3694,7 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline * P("@") * parsers.urlchar^1) * parsers.more / function(email) - return writer.link(writer.string(email), + return writer.link(writer.escape(email), "mailto:"..email) end @@ -3633,20 +3726,22 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline larsers.Image = larsers.DirectImage + larsers.IndirectImage - larsers.TextCitations = Ct(Cc("") + larsers.TextCitations = Ct((parsers.spnl + * Cc("") * parsers.citation_name * ((parsers.spnl * parsers.lbracket * parsers.citation_headless_body - * parsers.rbracket) + Cc(""))) + * parsers.rbracket) + Cc("")))^1) / function(raw_cites) return larsers.citations(true, raw_cites) end larsers.ParenthesizedCitations - = Ct(parsers.lbracket + = Ct((parsers.spnl + * parsers.lbracket * parsers.citation_body - * parsers.rbracket) + * parsers.rbracket)^1) / function(raw_cites) return larsers.citations(false, raw_cites) end @@ -3657,9 +3752,14 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline larsers.UlOrStarLine = parsers.asterisk^4 + parsers.underscore^4 / writer.string - larsers.EscapedChar = S("\\") * C(parsers.escapable) / writer.string + larsers.EscapedChar = parsers.backslash * C(parsers.escapable) / writer.string - larsers.InlineHtml = C(parsers.inlinehtml) / writer.inline_html + larsers.InlineHtml = parsers.emptyelt_any + + (parsers.htmlcomment / parse_inlines_no_html) + / writer.inline_html_comment + + parsers.htmlinstruction + + parsers.openelt_any + + parsers.closeelt_any larsers.HtmlEntity = parsers.hexentity / entities.hex_entity / writer.string + parsers.decentity / entities.dec_entity / writer.string @@ -3669,8 +3769,7 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline * parsers.contentblock_tail / writer.contentblock - larsers.DisplayHtml = C(parsers.displayhtml) - / expandtabs / writer.display_html + larsers.DisplayHtml = parsers.displayhtml larsers.Verbatim = Cs( (parsers.blanklines * ((parsers.indentedline - parsers.blankline))^1)^1 @@ -3995,6 +4094,10 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline syntax.Smart = parsers.fail end + if options.preserveTabs then + options.stripIndent = false + end + if not options.pipeTables then syntax.PipeTable = parsers.fail end @@ -4018,6 +4121,12 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline inlines_no_inline_note_t.InlineNote = parsers.fail larsers.inlines_no_inline_note = Ct(inlines_no_inline_note_t) + local inlines_no_html_t = util.table_copy(inlines_t) + inlines_no_html_t.DisplayHtml = parsers.fail + inlines_no_html_t.InlineHtml = parsers.fail + inlines_no_html_t.HtmlEntity = parsers.fail + larsers.inlines_no_html = Ct(inlines_no_html_t) + local inlines_nbsp_t = util.table_copy(inlines_t) inlines_nbsp_t.Endline = larsers.NonbreakingEndline inlines_nbsp_t.Space = larsers.NonbreakingSpace @@ -4044,10 +4153,12 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline else mode = "w" end - file = assert(io.open(options.frozenCacheFileName, mode)) - assert(file:write([[\expandafter\def\csname markdownFrozenCache]] .. - options.frozenCacheCounter .. [[\endcsname{]] .. output .. [[}]] .. - "\n")) + file = assert(io.open(options.frozenCacheFileName, mode), + [[could not open file "]] .. options.frozenCacheFileName + .. [[" for writing]]) + assert(file:write([[\expandafter\global\expandafter\def\csname ]] + .. [[markdownFrozenCache]] .. options.frozenCacheCounter + .. [[\endcsname{]] .. output .. [[}]] .. "\n")) assert(file:close()) end return output |