-
Notifications
You must be signed in to change notification settings - Fork 0
/
fs_template_handler.go
132 lines (115 loc) · 4 KB
/
fs_template_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package handlers
import (
"html/template"
"io/fs"
"log/slog"
"net/http"
"strings"
"github.com/relvox/iridescence_go/files"
"github.com/relvox/iridescence_go/logging"
"github.com/relvox/iridescence_go/middleware"
)
// TemplateModelGetter is a function type that takes a template name and returns a model (data) for that template.
type TemplateModelGetter func(template string) any
// FSTemplateHandler is a struct that handles template rendering from a file system.
type FSTemplateHandler struct {
fs.FS // Embedding file system interface to access templates.
Patterns []string // Selecting templates within the FS.
FuncMap template.FuncMap // Custom template functions.
GetModel TemplateModelGetter // Function to get the model for a given template.
Root *template.Template // Root template object.
DebugMode bool // Flag to indicate debug mode.
Log *slog.Logger // Logger instance.
templateExtensions map[string]string // an internal cache of extensions to match file for requests.
}
// NewFSTemplateHandler creates a new instance of FSTemplateHandler with the provided file system and file patterns.
func NewFSTemplateHandler(fs fs.FS, patterns ...string) FSTemplateHandler {
return FSTemplateHandler{
FS: fs,
Patterns: patterns,
Root: template.New("root"),
}
}
// WithModelGetter sets the function to retrieve models for templates.
func (s FSTemplateHandler) WithModelGetter(getter TemplateModelGetter) FSTemplateHandler {
s.GetModel = getter
return s
}
// WithModelMap sets a map of models for templates.
func (s FSTemplateHandler) WithModelMap(modelMap map[string]any) FSTemplateHandler {
s.GetModel = func(template string) any {
return modelMap[template]
}
return s
}
// WithFuncs sets custom functions to be used in templates.
func (s FSTemplateHandler) WithFuncs(funcs template.FuncMap) FSTemplateHandler {
s.FuncMap = funcs
s.Root.Funcs(s.FuncMap)
return s
}
// WithLog sets the logger for the handler.
func (s FSTemplateHandler) WithLog(log *slog.Logger) FSTemplateHandler {
s.Log = log
return s
}
// WithDebug enables debug mode.
func (s FSTemplateHandler) WithDebug() FSTemplateHandler {
s.DebugMode = true
return s
}
// Parse parses the templates from the file system.
func (s FSTemplateHandler) Parse() FSTemplateHandler {
s.Root = template.Must(s.Root.ParseFS(s.FS, s.Patterns...))
for _, pat := range s.Patterns {
patFiles, err := files.ListFS(s.FS, ".", pat)
if err != nil {
s.Log.Error("error listing files", slog.String("pattern", pat), logging.Error(err))
continue
}
for _, file := range patFiles {
_, name, ext := files.Split(file)
s.templateExtensions[name] = ext
}
}
return s
}
// ServeHTTP handles HTTP requests and renders templates.
func (s FSTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
contextLog := r.Context().Value(middleware.Key("log"))
if contextLog != nil {
s.Log = contextLog.(*slog.Logger)
}
if s.DebugMode {
// Re-parse templates in debug mode for each request.
s.Root = template.Must(template.New("root").Funcs(s.FuncMap).ParseFS(s.FS, s.Patterns...))
}
requestPath := r.URL.Path
var requestPaths []string
if strings.Contains(requestPath, "|") {
// Support for multiple template paths.
requestPaths = strings.Split(requestPath, "|")
} else {
requestPaths = []string{requestPath}
}
for _, requestPath := range requestPaths {
model := s.GetModel(requestPath)
requestPath = strings.TrimLeft(requestPath, "/")
tmplName := requestPath + s.templateExtensions[requestPath]
if model == nil {
s.Log.Error("missing model for template", slog.String("template", tmplName))
continue
}
tmpl := s.Root.Lookup(tmplName)
if tmpl == nil {
s.Log.Error("Failed to lookup template", slog.String("template", tmplName))
continue
}
// Execute the template with the model.
err := tmpl.Execute(w, model)
if err != nil {
s.Log.Error("Failed to execute template", slog.String("template", tmplName), logging.Error(err))
continue
}
}
}