+122
legit/routes/file.go
+122
legit/routes/file.go
···
1
+
package routes
2
+
3
+
import (
4
+
"bytes"
5
+
"html/template"
6
+
"io"
7
+
"log"
8
+
"net/http"
9
+
"strings"
10
+
11
+
"github.com/alecthomas/chroma/v2/formatters/html"
12
+
"github.com/alecthomas/chroma/v2/lexers"
13
+
"github.com/alecthomas/chroma/v2/styles"
14
+
"github.com/icyphox/bild/legit/git"
15
+
)
16
+
17
+
func (h *Handle) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) {
18
+
data["files"] = files
19
+
data["meta"] = h.c.Meta
20
+
21
+
if err := h.t.ExecuteTemplate(w, "repo/tree", data); err != nil {
22
+
log.Println(err)
23
+
return
24
+
}
25
+
}
26
+
27
+
func countLines(r io.Reader) (int, error) {
28
+
buf := make([]byte, 32*1024)
29
+
bufLen := 0
30
+
count := 0
31
+
nl := []byte{'\n'}
32
+
33
+
for {
34
+
c, err := r.Read(buf)
35
+
if c > 0 {
36
+
bufLen += c
37
+
}
38
+
count += bytes.Count(buf[:c], nl)
39
+
40
+
switch {
41
+
case err == io.EOF:
42
+
/* handle last line not having a newline at the end */
43
+
if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {
44
+
count++
45
+
}
46
+
return count, nil
47
+
case err != nil:
48
+
return 0, err
49
+
}
50
+
}
51
+
}
52
+
53
+
func (h *Handle) showFileWithHighlight(name, content string, data map[string]any, w http.ResponseWriter) {
54
+
lexer := lexers.Get(name)
55
+
if lexer == nil {
56
+
lexer = lexers.Get(".txt")
57
+
}
58
+
59
+
style := styles.Get(h.c.Meta.SyntaxHighlight)
60
+
if style == nil {
61
+
style = styles.Get("monokailight")
62
+
}
63
+
64
+
formatter := html.New(
65
+
html.WithLineNumbers(true),
66
+
html.WithLinkableLineNumbers(true, "L"),
67
+
)
68
+
69
+
iterator, err := lexer.Tokenise(nil, content)
70
+
if err != nil {
71
+
h.Write500(w)
72
+
return
73
+
}
74
+
75
+
var code bytes.Buffer
76
+
err = formatter.Format(&code, style, iterator)
77
+
if err != nil {
78
+
h.Write500(w)
79
+
return
80
+
}
81
+
82
+
data["content"] = template.HTML(code.String())
83
+
data["meta"] = h.c.Meta
84
+
data["chroma"] = true
85
+
86
+
if err := h.t.ExecuteTemplate(w, "repo/file", data); err != nil {
87
+
log.Println(err)
88
+
return
89
+
}
90
+
}
91
+
92
+
func (h *Handle) showFile(content string, data map[string]any, w http.ResponseWriter) {
93
+
lc, err := countLines(strings.NewReader(content))
94
+
if err != nil {
95
+
// Non-fatal, we'll just skip showing line numbers in the template.
96
+
log.Printf("counting lines: %s", err)
97
+
}
98
+
99
+
lines := make([]int, lc)
100
+
if lc > 0 {
101
+
for i := range lines {
102
+
lines[i] = i + 1
103
+
}
104
+
}
105
+
106
+
data["linecount"] = lines
107
+
data["content"] = content
108
+
data["meta"] = h.c.Meta
109
+
data["chroma"] = false
110
+
111
+
if err := h.t.ExecuteTemplate(w, "repo/file", data); err != nil {
112
+
log.Println(err)
113
+
return
114
+
}
115
+
}
116
+
117
+
func (h *Handle) showRaw(content string, w http.ResponseWriter) {
118
+
w.WriteHeader(http.StatusOK)
119
+
w.Header().Set("Content-Type", "text/plain")
120
+
w.Write([]byte(content))
121
+
return
122
+
}
+6
-4
legit/routes/handler.go
+6
-4
legit/routes/handler.go
···
2
2
3
3
import (
4
4
"fmt"
5
-
"html/template"
6
5
"net/http"
7
-
"path/filepath"
8
6
9
7
_ "github.com/bluesky-social/indigo/atproto/identity"
10
8
_ "github.com/bluesky-social/indigo/atproto/syntax"
···
13
11
"github.com/gorilla/sessions"
14
12
"github.com/icyphox/bild/legit/config"
15
13
"github.com/icyphox/bild/legit/db"
14
+
"github.com/icyphox/bild/legit/routes/tmpl"
16
15
)
17
16
18
17
// Checks for gitprotocol-http(5) specific smells; if found, passes
···
39
38
40
39
func Setup(c *config.Config) (http.Handler, error) {
41
40
r := chi.NewRouter()
42
-
t := template.Must(template.ParseGlob(filepath.Join(c.Dirs.Templates, "*")))
43
41
s := sessions.NewCookieStore([]byte("TODO_CHANGE_ME"))
42
+
t, err := tmpl.Load(c.Dirs.Templates)
43
+
if err != nil {
44
+
return nil, fmt.Errorf("failed to load templates: %w", err)
45
+
}
46
+
44
47
db, err := db.Setup(c.Server.DBPath)
45
-
46
48
if err != nil {
47
49
return nil, fmt.Errorf("failed to setup db: %w", err)
48
50
}
+29
legit/routes/html_util.go
+29
legit/routes/html_util.go
···
1
+
package routes
2
+
3
+
import (
4
+
"fmt"
5
+
"log"
6
+
"net/http"
7
+
)
8
+
9
+
func (h *Handle) Write404(w http.ResponseWriter) {
10
+
w.WriteHeader(404)
11
+
if err := h.t.ExecuteTemplate(w, "errors/404", nil); err != nil {
12
+
log.Printf("404 template: %s", err)
13
+
}
14
+
}
15
+
16
+
func (h *Handle) Write500(w http.ResponseWriter) {
17
+
w.WriteHeader(500)
18
+
if err := h.t.ExecuteTemplate(w, "errors/500", nil); err != nil {
19
+
log.Printf("500 template: %s", err)
20
+
}
21
+
}
22
+
23
+
func (h *Handle) WriteOOBNotice(w http.ResponseWriter, id, msg string) {
24
+
html := fmt.Sprintf(`<span id="%s" hx-swap-oob="innerHTML">%s</span>`, id, msg)
25
+
26
+
w.Header().Set("Content-Type", "text/html")
27
+
w.WriteHeader(http.StatusOK)
28
+
w.Write([]byte(html))
29
+
}
+7
-9
legit/routes/routes.go
+7
-9
legit/routes/routes.go
···
106
106
name = filepath.Clean(name)
107
107
path := filepath.Join(h.c.Repo.ScanPath, name)
108
108
109
-
fmt.Println(path)
110
109
gr, err := git.Open(path, "")
111
110
if err != nil {
112
111
if errors.Is(err, plumbing.ErrReferenceNotFound) {
113
-
h.t.ExecuteTemplate(w, "empty", nil)
112
+
h.t.ExecuteTemplate(w, "repo/empty", nil)
114
113
return
115
114
} else {
116
115
h.Write404(w)
···
173
172
data["meta"] = h.c.Meta
174
173
data["gomod"] = isGoModule(gr)
175
174
176
-
if err := h.t.ExecuteTemplate(w, "repo", data); err != nil {
175
+
if err := h.t.ExecuteTemplate(w, "repo/repo", data); err != nil {
177
176
log.Println(err)
178
177
return
179
178
}
···
192
191
193
192
name = filepath.Clean(name)
194
193
path := filepath.Join(h.c.Repo.ScanPath, name)
195
-
fmt.Println(path)
196
194
gr, err := git.Open(path, ref)
197
195
if err != nil {
198
196
h.Write404(w)
···
348
346
data["desc"] = getDescription(path)
349
347
data["log"] = true
350
348
351
-
if err := h.t.ExecuteTemplate(w, "log", data); err != nil {
349
+
if err := h.t.ExecuteTemplate(w, "repo/log", data); err != nil {
352
350
log.Println(err)
353
351
return
354
352
}
···
387
385
data["ref"] = ref
388
386
data["desc"] = getDescription(path)
389
387
390
-
if err := h.t.ExecuteTemplate(w, "commit", data); err != nil {
388
+
if err := h.t.ExecuteTemplate(w, "repo/commit", data); err != nil {
391
389
log.Println(err)
392
390
return
393
391
}
···
429
427
data["tags"] = tags
430
428
data["desc"] = getDescription(path)
431
429
432
-
if err := h.t.ExecuteTemplate(w, "refs", data); err != nil {
430
+
if err := h.t.ExecuteTemplate(w, "repo/refs", data); err != nil {
433
431
log.Println(err)
434
432
return
435
433
}
···
509
507
510
508
data := make(map[string]interface{})
511
509
data["keys"] = keys
512
-
if err := h.t.ExecuteTemplate(w, "keys", data); err != nil {
510
+
if err := h.t.ExecuteTemplate(w, "settings/keys", data); err != nil {
513
511
log.Println(err)
514
512
return
515
513
}
···
541
539
542
540
switch r.Method {
543
541
case http.MethodGet:
544
-
if err := h.t.ExecuteTemplate(w, "new", nil); err != nil {
542
+
if err := h.t.ExecuteTemplate(w, "repo/new", nil); err != nil {
545
543
log.Println(err)
546
544
return
547
545
}
-155
legit/routes/template.go
-155
legit/routes/template.go
···
1
-
package routes
2
-
3
-
import (
4
-
"bytes"
5
-
"fmt"
6
-
"html/template"
7
-
"io"
8
-
"log"
9
-
"net/http"
10
-
"path/filepath"
11
-
"strings"
12
-
13
-
"github.com/alecthomas/chroma/v2/formatters/html"
14
-
"github.com/alecthomas/chroma/v2/lexers"
15
-
"github.com/alecthomas/chroma/v2/styles"
16
-
"github.com/icyphox/bild/legit/git"
17
-
)
18
-
19
-
func (h *Handle) Write404(w http.ResponseWriter) {
20
-
w.WriteHeader(404)
21
-
if err := h.t.ExecuteTemplate(w, "404", nil); err != nil {
22
-
log.Printf("404 template: %s", err)
23
-
}
24
-
}
25
-
26
-
func (h *Handle) Write500(w http.ResponseWriter) {
27
-
w.WriteHeader(500)
28
-
if err := h.t.ExecuteTemplate(w, "500", nil); err != nil {
29
-
log.Printf("500 template: %s", err)
30
-
}
31
-
}
32
-
33
-
func (h *Handle) WriteOOBNotice(w http.ResponseWriter, id, msg string) {
34
-
html := fmt.Sprintf(`<span id="%s" hx-swap-oob="innerHTML">%s</span>`, id, msg)
35
-
36
-
w.Header().Set("Content-Type", "text/html")
37
-
w.WriteHeader(http.StatusOK)
38
-
w.Write([]byte(html))
39
-
}
40
-
41
-
func (h *Handle) listFiles(files []git.NiceTree, data map[string]any, w http.ResponseWriter) {
42
-
tpath := filepath.Join(h.c.Dirs.Templates, "*")
43
-
t := template.Must(template.ParseGlob(tpath))
44
-
45
-
data["files"] = files
46
-
data["meta"] = h.c.Meta
47
-
48
-
if err := t.ExecuteTemplate(w, "tree", data); err != nil {
49
-
log.Println(err)
50
-
return
51
-
}
52
-
}
53
-
54
-
func countLines(r io.Reader) (int, error) {
55
-
buf := make([]byte, 32*1024)
56
-
bufLen := 0
57
-
count := 0
58
-
nl := []byte{'\n'}
59
-
60
-
for {
61
-
c, err := r.Read(buf)
62
-
if c > 0 {
63
-
bufLen += c
64
-
}
65
-
count += bytes.Count(buf[:c], nl)
66
-
67
-
switch {
68
-
case err == io.EOF:
69
-
/* handle last line not having a newline at the end */
70
-
if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' {
71
-
count++
72
-
}
73
-
return count, nil
74
-
case err != nil:
75
-
return 0, err
76
-
}
77
-
}
78
-
}
79
-
80
-
func (d *Handle) showFileWithHighlight(name, content string, data map[string]any, w http.ResponseWriter) {
81
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
82
-
t := template.Must(template.ParseGlob(tpath))
83
-
84
-
lexer := lexers.Get(name)
85
-
if lexer == nil {
86
-
lexer = lexers.Get(".txt")
87
-
}
88
-
89
-
style := styles.Get(d.c.Meta.SyntaxHighlight)
90
-
if style == nil {
91
-
style = styles.Get("monokailight")
92
-
}
93
-
94
-
formatter := html.New(
95
-
html.WithLineNumbers(true),
96
-
html.WithLinkableLineNumbers(true, "L"),
97
-
)
98
-
99
-
iterator, err := lexer.Tokenise(nil, content)
100
-
if err != nil {
101
-
d.Write500(w)
102
-
return
103
-
}
104
-
105
-
var code bytes.Buffer
106
-
err = formatter.Format(&code, style, iterator)
107
-
if err != nil {
108
-
d.Write500(w)
109
-
return
110
-
}
111
-
112
-
data["content"] = template.HTML(code.String())
113
-
data["meta"] = d.c.Meta
114
-
data["chroma"] = true
115
-
116
-
if err := t.ExecuteTemplate(w, "file", data); err != nil {
117
-
log.Println(err)
118
-
return
119
-
}
120
-
}
121
-
122
-
func (d *Handle) showFile(content string, data map[string]any, w http.ResponseWriter) {
123
-
tpath := filepath.Join(d.c.Dirs.Templates, "*")
124
-
t := template.Must(template.ParseGlob(tpath))
125
-
126
-
lc, err := countLines(strings.NewReader(content))
127
-
if err != nil {
128
-
// Non-fatal, we'll just skip showing line numbers in the template.
129
-
log.Printf("counting lines: %s", err)
130
-
}
131
-
132
-
lines := make([]int, lc)
133
-
if lc > 0 {
134
-
for i := range lines {
135
-
lines[i] = i + 1
136
-
}
137
-
}
138
-
139
-
data["linecount"] = lines
140
-
data["content"] = content
141
-
data["meta"] = d.c.Meta
142
-
data["chroma"] = false
143
-
144
-
if err := t.ExecuteTemplate(w, "file", data); err != nil {
145
-
log.Println(err)
146
-
return
147
-
}
148
-
}
149
-
150
-
func (d *Handle) showRaw(content string, w http.ResponseWriter) {
151
-
w.WriteHeader(http.StatusOK)
152
-
w.Header().Set("Content-Type", "text/plain")
153
-
w.Write([]byte(content))
154
-
return
155
-
}
+54
legit/routes/tmpl/tmpl.go
+54
legit/routes/tmpl/tmpl.go
···
1
+
package tmpl
2
+
3
+
import (
4
+
"html/template"
5
+
"log"
6
+
"os"
7
+
"path/filepath"
8
+
"strings"
9
+
)
10
+
11
+
func Load(tpath string) (*template.Template, error) {
12
+
tmpl := template.New("")
13
+
loadedTemplates := make(map[string]bool)
14
+
15
+
err := filepath.Walk(tpath, func(path string, info os.FileInfo, err error) error {
16
+
if err != nil {
17
+
return err
18
+
}
19
+
20
+
if !info.IsDir() && strings.HasSuffix(path, ".html") {
21
+
content, err := os.ReadFile(path)
22
+
if err != nil {
23
+
return err
24
+
}
25
+
26
+
relPath, err := filepath.Rel(tpath, path)
27
+
if err != nil {
28
+
return err
29
+
}
30
+
31
+
name := strings.TrimSuffix(relPath, ".html")
32
+
name = strings.ReplaceAll(name, string(filepath.Separator), "/")
33
+
34
+
_, err = tmpl.New(name).Parse(string(content))
35
+
if err != nil {
36
+
log.Printf("error parsing template %s: %v", name, err)
37
+
return err
38
+
}
39
+
40
+
loadedTemplates[name] = true
41
+
log.Printf("loaded template: %s", name)
42
+
return err
43
+
}
44
+
return nil
45
+
})
46
+
47
+
if err != nil {
48
+
return nil, err
49
+
}
50
+
51
+
log.Printf("total templates loaded: %d", len(loadedTemplates))
52
+
return tmpl, nil
53
+
54
+
}
-13
legit/templates/404.html
-13
legit/templates/404.html
-13
legit/templates/500.html
-13
legit/templates/500.html
+5
-9
legit/templates/commit.html
legit/templates/repo/commit.html
+5
-9
legit/templates/commit.html
legit/templates/repo/commit.html
···
1
-
{{ define "commit" }}
2
1
<html>
3
-
{{ template "head" . }}
2
+
{{ template "layouts/head" . }}
4
3
5
-
{{ template "repoheader" . }}
4
+
{{ template "layouts/repo-header" . }}
6
5
<body>
7
-
{{ template "nav" . }}
6
+
{{ template "layouts/nav" . }}
8
7
<main>
9
8
<section class="commit">
10
-
<pre>
11
-
{{- .commit.Message -}}
12
-
</pre>
9
+
<pre>{{- .commit.Message -}}</pre>
13
10
<div class="commit-info">
14
11
{{ .commit.Author.Name }} <a href="mailto:{{ .commit.Author.Email }}" class="commit-email">{{ .commit.Author.Email}}</a>
15
12
<div>{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div>
···
100
97
</section>
101
98
</main>
102
99
</body>
103
-
</html>
104
-
{{ end }}
100
+
</html>
+1
-3
legit/templates/empty.html
legit/templates/repo/empty.html
+1
-3
legit/templates/empty.html
legit/templates/repo/empty.html
+10
legit/templates/errors/404.html
+10
legit/templates/errors/404.html
+10
legit/templates/errors/500.html
+10
legit/templates/errors/500.html
+3
-5
legit/templates/file.html
legit/templates/repo/file.html
+3
-5
legit/templates/file.html
legit/templates/repo/file.html
···
1
-
{{ define "file" }}
2
1
<html>
3
-
{{ template "head" . }}
4
-
{{ template "repoheader" . }}
2
+
{{ template "layouts/head" . }}
3
+
{{ template "layouts/repo-header" . }}
5
4
<body>
6
-
{{ template "nav" . }}
5
+
{{ template "layouts/nav" . }}
7
6
<main>
8
7
<p>{{ .path }} (<a style="color: gray" href="?raw=true">view raw</a>)</p>
9
8
{{if .chroma }}
···
33
32
</main>
34
33
</body>
35
34
</html>
36
-
{{ end }}
-2
legit/templates/head.html
legit/templates/layouts/head.html
-2
legit/templates/head.html
legit/templates/layouts/head.html
+1
-1
legit/templates/index.html
+1
-1
legit/templates/index.html
+2
-4
legit/templates/keys.html
legit/templates/settings/keys.html
+2
-4
legit/templates/keys.html
legit/templates/settings/keys.html
+9
legit/templates/layouts/repo-header.html
+9
legit/templates/layouts/repo-header.html
+3
-5
legit/templates/log.html
legit/templates/repo/log.html
+3
-5
legit/templates/log.html
legit/templates/repo/log.html
···
1
-
{{ define "log" }}
2
1
<html>
3
-
{{ template "head" . }}
2
+
{{ template "layouts/head" . }}
4
3
5
-
{{ template "repoheader" . }}
4
+
{{ template "layouts/repo-header" . }}
6
5
<body>
7
-
{{ template "nav" . }}
6
+
{{ template "layouts/nav" . }}
8
7
<main>
9
8
{{ $repo := .name }}
10
9
<div class="log">
···
22
21
</main>
23
22
</body>
24
23
</html>
25
-
{{ end }}
+3
-9
legit/templates/login.html
legit/templates/user/login.html
+3
-9
legit/templates/login.html
legit/templates/user/login.html
···
1
-
{{ define "login" }}
2
1
<html>
3
-
{{ template "head" . }}
2
+
{{ template "layouts/head" . }}
4
3
5
4
<body>
6
5
<main>
7
-
<form
8
-
class="form-login"
9
-
method="post"
10
-
action="/login"
11
-
>
6
+
<form class="form-login" method="post" action="/login">
12
7
<p>
13
8
You will be redirected to bsky.social (or your PDS) to
14
9
complete login.
···
31
26
</form>
32
27
</main>
33
28
</body>
34
-
</html>
35
-
{{ end }}
29
+
</html>
+1
-3
legit/templates/new.html
legit/templates/repo/new.html
+1
-3
legit/templates/new.html
legit/templates/repo/new.html
+4
-6
legit/templates/refs.html
legit/templates/repo/refs.html
+4
-6
legit/templates/refs.html
legit/templates/repo/refs.html
···
1
-
{{ define "refs" }}
2
1
<html>
3
-
{{ template "head" . }}
2
+
{{ template "layouts/head" . }}
4
3
5
-
{{ template "repoheader" . }}
4
+
{{ template "layouts/repo-header" . }}
6
5
<body>
7
-
{{ template "nav" . }}
6
+
{{ template "layouts/nav" . }}
8
7
<main>
9
8
{{ $name := .name }}
10
9
<h3>branches</h3>
···
36
35
{{ end }}
37
36
</main>
38
37
</body>
39
-
</html>
40
-
{{ end }}
38
+
</html>
-12
legit/templates/repo-header.html
-12
legit/templates/repo-header.html
+3
-5
legit/templates/repo.html
legit/templates/repo/repo.html
+3
-5
legit/templates/repo.html
legit/templates/repo/repo.html
···
1
-
{{ define "repo" }}
2
1
<html>
3
-
{{ template "head" . }}
2
+
{{ template "layouts/head" . }}
4
3
5
-
{{ template "repoheader" . }}
4
+
{{ template "layouts/repo-header" . }}
6
5
7
6
<body>
8
-
{{ template "nav" . }}
7
+
{{ template "layouts/nav" . }}
9
8
<main>
10
9
{{ $repo := .name }}
11
10
<div class="log">
···
35
34
</main>
36
35
</body>
37
36
</html>
38
-
{{ end }}
+4
-6
legit/templates/tree.html
legit/templates/repo/tree.html
+4
-6
legit/templates/tree.html
legit/templates/repo/tree.html
···
1
-
{{ define "tree" }}
2
1
<html>
3
2
4
-
{{ template "head" . }}
3
+
{{ template "layouts/head" . }}
5
4
6
-
{{ template "repoheader" . }}
5
+
{{ template "layouts/repo-header" . }}
7
6
<body>
8
-
{{ template "nav" . }}
7
+
{{ template "layouts/nav" . }}
9
8
<main>
10
9
{{ $repo := .name }}
11
10
{{ $ref := .ref }}
···
51
50
</article>
52
51
</main>
53
52
</body>
54
-
</html>
55
-
{{ end }}
53
+
</html>