[#3476] added raw template function

This commit is contained in:
Gani Georgiev
2023-10-08 23:17:13 +03:00
parent 7fa1ff53c9
commit b8219af941
35 changed files with 141 additions and 57 deletions
+47 -7
View File
@@ -25,17 +25,24 @@ import (
"fmt"
"html/template"
"io/fs"
"path/filepath"
"strings"
"github.com/pocketbase/pocketbase/tools/store"
)
// NewRegistry creates and initializes a new blank templates registry.
// 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)
},
},
}
}
@@ -44,6 +51,31 @@ func NewRegistry() *Registry {
// 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
@@ -57,7 +89,7 @@ func (r *Registry) LoadFiles(filenames ...string) *Renderer {
if found == nil {
// parse and cache
tpl, err := template.ParseFiles(filenames...)
tpl, err := template.New(filepath.Base(filenames[0])).Funcs(r.funcs).ParseFiles(filenames...)
found = &Renderer{template: tpl, parseError: err}
r.cache.Set(key, found)
}
@@ -72,7 +104,7 @@ func (r *Registry) LoadString(text string) *Renderer {
if found == nil {
// parse and cache (using the text as key)
tpl, err := template.New("").Parse(text)
tpl, err := template.New("").Funcs(r.funcs).Parse(text)
found = &Renderer{template: tpl, parseError: err}
r.cache.Set(text, found)
}
@@ -85,14 +117,22 @@ func (r *Registry) LoadString(text string) *Renderer {
//
// 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(fs fs.FS, globPatterns ...string) *Renderer {
key := fmt.Sprintf("%v%v", fs, globPatterns)
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 {
// parse and cache
tpl, err := template.ParseFS(fs, globPatterns...)
// 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)
}
+45 -11
View File
@@ -8,6 +8,18 @@ import (
"testing"
)
func checkRegistryFuncs(t *testing.T, r *Registry, expectedFuncs ...string) {
if v := len(r.funcs); v != len(expectedFuncs) {
t.Fatalf("Expected total %d funcs, got %d", len(expectedFuncs), v)
}
for _, name := range expectedFuncs {
if _, ok := r.funcs[name]; !ok {
t.Fatalf("Missing %q func", name)
}
}
}
func TestNewRegistry(t *testing.T) {
r := NewRegistry()
@@ -18,6 +30,28 @@ func TestNewRegistry(t *testing.T) {
if v := r.cache.Length(); v != 0 {
t.Fatalf("Expected cache store length to be 0, got %d", v)
}
checkRegistryFuncs(t, r, "raw")
}
func TestRegistryAddFuncs(t *testing.T) {
r := NewRegistry()
r.AddFuncs(map[string]any{
"test": func(a string) string { return a + "-TEST" },
})
checkRegistryFuncs(t, r, "raw", "test")
result, err := r.LoadString(`{{.|test}}`).Render("example")
if err != nil {
t.Fatalf("Unexpected Render() error, got %v", err)
}
expected := "example-TEST"
if result != expected {
t.Fatalf("Expected Render() result %q, got %q", expected, result)
}
}
func TestRegistryLoadFiles(t *testing.T) {
@@ -48,10 +82,10 @@ func TestRegistryLoadFiles(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "base.html"), []byte(`Base:{{template "content"}}`), 0644); err != nil {
if err := os.WriteFile(filepath.Join(dir, "base.html"), []byte(`Base:{{template "content" .}}`), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "content.html"), []byte(`{{define "content"}}Content:123{{end}}`), 0644); err != nil {
if err := os.WriteFile(filepath.Join(dir, "content.html"), []byte(`{{define "content"}}Content:{{.|raw}}{{end}}`), 0644); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
@@ -74,12 +108,12 @@ func TestRegistryLoadFiles(t *testing.T) {
t.Fatalf("Expected renderer parseError to be nil, got %v", renderer.parseError)
}
result, err := renderer.Render(nil)
result, err := renderer.Render("<h1>123</h1>")
if err != nil {
t.Fatalf("Unexpected Render() error, got %v", err)
}
expected := "Base:Content:123"
expected := "Base:Content:<h1>123</h1>"
if result != expected {
t.Fatalf("Expected Render() result %q, got %q", expected, result)
}
@@ -110,7 +144,7 @@ func TestRegistryLoadString(t *testing.T) {
})
t.Run("valid template string", func(t *testing.T) {
txt := `test {{.}}`
txt := `test {{.|raw}}`
r.LoadString(txt)
@@ -128,12 +162,12 @@ func TestRegistryLoadString(t *testing.T) {
t.Fatalf("Expected renderer parseError to be nil, got %v", renderer.parseError)
}
result, err := renderer.Render(123)
result, err := renderer.Render("<h1>123</h1>")
if err != nil {
t.Fatalf("Unexpected Render() error, got %v", err)
}
expected := "test 123"
expected := "test <h1>123</h1>"
if result != expected {
t.Fatalf("Expected Render() result %q, got %q", expected, result)
}
@@ -173,10 +207,10 @@ func TestRegistryLoadFS(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "base.html"), []byte(`Base:{{template "content"}}`), 0644); err != nil {
if err := os.WriteFile(filepath.Join(dir, "base.html"), []byte(`Base:{{template "content" .}}`), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "content.html"), []byte(`{{define "content"}}Content:123{{end}}`), 0644); err != nil {
if err := os.WriteFile(filepath.Join(dir, "content.html"), []byte(`{{define "content"}}Content:{{.|raw}}{{end}}`), 0644); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
@@ -203,12 +237,12 @@ func TestRegistryLoadFS(t *testing.T) {
t.Fatalf("Expected renderer parseError to be nil, got %v", renderer.parseError)
}
result, err := renderer.Render(nil)
result, err := renderer.Render("<h1>123</h1>")
if err != nil {
t.Fatalf("Unexpected Render() error, got %v", err)
}
expected := "Base:Content:123"
expected := "Base:Content:<h1>123</h1>"
if result != expected {
t.Fatalf("Expected Render() result %q, got %q", expected, result)
}