diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Give readme headings anchors like GitHub's
A readme's links to its own sections, as in a table of contents, had
nothing on the page to land on.
| -rw-r--r-- | custom/extensions/about-render.lua | 37 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rw-r--r-- | tests/extensions/test-about-render.lua | 29 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -rwxr-xr-x | tests/t0501-about-render.sh | 2 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3 files changed, 58 insertions, 10 deletions
diff --git a/custom/extensions/about-render.lua b/custom/extensions/about-render.lua index 73b1336..6ee790b 100644 --- a/custom/extensions/about-render.lua +++ b/custom/extensions/about-render.lua @@ -32,6 +32,10 @@ local max_depth = 24 local filename, tree_base, tree_query, chunks = "", "", "", {} +-- How often each heading anchor has been handed out. The ids already on the +-- page around the readme start at one, so no heading takes them. +local id_counts = {} + -- Lua patterns backtrack, so "%s+$" costs the square of a run of spaces in -- the middle of a line. Finding each end directly stays linear. @@ -204,6 +208,34 @@ local function shows_something(lines) return false end +local function plain_text(nodes, parts) + for _, node in ipairs(nodes) do + if node.kind == "text" or node.kind == "code" then + parts[#parts + 1] = node.text + elseif node.kids then + plain_text(node.kids, parts) + end + end + return parts +end + +-- Gives a heading the anchor GitHub would, so a readme's links to its own +-- sections work. That is its text in lower case, with punctuation dropped +-- and each space a hyphen, and a count added to a repeat. Bytes past ASCII +-- stay, keeping the letters of other scripts. +local function emit_id(nodes) + local base = table.concat(plain_text(nodes, {})):lower() + :gsub("[^%w_%- \128-\255]", ""):gsub(" ", "-") + if base == "" then + return + end + local count = id_counts[base] or 0 + id_counts[base] = count + 1 + html(" id='") + html_attr(count > 0 and base .. "-" .. count or base) + html("'") +end + local function emit_lines(lines) for i, line in ipairs(lines) do if i > 1 then @@ -219,7 +251,9 @@ emit_blocks = function(blocks) for _, block in ipairs(blocks) do local kind = block.kind if kind == "heading" then - html("<h" .. block.level .. ">") + html("<h" .. block.level) + emit_id(block.kids) + html(">") emit_inline(block.kids) html("</h" .. block.level .. ">") elseif kind == "hr" then @@ -778,6 +812,7 @@ function filter_open(name, base, query) tree_base = base or "" tree_query = query or "" chunks = {} + id_counts = { cgit = 1, header = 1, about = 1 } end function filter_write(str) diff --git a/tests/extensions/test-about-render.lua b/tests/extensions/test-about-render.lua index 3925dfd..cb1c736 100644 --- a/tests/extensions/test-about-render.lua +++ b/tests/extensions/test-about-render.lua @@ -73,19 +73,31 @@ end -- Markdown. out = render("README.md", "# Title") -h.contains("a heading renders inside the wrapper", out, "<div class='markdown'><h1>Title</h1></div>") +h.contains("a heading renders inside the wrapper", out, "<div class='markdown'><h1 id='title'>Title</h1></div>") out = render("README.md", "## Sub ##") -h.contains("trailing hashes are stripped from a heading", out, "<h2>Sub</h2>") +h.contains("trailing hashes are stripped from a heading", out, "<h2 id='sub'>Sub</h2>") out = render("README.md", "# C#") -h.contains("a hash ending a word stays in the heading", out, "<h1>C#</h1>") +h.contains("a hash ending a word stays in the heading", out, "<h1 id='c'>C#</h1>") + +out = render("README.md", "## The `run` [Command](x), Again!") +h.contains("an anchor is the heading's text in lower case", out, "<h2 id='the-run-command-again'>") + +out = render("README.md", "# Twice\n# Twice\n# twice") +h.contains("a repeated anchor is counted", out, "<h1 id='twice'>Twice</h1><h1 id='twice-1'>Twice</h1><h1 id='twice-2'>") + +out = render("README.md", "# About") +h.contains("no heading takes an id of the page", out, "<h1 id='about-1'>") + +out = render("README.md", "# ???") +h.contains("a heading of punctuation gets no anchor", out, "<h1>???</h1>") out = render("README.MD", "# Upper") -h.contains("the extension matches whatever its case", out, "<h1>Upper</h1>") +h.contains("the extension matches whatever its case", out, "<h1 id='upper'>Upper</h1>") out = render("readme.markdown", "# Long") -h.contains("the long markdown extension dispatches too", out, "<h1>Long</h1>") +h.contains("the long markdown extension dispatches too", out, "<h1 id='long'>Long</h1>") out = render("README.md", "line one\nline two") h.contains("the lines of a paragraph run together", out, "<p>line one\nline two</p>") @@ -241,6 +253,7 @@ h.check("an emphasis bomb still renders something", #out > 0) -- request for minutes. Both lines below took seconds before. local started = os.clock() render("README.md", "# a" .. string.rep(" ", 3000) .. "b") +render("README.md", string.rep("# a\n", 50000)) render("README.md", "| a | b |\n|---|---|\n| x" .. string.rep(" ", 40000) .. "y | z |") h.check("a long run of spaces is trimmed quickly", os.clock() - started < 1) @@ -281,13 +294,13 @@ h.contains("an oversized readme renders as plain text", out, "<pre class='plaint -- Man pages. out = render("page.1", ".SH NAME") -h.contains("a section heading renders", out, "<div class='markdown manpage'><h2>NAME</h2></div>") +h.contains("a section heading renders", out, "<div class='markdown manpage'><h2 id='name'>NAME</h2></div>") out = render("page.man", '.SH "TWO WORDS"') -h.contains("a quoted heading argument is unquoted", out, "<h2>TWO WORDS</h2>") +h.contains("a quoted heading argument is unquoted", out, "<h2 id='two-words'>TWO WORDS</h2>") out = render("page.3", ".SS Details") -h.contains("a subsection renders one level down", out, "<h3>Details</h3>") +h.contains("a subsection renders one level down", out, "<h3 id='details'>Details</h3>") out = render("page.1", ".B bold words\nplain after") h.contains("the bold macro renders strong", out, "<p><strong>bold words</strong>\nplain after</p>") diff --git a/tests/t0501-about-render.sh b/tests/t0501-about-render.sh index b47c11d..f339526 100755 --- a/tests/t0501-about-render.sh +++ b/tests/t0501-about-render.sh @@ -71,7 +71,7 @@ fi test_expect_success CGIT_LUA_LPEG 'the about page renders the markdown' ' cgit_url "md/about/" >tmp && - grep "<h1>The Title</h1>" tmp && + grep "<h1 id=.the-title.>The Title</h1>" tmp && grep "<strong>bold</strong>" tmp ' |
