diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/markdown/markdown.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/markdown/markdown.lua | 1057 |
1 files changed, 1057 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/luatex/markdown/markdown.lua b/Master/texmf-dist/tex/luatex/markdown/markdown.lua new file mode 100644 index 00000000000..e740d18c92e --- /dev/null +++ b/Master/texmf-dist/tex/luatex/markdown/markdown.lua @@ -0,0 +1,1057 @@ +-- +-- 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 <witiko@mail.muni.cz>. +-- +-- 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. +-- +if not modules then modules = { } end modules ['markdown'] = { + version = "1.0.1", + 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 Vít Novotný", + license = "LPPL 1.3" +} +local lpeg = require("lpeg") +local unicode = require("unicode") +local md5 = require("md5") +local M = {} +M.new = {} +local defaultOptions = {} +defaultOptions.blankBeforeBlockquote = false +defaultOptions.blankBeforeHeading = false +defaultOptions.cacheDir = "." +defaultOptions.definitionLists = false +defaultOptions.hashEnumerators = false +defaultOptions.hybrid = false +defaultOptions.footnotes = 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 +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 = " " + 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 = "\n\n" + self.containersep = "\n\n" + self.eof = [[\relax]] + self.linebreak = "\\markdownRendererLineBreak " + self.ellipsis = "\\markdownRendererEllipsis{}" + self.hrule = "\\markdownRendererHorizontalRule " + local escaped = { + ["{"] = "\\{", + ["}"] = "\\}", + ["$"] = "\\$", + ["%"] = "\\%", + ["&"] = "\\&", + ["_"] = "\\_", + ["#"] = "\\#", + ["^"] = "\\^{}", + ["\\"] = "\\char92{}", + ["~"] = "\\char126{}", + ["|"] = "\\char124{}", } + local escape = util.escaper(escaped) + if options.hybrid then + self.string = function(s) return s end + else + self.string = escape + end + function self.code(s) + return {"\\markdownRendererCodeSpan{",escape(s),"}"} + end + function self.link(lab,src,tit) + return {"\\markdownRendererLink{",lab,"}", + "{",self.string(src),"}", + "{",self.string(tit),"}"} + end + function self.image(lab,src,tit) + return {"\\markdownRendererImage{",lab,"}", + "{",self.string(src),"}", + "{",self.string(tit),"}"} + end + local function ulitem(s) + return {"\\markdownRendererUlItem ",s} + 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} + else + return {"\\markdownRendererOlItem ",s} + 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 + local function dlitem(term,defs) + return {"\\markdownRendererDlItem{",term,"}\n",defs} + end + + function self.definitionlist(items,tight) + local buffer = {} + for _,item in ipairs(items) do + buffer[#buffer + 1] = dlitem(item.term, + util.intersperse(item.definitions, self.interblocksep)) + end + local contents = util.intersperse(buffer, self.containersep) + if tight and options.tightLists then + return {"\\markdownRendererDlBeginTight\n\n", contents, + "\n\n\\markdownRendererDlEndTight\n"} + else + return {"\\markdownRendererDlBegin\n\n", contents, + "\n\n\\markdownRendererDlEnd\n"} + 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.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 + + return self +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 syntax + local blocks + local inlines + + local parse_blocks = + function(str) + local res = lpeg.match(blocks, str) + if res == nil then + error(format("parse_blocks failed on:\n%s", str:sub(1,20))) + else + return res + end + end + + local parse_inlines = + function(str) + local res = lpeg.match(inlines, str) + if res == nil then + error(format("parse_inlines failed on:\n%s", + str:sub(1,20))) + else + return res + end + end + + local parse_inlines_no_link = + function(str) + local res = lpeg.match(inlines_no_link, str) + if res == nil then + error(format("parse_inlines_no_link failed on:\n%s", + str:sub(1,20))) + else + return res + end + end + local percent = P("%") + local asterisk = P("*") + local dash = P("-") + local plus = P("+") + local underscore = P("_") + local period = P(".") + local hash = P("#") + local ampersand = P("&") + local backtick = P("`") + local less = P("<") + local more = P(">") + local space = P(" ") + local squote = P("'") + local dquote = P('"') + local lparent = P("(") + local rparent = P(")") + local lbracket = P("[") + local rbracket = P("]") + local circumflex = P("^") + local slash = P("/") + local equal = P("=") + local colon = P(":") + local semicolon = P(";") + local exclamation = P("!") + + local digit = R("09") + local hexdigit = R("09","af","AF") + local letter = R("AZ","az") + local alphanumeric = R("AZ","az","09") + local keyword = letter * alphanumeric^0 + + local doubleasterisks = P("**") + local doubleunderscores = P("__") + local fourspaces = P(" ") + + local any = P(1) + local fail = any - 1 + local always = P("") + + local escapable = S("\\`*_{}[]()+_.!#-~:^") + local anyescaped = P("\\") / "" * escapable + + any + + local tab = P("\t") + local spacechar = S("\t ") + local spacing = S(" \n\r\t") + local newline = P("\n") + local nonspacechar = any - spacing + local tightblocksep = P("\001") + + local specialchar + if options.smartEllipses then + specialchar = S("*_`&[]!\\.") + else + specialchar = S("*_`&[]!\\") + end + + local normalchar = any - + (specialchar + spacing + tightblocksep) + local optionalspace = spacechar^0 + local spaces = spacechar^1 + local eof = - any + local nonindentspace = space^-3 * - spacechar + local indent = space^-3 * tab + + fourspaces / "" + local linechar = P(1 - newline) + + local blankline = optionalspace * newline / "\n" + local blanklines = blankline^0 + local skipblanklines = (optionalspace * newline)^0 + local indentedline = indent /"" * C(linechar^1 * newline^-1) + local optionallyindentedline = indent^-1 /"" * C(linechar^1 * newline^-1) + local sp = spacing^0 + local spnl = optionalspace * (newline * optionalspace)^-1 + local line = linechar^0 * newline + + linechar^1 * eof + local nonemptyline = line - blankline + + local chunk = line * (optionallyindentedline - blankline)^0 + + -- block followed by 0 or more optionally + -- indented blocks with first line indented. + local function indented_blocks(bl) + return Cs( bl + * (blankline^1 * indent * -blankline * bl)^0 + * blankline^1 ) + end + local bulletchar = C(plus + asterisk + dash) + + local bullet = ( bulletchar * #spacing * (tab + space^-3) + + space * bulletchar * #spacing * (tab + space^-2) + + space * space * bulletchar * #spacing * (tab + space^-1) + + space * space * space * bulletchar * #spacing + ) * -bulletchar + + if options.hashEnumerators then + dig = digit + hash + else + dig = digit + end + + local enumerator = C(dig^3 * period) * #spacing + + C(dig^2 * period) * #spacing * (tab + space^1) + + C(dig * period) * #spacing * (tab + space^-2) + + space * C(dig^2 * period) * #spacing + + space * C(dig * period) * #spacing * (tab + space^-1) + + space * space * C(dig^1 * period) * #spacing + local openticks = Cg(backtick^1, "ticks") + + local function captures_equal_length(s,i,a,b) + return #a == #b and i + end + + local closeticks = space^-1 * + Cmt(C(backtick^1) * Cb("ticks"), captures_equal_length) + + local intickschar = (any - S(" \n\r`")) + + (newline * -blankline) + + (space - closeticks) + + (backtick^1 - closeticks) + + local inticks = openticks * space^-1 * C(intickschar^1) * closeticks + local leader = space^-3 + + -- in balanced brackets, parentheses, quotes: + local bracketed = P{ lbracket + * ((anyescaped - (lbracket + rbracket + + blankline^2)) + V(1))^0 + * rbracket } + + local inparens = P{ lparent + * ((anyescaped - (lparent + rparent + + blankline^2)) + V(1))^0 + * rparent } + + local squoted = P{ squote * alphanumeric + * ((anyescaped - (squote + blankline^2)) + + V(1))^0 + * squote } + + local dquoted = P{ dquote * alphanumeric + * ((anyescaped - (dquote + blankline^2)) + + V(1))^0 + * dquote } + + -- bracketed 'tag' for markdown links, allowing nested brackets: + local tag = lbracket + * Cs((alphanumeric^1 + + bracketed + + inticks + + (anyescaped - (rbracket + blankline^2)))^0) + * rbracket + + -- url for markdown links, allowing balanced parentheses: + local url = less * Cs((anyescaped-more)^0) * more + + Cs((inparens + (anyescaped-spacing-rparent))^1) + + -- quoted text possibly with nested quotes: + local title_s = squote * Cs(((anyescaped-squote) + squoted)^0) * + squote + + local title_d = dquote * Cs(((anyescaped-dquote) + dquoted)^0) * + dquote + + local title_p = lparent + * Cs((inparens + (anyescaped-rparent))^0) + * rparent + + local title = title_d + title_s + title_p + + local optionaltitle = spnl * title * spacechar^0 + + Cc("") + local rawnotes = {} + + local function strip_first_char(s) + return s:sub(2) + end + + -- like indirect_link + local function lookup_note(ref) + return function() + local found = rawnotes[normalize_tag(ref)] + if found then + return writer.note(parse_blocks(found)) + else + return {"[^", ref, "]"} + end + end + end + + local function register_note(ref,rawnote) + rawnotes[normalize_tag(ref)] = rawnote + return "" + end + + local RawNoteRef = #(lbracket * circumflex) * tag / strip_first_char + + local NoteRef = RawNoteRef / lookup_note + + local NoteBlock + + if options.footnotes then + NoteBlock = leader * RawNoteRef * colon * spnl * + indented_blocks(chunk) / register_note + else + NoteBlock = fail + end + -- 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 + + -- parse a reference definition: [foo]: /bar "title" + local define_reference_parser = + leader * tag * colon * spacechar^0 * url * optionaltitle * blankline^1 + + -- 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 + local Inline = V("Inline") + + local Str = normalchar^1 / writer.string + + local Ellipsis = P("...") / writer.ellipsis + + local Smart = Ellipsis + + local Symbol = (specialchar - tightblocksep) / writer.string + + local Code = inticks / writer.code + + local bqstart = more + local headerstart = hash + + (line * (equal^1 + dash^1) * optionalspace * newline) + + if options.blankBeforeBlockquote then + bqstart = fail + end + + if options.blankBeforeHeading then + headerstart = fail + end + + local Endline = newline * -( -- newline, but not before... + blankline -- paragraph break + + tightblocksep -- nested list + + eof -- end of document + + bqstart + + headerstart + ) * spacechar^0 / writer.space + + local Space = spacechar^2 * Endline / writer.linebreak + + spacechar^1 * Endline^-1 * eof / "" + + spacechar^1 * Endline^-1 * optionalspace / writer.space + + -- parse many p between starter and ender + local function between(p, starter, ender) + local ender2 = B(nonspacechar) * ender + return (starter * #nonspacechar * Ct(p * (p - ender2)^0) * ender2) + end + + local Strong = ( between(Inline, doubleasterisks, doubleasterisks) + + between(Inline, doubleunderscores, doubleunderscores) + ) / writer.strong + + local Emph = ( between(Inline, asterisk, asterisk) + + between(Inline, underscore, underscore) + ) / writer.emphasis + + local urlchar = anyescaped - newline - more + + local AutoLinkUrl = less + * C(alphanumeric^1 * P("://") * urlchar^1) + * more + / function(url) + return writer.link(writer.string(url), url) + end + + local AutoLinkEmail = less + * C((alphanumeric + S("-._+"))^1 * P("@") * urlchar^1) + * more + / function(email) + return writer.link(writer.string(email), + "mailto:"..email) + end + + local DirectLink = (tag / parse_inlines_no_link) -- no links inside links + * spnl + * lparent + * (url + Cc("")) -- link can be empty [foo]() + * optionaltitle + * rparent + / writer.link + + local IndirectLink = tag * (C(spnl) * tag)^-1 / indirect_link + + -- parse a link or image (direct or indirect) + local Link = DirectLink + IndirectLink + + local DirectImage = exclamation + * (tag / parse_inlines) + * spnl + * lparent + * (url + Cc("")) -- link can be empty [foo]() + * optionaltitle + * rparent + / writer.image + + local IndirectImage = exclamation * tag * (C(spnl) * tag)^-1 / + indirect_image + + local Image = DirectImage + IndirectImage + + -- avoid parsing long strings of * or _ as emph/strong + local UlOrStarLine = asterisk^4 + underscore^4 / writer.string + + local EscapedChar = S("\\") * C(escapable) / writer.string + local Block = V("Block") + + local Verbatim = Cs( (blanklines + * ((indentedline - blankline))^1)^1 + ) / expandtabs / writer.verbatim + + -- strip off leading > and indents, and run through blocks + local Blockquote = Cs(( + ((leader * more * space^-1)/"" * linechar^0 * newline)^1 + * (-blankline * linechar^1 * newline)^0 + * blankline^0 + )^1) / parse_blocks / writer.blockquote + + local function lineof(c) + return (leader * (P(c) * optionalspace)^3 * newline * blankline^1) + end + + local HorizontalRule = ( lineof(asterisk) + + lineof(dash) + + lineof(underscore) + ) / writer.hrule + + local Reference = define_reference_parser / register_link + + local Paragraph = nonindentspace * Ct(Inline^1) * newline + * ( blankline^1 + + #hash + + #(leader * more * space^-1) + ) + / writer.paragraph + + local Plain = nonindentspace * Ct(Inline^1) / writer.plain + local starter = bullet + enumerator + + -- we use \001 as a separator between a tight list item and a + -- nested list under it. + local NestedList = Cs((optionallyindentedline - starter)^1) + / function(a) return "\001"..a end + + local ListBlockLine = optionallyindentedline + - blankline - (indent^-1 * starter) + + local ListBlock = line * ListBlockLine^0 + + local ListContinuationBlock = blanklines * (indent / "") * ListBlock + + local function TightListItem(starter) + return -HorizontalRule + * (Cs(starter / "" * ListBlock * NestedList^-1) / + parse_blocks) + * -(blanklines * indent) + end + + local function LooseListItem(starter) + return -HorizontalRule + * Cs( starter / "" * ListBlock * Cc("\n") + * (NestedList + ListContinuationBlock^0) + * (blanklines / "\n\n") + ) / parse_blocks + end + + local BulletList = ( Ct(TightListItem(bullet)^1) + * Cc(true) * skipblanklines * -bullet + + Ct(LooseListItem(bullet)^1) + * Cc(false) * skipblanklines ) / + writer.bulletlist + + local function orderedlist(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 + + local OrderedList = Cg(enumerator, "listtype") * + ( Ct(TightListItem(Cb("listtype")) * + TightListItem(enumerator)^0) + * Cc(true) * skipblanklines * -enumerator + + Ct(LooseListItem(Cb("listtype")) * + LooseListItem(enumerator)^0) + * Cc(false) * skipblanklines + ) * Cb("listtype") / orderedlist + + local defstartchar = S("~:") + local defstart = ( defstartchar * #spacing * (tab + space^-3) + + space * defstartchar * #spacing * (tab + space^-2) + + space * space * defstartchar * #spacing * + (tab + space^-1) + + space * space * space * defstartchar * #spacing + ) + + local dlchunk = Cs(line * (indentedline - blankline)^0) + + local function definition_list_item(term, defs, tight) + return { term = parse_inlines(term), definitions = defs } + end + + local DefinitionListItemLoose = C(line) * skipblanklines + * Ct((defstart * + indented_blocks(dlchunk) / + parse_blocks)^1) + * Cc(false) + / definition_list_item + + local DefinitionListItemTight = C(line) + * Ct((defstart * dlchunk / + parse_blocks)^1) + * Cc(true) + / definition_list_item + + local DefinitionList = ( Ct(DefinitionListItemLoose^1) * Cc(false) + + Ct(DefinitionListItemTight^1) + * (skipblanklines * + -DefinitionListItemLoose * Cc(true)) + ) / writer.definitionlist + local Blank = blankline / "" + + NoteBlock + + Reference + + (tightblocksep / "\n") + -- parse Atx heading start and return level + local HeadingStart = #hash * C(hash^-6) * -hash / length + + -- parse setext header ending and return level + local HeadingLevel = equal^1 * Cc(1) + dash^1 * Cc(2) + + local function strip_atx_end(s) + return s:gsub("[#%s]*\n$","") + end + + -- parse atx header + local AtxHeading = Cg(HeadingStart,"level") + * optionalspace + * (C(line) / strip_atx_end / parse_inlines) + * Cb("level") + / writer.heading + + -- parse setext header + local SetextHeading = #(line * S("=-")) + * Ct(line / parse_inlines) + * HeadingLevel + * optionalspace * newline + / writer.heading + syntax = + { "Blocks", + + Blocks = Blank^0 * + Block^-1 * + (Blank^0 / function() + return writer.interblocksep + end * Block)^0 * + Blank^0 * + eof, + + Blank = Blank, + + Block = V("Blockquote") + + V("Verbatim") + + V("HorizontalRule") + + V("BulletList") + + V("OrderedList") + + V("Heading") + + V("DefinitionList") + + V("Paragraph") + + V("Plain"), + + Blockquote = Blockquote, + Verbatim = Verbatim, + HorizontalRule = HorizontalRule, + BulletList = BulletList, + OrderedList = OrderedList, + Heading = AtxHeading + SetextHeading, + DefinitionList = DefinitionList, + DisplayHtml = DisplayHtml, + Paragraph = Paragraph, + Plain = Plain, + + Inline = V("Str") + + V("Space") + + V("Endline") + + V("UlOrStarLine") + + V("Strong") + + V("Emph") + + V("NoteRef") + + V("Link") + + V("Image") + + V("Code") + + V("AutoLinkUrl") + + V("AutoLinkEmail") + + V("EscapedChar") + + V("Smart") + + V("Symbol"), + + Str = Str, + Space = Space, + Endline = Endline, + UlOrStarLine = UlOrStarLine, + Strong = Strong, + Emph = Emph, + NoteRef = NoteRef, + Link = Link, + Image = Image, + Code = Code, + AutoLinkUrl = AutoLinkUrl, + AutoLinkEmail = AutoLinkEmail, + InlineHtml = InlineHtml, + HtmlEntity = HtmlEntity, + EscapedChar = EscapedChar, + Smart = Smart, + Symbol = Symbol, + } + + if not options.definitionLists then + syntax.DefinitionList = fail + end + + if not options.footnotes then + syntax.NoteRef = fail + end + + if not options.smartEllipses then + syntax.Smart = fail + end + + blocks = Ct(syntax) + + local inlines_t = util.table_copy(syntax) + inlines_t[1] = "Inlines" + inlines_t.Inlines = Inline^0 * (spacing^0 * eof / "") + inlines = Ct(inlines_t) + + inlines_no_link_t = util.table_copy(inlines_t) + inlines_no_link_t.Link = fail + inlines_no_link = Ct(inlines_no_link_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, ",") + local name = util.cache(options.cacheDir, input, salt, function(input) + return util.rope_to_string(parse_blocks(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 |