summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/luatex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2021-10-01 22:00:34 +0000
committerKarl Berry <karl@freefriends.org>2021-10-01 22:00:34 +0000
commitc715cd9bf039c14787d154d495e9ef181b07bfce (patch)
treea67caf93acb953c8fd2d4c2c294200b265fa10bd /Master/texmf-dist/tex/luatex
parent070e5192bad37b3ceee5d098ea0e8c7519986734 (diff)
markdown (2oct21)
git-svn-id: svn://tug.org/texlive/trunk@60667 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/luatex')
-rw-r--r--Master/texmf-dist/tex/luatex/markdown/markdown-tinyyaml.lua838
-rw-r--r--Master/texmf-dist/tex/luatex/markdown/markdown.lua202
2 files changed, 1010 insertions, 30 deletions
diff --git a/Master/texmf-dist/tex/luatex/markdown/markdown-tinyyaml.lua b/Master/texmf-dist/tex/luatex/markdown/markdown-tinyyaml.lua
new file mode 100644
index 00000000000..f921ac45716
--- /dev/null
+++ b/Master/texmf-dist/tex/luatex/markdown/markdown-tinyyaml.lua
@@ -0,0 +1,838 @@
+-- MIT License
+--
+-- Copyright (c) 2017 peposso
+--
+-- 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.
+
+-------------------------------------------------------------------------------
+-- tinyyaml - YAML subset parser
+-------------------------------------------------------------------------------
+
+local table = table
+local string = string
+local schar = string.char
+local ssub, gsub = string.sub, string.gsub
+local sfind, smatch = string.find, string.match
+local tinsert, tconcat, tremove = table.insert, table.concat, table.remove
+local setmetatable = setmetatable
+local pairs = pairs
+local type = type
+local tonumber = tonumber
+local math = math
+local getmetatable = getmetatable
+local error = error
+
+local UNESCAPES = {
+ ['0'] = "\x00", z = "\x00", N = "\x85",
+ a = "\x07", b = "\x08", t = "\x09",
+ n = "\x0a", v = "\x0b", f = "\x0c",
+ r = "\x0d", e = "\x1b", ['\\'] = '\\',
+};
+
+-------------------------------------------------------------------------------
+-- utils
+local function select(list, pred)
+ local selected = {}
+ for i = 0, #list do
+ local v = list[i]
+ if v and pred(v, i) then
+ tinsert(selected, v)
+ end
+ end
+ return selected
+end
+
+local function startswith(haystack, needle)
+ return ssub(haystack, 1, #needle) == needle
+end
+
+local function ltrim(str)
+ return smatch(str, "^%s*(.-)$")
+end
+
+local function rtrim(str)
+ return smatch(str, "^(.-)%s*$")
+end
+
+local function trim(str)
+ return smatch(str, "^%s*(.-)%s*$")
+end
+
+-------------------------------------------------------------------------------
+-- Implementation.
+--
+local class = {__meta={}}
+function class.__meta.__call(cls, ...)
+ local self = setmetatable({}, cls)
+ if cls.__init then
+ cls.__init(self, ...)
+ end
+ return self
+end
+
+function class.def(base, typ, cls)
+ base = base or class
+ local mt = {__metatable=base, __index=base}
+ for k, v in pairs(base.__meta) do mt[k] = v end
+ cls = setmetatable(cls or {}, mt)
+ cls.__index = cls
+ cls.__metatable = cls
+ cls.__type = typ
+ cls.__meta = mt
+ return cls
+end
+
+
+local types = {
+ null = class:def('null'),
+ map = class:def('map'),
+ omap = class:def('omap'),
+ pairs = class:def('pairs'),
+ set = class:def('set'),
+ seq = class:def('seq'),
+ timestamp = class:def('timestamp'),
+}
+
+local Null = types.null
+function Null.__tostring() return 'yaml.null' end
+function Null.isnull(v)
+ if v == nil then return true end
+ if type(v) == 'table' and getmetatable(v) == Null then return true end
+ return false
+end
+local null = Null()
+
+function types.timestamp:__init(y, m, d, h, i, s, f, z)
+ self.year = tonumber(y)
+ self.month = tonumber(m)
+ self.day = tonumber(d)
+ self.hour = tonumber(h or 0)
+ self.minute = tonumber(i or 0)
+ self.second = tonumber(s or 0)
+ if type(f) == 'string' and sfind(f, '^%d+$') then
+ self.fraction = tonumber(f) * math.pow(10, 3 - #f)
+ elseif f then
+ self.fraction = f
+ else
+ self.fraction = 0
+ end
+ self.timezone = z
+end
+
+function types.timestamp:__tostring()
+ return string.format(
+ '%04d-%02d-%02dT%02d:%02d:%02d.%03d%s',
+ self.year, self.month, self.day,
+ self.hour, self.minute, self.second, self.fraction,
+ self:gettz())
+end
+
+function types.timestamp:gettz()
+ if not self.timezone then
+ return ''
+ end
+ if self.timezone == 0 then
+ return 'Z'
+ end
+ local sign = self.timezone > 0
+ local z = sign and self.timezone or -self.timezone
+ local zh = math.floor(z)
+ local zi = (z - zh) * 60
+ return string.format(
+ '%s%02d:%02d', sign and '+' or '-', zh, zi)
+end
+
+
+local function countindent(line)
+ local _, j = sfind(line, '^%s+')
+ if not j then
+ return 0, line
+ end
+ return j, ssub(line, j+1)
+end
+
+local function parsestring(line, stopper)
+ stopper = stopper or ''
+ local q = ssub(line, 1, 1)
+ if q == ' ' or q == '\t' then
+ return parsestring(ssub(line, 2))
+ end
+ if q == "'" then
+ local i = sfind(line, "'", 2, true)
+ if not i then
+ return nil, line
+ end
+ return ssub(line, 2, i-1), ssub(line, i+1)
+ end
+ if q == '"' then
+ local i, buf = 2, ''
+ while i < #line do
+ local c = ssub(line, i, i)
+ if c == '\\' then
+ local n = ssub(line, i+1, i+1)
+ if UNESCAPES[n] ~= nil then
+ buf = buf..UNESCAPES[n]
+ elseif n == 'x' then
+ local h = ssub(i+2,i+3)
+ if sfind(h, '^[0-9a-fA-F]$') then
+ buf = buf..schar(tonumber(h, 16))
+ i = i + 2
+ else
+ buf = buf..'x'
+ end
+ else
+ buf = buf..n
+ end
+ i = i + 1
+ elseif c == q then
+ break
+ else
+ buf = buf..c
+ end
+ i = i + 1
+ end
+ return buf, ssub(line, i+1)
+ end
+ if q == '{' or q == '[' then -- flow style
+ return nil, line
+ end
+ if q == '|' or q == '>' then -- block
+ return nil, line
+ end
+ if q == '-' or q == ':' then
+ if ssub(line, 2, 2) == ' ' or ssub(line, 2, 2) == '\n' or #line == 1 then
+ return nil, line
+ end
+ end
+ local buf = ''
+ while #line > 0 do
+ local c = ssub(line, 1, 1)
+ if sfind(stopper, c, 1, true) then
+ break
+ elseif c == ':' and (ssub(line, 2, 2) == ' ' or ssub(line, 2, 2) == '\n' or #line == 1) then
+ break
+ elseif c == '#' and (ssub(buf, #buf, #buf) == ' ') then
+ break
+ else
+ buf = buf..c
+ end
+ line = ssub(line, 2)
+ end
+ return rtrim(buf), line
+end
+
+local function isemptyline(line)
+ return line == '' or sfind(line, '^%s*$') or sfind(line, '^%s*#')
+end
+
+local function equalsline(line, needle)
+ return startswith(line, needle) and isemptyline(ssub(line, #needle+1))
+end
+
+local function compactifyemptylines(lines)
+ -- Appends empty lines as "\n" to the end of the nearest preceding non-empty line
+ local compactified = {}
+ local lastline = {}
+ for i = 1, #lines do
+ local line = lines[i]
+ if isemptyline(line) then
+ if #compactified > 0 and i < #lines then
+ tinsert(lastline, "\n")
+ end
+ else
+ if #lastline > 0 then
+ tinsert(compactified, tconcat(lastline, ""))
+ end
+ lastline = {line}
+ end
+ end
+ if #lastline > 0 then
+ tinsert(compactified, tconcat(lastline, ""))
+ end
+ return compactified
+end
+
+local function checkdupekey(map, key)
+ if map[key] ~= nil then
+ -- print("found a duplicate key '"..key.."' in line: "..line)
+ local suffix = 1
+ while map[key..'_'..suffix] do
+ suffix = suffix + 1
+ end
+ key = key ..'_'..suffix
+ end
+ return key
+end
+
+local function parseflowstyle(line, lines)
+ local stack = {}
+ while true do
+ if #line == 0 then
+ if #lines == 0 then
+ break
+ else
+ line = tremove(lines, 1)
+ end
+ end
+ local c = ssub(line, 1, 1)
+ if c == '#' then
+ line = ''
+ elseif c == ' ' or c == '\t' or c == '\r' or c == '\n' then
+ line = ssub(line, 2)
+ elseif c == '{' or c == '[' then
+ tinsert(stack, {v={},t=c})
+ line = ssub(line, 2)
+ elseif c == ':' then
+ local s = tremove(stack)
+ tinsert(stack, {v=s.v, t=':'})
+ line = ssub(line, 2)
+ elseif c == ',' then
+ local value = tremove(stack)
+ if value.t == ':' or value.t == '{' or value.t == '[' then error() end
+ if stack[#stack].t == ':' then
+ -- map
+ local key = tremove(stack)
+ key.v = checkdupekey(stack[#stack].v, key.v)
+ stack[#stack].v[key.v] = value.v
+ elseif stack[#stack].t == '{' then
+ -- set
+ stack[#stack].v[value.v] = true
+ elseif stack[#stack].t == '[' then
+ -- seq
+ tinsert(stack[#stack].v, value.v)
+ end
+ line = ssub(line, 2)
+ elseif c == '}' then
+ if stack[#stack].t == '{' then
+ if #stack == 1 then break end
+ stack[#stack].t = '}'
+ line = ssub(line, 2)
+ else
+ line = ','..line
+ end
+ elseif c == ']' then
+ if stack[#stack].t == '[' then
+ if #stack == 1 then break end
+ stack[#stack].t = ']'
+ line = ssub(line, 2)
+ else
+ line = ','..line
+ end
+ else
+ local s, rest = parsestring(line, ',{}[]')
+ if not s then
+ error('invalid flowstyle line: '..line)
+ end
+ tinsert(stack, {v=s, t='s'})
+ line = rest
+ end
+ end
+ return stack[1].v, line
+end
+
+local function parseblockstylestring(line, lines, indent)
+ if #lines == 0 then
+ error("failed to find multi-line scalar content")
+ end
+ local s = {}
+ local firstindent = -1
+ local endline = -1
+ for i = 1, #lines do
+ local ln = lines[i]
+ local idt = countindent(ln)
+ if idt <= indent then
+ break
+ end
+ if ln == '' then
+ tinsert(s, '')
+ else
+ if firstindent == -1 then
+ firstindent = idt
+ elseif idt < firstindent then
+ break
+ end
+ tinsert(s, ssub(ln, firstindent + 1))
+ end
+ endline = i
+ end
+
+ local striptrailing = true
+ local sep = '\n'
+ local newlineatend = true
+ if line == '|' then
+ striptrailing = true
+ sep = '\n'
+ newlineatend = true
+ elseif line == '|+' then
+ striptrailing = false
+ sep = '\n'
+ newlineatend = true
+ elseif line == '|-' then
+ striptrailing = true
+ sep = '\n'
+ newlineatend = false
+ elseif line == '>' then
+ striptrailing = true
+ sep = ' '
+ newlineatend = true
+ elseif line == '>+' then
+ striptrailing = false
+ sep = ' '
+ newlineatend = true
+ elseif line == '>-' then
+ striptrailing = true
+ sep = ' '
+ newlineatend = false
+ else
+ error('invalid blockstyle string:'..line)
+ end
+ local _, eonl = s[#s]:gsub('\n', '\n')
+ s[#s] = rtrim(s[#s])
+ if striptrailing then
+ eonl = 0
+ end
+ if newlineatend then
+ eonl = eonl + 1
+ end
+ for i = endline, 1, -1 do
+ tremove(lines, i)
+ end
+ return tconcat(s, sep)..string.rep('\n', eonl)
+end
+
+local function parsetimestamp(line)
+ local _, p1, y, m, d = sfind(line, '^(%d%d%d%d)%-(%d%d)%-(%d%d)')
+ if not p1 then
+ return nil, line
+ end
+ if p1 == #line then
+ return types.timestamp(y, m, d), ''
+ end
+ local _, p2, h, i, s = sfind(line, '^[Tt ](%d+):(%d+):(%d+)', p1+1)
+ if not p2 then
+ return types.timestamp(y, m, d), ssub(line, p1+1)
+ end
+ if p2 == #line then
+ return types.timestamp(y, m, d, h, i, s), ''
+ end
+ local _, p3, f = sfind(line, '^%.(%d+)', p2+1)
+ if not p3 then
+ p3 = p2
+ f = 0
+ end
+ local zc = ssub(line, p3+1, p3+1)
+ local _, p4, zs, z = sfind(line, '^ ?([%+%-])(%d+)', p3+1)
+ if p4 then
+ z = tonumber(z)
+ local _, p5, zi = sfind(line, '^:(%d+)', p4+1)
+ if p5 then
+ z = z + tonumber(zi) / 60
+ end
+ z = zs == '-' and -tonumber(z) or tonumber(z)
+ elseif zc == 'Z' then
+ p4 = p3 + 1
+ z = 0
+ else
+ p4 = p3
+ z = false
+ end
+ return types.timestamp(y, m, d, h, i, s, f, z), ssub(line, p4+1)
+end
+
+local function parsescalar(line, lines, indent)
+ line = trim(line)
+ line = gsub(line, '^%s*#.*$', '') -- comment only -> ''
+ line = gsub(line, '^%s*', '') -- trim head spaces
+
+ if line == '' or line == '~' then
+ return null
+ end
+
+ local ts, _ = parsetimestamp(line)
+ if ts then
+ return ts
+ end
+
+ local s, _ = parsestring(line)
+ -- startswith quote ... string
+ -- not startswith quote ... maybe string
+ if s and (startswith(line, '"') or startswith(line, "'")) then
+ return s
+ end
+
+ if startswith('!', line) then -- unexpected tagchar
+ error('unsupported line: '..line)
+ end
+
+ if equalsline(line, '{}') then
+ return {}
+ end
+ if equalsline(line, '[]') then
+ return {}
+ end
+
+ if startswith(line, '{') or startswith(line, '[') then
+ return parseflowstyle(line, lines)
+ end
+
+ if startswith(line, '|') or startswith(line, '>') then
+ return parseblockstylestring(line, lines, indent)
+ end
+
+ -- Regular unquoted string
+ line = gsub(line, '%s*#.*$', '') -- trim tail comment
+ local v = line
+ if v == 'null' or v == 'Null' or v == 'NULL'then
+ return null
+ elseif v == 'true' or v == 'True' or v == 'TRUE' then
+ return true
+ elseif v == 'false' or v == 'False' or v == 'FALSE' then
+ return false
+ elseif v == '.inf' or v == '.Inf' or v == '.INF' then
+ return math.huge
+ elseif v == '+.inf' or v == '+.Inf' or v == '+.INF' then
+ return math.huge
+ elseif v == '-.inf' or v == '-.Inf' or v == '-.INF' then
+ return -math.huge
+ elseif v == '.nan' or v == '.NaN' or v == '.NAN' then
+ return 0 / 0
+ elseif sfind(v, '^[%+%-]?[0-9]+$') or sfind(v, '^[%+%-]?[0-9]+%.$')then
+ return tonumber(v) -- : int
+ elseif sfind(v, '^[%+%-]?[0-9]+%.[0-9]+$') then
+ return tonumber(v)
+ end
+ return s or v
+end
+
+local parsemap; -- : func
+
+local function parseseq(line, lines, indent)
+ local seq = setmetatable({}, types.seq)
+ if line ~= '' then
+ error()
+ end
+ while #lines > 0 do
+ -- Check for a new document
+ line = lines[1]
+ if startswith(line, '---') then
+ while #lines > 0 and not startswith(lines, '---') do
+ tremove(lines, 1)
+ end
+ return seq
+ end
+
+ -- Check the indent level
+ local level = countindent(line)
+ if level < indent then
+ return seq
+ elseif level > indent then
+ error("found bad indenting in line: ".. line)
+ end
+
+ local i, j = sfind(line, '%-%s+')
+ if not i then
+ i, j = sfind(line, '%-$')
+ if not i then
+ return seq
+ end
+ end
+ local rest = ssub(line, j+1)
+
+ if sfind(rest, '^[^\'\"%s]*:%s*$') or sfind(rest, '^[^\'\"%s]*:%s+.') then
+ -- Inline nested hash
+ -- There are two patterns need to match as inline nested hash
+ -- first one should have no other characters except whitespace after `:`
+ -- and the second one should have characters besides whitespace after `:`
+ --
+ -- value:
+ -- - foo:
+ -- bar: 1
+ --
+ -- and
+ --
+ -- value:
+ -- - foo: bar
+ --
+ -- And there is one pattern should not be matched, where there is no space after `:`
+ -- in below, `foo:bar` should be parsed into a single string
+ --
+ -- value:
+ -- - foo:bar
+ local indent2 = j
+ lines[1] = string.rep(' ', indent2)..rest
+ tinsert(seq, parsemap('', lines, indent2))
+ elseif sfind(rest, '^%-%s+') then
+ -- Inline nested seq
+ local indent2 = j
+ lines[1] = string.rep(' ', indent2)..rest
+ tinsert(seq, parseseq('', lines, indent2))
+ elseif isemptyline(rest) then
+ tremove(lines, 1)
+ if #lines == 0 then
+ tinsert(seq, null)
+ return seq
+ end
+ if sfind(lines[1], '^%s*%-') then
+ local nextline = lines[1]
+ local indent2 = countindent(nextline)
+ if indent2 == indent then
+ -- Null seqay entry
+ tinsert(seq, null)
+ else
+ tinsert(seq, parseseq('', lines, indent2))
+ end
+ else
+ -- - # comment
+ -- key: value
+ local nextline = lines[1]
+ local indent2 = countindent(nextline)
+ tinsert(seq, parsemap('', lines, indent2))
+ end
+ elseif rest then
+ -- Array entry with a value
+ tremove(lines, 1)
+ tinsert(seq, parsescalar(rest, lines))
+ end
+ end
+ return seq
+end
+
+local function parseset(line, lines, indent)
+ if not isemptyline(line) then
+ error('not seq line: '..line)
+ end
+ local set = setmetatable({}, types.set)
+ while #lines > 0 do
+ -- Check for a new document
+ line = lines[1]
+ if startswith(line, '---') then
+ while #lines > 0 and not startswith(lines, '---') do
+ tremove(lines, 1)
+ end
+ return set
+ end
+
+ -- Check the indent level
+ local level = countindent(line)
+ if level < indent then
+ return set
+ elseif level > indent then
+ error("found bad indenting in line: ".. line)
+ end
+
+ local i, j = sfind(line, '%?%s+')
+ if not i then
+ i, j = sfind(line, '%?$')
+ if not i then
+ return set
+ end
+ end
+ local rest = ssub(line, j+1)
+
+ if sfind(rest, '^[^\'\"%s]*:') then
+ -- Inline nested hash
+ local indent2 = j
+ lines[1] = string.rep(' ', indent2)..rest
+ set[parsemap('', lines, indent2)] = true
+ elseif sfind(rest, '^%s+$') then
+ tremove(lines, 1)
+ if #lines == 0 then
+ tinsert(set, null)
+ return set
+ end
+ if sfind(lines[1], '^%s*%?') then
+ local indent2 = countindent(lines[1])
+ if indent2 == indent then
+ -- Null array entry
+ set[null] = true
+ else
+ set[parseseq('', lines, indent2)] = true
+ end
+ end
+
+ elseif rest then
+ tremove(lines, 1)
+ set[parsescalar(rest, lines)] = true
+ else
+ error("failed to classify line: "..line)
+ end
+ end
+ return set
+end
+
+function parsemap(line, lines, indent)
+ if not isemptyline(line) then
+ error('not map line: '..line)
+ end
+ local map = setmetatable({}, types.map)
+ while #lines > 0 do
+ -- Check for a new document
+ line = lines[1]
+ if startswith(line, '---') then
+ while #lines > 0 and not startswith(lines, '---') do
+ tremove(lines, 1)
+ end
+ return map
+ end
+
+ -- Check the indent level
+ local level, _ = countindent(line)
+ if level < indent then
+ return map
+ elseif level > indent then
+ error("found bad indenting in line: ".. line)
+ end
+
+ -- Find the key
+ local key
+ local s, rest = parsestring(line)
+
+ -- Quoted keys
+ if s and startswith(rest, ':') then
+ local sc = parsescalar(s, {}, 0)
+ if sc and type(sc) ~= 'string' then
+ key = sc
+ else
+ key = s
+ end
+ line = ssub(rest, 2)
+ else
+ error("failed to classify line: "..line)
+ end
+
+ key = checkdupekey(map, key)
+ line = ltrim(line)
+
+ if ssub(line, 1, 1) == '!' then
+ -- ignore type
+ local rh = ltrim(ssub(line, 3))
+ local typename = smatch(rh, '^!?[^%s]+')
+ line = ltrim(ssub(rh, #typename+1))
+ end
+
+ if not isemptyline(line) then
+ tremove(lines, 1)
+ line = ltrim(line)
+ map[key] = parsescalar(line, lines, indent)
+ else
+ -- An indent
+ tremove(lines, 1)
+ if #lines == 0 then
+ map[key] = null
+ return map;
+ end
+ if sfind(lines[1], '^%s*%-') then
+ local indent2 = countindent(lines[1])
+ map[key] = parseseq('', lines, indent2)
+ elseif sfind(lines[1], '^%s*%?') then
+ local indent2 = countindent(lines[1])
+ map[key] = parseset('', lines, indent2)
+ else
+ local indent2 = countindent(lines[1])
+ if indent >= indent2 then
+ -- Null hash entry
+ map[key] = null
+ else
+ map[key] = parsemap('', lines, indent2)
+ end
+ end
+ end
+ end
+ return map
+end
+
+
+-- : (list<str>)->dict
+local function parsedocuments(lines)
+ lines = compactifyemptylines(lines)
+
+ if sfind(lines[1], '^%%YAML') then tremove(lines, 1) end
+
+ local root = {}
+ local in_document = false
+ while #lines > 0 do
+ local line = lines[1]
+ -- Do we have a document header?
+ local docright;
+ if sfind(line, '^%-%-%-') then
+ -- Handle scalar documents
+ docright = ssub(line, 4)
+ tremove(lines, 1)
+ in_document = true
+ end
+ if docright then
+ if (not sfind(docright, '^%s+$') and
+ not sfind(docright, '^%s+#')) then
+ tinsert(root, parsescalar(docright, lines))
+ end
+ elseif #lines == 0 or startswith(line, '---') then
+ -- A naked document
+ tinsert(root, null)
+ while #lines > 0 and not sfind(lines[1], '---') do
+ tremove(lines, 1)
+ end
+ in_document = false
+ -- XXX The final '-+$' is to look for -- which ends up being an
+ -- error later.
+ elseif not in_document and #root > 0 then
+ -- only the first document can be explicit
+ error('parse error: '..line)
+ elseif sfind(line, '^%s*%-') then
+ -- An array at the root
+ tinsert(root, parseseq('', lines, 0))
+ elseif sfind(line, '^%s*[^%s]') then
+ -- A hash at the root
+ local level = countindent(line)
+ tinsert(root, parsemap('', lines, level))
+ else
+ -- Shouldn't get here. @lines have whitespace-only lines
+ -- stripped, and previous match is a line with any
+ -- non-whitespace. So this clause should only be reachable via
+ -- a perlbug where \s is not symmetric with \S
+
+ -- uncoverable statement
+ error('parse error: '..line)
+ end
+ end
+ if #root > 1 and Null.isnull(root[1]) then
+ tremove(root, 1)
+ return root
+ end
+ return root
+end
+
+--- Parse yaml string into table.
+local function parse(source)
+ local lines = {}
+ for line in string.gmatch(source .. '\n', '(.-)\r?\n') do
+ tinsert(lines, line)
+ end
+
+ local docs = parsedocuments(lines)
+ if #docs == 1 then
+ return docs[1]
+ end
+
+ return docs
+end
+
+return {
+ version = 0.1,
+ parse = parse,
+}
diff --git a/Master/texmf-dist/tex/luatex/markdown/markdown.lua b/Master/texmf-dist/tex/luatex/markdown/markdown.lua
index 85900563718..eb68cc175d5 100644
--- a/Master/texmf-dist/tex/luatex/markdown/markdown.lua
+++ b/Master/texmf-dist/tex/luatex/markdown/markdown.lua
@@ -58,12 +58,12 @@
-- those in the standard .ins files.
--
local metadata = {
- version = "2.10.1",
+ version = "2.11.0-0-g4505824",
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-2021 Vít Novotný"},
- license = "LPPL 1.3"
+ license = "LPPL 1.3c"
}
if not modules then modules = { } end
@@ -97,6 +97,7 @@ defaultOptions.headerAttributes = false
defaultOptions.html = false
defaultOptions.hybrid = false
defaultOptions.inlineFootnotes = false
+defaultOptions.jekyllData = false
defaultOptions.pipeTables = false
defaultOptions.preserveTabs = false
defaultOptions.shiftHeadings = 0
@@ -105,6 +106,7 @@ defaultOptions.smartEllipses = false
defaultOptions.startNumber = true
defaultOptions.stripIndent = false
defaultOptions.tableCaptions = false
+defaultOptions.taskLists = false
defaultOptions.texComments = false
defaultOptions.tightLists = true
defaultOptions.underscores = true
@@ -2346,19 +2348,6 @@ function M.writer.new(options)
if not self.is_writing then return "" end
return "\\markdownRendererHorizontalRule{}"
end
- local escaped_chars = {
- ["{"] = "\\markdownRendererLeftBrace{}",
- ["}"] = "\\markdownRendererRightBrace{}",
- ["$"] = "\\markdownRendererDollarSign{}",
- ["%"] = "\\markdownRendererPercentSign{}",
- ["&"] = "\\markdownRendererAmpersand{}",
- ["_"] = "\\markdownRendererUnderscore{}",
- ["#"] = "\\markdownRendererHash{}",
- ["^"] = "\\markdownRendererCircumflex{}",
- ["\\"] = "\\markdownRendererBackslash{}",
- ["~"] = "\\markdownRendererTilde{}",
- ["|"] = "\\markdownRendererPipe{}",
- }
local escaped_uri_chars = {
["{"] = "\\markdownRendererLeftBrace{}",
["}"] = "\\markdownRendererRightBrace{}",
@@ -2369,20 +2358,37 @@ function M.writer.new(options)
["{"] = "\\markdownRendererLeftBrace{}",
["}"] = "\\markdownRendererRightBrace{}",
["%"] = "\\markdownRendererPercentSign{}",
- ["#"] = "\\markdownRendererHash{}",
["\\"] = "\\markdownRendererBackslash{}",
+ ["#"] = "\\markdownRendererHash{}",
}
local escaped_minimal_strings = {
["^^"] = "\\markdownRendererCircumflex\\markdownRendererCircumflex ",
+ ["☒"] = "\\markdownRendererTickedBox{}",
+ ["⌛"] = "\\markdownRendererHalfTickedBox{}",
+ ["☐"] = "\\markdownRendererUntickedBox{}",
+ }
+ local escaped_chars = {
+ ["{"] = "\\markdownRendererLeftBrace{}",
+ ["}"] = "\\markdownRendererRightBrace{}",
+ ["%"] = "\\markdownRendererPercentSign{}",
+ ["\\"] = "\\markdownRendererBackslash{}",
+ ["#"] = "\\markdownRendererHash{}",
+ ["$"] = "\\markdownRendererDollarSign{}",
+ ["&"] = "\\markdownRendererAmpersand{}",
+ ["_"] = "\\markdownRendererUnderscore{}",
+ ["^"] = "\\markdownRendererCircumflex{}",
+ ["~"] = "\\markdownRendererTilde{}",
+ ["|"] = "\\markdownRendererPipe{}",
}
- local escape = util.escaper(escaped_chars)
+ local escape = util.escaper(escaped_chars, escaped_minimal_strings)
local escape_citation = util.escaper(escaped_citation_chars,
escaped_minimal_strings)
local escape_uri = util.escaper(escaped_uri_chars, escaped_minimal_strings)
+ local escape_minimal = util.escaper({}, escaped_minimal_strings)
if options.hybrid then
- self.string = function(s) return s end
- self.citation = function(c) return c end
- self.uri = function(u) return u end
+ self.string = escape_minimal
+ self.citation = escape_minimal
+ self.uri = escape_minimal
else
self.string = escape
self.citation = escape_citation
@@ -2556,6 +2562,15 @@ function M.writer.new(options)
function self.emphasis(s)
return {"\\markdownRendererEmphasis{",s,"}"}
end
+ function self.tickbox(f)
+ if f == 1.0 then
+ return "☒ "
+ elseif f == 0.0 then
+ return "☐ "
+ else
+ return "⌛ "
+ end
+ end
function self.strong(s)
return {"\\markdownRendererStrongEmphasis{",s,"}"}
end
@@ -2576,6 +2591,88 @@ function M.writer.new(options)
local name = util.cache(options.cacheDir, s, nil, nil, ".verbatim")
return {"\\markdownRendererInputFencedCode{",name,"}{",i,"}"}
end
+ function self.jekyllData(d, t, p)
+ if not self.is_writing then return "" end
+
+ local buf = {}
+
+ local keys = {}
+ for k, _ in pairs(d) do
+ table.insert(keys, k)
+ end
+ table.sort(keys)
+
+ if not p then
+ table.insert(buf, "\\markdownRendererJekyllDataBegin")
+ end
+
+ if #d > 0 then
+ table.insert(buf, "\\markdownRendererJekyllDataSequenceBegin{")
+ table.insert(buf, self.uri(p or "null"))
+ table.insert(buf, "}{")
+ table.insert(buf, #keys)
+ table.insert(buf, "}")
+ else
+ table.insert(buf, "\\markdownRendererJekyllDataMappingBegin{")
+ table.insert(buf, self.uri(p or "null"))
+ table.insert(buf, "}{")
+ table.insert(buf, #keys)
+ table.insert(buf, "}")
+ end
+
+ for _, k in ipairs(keys) do
+ local v = d[k]
+ local typ = type(v)
+ k = tostring(k or "null")
+ if typ == "table" and next(v) ~= nil then
+ table.insert(
+ buf,
+ self.jekyllData(v, t, k)
+ )
+ else
+ k = self.uri(k)
+ v = tostring(v)
+ if typ == "boolean" then
+ table.insert(buf, "\\markdownRendererJekyllDataBoolean{")
+ table.insert(buf, k)
+ table.insert(buf, "}{")
+ table.insert(buf, v)
+ table.insert(buf, "}")
+ elseif typ == "number" then
+ table.insert(buf, "\\markdownRendererJekyllDataNumber{")
+ table.insert(buf, k)
+ table.insert(buf, "}{")
+ table.insert(buf, v)
+ table.insert(buf, "}")
+ elseif typ == "string" then
+ table.insert(buf, "\\markdownRendererJekyllDataString{")
+ table.insert(buf, k)
+ table.insert(buf, "}{")
+ table.insert(buf, t(v))
+ table.insert(buf, "}")
+ elseif typ == "table" then
+ table.insert(buf, "\\markdownRendererJekyllDataEmpty{")
+ table.insert(buf, k)
+ table.insert(buf, "}")
+ else
+ error(format("Unexpected type %s for value of " ..
+ "YAML key %s", typ, k))
+ end
+ end
+ end
+
+ if #d > 0 then
+ table.insert(buf, "\\markdownRendererJekyllDataSequenceEnd")
+ else
+ table.insert(buf, "\\markdownRendererJekyllDataMappingEnd")
+ end
+
+ if not p then
+ table.insert(buf, "\\markdownRendererJekyllDataEnd")
+ end
+
+ return buf
+ end
self.active_headings = {}
function self.heading(s,level,attributes)
local active_headings = self.active_headings
@@ -2660,16 +2757,16 @@ function M.writer.new(options)
}
end
function self.set_state(s)
- previous_state = self.get_state()
- for key, value in pairs(state) do
+ local previous_state = self.get_state()
+ for key, value in pairs(s) do
self[key] = value
end
return previous_state
end
function self.defer_call(f)
- state = self.get_state()
+ local previous_state = self.get_state()
return function(...)
- state = self.set_state(state)
+ local state = self.set_state(previous_state)
local return_value = f(...)
self.set_state(state)
return return_value
@@ -2764,7 +2861,6 @@ parsers.spnl = parsers.optionalspace
* (parsers.newline * parsers.optionalspace)^-1
parsers.line = parsers.linechar^0 * parsers.newline
parsers.nonemptyline = parsers.line - parsers.blankline
-
parsers.commented_line_letter = parsers.linechar
+ parsers.newline
- parsers.backslash
@@ -2803,7 +2899,9 @@ parsers.commented_line = Cg(Cc(""), "backslashes")
+ parsers.percent -- comment
* parsers.line
* parsers.optionalspace) -- leading tabs and spaces
- + C(parsers.newline))
+ + #(parsers.newline)
+ * Cb("backslashes")
+ * C(parsers.newline))
parsers.chunk = parsers.line * (parsers.optionallyindentedline
- parsers.blankline)^0
@@ -2843,6 +2941,16 @@ parsers.bullet = ( parsers.bulletchar * #parsers.spacing
+ parsers.space * parsers.space * parsers.space
* parsers.bulletchar * #parsers.spacing
)
+
+local function tickbox(interior)
+ return parsers.optionalspace * parsers.lbracket
+ * interior * parsers.rbracket * parsers.spacechar^1
+end
+
+parsers.ticked_box = tickbox(S("xX")) * Cc(1.0)
+parsers.halfticked_box = tickbox(S("./")) * Cc(0.5)
+parsers.unticked_box = tickbox(parsers.spacechar^1) * Cc(0.0)
+
parsers.openticks = Cg(parsers.backtick^1, "ticks")
local function captures_equal_length(s,i,a,b)
@@ -3309,6 +3417,11 @@ parsers.BacktickFencedCode
* Cs(parsers.fencedline(parsers.backtick)^0)
* parsers.fencetail(parsers.backtick)
+parsers.JekyllFencedCode
+ = parsers.fencehead(parsers.dash)
+ * Cs(parsers.fencedline(parsers.dash)^0)
+ * parsers.fencetail(parsers.dash)
+
parsers.lineof = function(c)
return (parsers.leader * (P(c) * parsers.optionalspace)^3
* (parsers.newline * parsers.blankline^1
@@ -3797,6 +3910,19 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline
expandtabs(code))
end
+ larsers.JekyllData = P("---")
+ * parsers.blankline / 0
+ * #(-parsers.blankline) -- if followed by blank, it's an hrule
+ * C((parsers.line - P("---") - P("..."))^0)
+ * (P("---") + P("..."))
+ / function(text)
+ local tinyyaml = require("markdown-tinyyaml")
+ data = tinyyaml.parse(text)
+ return writer.jekyllData(data, function(s)
+ return parse_blocks(s)
+ end, nil)
+ end
+
larsers.Blockquote = Cs(larsers.blockquote_body^1)
/ parse_blocks_toplevel / writer.blockquote
@@ -3830,6 +3956,15 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline
/ writer.plain
larsers.starter = parsers.bullet + larsers.enumerator
+ if options.taskLists then
+ larsers.tickbox = ( parsers.ticked_box
+ + parsers.halfticked_box
+ + parsers.unticked_box
+ ) / writer.tickbox
+ else
+ larsers.tickbox = parsers.fail
+ end
+
-- we use \001 as a separator between a tight list item and a
-- nested list under it.
larsers.NestedList = Cs((parsers.optionallyindentedline
@@ -3847,14 +3982,14 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline
larsers.TightListItem = function(starter)
return -larsers.HorizontalRule
- * (Cs(starter / "" * larsers.ListBlock * larsers.NestedList^-1)
+ * (Cs(starter / "" * larsers.tickbox^-1 * 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")
+ * Cs( starter / "" * larsers.tickbox^-1 * larsers.ListBlock * Cc("\n")
* (larsers.NestedList + larsers.ListContinuationBlock^0)
* (parsers.blanklines / "\n\n")
) / parse_blocks
@@ -3981,7 +4116,10 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline
Blank = larsers.Blank,
+ JekyllData = larsers.JekyllData,
+
Block = V("ContentBlock")
+ + V("JekyllData")
+ V("Blockquote")
+ V("PipeTable")
+ V("Verbatim")
@@ -4105,8 +4243,8 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline
syntax.InlineNote = parsers.fail
end
- if not options.smartEllipses then
- syntax.Smart = parsers.fail
+ if not options.jekyllData then
+ syntax.JekyllData = parsers.fail
end
if options.preserveTabs then
@@ -4117,6 +4255,10 @@ larsers.PipeTable = Ct(larsers.table_row * parsers.newline
syntax.PipeTable = parsers.fail
end
+ if not options.smartEllipses then
+ syntax.Smart = parsers.fail
+ end
+
local blocks_toplevel_t = util.table_copy(syntax)
blocks_toplevel_t.Paragraph = larsers.ToplevelParagraph
larsers.blocks_toplevel = Ct(blocks_toplevel_t)