Files
uPortal2/backend/internal/template/service.go
T

101 lines
1.9 KiB
Go
Raw Normal View History

2026-10-21 13:17:44 +03:00
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
}