Diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Render more of the markdown seen in readmes
A line of equals signs or dashes under a paragraph now makes a heading
of it, as on GitHub, instead of running into its text, and any other
rule ends the paragraph. Four spaces or a tab start a code block, and an
escaped pipe stays in its table cell. A link opening with two slashes
or backslashes in any mix leads to another site, so it renders as text
like the plain scheme relative one already did.
Diffstat (limited to '')
| -rw-r--r-- | custom/extensions/about-render.lua | 124 |
1 file changed, 105 insertions, 19 deletions
diff --git a/custom/extensions/about-render.lua b/custom/extensions/about-render.lua index d7753a3..040768c 100644 --- a/custom/extensions/about-render.lua +++ b/custom/extensions/about-render.lua @@ -81,11 +81,36 @@ local function split_lines(text) end end +-- A pipe escaped with a backslash belongs to its cell, even inside a code +-- span, so as on GitHub the backslash goes and the pipe stays. An even run of +-- backslashes escapes only itself, so the pipe after it still divides. local function split_cells(row) - row = trim(row):gsub("^|", ""):gsub("|$", "") - local cells = {} - for cell in (row .. "|"):gmatch("(.-)|") do - cells[#cells + 1] = trim(cell) + row = trim(row) + local cells, parts, start = {}, {}, 1 + if row:sub(1, 1) == "|" then + start = 2 + end + while true do + local bar = row:find("|", start, true) + if not bar then + break + end + local slashes = 0 + while row:sub(bar - slashes - 1, bar - slashes - 1) == "\\" do + slashes = slashes + 1 + end + if slashes % 2 == 1 then + parts[#parts + 1] = row:sub(start, bar - 2) .. "|" + else + parts[#parts + 1] = row:sub(start, bar - 1) + cells[#cells + 1] = trim(table.concat(parts)) + parts = {} + end + start = bar + 1 + end + if start <= #row or #parts > 0 then + parts[#parts + 1] = row:sub(start) + cells[#cells + 1] = trim(table.concat(parts)) end return cells end @@ -96,10 +121,11 @@ end -- so it is safe on Lua 5.1, where an embedded zero byte ends a pattern. local function safe_url(url) url = url:gsub("[%c%s]", "") - -- A leading "//" or "/\" is scheme-relative, which a browser - -- resolves to another origin, so it is not the local target the - -- parser meant to allow. - if url:sub(1, 2) == "//" or url:sub(1, 2) == "/\\" then + -- A leading "//" is scheme-relative, which a browser resolves to + -- another origin, and it reads a backslash there as a slash, so no + -- pair of the two is the local target the parser meant to allow. + local head = url:sub(1, 2):gsub("\\", "/") + if head == "//" then return nil end local scheme = url:match("^(%a[%w%+%.%-]*):") @@ -316,11 +342,10 @@ end -- Markdown is a deliberate subset rather than CommonMark, leaving out --- reference links, raw HTML passthrough, nested lists and setext headings, --- and inline markup does not span the end of a line. Man rendering covers the --- common macros and drops the rest. Both parse into a node tree before --- anything is emitted, so a failed parse falls back to plain text without --- leaving half a page behind. +-- reference links, raw HTML passthrough and nested lists, and inline markup +-- does not span the end of a line. Man rendering covers the common macros and +-- drops the rest. Both parse into a node tree before anything is emitted, so a +-- failed parse falls back to plain text without leaving half a page behind. local render_markdown, render_man @@ -428,6 +453,11 @@ if has_lpeg then local lang_char = R("az", "AZ", "09") + S("_.+#-") local heading_line = C(P("#") * P("#") ^ -5) * space ^ 1 * C(P(1) ^ 0) + -- A run of equals signs or dashes under a paragraph turns it into a + -- heading, as on GitHub, rather than reading as more of its text. + local setext_line = space ^ 0 * (C(P("=") ^ 1) + C(P("-") ^ 1)) + * space ^ 0 * eol + local code_indent = P(" ") + P("\t") local function thematic(mark) return space ^ 0 * P(mark) * (space ^ 0 * P(mark)) ^ 2 * space ^ 0 * eol end @@ -454,6 +484,28 @@ if has_lpeg then local function ordered(mark) return mark:match("%d") ~= nil end + local function follows_list(blocks) + local last = blocks[#blocks] + return last ~= nil and last.kind == "list" + end + + -- An indented block runs on through blank lines, and up to four spaces + -- or one tab come off each of its lines. + local function continues_code(line) + return code_indent:match(line) or not line:find("%S") + end + + local function unindent(line) + return line:match("^\t(.*)$") or line:match("^ ? ? ? ?(.*)$") + end + + local function setext_level(line) + local run = setext_line:match(line) + if run then + return run:sub(1, 1) == "=" and 1 or 2 + end + end + local function table_starts(lines, i) return i < #lines and lines[i]:find("|", 1, true) ~= nil and table_delimiter:match(lines[i + 1]) ~= nil @@ -589,17 +641,51 @@ if has_lpeg then ordered = is_ordered, items = items, } + -- Four spaces or a tab start a code block, except straight + -- after a list, where an indented paragraph more likely + -- carries an item on. + elseif code_indent:match(line) and not follows_list(blocks) then + local code = {} + while i <= n and continues_code(lines[i]) do + code[#code + 1] = unindent(lines[i]) + i = i + 1 + end + while code[#code]:match("^%s*$") do + code[#code] = nil + end + blocks[#blocks + 1] = { + kind = "code", + text = table.concat(code, "\n") .. "\n", + } else - local paragraph = { line } + local paragraph, level = { line }, nil i = i + 1 - while i <= n and not ends_paragraph(lines, i) do + while i <= n do + level = setext_level(lines[i]) + if level or ends_paragraph(lines, i) + or break_line:match(lines[i]) then + break + end paragraph[#paragraph + 1] = lines[i] i = i + 1 end - blocks[#blocks + 1] = { - kind = "para", - lines = paragraph_lines(paragraph), - } + if level then + local words = {} + for k, text in ipairs(paragraph) do + words[k] = trim(text) + end + blocks[#blocks + 1] = { + kind = "heading", + level = level, + kids = parse_inline(table.concat(words, " ")), + } + i = i + 1 + else + blocks[#blocks + 1] = { + kind = "para", + lines = paragraph_lines(paragraph), + } + end end end return blocks |
