summaryrefslogtreecommitdiff
path: root/prose.lua
blob: 83e6f5da0df1b95d60a6d91f202057e42935799a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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"&nbsp;",
		T.a({href="#"..link_id, role="doc-backlink"}, html.safe"&#x21a9;&#xfe0e;")})
	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