summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/luatex/markdown/markdown.lua
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2021-08-07 20:19:27 +0000
committerKarl Berry <karl@freefriends.org>2021-08-07 20:19:27 +0000
commitd0d20a6ba7cff5159a2f78b414326262f45b0542 (patch)
treeb14872bbfe4e416eed32b7707844a2078189d27f /Master/texmf-dist/tex/luatex/markdown/markdown.lua
parent84dbd3f0f8c0982f6944cc314d7ac0cbefec0769 (diff)
markdown (7aug21)
git-svn-id: svn://tug.org/texlive/trunk@60182 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/luatex/markdown/markdown.lua')
-rw-r--r--Master/texmf-dist/tex/luatex/markdown/markdown.lua223
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