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
|
local html = require'garkup.html'
local T = html.T
local prose = require'garkup.prose'
local function highlight(code, lang)
-- you'd better not already be using that file
local infile = assert(io.open("/tmp/highlighter_input","w"))
assert(infile:write(code))
local outhandle = assert(io.popen("highlight -f -S "..lang.." --inline-css --enclose-pre </tmp/highlighter_input", "r"))
local output = assert(outhandle:read"a")
outhandle:close()
infile:close()
return html.safe(output)
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
if lang then
S:emit(highlight(text, lang))
else
S:emit(T.pre(text))
end
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 -- because () capture is offset by 1
level = level + (S.heading_level or 0)
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, options)
options = options or {}
local self = {
the_lines = split_lines(input),
output = "",
}
for k,v in pairs(options) do self[k] = v end
return setmetatable(self, {__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(...)
local S = State.new(...)
return S:toplevel(), S
end
-- print(garkup(io.read"a")
return garkup
|