-- -- Copyright (C) 2009-2016 John MacFarlane, Hans Hagen -- -- Permission is hereby granted, free of charge, to any person obtaining -- a copy of this software and associated documentation files (the -- "Software"), to deal in the Software without restriction, including -- without limitation the rights to use, copy, modify, merge, publish, -- distribute, sublicense, and/or sell copies of the Software, and to -- permit persons to whom the Software is furnished to do so, subject to -- the following conditions: -- -- The above copyright notice and this permission notice shall be included -- in all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -- 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 Vít Novotný -- -- This work may be distributed and/or modified under the -- conditions of the LaTeX Project Public License, either version 1.3 -- of this license or (at your option) any later version. -- The latest version of this license is in -- -- http://www.latex-project.org/lppl.txt -- -- and version 1.3 or later is part of all distributions of LaTeX -- version 2005/12/01 or later. -- -- This work has the LPPL maintenance status `maintained'. -- The Current Maintainer of this work is Vít Novotný. -- -- Send bug reports, requests for additions and questions -- either to the GitHub issue tracker at -- -- https://github.com/witiko/markdown/issues -- -- or to the e-mail address . -- -- MODIFICATION ADVICE: -- -- If you want to customize this file, it is best to make a copy of -- the source file(s) from which it was produced. Use a different -- name for your copy(ies) and modify the copy(ies); this will ensure -- that your modifications do not get overwritten when you install a -- new release of the standard system. You should also ensure that -- your modified source file does not generate any modified file with -- the same name as a standard file. -- -- You will also need to produce your own, suitably named, .ins file to -- control the generation of files from your source file; this file -- should contain your own preambles for the files it generates, not -- those in the standard .ins files. -- local metadata = { version = "2.3.0", comment = "A module for the conversion from markdown to plain TeX", author = "John MacFarlane, Hans Hagen, Vít Novotný", copyright = "2009-2017 John MacFarlane, Hans Hagen; " .. "2016-2017 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 md5 = require("md5") local M = {} local defaultOptions = {} defaultOptions.blankBeforeBlockquote = false defaultOptions.blankBeforeCodeFence = false defaultOptions.blankBeforeHeading = false defaultOptions.breakableBlockquotes = false defaultOptions.cacheDir = "." defaultOptions.citationNbsps = true defaultOptions.citations = false defaultOptions.definitionLists = false defaultOptions.hashEnumerators = false defaultOptions.html = false defaultOptions.hybrid = false defaultOptions.fencedCode = false defaultOptions.footnotes = false defaultOptions.inlineFootnotes = false defaultOptions.preserveTabs = false defaultOptions.smartEllipses = false defaultOptions.startNumber = true defaultOptions.tightLists = true local upper, gsub, format, length = string.upper, string.gsub, string.format, string.len local concat = table.concat 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) local util = {} function util.err(msg, exit_code) io.stderr:write("markdown.lua: " .. msg .. "\n") os.exit(exit_code or 1) end function util.cache(dir, string, salt, transform, suffix) local digest = md5.sumhexa(string .. (salt or "")) 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 result = string if transform ~= nil then result = transform(result) end assert(file:write(result)) assert(file:close()) end return name end function util.table_copy(t) local u = { } for k, v in pairs(t) do u[k] = v end return setmetatable(u, getmetatable(t)) end function util.expand_tabs_in_line(s, tabstop) local tab = tabstop or 4 local corr = 0 return (s:gsub("()\t", function(p) local sp = tab - (p - 1 + corr) % tab corr = corr - 1 + sp return string.rep(" ", sp) end)) end function util.walk(t, f) local typ = type(t) if typ == "string" then f(t) elseif typ == "table" then local i = 1 local n n = t[i] while n do util.walk(n, f) i = i + 1 n = t[i] end elseif typ == "function" then local ok, val = pcall(t) if ok then util.walk(val,f) end else f(tostring(t)) end end function util.flatten(ary) local new = {} for _,v in ipairs(ary) do if type(v) == "table" then for _,w in ipairs(util.flatten(v)) do new[#new + 1] = w end else new[#new + 1] = v end end return new end function util.rope_to_string(rope) local buffer = {} util.walk(rope, function(x) buffer[#buffer + 1] = x end) return table.concat(buffer) end function util.rope_last(rope) if #rope == 0 then return nil else local l = rope[#rope] if type(l) == "table" then return util.rope_last(l) else return l end end end function util.intersperse(ary, x) local new = {} local l = #ary for i,v in ipairs(ary) do local n = #new new[n + 1] = v if i ~= l then new[n + 2] = x end end return new end function util.map(ary, f) local new = {} for i,v in ipairs(ary) do new[i] = f(v) end return new end function util.escaper(char_escapes, string_escapes) local char_escapes_list = "" for i,_ in pairs(char_escapes) do char_escapes_list = char_escapes_list .. i end local escapable = S(char_escapes_list) / char_escapes if string_escapes then for k,v in pairs(string_escapes) do escapable = P(k) / v + escapable end end local escape_string = Cs((escapable + any)^0) return function(s) return lpeg.match(escape_string, s) end end function util.pathname(dir, file) if #dir == 0 then return file else return dir .. "/" .. file end end local entities = {} local character_entities = { ["quot"] = 0x0022, ["amp"] = 0x0026, ["apos"] = 0x0027, ["lt"] = 0x003C, ["gt"] = 0x003E, ["nbsp"] = 160, ["iexcl"] = 0x00A1, ["cent"] = 0x00A2, ["pound"] = 0x00A3, ["curren"] = 0x00A4, ["yen"] = 0x00A5, ["brvbar"] = 0x00A6, ["sect"] = 0x00A7, ["uml"] = 0x00A8, ["copy"] = 0x00A9, ["ordf"] = 0x00AA, ["laquo"] = 0x00AB, ["not"] = 0x00AC, ["shy"] = 173, ["reg"] = 0x00AE, ["macr"] = 0x00AF, ["deg"] = 0x00B0, ["plusmn"] = 0x00B1, ["sup2"] = 0x00B2, ["sup3"] = 0x00B3, ["acute"] = 0x00B4, ["micro"] = 0x00B5, ["para"] = 0x00B6, ["middot"] = 0x00B7, ["cedil"] = 0x00B8, ["sup1"] = 0x00B9, ["ordm"] = 0x00BA, ["raquo"] = 0x00BB, ["frac14"] = 0x00BC, ["frac12"] = 0x00BD, ["frac34"] = 0x00BE, ["iquest"] = 0x00BF, ["Agrave"] = 0x00C0, ["Aacute"] = 0x00C1, ["Acirc"] = 0x00C2, ["Atilde"] = 0x00C3, ["Auml"] = 0x00C4, ["Aring"] = 0x00C5, ["AElig"] = 0x00C6, ["Ccedil"] = 0x00C7, ["Egrave"] = 0x00C8, ["Eacute"] = 0x00C9, ["Ecirc"] = 0x00CA, ["Euml"] = 0x00CB, ["Igrave"] = 0x00CC, ["Iacute"] = 0x00CD, ["Icirc"] = 0x00CE, ["Iuml"] = 0x00CF, ["ETH"] = 0x00D0, ["Ntilde"] = 0x00D1, ["Ograve"] = 0x00D2, ["Oacute"] = 0x00D3, ["Ocirc"] = 0x00D4, ["Otilde"] = 0x00D5, ["Ouml"] = 0x00D6, ["times"] = 0x00D7, ["Oslash"] = 0x00D8, ["Ugrave"] = 0x00D9, ["Uacute"] = 0x00DA, ["Ucirc"] = 0x00DB, ["Uuml"] = 0x00DC, ["Yacute"] = 0x00DD, ["THORN"] = 0x00DE, ["szlig"] = 0x00DF, ["agrave"] = 0x00E0, ["aacute"] = 0x00E1, ["acirc"] = 0x00E2, ["atilde"] = 0x00E3, ["auml"] = 0x00E4, ["aring"] = 0x00E5, ["aelig"] = 0x00E6, ["ccedil"] = 0x00E7, ["egrave"] = 0x00E8, ["eacute"] = 0x00E9, ["ecirc"] = 0x00EA, ["euml"] = 0x00EB, ["igrave"] = 0x00EC, ["iacute"] = 0x00ED, ["icirc"] = 0x00EE, ["iuml"] = 0x00EF, ["eth"] = 0x00F0, ["ntilde"] = 0x00F1, ["ograve"] = 0x00F2, ["oacute"] = 0x00F3, ["ocirc"] = 0x00F4, ["otilde"] = 0x00F5, ["ouml"] = 0x00F6, ["divide"] = 0x00F7, ["oslash"] = 0x00F8, ["ugrave"] = 0x00F9, ["uacute"] = 0x00FA, ["ucirc"] = 0x00FB, ["uuml"] = 0x00FC, ["yacute"] = 0x00FD, ["thorn"] = 0x00FE, ["yuml"] = 0x00FF, ["OElig"] = 0x0152, ["oelig"] = 0x0153, ["Scaron"] = 0x0160, ["scaron"] = 0x0161, ["Yuml"] = 0x0178, ["fnof"] = 0x0192, ["circ"] = 0x02C6, ["tilde"] = 0x02DC, ["Alpha"] = 0x0391, ["Beta"] = 0x0392, ["Gamma"] = 0x0393, ["Delta"] = 0x0394, ["Epsilon"] = 0x0395, ["Zeta"] = 0x0396, ["Eta"] = 0x0397, ["Theta"] = 0x0398, ["Iota"] = 0x0399, ["Kappa"] = 0x039A, ["Lambda"] = 0x039B, ["Mu"] = 0x039C, ["Nu"] = 0x039D, ["Xi"] = 0x039E, ["Omicron"] = 0x039F, ["Pi"] = 0x03A0, ["Rho"] = 0x03A1, ["Sigma"] = 0x03A3, ["Tau"] = 0x03A4, ["Upsilon"] = 0x03A5, ["Phi"] = 0x03A6, ["Chi"] = 0x03A7, ["Psi"] = 0x03A8, ["Omega"] = 0x03A9, ["alpha"] = 0x03B1, ["beta"] = 0x03B2, ["gamma"] = 0x03B3, ["delta"] = 0x03B4, ["epsilon"] = 0x03B5, ["zeta"] = 0x03B6, ["eta"] = 0x03B7, ["theta"] = 0x03B8, ["iota"] = 0x03B9, ["kappa"] = 0x03BA, ["lambda"] = 0x03BB, ["mu"] = 0x03BC, ["nu"] = 0x03BD, ["xi"] = 0x03BE, ["omicron"] = 0x03BF, ["pi"] = 0x03C0, ["rho"] = 0x03C1, ["sigmaf"] = 0x03C2, ["sigma"] = 0x03C3, ["tau"] = 0x03C4, ["upsilon"] = 0x03C5, ["phi"] = 0x03C6, ["chi"] = 0x03C7, ["psi"] = 0x03C8, ["omega"] = 0x03C9, ["thetasym"] = 0x03D1, ["upsih"] = 0x03D2, ["piv"] = 0x03D6, ["ensp"] = 0x2002, ["emsp"] = 0x2003, ["thinsp"] = 0x2009, ["ndash"] = 0x2013, ["mdash"] = 0x2014, ["lsquo"] = 0x2018, ["rsquo"] = 0x2019, ["sbquo"] = 0x201A, ["ldquo"] = 0x201C, ["rdquo"] = 0x201D, ["bdquo"] = 0x201E, ["dagger"] = 0x2020, ["Dagger"] = 0x2021, ["bull"] = 0x2022, ["hellip"] = 0x2026, ["permil"] = 0x2030, ["prime"] = 0x2032, ["Prime"] = 0x2033, ["lsaquo"] = 0x2039, ["rsaquo"] = 0x203A, ["oline"] = 0x203E, ["frasl"] = 0x2044, ["euro"] = 0x20AC, ["image"] = 0x2111, ["weierp"] = 0x2118, ["real"] = 0x211C, ["trade"] = 0x2122, ["alefsym"] = 0x2135, ["larr"] = 0x2190, ["uarr"] = 0x2191, ["rarr"] = 0x2192, ["darr"] = 0x2193, ["harr"] = 0x2194, ["crarr"] = 0x21B5, ["lArr"] = 0x21D0, ["uArr"] = 0x21D1, ["rArr"] = 0x21D2, ["dArr"] = 0x21D3, ["hArr"] = 0x21D4, ["forall"] = 0x2200, ["part"] = 0x2202, ["exist"] = 0x2203, ["empty"] = 0x2205, ["nabla"] = 0x2207, ["isin"] = 0x2208, ["notin"] = 0x2209, ["ni"] = 0x220B, ["prod"] = 0x220F, ["sum"] = 0x2211, ["minus"] = 0x2212, ["lowast"] = 0x2217, ["radic"] = 0x221A, ["prop"] = 0x221D, ["infin"] = 0x221E, ["ang"] = 0x2220, ["and"] = 0x2227, ["or"] = 0x2228, ["cap"] = 0x2229, ["cup"] = 0x222A, ["int"] = 0x222B, ["there4"] = 0x2234, ["sim"] = 0x223C, ["cong"] = 0x2245, ["asymp"] = 0x2248, ["ne"] = 0x2260, ["equiv"] = 0x2261, ["le"] = 0x2264, ["ge"] = 0x2265, ["sub"] = 0x2282, ["sup"] = 0x2283, ["nsub"] = 0x2284, ["sube"] = 0x2286, ["supe"] = 0x2287, ["oplus"] = 0x2295, ["otimes"] = 0x2297, ["perp"] = 0x22A5, ["sdot"] = 0x22C5, ["lceil"] = 0x2308, ["rceil"] = 0x2309, ["lfloor"] = 0x230A, ["rfloor"] = 0x230B, ["lang"] = 0x27E8, ["rang"] = 0x27E9, ["loz"] = 0x25CA, ["spades"] = 0x2660, ["clubs"] = 0x2663, ["hearts"] = 0x2665, ["diams"] = 0x2666, } function entities.dec_entity(s) return unicode.utf8.char(tonumber(s)) end function entities.hex_entity(s) return unicode.utf8.char(tonumber("0x"..s)) end function entities.char_entity(s) local n = character_entities[s] return unicode.utf8.char(n) end M.writer = {} function M.writer.new(options) local self = {} options = options or {} setmetatable(options, { __index = function (_, key) return defaultOptions[key] end }) self.suffix = ".tex" self.space = " " self.nbsp = "\\markdownRendererNbsp{}" function self.plain(s) return s end function self.paragraph(s) return s end function self.pack(name) return [[\input"]] .. name .. [["\relax]] end self.interblocksep = "\\markdownRendererInterblockSeparator\n{}" self.eof = [[\relax]] self.linebreak = "\\markdownRendererLineBreak\n{}" self.ellipsis = "\\markdownRendererEllipsis{}" self.hrule = "\\markdownRendererHorizontalRule{}" local escaped_chars = { ["{"] = "\\markdownRendererLeftBrace{}", ["}"] = "\\markdownRendererRightBrace{}", ["$"] = "\\markdownRendererDollarSign{}", ["%"] = "\\markdownRendererPercentSign{}", ["&"] = "\\markdownRendererAmpersand{}", ["_"] = "\\markdownRendererUnderscore{}", ["#"] = "\\markdownRendererHash{}", ["^"] = "\\markdownRendererCircumflex{}", ["\\"] = "\\markdownRendererBackslash{}", ["~"] = "\\markdownRendererTilde{}", ["|"] = "\\markdownRendererPipe{}", } local escaped_minimal_chars = { ["{"] = "\\markdownRendererLeftBrace{}", ["}"] = "\\markdownRendererRightBrace{}", ["%"] = "\\markdownRendererPercentSign{}", ["\\"] = "\\markdownRendererBackslash{}", } local escaped_minimal_strings = { ["^^"] = "\\markdownRendererCircumflex\\markdownRendererCircumflex ", } local escape = util.escaper(escaped_chars) local escape_minimal = util.escaper(escaped_minimal_chars, escaped_minimal_strings) if options.hybrid then self.string = function(s) return s end self.uri = function(u) return u end else self.string = escape self.uri = escape_minimal end function self.code(s) return {"\\markdownRendererCodeSpan{",escape(s),"}"} end function self.link(lab,src,tit) return {"\\markdownRendererLink{",lab,"}", "{",self.string(src),"}", "{",self.uri(src),"}", "{",self.string(tit or ""),"}"} end function self.image(lab,src,tit) return {"\\markdownRendererImage{",lab,"}", "{",self.string(src),"}", "{",self.uri(src),"}", "{",self.string(tit or ""),"}"} end local function ulitem(s) return {"\\markdownRendererUlItem ",s, "\\markdownRendererUlItemEnd "} end function self.bulletlist(items,tight) local buffer = {} for _,item in ipairs(items) do buffer[#buffer + 1] = ulitem(item) end local contents = util.intersperse(buffer,"\n") if tight and options.tightLists then return {"\\markdownRendererUlBeginTight\n",contents, "\n\\markdownRendererUlEndTight "} else return {"\\markdownRendererUlBegin\n",contents, "\n\\markdownRendererUlEnd "} end end local function olitem(s,num) if num ~= nil then return {"\\markdownRendererOlItemWithNumber{",num,"}",s, "\\markdownRendererOlItemEnd "} else return {"\\markdownRendererOlItem ",s, "\\markdownRendererOlItemEnd "} end end function self.orderedlist(items,tight,startnum) local buffer = {} local num = startnum for _,item in ipairs(items) do buffer[#buffer + 1] = olitem(item,num) if num ~= nil then num = num + 1 end end local contents = util.intersperse(buffer,"\n") if tight and options.tightLists then return {"\\markdownRendererOlBeginTight\n",contents, "\n\\markdownRendererOlEndTight "} else return {"\\markdownRendererOlBegin\n",contents, "\n\\markdownRendererOlEnd "} end end function self.inline_html(html) return "" end function self.display_html(html) return "" end local function dlitem(term, defs) local retVal = {"\\markdownRendererDlItem{",term,"}"} for _, def in ipairs(defs) do retVal[#retVal+1] = {"\\markdownRendererDlDefinitionBegin ",def, "\\markdownRendererDlDefinitionEnd "} end retVal[#retVal+1] = "\\markdownRendererDlItemEnd " return retVal end function self.definitionlist(items,tight) local buffer = {} for _,item in ipairs(items) do buffer[#buffer + 1] = dlitem(item.term, item.definitions) end if tight and options.tightLists then return {"\\markdownRendererDlBeginTight\n", buffer, "\n\\markdownRendererDlEndTight"} else return {"\\markdownRendererDlBegin\n", buffer, "\n\\markdownRendererDlEnd"} end end function self.emphasis(s) return {"\\markdownRendererEmphasis{",s,"}"} end function self.strong(s) return {"\\markdownRendererStrongEmphasis{",s,"}"} end function self.blockquote(s) return {"\\markdownRendererBlockQuoteBegin\n",s, "\n\\markdownRendererBlockQuoteEnd "} end function self.verbatim(s) local name = util.cache(options.cacheDir, s, nil, nil, ".verbatim") return {"\\markdownRendererInputVerbatim{",name,"}"} end function self.fencedCode(i, s) local name = util.cache(options.cacheDir, s, nil, nil, ".verbatim") return {"\\markdownRendererInputFencedCode{",name,"}{",i,"}"} end function self.heading(s,level) local cmd if level == 1 then cmd = "\\markdownRendererHeadingOne" elseif level == 2 then cmd = "\\markdownRendererHeadingTwo" elseif level == 3 then cmd = "\\markdownRendererHeadingThree" elseif level == 4 then cmd = "\\markdownRendererHeadingFour" elseif level == 5 then cmd = "\\markdownRendererHeadingFive" elseif level == 6 then cmd = "\\markdownRendererHeadingSix" else cmd = "" end return {cmd,"{",s,"}"} end function self.note(s) return {"\\markdownRendererFootnote{",s,"}"} end function self.citations(text_cites, cites) local buffer = {"\\markdownRenderer", text_cites and "TextCite" or "Cite", "{", #cites, "}"} for _,cite in ipairs(cites) do buffer[#buffer+1] = {cite.suppress_author and "-" or "+", "{", cite.prenote or "", "}{", cite.postnote or "", "}{", cite.name, "}"} end return buffer end return self end local parsers = {} parsers.percent = P("%") parsers.at = P("@") parsers.comma = P(",") parsers.asterisk = P("*") parsers.dash = P("-") parsers.plus = P("+") parsers.underscore = P("_") parsers.period = P(".") parsers.hash = P("#") parsers.ampersand = P("&") parsers.backtick = P("`") parsers.less = P("<") parsers.more = P(">") parsers.space = P(" ") parsers.squote = P("'") parsers.dquote = P('"') parsers.lparent = P("(") parsers.rparent = P(")") parsers.lbracket = P("[") parsers.rbracket = P("]") parsers.circumflex = P("^") parsers.slash = P("/") parsers.equal = P("=") parsers.colon = P(":") parsers.semicolon = P(";") parsers.exclamation = P("!") parsers.tilde = P("~") parsers.tab = P("\t") parsers.newline = P("\n") parsers.tightblocksep = P("\001") parsers.digit = R("09") parsers.hexdigit = R("09","af","AF") parsers.letter = R("AZ","az") parsers.alphanumeric = R("AZ","az","09") parsers.keyword = parsers.letter * parsers.alphanumeric^0 parsers.internal_punctuation = S(":;,.#$%&-+?<>~/") parsers.doubleasterisks = P("**") parsers.doubleunderscores = P("__") parsers.fourspaces = P(" ") parsers.any = P(1) parsers.fail = parsers.any - 1 parsers.escapable = S("\\`*_{}[]()+_.!<>#-~:^@;") parsers.anyescaped = P("\\") / "" * parsers.escapable + parsers.any parsers.spacechar = S("\t ") parsers.spacing = S(" \n\r\t") parsers.nonspacechar = parsers.any - parsers.spacing parsers.optionalspace = parsers.spacechar^0 parsers.specialchar = S("*_`&[]= #b and i end parsers.infostring = (parsers.linechar - (parsers.backtick + parsers.space^1 * (parsers.newline + parsers.eof)))^0 local fenceindent parsers.fencehead = function(char) return C(parsers.nonindentspace) / function(s) fenceindent = #s end * Cg(char^3, "fencelength") * parsers.optionalspace * C(parsers.infostring) * parsers.optionalspace * (parsers.newline + parsers.eof) end parsers.fencetail = function(char) return parsers.nonindentspace * Cmt(C(char^3) * Cb("fencelength"), captures_geq_length) * parsers.optionalspace * (parsers.newline + parsers.eof) + parsers.eof end parsers.fencedline = function(char) return C(parsers.line - parsers.fencetail(char)) / function(s) return s:gsub("^" .. string.rep(" ?", fenceindent), "") end end parsers.leader = parsers.space^-3 -- in balanced brackets, parentheses, quotes: parsers.bracketed = P{ parsers.lbracket * ((parsers.anyescaped - (parsers.lbracket + parsers.rbracket + parsers.blankline^2) ) + V(1))^0 * parsers.rbracket } parsers.inparens = P{ parsers.lparent * ((parsers.anyescaped - (parsers.lparent + parsers.rparent + parsers.blankline^2) ) + V(1))^0 * parsers.rparent } parsers.squoted = P{ parsers.squote * parsers.alphanumeric * ((parsers.anyescaped - (parsers.squote + parsers.blankline^2) ) + V(1))^0 * parsers.squote } parsers.dquoted = P{ parsers.dquote * parsers.alphanumeric * ((parsers.anyescaped - (parsers.dquote + parsers.blankline^2) ) + V(1))^0 * parsers.dquote } -- bracketed 'tag' for markdown links, allowing nested brackets: parsers.tag = parsers.lbracket * Cs((parsers.alphanumeric^1 + parsers.bracketed + parsers.inticks + (parsers.anyescaped - (parsers.rbracket + parsers.blankline^2)))^0) * parsers.rbracket -- url for markdown links, allowing balanced parentheses: parsers.url = parsers.less * Cs((parsers.anyescaped-parsers.more)^0) * parsers.more + Cs((parsers.inparens + (parsers.anyescaped -parsers.spacing-parsers.rparent))^1) -- quoted text possibly with nested quotes: parsers.title_s = parsers.squote * Cs(((parsers.anyescaped-parsers.squote) + parsers.squoted)^0) * parsers.squote parsers.title_d = parsers.dquote * Cs(((parsers.anyescaped-parsers.dquote) + parsers.dquoted)^0) * parsers.dquote parsers.title_p = parsers.lparent * Cs((parsers.inparens + (parsers.anyescaped-parsers.rparent))^0) * parsers.rparent parsers.title = parsers.title_d + parsers.title_s + parsers.title_p parsers.optionaltitle = parsers.spnl * parsers.title * parsers.spacechar^0 + Cc("") parsers.citation_name = Cs(parsers.dash^-1) * parsers.at * Cs(parsers.alphanumeric * (parsers.alphanumeric + parsers.internal_punctuation - parsers.comma - parsers.semicolon)^0) parsers.citation_body_prenote = Cs((parsers.alphanumeric^1 + parsers.bracketed + parsers.inticks + (parsers.anyescaped - (parsers.rbracket + parsers.blankline^2)) - (parsers.spnl * parsers.dash^-1 * parsers.at))^0) parsers.citation_body_postnote = Cs((parsers.alphanumeric^1 + parsers.bracketed + parsers.inticks + (parsers.anyescaped - (parsers.rbracket + parsers.semicolon + parsers.blankline^2)) - (parsers.spnl * parsers.rbracket))^0) parsers.citation_body_chunk = parsers.citation_body_prenote * parsers.spnl * parsers.citation_name * (parsers.comma * parsers.spnl)^-1 * parsers.citation_body_postnote parsers.citation_body = parsers.citation_body_chunk * (parsers.semicolon * parsers.spnl * parsers.citation_body_chunk)^0 parsers.citation_headless_body_postnote = Cs((parsers.alphanumeric^1 + parsers.bracketed + parsers.inticks + (parsers.anyescaped - (parsers.rbracket + parsers.at + parsers.semicolon + parsers.blankline^2)) - (parsers.spnl * parsers.rbracket))^0) parsers.citation_headless_body = parsers.citation_headless_body_postnote * (parsers.sp * parsers.semicolon * parsers.spnl * parsers.citation_body_chunk)^0 local function strip_first_char(s) return s:sub(2) end parsers.RawNoteRef = #(parsers.lbracket * parsers.circumflex) * parsers.tag / strip_first_char -- case-insensitive match (we assume s is lowercase). must be single byte encoding parsers.keyword_exact = function(s) local parser = P(0) for i=1,#s do local c = s:sub(i,i) local m = c .. upper(c) parser = parser * S(m) end return parser end parsers.block_keyword = parsers.keyword_exact("address") + parsers.keyword_exact("blockquote") + parsers.keyword_exact("center") + parsers.keyword_exact("del") + parsers.keyword_exact("dir") + parsers.keyword_exact("div") + parsers.keyword_exact("p") + parsers.keyword_exact("pre") + parsers.keyword_exact("li") + parsers.keyword_exact("ol") + parsers.keyword_exact("ul") + parsers.keyword_exact("dl") + parsers.keyword_exact("dd") + parsers.keyword_exact("form") + parsers.keyword_exact("fieldset") + parsers.keyword_exact("isindex") + parsers.keyword_exact("ins") + parsers.keyword_exact("menu") + parsers.keyword_exact("noframes") + parsers.keyword_exact("frameset") + parsers.keyword_exact("h1") + parsers.keyword_exact("h2") + parsers.keyword_exact("h3") + parsers.keyword_exact("h4") + parsers.keyword_exact("h5") + parsers.keyword_exact("h6") + parsers.keyword_exact("hr") + parsers.keyword_exact("script") + parsers.keyword_exact("noscript") + parsers.keyword_exact("table") + parsers.keyword_exact("tbody") + parsers.keyword_exact("tfoot") + parsers.keyword_exact("thead") + parsers.keyword_exact("th") + parsers.keyword_exact("td") + parsers.keyword_exact("tr") -- There is no reason to support bad html, so we expect quoted attributes parsers.htmlattributevalue = parsers.squote * (parsers.any - (parsers.blankline + parsers.squote))^0 * parsers.squote + parsers.dquote * (parsers.any - (parsers.blankline + parsers.dquote))^0 * parsers.dquote parsers.htmlattribute = parsers.spacing^1 * (parsers.alphanumeric + S("_-"))^1 * parsers.sp * parsers.equal * parsers.sp * parsers.htmlattributevalue parsers.htmlcomment = P(""))^0 * P("-->") parsers.htmlinstruction = P("" ))^0 * P("?>" ) parsers.openelt_any = parsers.less * parsers.keyword * parsers.htmlattribute^0 * parsers.sp * parsers.more parsers.openelt_exact = function(s) return parsers.less * parsers.sp * parsers.keyword_exact(s) * parsers.htmlattribute^0 * parsers.sp * parsers.more end parsers.openelt_block = parsers.sp * parsers.block_keyword * parsers.htmlattribute^0 * parsers.sp * parsers.more parsers.closeelt_any = parsers.less * parsers.sp * parsers.slash * parsers.keyword * parsers.sp * parsers.more parsers.closeelt_exact = function(s) return parsers.less * parsers.sp * parsers.slash * parsers.keyword_exact(s) * parsers.sp * parsers.more end parsers.emptyelt_any = parsers.less * parsers.sp * parsers.keyword * parsers.htmlattribute^0 * parsers.sp * parsers.slash * parsers.more parsers.emptyelt_block = parsers.less * parsers.sp * parsers.block_keyword * parsers.htmlattribute^0 * parsers.sp * parsers.slash * parsers.more parsers.displaytext = (parsers.any - parsers.less)^1 -- return content between two matched HTML tags parsers.in_matched = function(s) return { parsers.openelt_exact(s) * (V(1) + parsers.displaytext + (parsers.less - parsers.closeelt_exact(s)))^0 * parsers.closeelt_exact(s) } end local function parse_matched_tags(s,pos) local t = string.lower(lpeg.match(C(parsers.keyword),s,pos)) return lpeg.match(parsers.in_matched(t),s,pos-1) end parsers.in_matched_block_tags = parsers.less * Cmt(#parsers.openelt_block, parse_matched_tags) 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 * C(parsers.digit^1) * parsers.semicolon parsers.tagentity = parsers.ampersand * C(parsers.alphanumeric^1) * parsers.semicolon -- parse a reference definition: [foo]: /bar "title" parsers.define_reference_parser = parsers.leader * parsers.tag * parsers.colon * parsers.spacechar^0 * parsers.url * parsers.optionaltitle * parsers.blankline^1 parsers.Inline = V("Inline") -- parse many p between starter and ender parsers.between = function(p, starter, ender) local ender2 = B(parsers.nonspacechar) * ender return (starter * #parsers.nonspacechar * Ct(p * (p - ender2)^0) * ender2) end parsers.urlchar = parsers.anyescaped - parsers.newline - parsers.more parsers.Block = V("Block") parsers.TildeFencedCode = parsers.fencehead(parsers.tilde) * Cs(parsers.fencedline(parsers.tilde)^0) * parsers.fencetail(parsers.tilde) parsers.BacktickFencedCode = parsers.fencehead(parsers.backtick) * Cs(parsers.fencedline(parsers.backtick)^0) * parsers.fencetail(parsers.backtick) parsers.lineof = function(c) return (parsers.leader * (P(c) * parsers.optionalspace)^3 * (parsers.newline * parsers.blankline^1 + parsers.newline^-1 * parsers.eof)) end parsers.defstartchar = S("~:") parsers.defstart = ( parsers.defstartchar * #parsers.spacing * (parsers.tab + parsers.space^-3) + parsers.space * parsers.defstartchar * #parsers.spacing * (parsers.tab + parsers.space^-2) + parsers.space * parsers.space * parsers.defstartchar * #parsers.spacing * (parsers.tab + parsers.space^-1) + parsers.space * parsers.space * parsers.space * parsers.defstartchar * #parsers.spacing ) parsers.dlchunk = Cs(parsers.line * (parsers.indentedline - parsers.blankline)^0) -- parse Atx heading start and return level parsers.HeadingStart = #parsers.hash * C(parsers.hash^-6) * -parsers.hash / length -- parse setext header ending and return level parsers.HeadingLevel = parsers.equal^1 * Cc(1) + parsers.dash^1 * Cc(2) local function strip_atx_end(s) return s:gsub("[#%s]*\n$","") end M.reader = {} function M.reader.new(writer, options) local self = {} options = options or {} setmetatable(options, { __index = function (_, key) return defaultOptions[key] end }) local function normalize_tag(tag) return unicode.utf8.lower( gsub(util.rope_to_string(tag), "[ \n\r\t]+", " ")) 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) else return s end end end local larsers = {} local function create_parser(name, grammar) return function(str) local res = lpeg.match(grammar(), str) if res == nil then error(format("%s failed on:\n%s", name, str:sub(1,20))) else return res end end end local parse_blocks = create_parser("parse_blocks", function() return larsers.blocks end) local parse_blocks_toplevel = create_parser("parse_blocks_toplevel", function() return larsers.blocks_toplevel end) local parse_inlines = create_parser("parse_inlines", function() return larsers.inlines end) local parse_inlines_no_link = create_parser("parse_inlines_no_link", function() return larsers.inlines_no_link end) local parse_inlines_no_inline_note = create_parser("parse_inlines_no_inline_note", function() return larsers.inlines_no_inline_note end) local parse_inlines_nbsp = create_parser("parse_inlines_nbsp", function() return larsers.inlines_nbsp end) if options.hashEnumerators then larsers.dig = parsers.digit + parsers.hash else larsers.dig = parsers.digit end larsers.enumerator = C(larsers.dig^3 * parsers.period) * #parsers.spacing + C(larsers.dig^2 * parsers.period) * #parsers.spacing * (parsers.tab + parsers.space^1) + C(larsers.dig * parsers.period) * #parsers.spacing * (parsers.tab + parsers.space^-2) + parsers.space * C(larsers.dig^2 * parsers.period) * #parsers.spacing + parsers.space * C(larsers.dig * parsers.period) * #parsers.spacing * (parsers.tab + parsers.space^-1) + parsers.space * parsers.space * C(larsers.dig^1 * parsers.period) * #parsers.spacing -- strip off leading > and indents, and run through blocks larsers.blockquote_body = ((parsers.leader * parsers.more * parsers.space^-1)/"" * parsers.linechar^0 * parsers.newline)^1 * (-(parsers.leader * parsers.more + parsers.blankline) * parsers.linechar^1 * parsers.newline)^0 if not options.breakableBlockquotes then larsers.blockquote_body = larsers.blockquote_body * (parsers.blankline^0 / "") end larsers.citations = function(text_cites, raw_cites) local function normalize(str) if str == "" then str = nil else str = (options.citationNbsps and parse_inlines_nbsp or parse_inlines)(str) end return str end local cites = {} for i = 1,#raw_cites,4 do cites[#cites+1] = { prenote = normalize(raw_cites[i]), suppress_author = raw_cites[i+1] == "-", name = writer.string(raw_cites[i+2]), postnote = normalize(raw_cites[i+3]), } end return writer.citations(text_cites, cites) end local rawnotes = {} -- like indirect_link local function lookup_note(ref) return 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 local function register_note(ref,rawnote) rawnotes[normalize_tag(ref)] = rawnote return "" end larsers.NoteRef = parsers.RawNoteRef / lookup_note larsers.NoteBlock = parsers.leader * parsers.RawNoteRef * parsers.colon * parsers.spnl * parsers.indented_blocks(parsers.chunk) / register_note larsers.InlineNote = parsers.circumflex * (parsers.tag / parse_inlines_no_inline_note) -- no notes inside notes / writer.note -- List of references defined in the document local references -- add a reference to the list local function register_link(tag,url,title) references[normalize_tag(tag)] = { url = url, title = title } return "" end -- lookup link reference and return either -- the link or nil and fallback text. local function lookup_reference(label,sps,tag) local tagpart if not tag then tag = label tagpart = "" elseif tag == "" then tag = label tagpart = "[]" else tagpart = {"[", parse_inlines(tag), "]"} end if sps then tagpart = {sps, tagpart} end local r = references[normalize_tag(tag)] if r then return r else return nil, {"[", parse_inlines(label), "]", tagpart} end end -- 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() 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 -- 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() 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 larsers.Str = parsers.normalchar^1 / writer.string larsers.Symbol = (parsers.specialchar - parsers.tightblocksep) / writer.string larsers.Ellipsis = P("...") / writer.ellipsis larsers.Smart = larsers.Ellipsis larsers.Code = parsers.inticks / writer.code if options.blankBeforeBlockquote then larsers.bqstart = parsers.fail else larsers.bqstart = parsers.more end if options.blankBeforeHeading then larsers.headerstart = parsers.fail else larsers.headerstart = parsers.hash + (parsers.line * (parsers.equal^1 + parsers.dash^1) * parsers.optionalspace * parsers.newline) end if not options.fencedCode or options.blankBeforeCodeFence then larsers.fencestart = parsers.fail else larsers.fencestart = parsers.fencehead(parsers.backtick) + parsers.fencehead(parsers.tilde) end larsers.Endline = parsers.newline * -( -- newline, but not before... parsers.blankline -- paragraph break + parsers.tightblocksep -- nested list + parsers.eof -- end of document + larsers.bqstart + larsers.headerstart + larsers.fencestart ) * parsers.spacechar^0 / writer.space larsers.Space = parsers.spacechar^2 * larsers.Endline / writer.linebreak + parsers.spacechar^1 * larsers.Endline^-1 * parsers.eof / "" + parsers.spacechar^1 * larsers.Endline^-1 * parsers.optionalspace / writer.space larsers.NonbreakingEndline = parsers.newline * -( -- newline, but not before... parsers.blankline -- paragraph break + parsers.tightblocksep -- nested list + parsers.eof -- end of document + larsers.bqstart + larsers.headerstart + larsers.fencestart ) * parsers.spacechar^0 / writer.nbsp larsers.NonbreakingSpace = parsers.spacechar^2 * larsers.Endline / writer.linebreak + parsers.spacechar^1 * larsers.Endline^-1 * parsers.eof / "" + parsers.spacechar^1 * larsers.Endline^-1 * parsers.optionalspace / writer.nbsp larsers.Strong = ( parsers.between(parsers.Inline, parsers.doubleasterisks, parsers.doubleasterisks) + parsers.between(parsers.Inline, parsers.doubleunderscores, parsers.doubleunderscores) ) / writer.strong larsers.Emph = ( parsers.between(parsers.Inline, parsers.asterisk, parsers.asterisk) + parsers.between(parsers.Inline, parsers.underscore, parsers.underscore) ) / writer.emphasis larsers.AutoLinkUrl = parsers.less * C(parsers.alphanumeric^1 * P("://") * parsers.urlchar^1) * parsers.more / function(url) return writer.link(writer.string(url), url) end larsers.AutoLinkEmail = parsers.less * C((parsers.alphanumeric + S("-._+"))^1 * P("@") * parsers.urlchar^1) * parsers.more / function(email) return writer.link(writer.string(email), "mailto:"..email) end larsers.DirectLink = (parsers.tag / parse_inlines_no_link) -- no links inside links * parsers.spnl * parsers.lparent * (parsers.url + Cc("")) -- link can be empty [foo]() * parsers.optionaltitle * parsers.rparent / writer.link larsers.IndirectLink = parsers.tag * (C(parsers.spnl) * parsers.tag)^-1 / indirect_link -- parse a link or image (direct or indirect) larsers.Link = larsers.DirectLink + larsers.IndirectLink larsers.DirectImage = parsers.exclamation * (parsers.tag / parse_inlines) * parsers.spnl * parsers.lparent * (parsers.url + Cc("")) -- link can be empty [foo]() * parsers.optionaltitle * parsers.rparent / writer.image larsers.IndirectImage = parsers.exclamation * parsers.tag * (C(parsers.spnl) * parsers.tag)^-1 / indirect_image larsers.Image = larsers.DirectImage + larsers.IndirectImage larsers.TextCitations = Ct(Cc("") * parsers.citation_name * ((parsers.spnl * parsers.lbracket * parsers.citation_headless_body * parsers.rbracket) + Cc(""))) / function(raw_cites) return larsers.citations(true, raw_cites) end larsers.ParenthesizedCitations = Ct(parsers.lbracket * parsers.citation_body * parsers.rbracket) / function(raw_cites) return larsers.citations(false, raw_cites) end larsers.Citations = larsers.TextCitations + larsers.ParenthesizedCitations -- avoid parsing long strings of * or _ as emph/strong larsers.UlOrStarLine = parsers.asterisk^4 + parsers.underscore^4 / writer.string larsers.EscapedChar = S("\\") * C(parsers.escapable) / writer.string larsers.InlineHtml = C(parsers.inlinehtml) / writer.inline_html larsers.HtmlEntity = parsers.hexentity / entities.hex_entity / writer.string + parsers.decentity / entities.dec_entity / writer.string + parsers.tagentity / entities.char_entity / writer.string larsers.DisplayHtml = C(parsers.displayhtml) / expandtabs / writer.display_html larsers.Verbatim = Cs( (parsers.blanklines * ((parsers.indentedline - parsers.blankline))^1)^1 ) / expandtabs / writer.verbatim larsers.FencedCode = (parsers.TildeFencedCode + parsers.BacktickFencedCode) / function(infostring, code) return writer.fencedCode(writer.string(infostring), expandtabs(code)) end larsers.Blockquote = Cs(larsers.blockquote_body^1) / parse_blocks_toplevel / writer.blockquote larsers.HorizontalRule = ( parsers.lineof(parsers.asterisk) + parsers.lineof(parsers.dash) + parsers.lineof(parsers.underscore) ) / writer.hrule larsers.Reference = parsers.define_reference_parser / register_link larsers.Paragraph = parsers.nonindentspace * Ct(parsers.Inline^1) * parsers.newline * ( parsers.blankline^1 + #parsers.hash + #(parsers.leader * parsers.more * parsers.space^-1) ) / writer.paragraph larsers.ToplevelParagraph = parsers.nonindentspace * Ct(parsers.Inline^1) * ( parsers.newline * ( parsers.blankline^1 + #parsers.hash + #(parsers.leader * parsers.more * parsers.space^-1) + parsers.eof ) + parsers.eof ) / writer.paragraph larsers.Plain = parsers.nonindentspace * Ct(parsers.Inline^1) / writer.plain larsers.starter = parsers.bullet + larsers.enumerator -- we use \001 as a separator between a tight list item and a -- nested list under it. larsers.NestedList = Cs((parsers.optionallyindentedline - larsers.starter)^1) / function(a) return "\001"..a end larsers.ListBlockLine = parsers.optionallyindentedline - parsers.blankline - (parsers.indent^-1 * larsers.starter) larsers.ListBlock = parsers.line * larsers.ListBlockLine^0 larsers.ListContinuationBlock = parsers.blanklines * (parsers.indent / "") * larsers.ListBlock larsers.TightListItem = function(starter) return -larsers.HorizontalRule * (Cs(starter / "" * larsers.ListBlock * larsers.NestedList^-1) / parse_blocks) * -(parsers.blanklines * parsers.indent) end larsers.LooseListItem = function(starter) return -larsers.HorizontalRule * Cs( starter / "" * larsers.ListBlock * Cc("\n") * (larsers.NestedList + larsers.ListContinuationBlock^0) * (parsers.blanklines / "\n\n") ) / parse_blocks end larsers.BulletList = ( Ct(larsers.TightListItem(parsers.bullet)^1) * Cc(true) * parsers.skipblanklines * -parsers.bullet + Ct(larsers.LooseListItem(parsers.bullet)^1) * Cc(false) * parsers.skipblanklines ) / writer.bulletlist local function ordered_list(items,tight,startNumber) if options.startNumber then startNumber = tonumber(startNumber) or 1 -- fallback for '#' else startNumber = nil end return writer.orderedlist(items,tight,startNumber) end larsers.OrderedList = Cg(larsers.enumerator, "listtype") * ( Ct(larsers.TightListItem(Cb("listtype")) * larsers.TightListItem(larsers.enumerator)^0) * Cc(true) * parsers.skipblanklines * -larsers.enumerator + Ct(larsers.LooseListItem(Cb("listtype")) * larsers.LooseListItem(larsers.enumerator)^0) * Cc(false) * parsers.skipblanklines ) * Cb("listtype") / ordered_list local function definition_list_item(term, defs, tight) return { term = parse_inlines(term), definitions = defs } end larsers.DefinitionListItemLoose = C(parsers.line) * parsers.skipblanklines * Ct((parsers.defstart * parsers.indented_blocks(parsers.dlchunk) / parse_blocks_toplevel)^1) * Cc(false) / definition_list_item larsers.DefinitionListItemTight = C(parsers.line) * Ct((parsers.defstart * parsers.dlchunk / parse_blocks)^1) * Cc(true) / definition_list_item larsers.DefinitionList = ( Ct(larsers.DefinitionListItemLoose^1) * Cc(false) + Ct(larsers.DefinitionListItemTight^1) * (parsers.skipblanklines * -larsers.DefinitionListItemLoose * Cc(true)) ) / writer.definitionlist larsers.Blank = parsers.blankline / "" + larsers.NoteBlock + larsers.Reference + (parsers.tightblocksep / "\n") -- parse atx header larsers.AtxHeading = Cg(parsers.HeadingStart,"level") * parsers.optionalspace * (C(parsers.line) / strip_atx_end / parse_inlines) * Cb("level") / writer.heading -- parse setext header larsers.SetextHeading = #(parsers.line * S("=-")) * Ct(parsers.line / parse_inlines) * parsers.HeadingLevel * parsers.optionalspace * parsers.newline / writer.heading larsers.Heading = larsers.AtxHeading + larsers.SetextHeading local syntax = { "Blocks", Blocks = larsers.Blank^0 * parsers.Block^-1 * (larsers.Blank^0 / function() return writer.interblocksep end * parsers.Block)^0 * larsers.Blank^0 * parsers.eof, Blank = larsers.Blank, Block = V("Blockquote") + V("Verbatim") + V("FencedCode") + V("HorizontalRule") + V("BulletList") + V("OrderedList") + V("Heading") + V("DefinitionList") + V("DisplayHtml") + V("Paragraph") + V("Plain"), Blockquote = larsers.Blockquote, Verbatim = larsers.Verbatim, FencedCode = larsers.FencedCode, HorizontalRule = larsers.HorizontalRule, BulletList = larsers.BulletList, OrderedList = larsers.OrderedList, Heading = larsers.Heading, DefinitionList = larsers.DefinitionList, DisplayHtml = larsers.DisplayHtml, Paragraph = larsers.Paragraph, Plain = larsers.Plain, Inline = V("Str") + V("Space") + V("Endline") + V("UlOrStarLine") + V("Strong") + V("Emph") + V("InlineNote") + V("NoteRef") + V("Citations") + V("Link") + V("Image") + V("Code") + V("AutoLinkUrl") + V("AutoLinkEmail") + V("InlineHtml") + V("HtmlEntity") + V("EscapedChar") + V("Smart") + V("Symbol"), Str = larsers.Str, Space = larsers.Space, Endline = larsers.Endline, UlOrStarLine = larsers.UlOrStarLine, Strong = larsers.Strong, Emph = larsers.Emph, InlineNote = larsers.InlineNote, NoteRef = larsers.NoteRef, Citations = larsers.Citations, Link = larsers.Link, Image = larsers.Image, Code = larsers.Code, AutoLinkUrl = larsers.AutoLinkUrl, AutoLinkEmail = larsers.AutoLinkEmail, InlineHtml = larsers.InlineHtml, HtmlEntity = larsers.HtmlEntity, EscapedChar = larsers.EscapedChar, Smart = larsers.Smart, Symbol = larsers.Symbol, } if not options.definitionLists then syntax.DefinitionList = parsers.fail end if not options.fencedCode then syntax.FencedCode = parsers.fail end if not options.citations then syntax.Citations = parsers.fail end if not options.footnotes then syntax.NoteRef = parsers.fail end if not options.inlineFootnotes then syntax.InlineNote = parsers.fail end if not options.smartEllipses then syntax.Smart = parsers.fail end if not options.html then syntax.DisplayHtml = parsers.fail syntax.InlineHtml = parsers.fail syntax.HtmlEntity = parsers.fail end local blocks_toplevel_t = util.table_copy(syntax) blocks_toplevel_t.Paragraph = larsers.ToplevelParagraph larsers.blocks_toplevel = Ct(blocks_toplevel_t) larsers.blocks = Ct(syntax) local inlines_t = util.table_copy(syntax) inlines_t[1] = "Inlines" inlines_t.Inlines = parsers.Inline^0 * (parsers.spacing^0 * parsers.eof / "") larsers.inlines = Ct(inlines_t) local inlines_no_link_t = util.table_copy(inlines_t) inlines_no_link_t.Link = parsers.fail larsers.inlines_no_link = Ct(inlines_no_link_t) local inlines_no_inline_note_t = util.table_copy(inlines_t) inlines_no_inline_note_t.InlineNote = parsers.fail larsers.inlines_no_inline_note = Ct(inlines_no_inline_note_t) local inlines_nbsp_t = util.table_copy(inlines_t) inlines_nbsp_t.Endline = larsers.NonbreakingEndline inlines_nbsp_t.Space = larsers.NonbreakingSpace larsers.inlines_nbsp = Ct(inlines_nbsp_t) function self.convert(input) references = {} local opt_string = {} for k,_ in pairs(defaultOptions) do local v = options[k] if k ~= "cacheDir" then opt_string[#opt_string+1] = k .. "=" .. tostring(v) end end table.sort(opt_string) local salt = table.concat(opt_string, ",") .. "," .. metadata.version local name = util.cache(options.cacheDir, input, salt, function(input) return util.rope_to_string(parse_blocks_toplevel(input)) .. writer.eof end, ".md" .. writer.suffix) return writer.pack(name) end return self end function M.new(options) local writer = M.writer.new(options) local reader = M.reader.new(writer, options) return reader.convert end return M