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 = {
["<"] = "<",
[">"] = ">",
['"'] = """,
["&"] = "&",
}
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
|