coverage~bigbes/sr-ht-spec64cae3afweb/templates.go

Coverage
76.9% 10/13 statements
Δ
+0.0
Blob
eff6a47
Uncovered L50L71L87-L89
1 package web
2
3 import (
4 "embed"
5 "html/template"
6 "log/slog"
7 "net/http"
8 "strings"
9
10 "go.bigb.es/auxilia/scribe"
11 "sourcecraft.dev/bigbes/sr-ht-ecore/chrome"
12 "sourcecraft.dev/bigbes/sr-ht-ecore/pages"
13 )
14
15 // tmplFS holds the page templates. sr-ht-ecore's pages.Load discovers them:
16 // layout.html is the chrome every page is executed through, a file whose name
17 // starts with "_" is a partial parsed into every set, and everything else is a
18 // page that must define "content".
19 //
20 // The glob and not the bare directory, because Go's embed excludes names
21 // starting with '_' when it walks a directory and _threads.html is exactly such
22 // a name.
23 //
24 //go:embed templates/*.html
25 var tmplFS embed.FS
26
27 // staticFS holds the built assets: the hashed stylesheet produced by `make css`
28 // and the logo. It is compiled into the binary, which is why `make css` alone
29 // does not restyle a running daemon — see the package doc.
30 //
31 //go:embed static
32 var staticFS embed.FS
33
34 // funcMap holds this service's own template helpers.
35 //
36 // It carries only what is ours: pages.Load merges it over chrome.Funcs — the
37 // generic helpers every custom service on this instance was carrying its own
38 // copy of, `shortsha` among them — in that order, so a name may be shadowed
39 // deliberately rather than by accident of map ordering. Nothing shadows one
40 // today, and a helper that diverged from the shared spelling of the same name
41 // would be the drift ecore exists to prevent.
42 var funcMap = template.FuncMap{
43 // indent renders a tree depth as non-breaking space, so the space view's
44 // hierarchy reads as a hierarchy without a nested-list template recursion.
45 // A negative depth (a level-1 heading, once decremented) indents nothing.
46 35 "indent": func(depth int) template.HTML {
47 35 if depth <= 0 {
48 35 return ""
49 35 }
50 0 return template.HTML(strings.Repeat("&nbsp;&nbsp;&nbsp;&nbsp;", depth))
51 },
52 // dec turns a 1-based heading level into a 0-based indent depth.
53 10 "dec": func(n int) int { return n - 1 },
54 }
55
56 // blockThreadsTmpl is the per-block comment markup, parsed on its own so the
57 // diff renderer — which builds its HTML in Go and cannot reach a page template
58 // through the usual {{template}} call — and the proposal page cannot drift into
59 // two spellings of a thread. It is the same file the page set parses as a
60 // partial, so there is one spelling and not two.
61 //
62 // A missing define is a build-time mistake in this package, so it panics at
63 // init the way template.Must does, rather than yielding a page with the
64 // comments silently absent.
65 1 var blockThreadsTmpl = func() *template.Template {
66 1 t := template.Must(template.New("_threads.html").
67 1 Funcs(chrome.Funcs()).Funcs(funcMap).
68 1 ParseFS(tmplFS, "templates/_threads.html"))
69 1 blocks := t.Lookup("blockthreads")
70 1 if blocks == nil {
71 0 panic(`web: templates/_threads.html does not define "blockthreads"`)
72 }
73 1 return blocks
74 }()
75
76 // renderError renders the chrome-wrapped error page: this service's view struct
77 // around ecore's shared error body.
78 //
79 // An empty message takes the standard sentence for the status, so a refusal
80 // with nothing of its own to add says what every other service on this instance
81 // says. It never recurses on failure — pages.Render answers the response itself
82 // and hands back only a line for the log.
83 34 func (s *Server) renderError(w http.ResponseWriter, r *http.Request, status int, message string) {
84 34 vd := s.view(r, http.StatusText(status))
85 34 vd.Data = pages.Error(status, message)
86 34 if err := s.pages.Render(w, status, pages.ErrorPage, vd); err != nil {
87 0 slog.ErrorContext(r.Context(), "rendering the error page failed after it was answered",
88 0 "status", status, "method", r.Method, "path", r.URL.Path, scribe.Err(err))
89 0 }
90 }