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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
local html = require'garkup.html'
local T = html.T
local prose = require'garkup.prose'
function empipe(prog, input)
local posix = require'posix'
local unistd = posix.unistd or require'posix.unistd'
local pipe = assert(posix.popen_pipeline({
function() io.write(input) end,
prog
}, "r"))
local output = ""
repeat
local next = assert(unistd.read(pipe.fd, 8192))
output = output .. next
until #next == 0
assert(posix.pclose(pipe))
return output
end
function highlight(code, lang)
local highlighted = empipe(
{"highlight", "-f", "-S", lang, "--inline-css", "--enclose-pre"},
code
)
return html.safe(highlighted)
end
local function mode_code(S)
local lang = S:line():match("^```(%w+) ?(.*)")
local text = ""
for line in S:lines() do
if line:match("^```") then
break
else
text = text .. line
end
end
S:emit(highlight(text, lang))
end
local function mode_list(S)
local items = {}
for line in S:lines() do
if line:match("^%*") then
local content = line:gsub("^%*%s*","")
table.insert(items, T.li(prose(S,content)))
else
S:unline(line)
break
end
end
S:emit(T.ul(items))
end
local function mode_heading(S)
local level, line = S:line():match("^#*()%s*(.*)")
level = level - 1
S:emit(html.tag('h'..level, prose(S,line)))
end
local function mode_hr(S)
local line = S:line()
S:emit(html.T.hr"")
end
local mode_patterns = {
{ "^```", mode_code },
{ "^*", mode_list },
{ "^#", mode_heading },
{ "^%-%-%-", mode_hr },
}
local function match_mode_pattern(S, line)
for i, row in ipairs(mode_patterns) do
local pattern, mode = row[1], row[2]
if line:match(pattern) then return mode end
end
end
local function mode_paragraphs(S)
local para = ""
local function finish()
local processed = prose(S, para)
if tostring(html.html(processed)):match("%S") then
S:emit(T.p(processed))
end
para = ""
end
for line in S:lines() do
local newmode = match_mode_pattern(S, line)
if newmode then
S:unline(line)
finish()
return newmode(S)
elseif line:match("%S") then
para = para .. line
else
finish()
end
end
finish()
end
local function split_lines(text)
local lines = {}
for x in text:gmatch("[^\n]*") do
table.insert(lines, x.."\n")
end
return lines
end
local State = {}
function State.new(input)
return setmetatable({
the_lines = split_lines(input),
output = "",
}, {__index=State})
end
function State.emit(self, x)
self.output = self.output..tostring(html.html(x)).."\n"
end
function State.line(self)
return table.remove(self.the_lines, 1)
end
function State.unline(self, line)
table.insert(self.the_lines, 1, line)
end
function State.lines(self)
return State.line, self
end
function State.remaining(self)
return #self.the_lines > 0
end
function State.toplevel(self)
while self:remaining() do
mode_paragraphs(self)
end
return self.output
end
local function garkup(text)
local S = State.new(text)
return S:toplevel(), S
end
-- print(garkup(io.read"a")
return garkup
|