summaryrefslogtreecommitdiff
path: root/html.lua
blob: 3b34cc345f0fa8e68c621b8f39b7d8aa07fdd21e (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
local M = {}

local void = {
	area = true, base = true, br = true, col = true, embed = true, hr = true,
	img = true, input = true, link = true, meta = true, param = true,
	source = true, track = true, wbr = true,
}

local char_refs = {
	["<"] = "&lt;",
	[">"] = "&gt;",
	['"'] = "&quot;",
	["&"] = "&amp;",
}
local function escape(x)
	return (tostring(x):gsub('.', char_refs))
end

local function ele(_, name)
	assert(name:match '^%w+$')
	return function(...)
		local params, inner
		if select('#', ...) == 2 or type((...)) == 'table' then
			params, inner = ...
		else inner = ...  end

		coroutine.yield("<", name)
		if params then
			coroutine.yield(" ")
			for k, v in pairs(params) do
				assert(not k:find '[%s"\'>/=]')
				coroutine.yield(k, "=\"", escape(v), "\" ")
			end
		end
		coroutine.yield(">")

		if not void[name] then
			if type(inner) == 'function' then
				inner()
			elseif inner then
				coroutine.yield(escape(inner))
			end
			coroutine.yield("</", name, ">")
		else
			assert(not inner)
		end
	end
end

setmetatable(M, {__index = ele})

local function document(fn)
	return function()
		coroutine.yield("<!doctype html>")
		M.html(fn)
		coroutine.yield("\n")
	end
end

function M.render(fn, ...)
	local output = {}

	local co = coroutine.create(fn, ...)
	local retvals
	while true do
		retvals = {assert(coroutine.resume(co))}
		if coroutine.status(co) == 'dead' then break end
		if retvals[2] == abort then
			coroutine.yield(unpack(retvals))
		end
		for i = 2, #retvals do
			table.insert(output, retvals[i])
		end
	end

	return table.concat(output), table.unpack(retvals, 2)
end

function M.render_doc(fn, ...)
	return M.render(document(fn), ...)
end

function M.text(str)
	coroutine.yield(escape(str))
end

function M.url_encode(str)
	return tostring(str):gsub('([^A-Za-z0-9%_%.%-%~])', function(v)
		return ('%%%02x'):format(v:byte()):upper()
	end)
end

function M.url_decode(str)
	return tostring(str):gsub('%%(%x%x)', function(c)
		return string.char(tonumber(c, 16))
	end)
end

return M