Index

resty-gitweb / dda0dae

A git web interface for Lua/OpenResty (you're on it right now!)

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
1224 Jan 2021 17:05dda0daeNew HTML generatorJosh Stockin1158G

Blob @ resty-gitweb / app.lua

text/plain9442 bytesdownload raw
1-- resty-gitweb@app.lua
2-- Entry point for git HTTP site implementation
3
4-- Copyright (c) 2020 Joshua 'joshuas3' Stockin
5-- <https://git.joshstock.in/resty-gitweb>
6-- This software is licensed under the MIT License.
7
8local puremagic = require("puremagic")
9
10local utils = require("utils/utils")
11local git = require("git/git")
12local parse_uri = require("utils/parse_uri")
13
14local parsed_uri = parse_uri()
15local view
16local content
17
18-- TODO: Rewrite app script completely
19
20if parsed_uri.repo == nil then
21 content = require("pages/index")(CONFIG)
22else -- repo found
23 local repo
24 for _,r in pairs(CONFIG) do
25 if parsed_uri.repo == r.name then
26 repo = r
27 break
28 end
29 end
30 if repo then
31 repo.loc = repo.location.dev
32 local success, repo_libgit2_object = git.repo.open(repo.loc)
33 if not success then
34 error("Failed to open repository at "..repo.loc)
35 end
36 repo.obj = repo_libgit2_object
37 view = parsed_uri.parts[2] or "tree"
38 local branch
39
40 local res, status = pcall(function() -- if branch is real
41 branch = git.find_rev(repo.obj, parsed_uri.parts[3]) -- if parts[3] is nil, defaults to "HEAD"
42 end)
43 if res then
44 local res, status = pcall(function() -- effectively catch any errors, 404 if any
45 if view == "tree" then -- directory display (with automatic README rendering)
46 local path = parsed_uri.parts
47 table.remove(path, 3) -- branch
48 table.remove(path, 2) -- "tree"
49 table.remove(path, 1) -- repo
50 if #path > 0 then
51 path = table.concat(path, "/").."/"
52 else
53 path = ""
54 end
55
56 content = require("pages/tree")(repo, repo.loc, branch, path)
57 elseif view == "blob" then
58 local path = parsed_uri.parts
59 table.remove(path, 3) -- branch
60 table.remove(path, 2) -- "tree"
61 table.remove(path, 1) -- repo
62 if #path > 0 then
63 path = table.concat(path, "/")
64 else
65 path = ""
66 end
67
68 content = require("pages/blob")(repo, repo.loc, branch, path)
69 elseif view == "raw" then
70 local path = parsed_uri.parts
71 table.remove(path, 3) -- branch
72 table.remove(path, 2) -- "tree"
73 table.remove(path, 1) -- repo
74 if #path > 0 then
75 path = table.concat(path, "/")
76 else
77 path = ""
78 end
79
80 content, is_binary = require("pages/raw")(repo, repo.loc, branch, path)
81 if content then
82 if is_binary then
83 mimetype = puremagic.via_content(content.body, path)
84 content.type = mimetype
85 else
86 content.type = "text/plain"
87 end
88 end
89
90 elseif view == "log" then
91 content = require("pages/log")(repo, repo.loc, branch, ngx.var.arg_n, ngx.var.arg_skip)
92 elseif view == "refs" then
93 content = require("pages/refs")(repo, repo.loc, branch)
94 elseif view == "download" then
95 content = require("pages/download")(repo, repo.loc, branch)
96 elseif view == "commit" then
97 -- /repo/commit/[COMMIT HASH]
98 else
99 error("bad view "..view)
100 end
101 end) -- pcall
102
103 if res ~= true then
104 if not PRODUCTION then
105 ngx.say(res)
106 ngx.say(status)
107 end
108 ngx.exit(ngx.HTTP_NOT_FOUND)
109 return
110 end
111 elseif not PRODUCTION then -- branch doesn't exist, show an error in non-prod environments
112 ngx.say(res)
113 ngx.say(status)
114 ngx.exit(ngx.HTTP_NOT_FOUND)
115 end
116 git.repo.free(repo.obj)
117 end
118end
119
120if content ~= nil then -- TODO: HTML templates from files, static serving
121 if view ~= "raw" then
122 ngx.header.content_type = "text/html"
123 ngx.say([[<!DOCTYPE html><html lang="en"><head>]])
124 ngx.say([[<link rel="stylesheet" href="https://raw.githubusercontent.com/necolas/normalize.css/master/normalize.css">]])
125
126 ngx.say(
127 [[<style>
128 @import url('https://fonts.googleapis.com/css?family=Fira+Sans:400,400i,700,700i&display=swap');
129 *{
130 box-sizing:border-box;
131 }
132 body{
133 color: #212121;
134 font-family:'Fira Sans', sans-serif;
135 padding-bottom:200px;
136 line-height:1.4;
137 max-width:1000px;
138 margin:20px auto;
139 }
140 body>h2{
141 margin-top:5px;
142 margin-bottom:0;
143 }
144 h3{
145 margin-bottom:4px;
146 }
147 td,th{
148 padding:2px 5px;
149 border:1px solid #858585;
150 text-align:left;
151 vertical-align:top;
152 }
153 th{
154 border:1px solid #000;
155 }
156 table.files,table.log,table.blob{
157 width:100%;
158 max-width:100%;
159 }
160 table{
161 border-collapse:collapse;
162 overflow:auto;
163 font-family: monospace;
164 font-size:14px;
165 line-height:1.2;
166 }
167 table.files td:first-child{
168 padding-right:calc(5px + 1em);
169 }
170 table.files td:not(:nth-child(2)), table.log td:not(:nth-child(4)){
171 width:1%;
172 white-space:nowrap;
173 }
174 span.q{
175 text-decoration:underline;
176 text-decoration-style:dotted;
177 }
178 .q:hover{
179 cursor:help;
180 }
181 th, tr:hover{ /*darker color for table head, hovered-over rows*/
182 background-color:#dedede;
183 }
184 div.markdown{
185 width:100%;
186 padding:20px 50px;
187 border:1px solid #858585;
188 border-radius:6px;
189 }
190 img{
191 max-width:100%;
192 }
193 pre{
194 background-color:#eee;
195 padding:15px;
196 overflow-x:auto;
197 border-radius:8px;
198 }
199 :not(pre)>code{
200 background-color:#eee;
201 padding:2.5px;
202 border-radius:4px;
203 }
204
205 div.blob.table {
206 overflow-x: auto;
207 border:1px solid #858585;
208 border-top: none;
209 }
210 div.blob.header {
211 font-family: monospace;
212 font-size:14px;
213 font-weight: bold;
214 border:1px solid #000;
215 background-color:#dedede;
216 }
217 div.blob.header span{
218 margin:0 4px;
219 }
220 table.blob {
221 font-size:1em;
222 width:100%;
223 max-width:100%;
224 line-height:1;
225 }
226 table.blob tr:hover {
227 background-color: inherit;
228 }
229 table.blob td{
230 border:none;
231 padding:1px 5px;
232 }
233 table.blob.binary td{
234 text-align:center;
235 padding: 0;
236 }
237 table.blob.binary td>img, table.blob.binary td>video{
238 max-width:100%;
239 max-height:600px;
240 }
241 table.blob.lines td:first-child{
242 text-align: right;
243 padding-left:20px;
244 user-select: none;
245 color:#858585;
246 max-width:1%;
247 white-space:nowrap;
248 }
249 table.blob.lines td:first-child:hover{
250 color: #454545;
251 }
252 table.blob.lines td:nth-child(2){
253 width:100%;
254 white-space:pre;
255 }
256
257 a{
258 text-decoration:none;
259 color: #0077aa;
260 display: inline-block;
261 }
262 a:hover{
263 text-decoration:underline;
264 }
265 center.index-banner h1.title {
266 margin-top:40px;
267 margin-bottom:0;
268 }
269 center.index-banner p.description {
270 margin-top:8px;
271 margin-bottom:30px;
272 }
273 div.repo-section .name {
274 margin-bottom:0;
275 }
276 div.repo-section h3 {
277 margin-top:10px;
278 }
279 div.repo-section .description {
280 margin-top:8px;
281 }
282 div.repo-section .nav {
283 margin-top:10px;
284 }
285 hr {
286 margin: 20px 0;
287 }
288 </style>]])
289 ngx.say("</head><body>")
290
291 if parsed_uri.repo then
292 local arrow_left_circle = [[<img style="width:1.2em;height:1.2em;vertical-align:middle;margin-right:0.2em" src="https://joshuas3.s3.amazonaws.com/svg/arrow-left.svg"/>]]
293 ngx.say("<a style=\"margin-left:-1.35em\" href=\"/\">"..arrow_left_circle.."<span style=\"vertical-align:middle\">Index</span></a>")
294 end
295 ngx.print(content:build())
296 ngx.say("</body></html>")
297 else
298 ngx.header.content_type = content.type
299 ngx.print(content:build())
300 end
301 ngx.exit(ngx.HTTP_OK)
302 return
303else
304 ngx.exit(ngx.HTTP_NOT_FOUND) -- default behavior
305 return
306end
307