Skip to content

Instantly share code, notes, and snippets.

@nicolasparada
Last active February 11, 2024 05:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicolasparada/7e46682d8f99f5944adc380f5232de37 to your computer and use it in GitHub Desktop.
Save nicolasparada/7e46682d8f99f5944adc380f5232de37 to your computer and use it in GitHub Desktop.
renderer.go
package renderer
import (
"fmt"
"html/template"
"io"
"io/fs"
"path/filepath"
"sync"
)
type Renderer struct {
FS fs.FS
BaseDir string
IncludePatters []string
mu sync.Mutex
templates map[string]*template.Template
}
func New(fs fs.FS, baseDir string, includePatterns ...string) *Renderer {
return &Renderer{
FS: fs,
BaseDir: baseDir,
IncludePatters: includePatterns,
templates: map[string]*template.Template{},
}
}
func (r *Renderer) Render(w io.Writer, name string, data any) error {
tmpl, ok := r.template(name)
if !ok {
tmpl, err := template.New(name).Funcs(funcMap).ParseFS(r.FS, r.patterns(name)...)
if err != nil {
return err
}
r.setTemplate(name, tmpl)
return tmpl.Execute(w, data)
}
return tmpl.Execute(w, data)
}
func (r *Renderer) patterns(name string) []string {
out := make([]string, len(r.IncludePatters)+1)
for i, p := range r.IncludePatters {
out[i] = filepath.Join(r.BaseDir, p)
}
out[len(out)-1] = filepath.Join(r.BaseDir, name)
return out
}
func (r *Renderer) template(name string) (*template.Template, bool) {
r.mu.Lock()
defer r.mu.Unlock()
t, ok := r.templates[name]
return t, ok
}
func (r *Renderer) setTemplate(name string, t *template.Template) {
r.mu.Lock()
defer r.mu.Unlock()
r.templates[name] = t
}
var funcMap = template.FuncMap{
"dict": func(keyvals ...any) (map[string]any, error) {
if len(keyvals)%2 != 0 {
return nil, fmt.Errorf("odd number of keyvals")
}
out := map[string]any{}
for i := 0; i < len(keyvals); i += 2 {
k, ok := keyvals[i].(string)
if !ok {
return nil, fmt.Errorf("key not a string: %T", keyvals[i])
}
out[k] = keyvals[i+1]
}
return out, nil
},
"list": func(elems ...any) []any {
return elems
},
}
package web
import (
"embed"
"net/http"
"sync"
"acme.example/renderer"
)
//go:embed templates/includes/*.tmpl templates/*.tmpl
var templatesFS embed.FS
type Handler struct {
renderer *renderer.Renderer
once sync.Once
handler http.Handler
}
func (h *Handler) init() {
h.renderer = renderer.New(templatesFS, "templates", "includes/*.tmpl")
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
h.renderer.Render(w, "welcome.tmpl", nil)
})
h.handler = mux
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.once.Do(h.init)
h.handler.ServeHTTP(w, r)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment