package server import ( "embed" "fmt" "html/template" "net/http" "path/filepath" "sync" ) //go:embed templates/**/*.html var templatesFS embed.FS // Templates holds lazily-parsed templates for each page. type Templates struct { once sync.Once pages map[string]*template.Template err error } // load parses all templates from the embedded filesystem on first call. func (t *Templates) load() error { t.once.Do(func() { pages := make(map[string]*template.Template) funcMap := template.FuncMap{ "add": func(a, b int) int { return a + b }, "sub": func(a, b int) int { return a - b }, "supportedEcosystems": supportedEcosystems, "ecosystemBadgeClass": ecosystemBadgeClasses, "ecosystemPillClass": ecosystemPillClasses, "ecosystemBadgeLabel": ecosystemBadgeLabel, "dict": templateDict, } pageFiles, err := templatesFS.ReadDir("templates/pages") if err != nil { t.err = err return } for _, pageFile := range pageFiles { if pageFile.IsDir() { continue } pageName := pageFile.Name() pageName = pageName[:len(pageName)-len(filepath.Ext(pageName))] tmpl, err := template.New("").Funcs(funcMap).ParseFS(templatesFS, "templates/layout/*.html", "templates/components/*.html", "templates/pages/"+pageFile.Name(), ) if err != nil { t.err = err return } pages[pageName] = tmpl } t.pages = pages }) return t.err } // Render renders a page template with the given data. func (t *Templates) Render(w http.ResponseWriter, pageName string, data any) error { if err := t.load(); err != nil { return err } w.Header().Set("Content-Type", "text/html; charset=utf-8") tmpl, ok := t.pages[pageName] if !ok { return http.ErrAbortHandler } return tmpl.ExecuteTemplate(w, "base", data) } // templateDict builds a map from alternating key/value arguments, so a // component can be invoked with named parameters rather than being handed a // whole page struct it would have to reach through. func templateDict(values ...any) (map[string]any, error) { const pair = 2 if len(values)%pair != 0 { return nil, fmt.Errorf("dict: got %d arguments, want an even number of key/value pairs", len(values)) } out := make(map[string]any, len(values)/pair) for i := 0; i < len(values); i += pair { key, ok := values[i].(string) if !ok { return nil, fmt.Errorf("dict: key %d is %T, want string", i, values[i]) } out[key] = values[i+1] } return out, nil }