101 lines
1.9 KiB
Go
101 lines
1.9 KiB
Go
package template
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"html/template"
|
||
|
|
"io"
|
||
|
|
"io/fs"
|
||
|
|
"log/slog"
|
||
|
|
"os"
|
||
|
|
"path"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Service struct {
|
||
|
|
baseDir string
|
||
|
|
cache map[string]*template.Template
|
||
|
|
}
|
||
|
|
|
||
|
|
func createFuncMap() template.FuncMap {
|
||
|
|
funcMap := template.FuncMap{
|
||
|
|
"dict": func(pairs ...any) (map[string]any, error) {
|
||
|
|
if len(pairs)%2 != 0 {
|
||
|
|
return nil, ErrDictOddArgs
|
||
|
|
}
|
||
|
|
m := make(map[string]any)
|
||
|
|
for i := 0; i < len(pairs); i += 2 {
|
||
|
|
key, ok := pairs[i].(string)
|
||
|
|
if !ok {
|
||
|
|
return nil, ErrDictKeyNotString
|
||
|
|
}
|
||
|
|
m[key] = pairs[i+1]
|
||
|
|
}
|
||
|
|
return m, nil
|
||
|
|
},
|
||
|
|
"default": func(fallback string, value any) any {
|
||
|
|
if value == nil {
|
||
|
|
return fallback
|
||
|
|
}
|
||
|
|
return value
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
return funcMap
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewService(uiDir string) (*Service, error) {
|
||
|
|
cache := make(map[string]*template.Template)
|
||
|
|
|
||
|
|
uiFS := os.DirFS(uiDir)
|
||
|
|
|
||
|
|
funcMap := createFuncMap()
|
||
|
|
|
||
|
|
err := fs.WalkDir(uiFS, "pages", func(filePath string, d fs.DirEntry, err error) error {
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if d.IsDir() {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
if !strings.HasSuffix(filePath, ".html") {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
relPath := strings.TrimPrefix(filePath, "pages/")
|
||
|
|
|
||
|
|
tmpl, err := template.New(path.Base(filePath)).Funcs(funcMap).ParseFS(uiFS, filePath, "layouts/*.html")
|
||
|
|
if err != nil {
|
||
|
|
slog.Info("parse templates failed", "file", filePath, "error", err)
|
||
|
|
return ErrParse
|
||
|
|
}
|
||
|
|
cache[relPath] = tmpl
|
||
|
|
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
if errors.Is(err, ErrParse) {
|
||
|
|
return nil, ErrParse
|
||
|
|
}
|
||
|
|
slog.Info("walk pages failed", "dir", uiDir, "error", err)
|
||
|
|
return nil, ErrWalk
|
||
|
|
}
|
||
|
|
|
||
|
|
return &Service{
|
||
|
|
baseDir: uiDir,
|
||
|
|
cache: cache,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (e *Service) Render(w io.Writer, pageName string, data any) error {
|
||
|
|
tmpl, ok := e.cache[pageName]
|
||
|
|
if !ok {
|
||
|
|
slog.Info("no such template", "page", pageName)
|
||
|
|
return ErrNoSuchTemplate
|
||
|
|
}
|
||
|
|
if err := tmpl.Execute(w, data); err != nil {
|
||
|
|
slog.Info("render template failed", "page", pageName, "error", err)
|
||
|
|
return ErrRender
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|