summaryrefslogtreecommitdiff
path: root/init.lua
blob: 6f09cb89462a227007ac5c4bdf10f7d52ac2ef15 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
local html = require'garkup.html'
local T = html.T
local prose = require'garkup.prose'

local function highlight(code, lang)
	local run = require'run'
	return html.safe(run{
		"highlight", "-f", "-S", lang, "--inline-css", "--enclose-pre",
		input = code,
	})
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