diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Restyle the filter extension headers and markup
Diffstat (limited to 'custom/extensions/link-commits.lua')
| -rw-r--r-- | custom/extensions/link-commits.lua | 146 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 file changed, 73 insertions, 73 deletions
diff --git a/custom/extensions/link-commits.lua b/custom/extensions/link-commits.lua index fabd05b..3f2429d 100644 --- a/custom/extensions/link-commits.lua +++ b/custom/extensions/link-commits.lua @@ -1,51 +1,45 @@ -- cgit commit-filter that turns git object names and configurable text --- references in commit messages into links. Use it with the commit-filter or --- repo.commit-filter setting and the lua: prefix. +-- references in a commit message into links, named by the commit-filter or +-- repo.commit-filter setting in cgitrc. cgit hands over the message already +-- HTML-escaped, so all this does is wrap matches in anchors. Every match is +-- resolved in one left-to-right pass, so nothing is ever linked twice. The +-- two tables below are the whole configuration, and the filter runs on Lua +-- 5.1 through 5.4 and LuaJIT with nothing outside the standard library. -- -- commit-filter=lua:/path/to/link-commits.lua --- --- cgit hands the filter the message already HTML-escaped, so this only wraps --- matches in anchors. No external dependencies. Runs on Lua 5.1 through 5.4 --- and LuaJIT. --- --- Two kinds of thing are linked, object names (runs of hex that look like git --- hashes) and any number of text-reference rules you define, each a pattern --- and a URL. Both are configured among the values below. All matches are resolved --- in a single left-to-right pass, so nothing is ever linked twice. --- Object names (git hashes). Handled specially, because the length rule cannot --- be written as a plain Lua pattern. --- --- Recognition is by shape, since a commit-filter cannot ask the repository --- whether a hash is real. Any hex run within the length bounds is linked, --- whatever mix of digits and letters it has, so abbreviated and all-digit --- hashes are both caught. The cost is that a long hex-looking number can now --- and then link to an object that does not exist, which cgit renders as a --- harmless "bad object name" page. Shape matching is inherently approximate, --- the length bounds are the only filter. +-- Object names are handled apart from the rules below because the length +-- bound on them cannot be written as a plain Lua pattern. Recognition is by +-- shape, since a commit-filter cannot ask the repository whether a hash is +-- real, so any hex run within the bounds is linked whatever mix of digits and +-- letters it has and abbreviated and all-digit names are both caught. The +-- cost is that a long hex-looking number now and then links to an object that +-- does not exist, which cgit renders as a harmless "bad object name" page. local objects = { -- Set false to stop linking bare hashes. enabled = true, - -- A hex run within these lengths is linked. Git abbreviations run about 7 - -- to 12 characters, full names are 40 (sha1) or 64 (sha256). + -- Git abbreviations run about 7 to 12 characters, and a full name is 40 + -- characters for sha1 or 64 for sha256. min_length = 7, max_length = 64, - -- Link target, %s is replaced with the matched hash. "./?id=%s" is relative - -- to the current page and works for the common virtual-root layout. + -- Link target, where %s is replaced with the matched hash. The relative + -- form is resolved against the current page and works for the common + -- virtual-root layout. url = "./?id=%s", } --- Text-reference rules. Each rule is a Lua pattern with ONE capture and a URL --- where %s is replaced by that capture, percent-encoded. The whole match is --- shown, the capture is what goes in the URL. Rules are tried in order and the --- leftmost match on the line wins, so put more specific patterns first. Leave --- the list empty to link only object names. +-- Text-reference rules, each one a Lua pattern with a single capture and a +-- URL where %s is replaced by that capture, percent-encoded. The whole match +-- is what gets shown and the capture is only what goes into the URL. Rules are +-- tried in order and the leftmost match on the line wins, so put the more +-- specific patterns first, and an empty list leaves only object names linked. -- -- Lua patterns are not regular expressions. There is no alternation and no --- {n,m} repetition. %d is a digit, %a a letter, %w a letter or digit, %x a hex --- digit, and a literal magic character is escaped with %, so a literal '-' is --- '%-'. Reference: https://www.lua.org/manual/5.1/manual.html#5.4.1 +-- {n,m} repetition, %d is a digit, %a a letter, %w a letter or digit, %x a +-- hex digit, and a literal magic character is escaped with %, so a literal +-- dash is '%-'. The whole set is in the reference manual at +-- https://www.lua.org/manual/5.1/manual.html#5.4.1 -- -- Patterns run against the escaped message, so '&', '<' and '>' reach them as -- '&', '<' and '>'. Match those entity spellings rather than the @@ -61,62 +55,67 @@ local rules = { local chunks = {} --- Percent-encode everything but the URL-unreserved characters, so a captured --- value cannot break out of the href attribute or the URL. +-- Percent-encode everything but the URL unreserved characters, so a captured +-- value cannot break out of the href attribute or out of the URL itself. local function url_encode(s) return (string.gsub(s, "[^%w._~-]", function(c) return string.format("%%%02X", string.byte(c)) end)) end --- Build one anchor. url_template has %s where the encoded capture goes, display --- is the text shown. A function replacement is used so a '%' in the encoded --- value is not treated as a gsub reference. +-- Build one anchor from a URL template holding %s and the text to show. The +-- replacement is a function so that a '%' in the encoded value is not taken +-- for a gsub reference. local function make_link(url_template, capture, display) local encoded = url_encode(capture) local href = string.gsub(url_template, "%%s", function() return encoded end) - return '<a href="' .. href .. '">' .. display .. '</a>' + return "<a href='" .. href .. "'>" .. display .. "</a>" end --- Collect every candidate match as {s, e, pri, link}. A lower pri wins a tie on --- the same start position. +-- Collect every candidate match in the message. Priority records which rule +-- found it, and the lower priority wins a tie on the same start position. local function collect(text) - local cands = {} - for pri, rule in ipairs(rules) do - -- A malformed pattern is an operator error, skip that rule rather than - -- failing the whole page. + local candidates = {} + for priority, rule in ipairs(rules) do + -- A malformed pattern is an operator error, so skip that + -- rule rather than fail the whole page. pcall(function() local init = 1 while init <= #text do - local s, e, cap = string.find(text, rule.pattern, init) - if not s then break end - if cap == nil then - cap = string.sub(text, s, e) + local start, stop, capture = + string.find(text, rule.pattern, init) + if not start then break end + if capture == nil then + capture = string.sub(text, start, stop) end - cands[#cands + 1] = { - s = s, e = e, pri = pri, - link = make_link(rule.url, cap, string.sub(text, s, e)), + candidates[#candidates + 1] = { + start = start, stop = stop, priority = priority, + link = make_link(rule.url, capture, + string.sub(text, start, stop)), } - init = (e >= s) and e + 1 or s + 1 + -- An empty match still has to advance the + -- scan, or it never reaches the end. + init = (stop >= start) and stop + 1 or start + 1 end end) end if objects.enabled then - local objpri = #rules + 1 + local priority = #rules + 1 local init = 1 while init <= #text do - local s, e, run = string.find(text, "%f[%w](%x+)%f[%W]", init) - if not s then break end + local start, stop, run = + string.find(text, "%f[%w](%x+)%f[%W]", init) + if not start then break end if #run >= objects.min_length and #run <= objects.max_length then - cands[#cands + 1] = { - s = s, e = e, pri = objpri, + candidates[#candidates + 1] = { + start = start, stop = stop, priority = priority, link = make_link(objects.url, run, run), } end - init = e + 1 + init = stop + 1 end end - return cands + return candidates end function filter_open(...) @@ -129,24 +128,25 @@ end function filter_close() local text = table.concat(chunks) - local cands = collect(text) - table.sort(cands, function(a, b) - if a.s ~= b.s then - return a.s < b.s + local candidates = collect(text) + table.sort(candidates, function(a, b) + if a.start ~= b.start then + return a.start < b.start end - return a.pri < b.pri + return a.priority < b.priority end) local out = {} - local i = 1 - for _, c in ipairs(cands) do - -- Skip a candidate that overlaps one already emitted. - if c.s >= i then - out[#out + 1] = string.sub(text, i, c.s - 1) - out[#out + 1] = c.link - i = c.e + 1 + local pos = 1 + for _, candidate in ipairs(candidates) do + -- A candidate reaching back into one already emitted is + -- dropped, so no run of text is ever wrapped twice. + if candidate.start >= pos then + out[#out + 1] = string.sub(text, pos, candidate.start - 1) + out[#out + 1] = candidate.link + pos = candidate.stop + 1 end end - out[#out + 1] = string.sub(text, i) + out[#out + 1] = string.sub(text, pos) html(table.concat(out)) return 0 end |
