Commit 0a4bb99d63
Verified · cmc ci/build: success ci/test: success ci/vuln: success
Layout: unified · split
internal/web/web.go +37 −5
| @@ -4,10 +4,13 @@ package web | ||
| 4 | 4 | |
| 5 | 5 | import ( |
| 6 | 6 | "embed" |
| 7 | "fmt" | |
| 7 | 8 | "html/template" |
| 8 | 9 | "io" |
| 10 | "io/fs" | |
| 9 | 11 | "reflect" |
| 10 | 12 | "runtime/debug" |
| 13 | "sort" | |
| 11 | 14 | "strconv" |
| 12 | 15 | "strings" |
| 13 | 16 | "sync" |
| @@ -225,13 +228,42 @@ var funcs = template.FuncMap{ | ||
| 225 | 228 | }, |
| 226 | 229 | } |
| 227 | 230 | |
| 231 | // pages holds every page template parsed once with the layout, at | |
| 232 | // start-up: a template that does not parse fails the process before it | |
| 233 | // serves anything, rather than the first visit to a rarely-hit page, and | |
| 234 | // a request no longer re-parses the layout (#116). | |
| 235 | var pages = func() map[string]*template.Template { | |
| 236 | entries, err := fs.ReadDir(templateFS, "templates") | |
| 237 | if err != nil { | |
| 238 | panic(err) | |
| 239 | } | |
| 240 | m := map[string]*template.Template{} | |
| 241 | for _, e := range entries { | |
| 242 | name := e.Name() | |
| 243 | if name == "layout.html" || !strings.HasSuffix(name, ".html") { | |
| 244 | continue | |
| 245 | } | |
| 246 | layout := template.Must(template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html")) | |
| 247 | m[name] = template.Must(layout.ParseFS(templateFS, "templates/"+name)) | |
| 248 | } | |
| 249 | return m | |
| 250 | }() | |
| 251 | ||
| 252 | // Pages lists the page template names, for tests that render each one. | |
| 253 | func Pages() []string { | |
| 254 | names := make([]string, 0, len(pages)) | |
| 255 | for name := range pages { | |
| 256 | names = append(names, name) | |
| 257 | } | |
| 258 | sort.Strings(names) | |
| 259 | return names | |
| 260 | } | |
| 261 | ||
| 228 | 262 | // Render executes the named page template with the shared layout. |
| 229 | 263 | func Render(w io.Writer, page string, data any) error { |
| 230 | t, err := template.Must( | |
| 231 | template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html"), | |
| 232 | ).ParseFS(templateFS, "templates/"+page) | |
| 233 | if err != nil { | |
| 234 | return err | |
| 264 | t, ok := pages[page] | |
| 265 | if !ok { | |
| 266 | return fmt.Errorf("no page template %q", page) | |
| 235 | 267 | } |
| 236 | 268 | return t.ExecuteTemplate(w, "layout", data) |
| 237 | 269 | } |
internal/web/web_test.go added +19
| @@ -0,0 +1,19 @@ | ||
| 1 | package web | |
| 2 | ||
| 3 | import "testing" | |
| 4 | ||
| 5 | // Every page template parses with the layout at start-up, so a broken | |
| 6 | // template fails the process rather than the first visit to its page. | |
| 7 | // Package init has already done the work; this asserts it covered every | |
| 8 | // file (#116). | |
| 9 | func TestEveryPageTemplateParses(t *testing.T) { | |
| 10 | names := Pages() | |
| 11 | if len(names) < 30 { | |
| 12 | t.Fatalf("only %d page templates parsed: %v", len(names), names) | |
| 13 | } | |
| 14 | for _, want := range []string{"tree.html", "blob.html", "mr.html", "issue.html", "dashboard.html", "registered.html"} { | |
| 15 | if _, ok := pages[want]; !ok { | |
| 16 | t.Errorf("%s not parsed", want) | |
| 17 | } | |
| 18 | } | |
| 19 | } | |