local html = require'garkup.html'
local T = html.T
local extensions = {}
local function prose(S, text)
-- this may be a hack
local replacements = {}
local SO, SI = "\x0e", "\x0f"
-- if debugview then SO,SI = "\x1b[7m", "\x1b[0m" end
local function emplace(s)
table.insert(replacements, s)
return SO .. #replacements .. SI
end
-- no way to match 'after ws or at start of string'
-- so, just add ws to start and end of string
-- this may also be a hack
text = ' ' .. text .. ' '
local function simple_sub(delim, tagname)
return {
"(%s)"..delim..'(.-)'..delim..'(%W)', -- wo wimple
function(a,x,b) return a..emplace(T[tagname](x))..b end
}
end
local function strip(s)
local q = s:match("^%s*(.-)%s*$")
return q or s
end
local function split(s)
local r = {}
for u in s:gmatch("%w+") do table.insert(r,u) end
return r
end
local function extension(str)
local verb, rest = assert(str:match("^{%s*([^%s;]+)(.*)}$"))
local args, body = rest:match("(.-);(.*)")
if not args then args = rest; body = '' end
verb = strip(verb)
args = split(args)
body = strip(body)
-- return verb, args, body
return emplace(extensions[verb](S, body, table.unpack(args)))
end
local subs = {
{ "%b{}", extension },
simple_sub('%*', 'strong'),
simple_sub('_', 'em'),
simple_sub('`', 'code'),
}
for i, row in ipairs(subs) do
local pattern,replacement = row[1], row[2]
text = text:gsub(pattern,replacement)
end
text = text:gsub("^%s*",""):gsub("%s*$","")
-- print(text)
local insects = {""}
local n = 1
while n <= #text do
local _, nn, id = text:find('^'..SO:gsub("%[","%%[").."(%d+)"..SI:gsub("%[","%%["), n)
if nn then
table.insert(insects, replacements[tonumber(id)])
table.insert(insects, "")
n = nn + 1
else
local c = text:sub(n,n)
-- print("'"..c.."'")
insects[#insects] = insects[#insects] .. c
n = n + 1
end
end
-- for i,n in ipairs(insects) do print(i,type(n),n) end
return insects
end
extensions.fn = function(S,text)
S.footnotes = S.footnotes or {}
local n = #S.footnotes + 1
local link_id = "fnref_"..n
local note_id = "fn_"..n
S.footnotes[n] = true -- for nested footnotes
S.footnotes[n] = T.li({id=note_id}, {
prose(S,text), html.safe" ",
T.a({href="#"..link_id, role="doc-backlink"}, html.safe"↩︎")})
return T.sup({id=link_id}, T.a({href="#"..note_id, role='doc-noteref'}, '('..n..')'))
end
extensions.fns = function(S)
if not S.footnotes then return "" end
return {T.hr"", T.ol({class="footnotes"},S.footnotes)}
end
extensions.meta = function(S,text)
local fn = assert(load(string.format("return {%s}", text),"meta", "t"))
S.meta = fn()
end
extensions.a = function(S,text)
local url, ni = text:match("(%S+)%s*()")
local body = prose(S, text:sub(ni))
return T.a({href=url},body)
end
extensions.def = function(S,text,name)
if extensions[name] then
io.stderr:write("warning: redefining extension "..name.."\n")
end
local fn = assert(load(
string.format("return %s", text),
"def "..name, "t",
setmetatable({S=S, T=T, html=html, prose=prose}, {__index=_G})
))
extensions[name]=fn()
end
setmetatable(extensions, {__index = function(t, k)
return function(S, text)
io.stderr:write("warning: unknown extension "..k.."\n")
return text
end
end})
return prose