diff options
Diffstat (limited to '')
-rw-r--r--custom/extensions/about-render.lua37
-rw-r--r--tests/extensions/test-about-render.lua29
-rwxr-xr-xtests/t0501-about-render.sh2
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
'