forked from pocketbase/pocketbase
-
Notifications
You must be signed in to change notification settings - Fork 0
/
registry.go
141 lines (123 loc) · 4.01 KB
/
registry.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
133
134
135
136
137
138
139
140
141
// Package template is a thin wrapper around the standard html/template
// and text/template packages that implements a convenient registry to
// load and cache templates on the fly concurrently.
//
// It was created to assist the JSVM plugin HTML rendering, but could be used in other Go code.
//
// Example:
//
// registry := template.NewRegistry()
//
// html1, err := registry.LoadFiles(
// // the files set wil be parsed only once and then cached
// "layout.html",
// "content.html",
// ).Render(map[string]any{"name": "John"})
//
// html2, err := registry.LoadFiles(
// // reuse the already parsed and cached files set
// "layout.html",
// "content.html",
// ).Render(map[string]any{"name": "Jane"})
package template
import (
"fmt"
"html/template"
"io/fs"
"path/filepath"
"strings"
"github.com/pocketbase/pocketbase/tools/store"
)
// NewRegistry creates and initializes a new templates registry with
// some defaults (eg. global "raw" template function for unescaped HTML).
//
// Use the Registry.Load* methods to load templates into the registry.
func NewRegistry() *Registry {
return &Registry{
cache: store.New[*Renderer](nil),
funcs: template.FuncMap{
"raw": func(str string) template.HTML {
return template.HTML(str)
},
},
}
}
// Registry defines a templates registry that is safe to be used by multiple goroutines.
//
// Use the Registry.Load* methods to load templates into the registry.
type Registry struct {
cache *store.Store[*Renderer]
funcs template.FuncMap
}
// AddFuncs registers new global template functions.
//
// The key of each map entry is the function name that will be used in the templates.
// If a function with the map entry name already exists it will be replaced with the new one.
//
// The value of each map entry is a function that must have either a
// single return value, or two return values of which the second has type error.
//
// Example:
//
// r.AddFuncs(map[string]any{
// "toUpper": func(str string) string {
// return strings.ToUppser(str)
// },
// ...
// })
func (r *Registry) AddFuncs(funcs map[string]any) *Registry {
for name, f := range funcs {
r.funcs[name] = f
}
return r
}
// LoadFiles caches (if not already) the specified filenames set as a
// single template and returns a ready to use Renderer instance.
//
// There must be at least 1 filename specified.
func (r *Registry) LoadFiles(filenames ...string) *Renderer {
key := strings.Join(filenames, ",")
found := r.cache.Get(key)
if found == nil {
// parse and cache
tpl, err := template.New(filepath.Base(filenames[0])).Funcs(r.funcs).ParseFiles(filenames...)
found = &Renderer{template: tpl, parseError: err}
r.cache.Set(key, found)
}
return found
}
// LoadString caches (if not already) the specified inline string as a
// single template and returns a ready to use Renderer instance.
func (r *Registry) LoadString(text string) *Renderer {
found := r.cache.Get(text)
if found == nil {
// parse and cache (using the text as key)
tpl, err := template.New("").Funcs(r.funcs).Parse(text)
found = &Renderer{template: tpl, parseError: err}
r.cache.Set(text, found)
}
return found
}
// LoadFS caches (if not already) the specified fs and globPatterns
// pair as single template and returns a ready to use Renderer instance.
//
// There must be at least 1 file matching the provided globPattern(s)
// (note that most file names serves as glob patterns matching themselves).
func (r *Registry) LoadFS(fsys fs.FS, globPatterns ...string) *Renderer {
key := fmt.Sprintf("%v%v", fsys, globPatterns)
found := r.cache.Get(key)
if found == nil {
// find the first file to use as template name (it is required when specifying Funcs)
var firstFilename string
if len(globPatterns) > 0 {
list, _ := fs.Glob(fsys, globPatterns[0])
if len(list) > 0 {
firstFilename = filepath.Base(list[0])
}
}
tpl, err := template.New(firstFilename).Funcs(r.funcs).ParseFS(fsys, globPatterns...)
found = &Renderer{template: tpl, parseError: err}
r.cache.Set(key, found)
}
return found
}