1 | -- html.lua |
2 | -- Formatting data with HTML |
3 |
|
4 | -- Copyright (c) 2020 Joshua 'joshuas3' Stockin |
5 | -- <https://joshstock.in> |
6 |
|
7 | local lyaml = require("lyaml") |
8 | local utils = require("utils") |
9 | local git = require("git_commands") |
10 | local request = require("request") |
11 | local tabulate = require("tabulate") |
12 |
|
13 | local create_html_template = function() |
14 | local t = {} |
15 | t.title = "" |
16 | t.meta_tags = {} |
17 | t.body = "" |
18 | return t |
19 | end |
20 |
|
21 | local tree = function(repo, repo_dir, branch, path) |
22 | local t = create_html_template() |
23 |
|
24 | if path ~= "" then -- make sure path exists |
25 | local path_tree = git.list_tree(repo_dir, branch.name, string.sub(path, 1, path:len() - 1)) |
26 | if #path_tree.dirs == 0 then -- no path found |
27 | return nil |
28 | end |
29 | end |
30 |
|
31 | -- Header with repository description and navigation links |
32 | t.body = t.body..string.format([[<h2><a href="/%s">%s</a> / <a href="/%s/tree/%s">%s</a></h2>]], repo.name, repo.name, repo.name, branch.name, branch.name) |
33 | t.body = t.body.."<p>"..repo.description.."</p>" |
34 |
|
35 | local navlinks_list = { |
36 | "<a href=\"/"..repo.name.."/download\">Download</a>", |
37 | "<a href=\"/"..repo.name.."/refs\">Refs</a>", |
38 | "<a href=\"/"..repo.name.."/log/"..branch.name.."\">Commit Log</a>", |
39 | "<b><a href=\"/"..repo.name.."/tree/"..branch.name.."\">Files</a></b>" |
40 | } |
41 |
|
42 | for _, special in pairs(repo.specialfiles) do |
43 | local split = string.split(special, " ") |
44 | table.insert(navlinks_list, string.format([[<a href="/%s/blob/%s/%s">%s</a>]], repo.name, branch.name, split[2], split[1])) |
45 | end |
46 |
|
47 | t.body = t.body.."<p>"..table.concat(navlinks_list, " | ").."</p>" |
48 |
|
49 | -- Latest Commit table |
50 | t.body = t.body.."<h3>Latest Commit</h3>" |
51 |
|
52 | local commits_table_data = {} |
53 | commits_table_data.class = "log" |
54 | commits_table_data.headers = { |
55 | {"timestamp", "Time"}, |
56 | {"shorthash", "Hash"}, |
57 | {"subject", "Subject"}, |
58 | {"author", "Author"}, |
59 | {"changed_files", [[<span class="q" title="# of files changed">#</span>]]}, |
60 | {"changed_plus", [[<span class="q" title="Insertions">(+)</span>]]}, |
61 | {"changed_minus", [[<span class="q" title="Deletions">(-)</span>]]}, |
62 | {"gpggood", [[<span class="q" title="GPG signature status |
63 | |
64 | G: Good (valid) signature |
65 | B: Bad signature |
66 | U: Good signature with unknown validity |
67 | X: Good signature that has expired |
68 | Y: Good signature made by an expired key |
69 | R: Good signature made by a revoked key |
70 | E: Signature can't be checked (e.g. missing key) |
71 | N: No signature">GPG?</span>]]} |
72 | } |
73 | commits_table_data.rows = {} |
74 |
|
75 | local commits_head = git.log(repo_dir, branch.name, path, 1, 0, true) |
76 |
|
77 | for _, commit in pairs(commits_head) do |
78 | table.insert(commits_table_data.rows, { |
79 | utils.iso8601(commit.timestamp), |
80 | string.format([[<a href="/%s/commit/%s">%s</a>]], repo.name, commit.hash, commit.shorthash), |
81 | utils.html_sanitize(commit.subject), |
82 | string.format([[<a href="mailto:%s">%s</a>]], commit.email, utils.html_sanitize(commit.author)), |
83 | commit.diff.num, |
84 | commit.diff.plus, |
85 | commit.diff.minus, |
86 | commit.gpggood |
87 | }) |
88 | end |
89 |
|
90 | t.body = t.body..tabulate(commits_table_data) |
91 |
|
92 | -- Tree/files table |
93 | t.body = t.body.."<h3>Tree" |
94 | if path == "" then |
95 | t.body = t.body.."</h3>" |
96 | else -- build path with hyperlinks for section header |
97 | local split = string.split(path, "/") |
98 | table.remove(split, #split) |
99 | local base = "/"..repo.name.."/tree/"..branch.name |
100 | t.body = t.body..string.format([[ @ <a href="%s">%s</a>]], base, repo.name) |
101 | local build = "" |
102 | for _, part in pairs(split) do |
103 | build = build.."/"..part |
104 | t.body = t.body..string.format([[ / <a href="%s%s">%s</a>]], base, build, part) |
105 | end |
106 | t.body = t.body.."</h3>" |
107 | end |
108 |
|
109 | local files_table_data = {} |
110 | files_table_data.class = "files" |
111 | files_table_data.headers = { |
112 | {"object", "Object"}, |
113 | {"subject", "Latest Commit Subject"}, |
114 | {"timestamp", "Time"}, |
115 | {"shorthash", "Hash"}} |
116 | files_table_data.rows = {} |
117 |
|
118 | local files = git.list_tree(repo_dir, branch.name, path) |
119 |
|
120 | local file_icon = [[<img style="width:1em;height:1em;vertical-align:middle;margin-right:0.5em;" src="https://joshuas3.s3.amazonaws.com/svg/file.svg"/>]] |
121 | local folder_icon = [[<img style="width:1em;height:1em;vertical-align:middle;margin-right:0.5em;fill:#ffe9a2;" src="https://joshuas3.s3.amazonaws.com/svg/folder.svg"/>]] |
122 |
|
123 | -- .. directory |
124 | if path ~= "" then |
125 | local split = string.split(string.sub(path, 1, path:len() - 1), "/") |
126 | table.remove(split, #split) |
127 | if #split > 0 then -- deeper than 1 directory |
128 | table.insert(files_table_data.rows, { |
129 | string.format([[%s<a href="/%s/tree/%s/%s">..</a>]], folder_icon, repo.name, branch.name, table.concat(split, "/")), |
130 | "","","" |
131 | }) |
132 | else -- only one directory deep |
133 | table.insert(files_table_data.rows, { |
134 | string.format([[%s<a href="/%s/tree/%s">..</a>]], folder_icon, repo.name, branch.name), |
135 | "","","" |
136 | }) |
137 | end |
138 | end |
139 |
|
140 | -- Regular directories |
141 | for _, dir in pairs(files.dirs) do |
142 | local lastedit = git.log(repo_dir, branch.name.." -1", dir, 1, 0, false)[1] |
143 | local split = string.split(dir, "/") |
144 | local name = split[#split] |
145 | table.insert(files_table_data.rows, { |
146 | string.format([[%s<a href="/%s/tree/%s/%s">%s</a>]], folder_icon, repo.name, branch.name, dir, name), |
147 | utils.html_sanitize(lastedit.subject), |
148 | utils.iso8601(lastedit.timestamp), |
149 | string.format([[<a href="/%s/commit/%s">%s</a>]], repo.name, lastedit.hash, lastedit.shorthash) |
150 | }) |
151 | end |
152 |
|
153 | -- Regular files |
154 | for _, file in pairs(files.files) do |
155 | local lastedit = git.log(repo_dir, branch.name.." -1", file, 1, 0, false)[1] |
156 | local split = string.split(file, "/") |
157 | local name = split[#split] |
158 | table.insert(files_table_data.rows, { |
159 | string.format([[%s<a href="/%s/blob/%s/%s">%s</a>]], file_icon, repo.name, branch.name, file, name), |
160 | utils.html_sanitize(lastedit.subject), |
161 | utils.iso8601(lastedit.timestamp), |
162 | string.format([[<a href="/%s/commit/%s">%s</a>]], repo.name, lastedit.hash, lastedit.shorthash) |
163 | }) |
164 | end |
165 |
|
166 | t.body = t.body..tabulate(files_table_data) |
167 |
|
168 | -- Look for and render README if it exists |
169 | for _, file in pairs(files.files) do |
170 | local l = file:lower() |
171 | if l:match("^readme") then |
172 | t.body = t.body.."<h3>README</h3>" |
173 | local text = git.show_file(repo_dir, branch.name, path..file) |
174 | local s = file:len() |
175 | if string.sub(l, s-2, s) == ".md" then |
176 | t.body = t.body..[[<div class="markdown">]]..utils.markdown(text).."</div>" |
177 | else |
178 | t.body = t.body.."<pre><code>"..text.."</code></pre>" |
179 | end |
180 | break |
181 | end |
182 | end |
183 |
|
184 | return t |
185 | end |
186 |
|
187 | local refs = function(repo, repo_dir, branch) |
188 | local t = create_html_template() |
189 |
|
190 | -- Header with repository description and navigation links |
191 | t.body = t.body..string.format([[<h2><a href="/%s">%s</a> / <a href="/%s/refs">refs</a></h2>]], repo.name, repo.name, repo.name) |
192 | t.body = t.body.."<p>"..repo.description.."</p>" |
193 |
|
194 | local navlinks_list = { |
195 | "<a href=\"/"..repo.name.."/download\">Download</a>", |
196 | "<b><a href=\"/"..repo.name.."/refs\">Refs</a></b>", |
197 | "<a href=\"/"..repo.name.."/log/"..branch.name.."\">Commit Log</a>", |
198 | "<a href=\"/"..repo.name.."/tree/"..branch.name.."\">Files</a>" |
199 | } |
200 |
|
201 | for _, special in pairs(repo.specialfiles) do |
202 | local split = string.split(special, " ") |
203 | table.insert(navlinks_list, string.format([[<a href="/%s/blob/%s/%s">%s</a>]], repo.name, branch.name, split[2], split[1])) |
204 | end |
205 |
|
206 | t.body = t.body.."<p>"..table.concat(navlinks_list, " | ").."</p>" |
207 |
|
208 | local all_refs = git.list_refs(repo_dir) |
209 |
|
210 | -- Branches |
211 | if #all_refs.heads > 0 then |
212 | t.body = t.body.."<h3>Branches</h3>" |
213 |
|
214 | local branches_table_data = {} |
215 | branches_table_data.class = "branches" |
216 | branches_table_data.headers = { |
217 | {"name", "Name"}, |
218 | {"ref", "Ref"}, |
219 | {"has", "Hash"} |
220 | } |
221 | branches_table_data.rows = {} |
222 |
|
223 | for _, b in pairs(all_refs.heads) do |
224 | table.insert(branches_table_data.rows, { |
225 | b.name ~= branch.name and b.name or b.name.." <b>(HEAD)</b>", |
226 | string.format([[<a href="/%s/tree/%s">%s</a>]], repo.name, b.name, b.full), |
227 | string.format([[<a href="/%s/commit/%s">%s</a>]], repo.name, b.hash, b.shorthash) |
228 | }) |
229 | end |
230 |
|
231 | t.body = t.body..tabulate(branches_table_data) |
232 | end |
233 |
|
234 | -- Tags |
235 | if #all_refs.tags > 0 then |
236 | t.body = t.body.."<h3>Tags</h3>" |
237 |
|
238 | local tags_table_data = {} |
239 | tags_table_data.class = "tags" |
240 | tags_table_data.headers = { |
241 | {"name", "Name"}, |
242 | {"ref", "Ref"}, |
243 | {"has", "Hash"} |
244 | } |
245 | tags_table_data.rows = {} |
246 | for _, t in pairs(all_refs.tags) do |
247 | table.insert(tags_table_data.rows, { |
248 | t.name ~= branch.name and t.name or t.name.." <b>(HEAD)</b>", |
249 | string.format([[<a href="/%s/tree/%s">%s</a>]], repo.name, t.name, t.full), |
250 | string.format([[<a href="/%s/commit/%s">%s</a>]], repo.name, t.hash, t.shorthash) |
251 | }) |
252 | end |
253 |
|
254 | t.body = t.body..tabulate(tags_table_data) |
255 | end |
256 |
|
257 | return t |
258 | end |
259 |
|
260 | local log = function(repo, repo_dir, branch, n, skip) |
261 | n = n or 40 |
262 | skip = skip or 0 |
263 |
|
264 | local t = create_html_template() |
265 |
|
266 | -- Header with repository description and navigation links |
267 | t.body = t.body..string.format([[<h2><a href="/%s">%s</a> / <a href="/%s/tree/%s">%s</a> / <a href="/%s/log/%s">log</a></h2>]], repo.name, repo.name, repo.name, branch.name, branch.name, repo.name, branch.name) |
268 | t.body = t.body.."<p>"..repo.description.."</p>" |
269 |
|
270 | local navlinks_list = { |
271 | "<a href=\"/"..repo.name.."/download\">Download</a>", |
272 | "<a href=\"/"..repo.name.."/refs\">Refs</a>", |
273 | "<b><a href=\"/"..repo.name.."/log/"..branch.name.."\">Commit Log</a></b>", |
274 | "<a href=\"/"..repo.name.."/tree/"..branch.name.."\">Files</a>" |
275 | } |
276 |
|
277 | for _, special in pairs(repo.specialfiles) do |
278 | local split = string.split(special, " ") |
279 | table.insert(navlinks_list, string.format([[<a href="/%s/blob/%s/%s">%s</a>]], repo.name, branch.name, split[2], split[1])) |
280 | end |
281 |
|
282 | t.body = t.body.."<p>"..table.concat(navlinks_list, " | ").."</p>" |
283 |
|
284 | -- Latest Commit table |
285 | t.body = t.body.."<h3>Commits</h3>" |
286 |
|
287 | local commits_table_data = {} |
288 | commits_table_data.class = "log" |
289 | commits_table_data.headers = { |
290 | {"timestamp", "Time"}, |
291 | {"shorthash", "Hash"}, |
292 | {"subject", "Subject"}, |
293 | {"author", "Author"}, |
294 | {"changed_files", [[<span class="q" title="# of files changed">#</span>]]}, |
295 | {"changed_plus", [[<span class="q" title="Insertions">(+)</span>]]}, |
296 | {"changed_minus", [[<span class="q" title="Deletions">(-)</span>]]}, |
297 | {"gpggood", [[<span class="q" title="GPG signature status |
298 | |
299 | G: Good (valid) signature |
300 | B: Bad signature |
301 | U: Good signature with unknown validity |
302 | X: Good signature that has expired |
303 | Y: Good signature made by an expired key |
304 | R: Good signature made by a revoked key |
305 | E: Signature can't be checked (e.g. missing key) |
306 | N: No signature">GPG?</span>]]} |
307 | } |
308 | commits_table_data.rows = {} |
309 |
|
310 | local commits_head = git.log(repo_dir, branch.name, path, n, skip, true) |
311 |
|
312 | for _, commit in pairs(commits_head) do |
313 | table.insert(commits_table_data.rows, { |
314 | utils.iso8601(commit.timestamp), |
315 | string.format([[<a href="/%s/commit/%s">%s</a>]], repo.name, commit.hash, commit.shorthash), |
316 | utils.html_sanitize(commit.subject), |
317 | string.format([[<a href="mailto:%s">%s</a>]], commit.email, utils.html_sanitize(commit.author)), |
318 | commit.diff.num, |
319 | commit.diff.plus, |
320 | commit.diff.minus, |
321 | commit.gpggood |
322 | }) |
323 | end |
324 |
|
325 | t.body = t.body..tabulate(commits_table_data) |
326 |
|
327 | return t |
328 | end |
329 |
|
330 | local download = function(repo, repo_dir, branch) |
331 | local t = create_html_template() |
332 |
|
333 | -- Header with repository description and navigation links |
334 | t.body = t.body..string.format([[<h2><a href="/%s">%s</a> / <a href="/%s/download">download</a></h2>]], repo.name, repo.name, repo.name) |
335 | t.body = t.body.."<p>"..repo.description.."</p>" |
336 |
|
337 | local navlinks_list = { |
338 | "<b><a href=\"/"..repo.name.."/download\">Download</a></b>", |
339 | "<a href=\"/"..repo.name.."/refs\">Refs</a>", |
340 | "<a href=\"/"..repo.name.."/log/"..branch.name.."\">Commit Log</a>", |
341 | "<a href=\"/"..repo.name.."/tree/"..branch.name.."\">Files</a>" |
342 | } |
343 |
|
344 | for _, special in pairs(repo.specialfiles) do |
345 | local split = string.split(special, " ") |
346 | table.insert(navlinks_list, string.format([[<a href="/%s/blob/%s/%s">%s</a>]], repo.name, branch.name, split[2], split[1])) |
347 | end |
348 |
|
349 | t.body = t.body.."<p>"..table.concat(navlinks_list, " | ").."</p>" |
350 |
|
351 | t.body = t.body.."<h3>Download URLs</h3>" |
352 |
|
353 | local urls = {} |
354 | urls.class = "download-urls" |
355 | urls.headers = { |
356 | {"protocol", "Protocol"}, |
357 | {"url", "URL"} |
358 | } |
359 | urls.rows = {} |
360 |
|
361 | for _, url in pairs(repo.download) do |
362 | local split = string.split(url, " ") |
363 | table.insert(urls.rows, {split[1], string.format([[<a href="%s">%s</a>]], split[2], split[2])}) |
364 | end |
365 |
|
366 | t.body = t.body..tabulate(urls) |
367 |
|
368 | t.body = t.body.."<h3>Websites</h3>" |
369 |
|
370 | local sites = {} |
371 | sites.class = "websites" |
372 | sites.headers = { |
373 | {"name", "Website"}, |
374 | {"url", "URL"} |
375 | } |
376 | sites.rows = {} |
377 |
|
378 | for _, site in pairs(repo.urls) do |
379 | local split = string.split(site, " ") |
380 | table.insert(sites.rows, {split[1], string.format([[<a href="%s">%s</a>]], split[2], split[2])}) |
381 | end |
382 |
|
383 | t.body = t.body..tabulate(sites) |
384 |
|
385 | return t |
386 | end |
387 |
|
388 |
|
389 | local _M = {} |
390 | _M.tree = tree |
391 | _M.refs = refs |
392 | _M.log = log |
393 | _M.download = download |
394 | return _M |
395 |
|