Pass template as value

This commit is contained in:
Elara 2023-10-30 21:18:35 -07:00
parent 69e515326f
commit 2a87e4781c
3 changed files with 33 additions and 73 deletions

View File

@ -26,7 +26,7 @@ import (
// Namespace represents a collection of templates that can include each other // Namespace represents a collection of templates that can include each other
type Namespace struct { type Namespace struct {
mu sync.Mutex mu sync.Mutex
tmpls map[string]*Template tmpls map[string]Template
vars map[string]reflect.Value vars map[string]reflect.Value
tags map[string]Tag tags map[string]Tag
} }
@ -34,7 +34,7 @@ type Namespace struct {
// New returns a new template namespace // New returns a new template namespace
func New() *Namespace { func New() *Namespace {
return &Namespace{ return &Namespace{
tmpls: map[string]*Template{}, tmpls: map[string]Template{},
vars: map[string]reflect.Value{}, vars: map[string]reflect.Value{},
tags: map[string]Tag{}, tags: map[string]Tag{},
} }
@ -72,7 +72,7 @@ func (n *Namespace) WithTagMap(m map[string]Tag) *Namespace {
// GetTemplate tries to get a template from the namespace's template map. // GetTemplate tries to get a template from the namespace's template map.
// If it finds the template, it returns the template and true. If it // If it finds the template, it returns the template and true. If it
// doesn't find it, it returns nil and false. // doesn't find it, it returns nil and false.
func (n *Namespace) GetTemplate(name string) (*Template, bool) { func (n *Namespace) GetTemplate(name string) (Template, bool) {
n.mu.Lock() n.mu.Lock()
defer n.mu.Unlock() defer n.mu.Unlock()
t, ok := n.tmpls[name] t, ok := n.tmpls[name]

View File

@ -26,6 +26,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
"sync"
"go.elara.ws/salix/ast" "go.elara.ws/salix/ast"
"go.elara.ws/salix/parser" "go.elara.ws/salix/parser"
@ -39,23 +40,24 @@ type NamedReader interface {
// Parse parses a salix template from a NamedReader, which is an io.Reader // Parse parses a salix template from a NamedReader, which is an io.Reader
// with a Name method that returns a string. os.File implements NamedReader. // with a Name method that returns a string. os.File implements NamedReader.
func (n *Namespace) Parse(r NamedReader) (*Template, error) { func (n *Namespace) Parse(r NamedReader) (Template, error) {
return n.ParseWithName(r.Name(), r) return n.ParseWithName(r.Name(), r)
} }
// ParseWithFilename parses a salix template from r, using the given name. // ParseWithFilename parses a salix template from r, using the given name.
func (n *Namespace) ParseWithName(name string, r io.Reader) (*Template, error) { func (n *Namespace) ParseWithName(name string, r io.Reader) (Template, error) {
astVal, err := parser.ParseReader(name, r) astVal, err := parser.ParseReader(name, r)
if err != nil { if err != nil {
return nil, err return Template{}, err
} }
t := &Template{ t := Template{
ns: n, ns: n,
name: name, name: name,
ast: astVal.([]ast.Node), ast: astVal.([]ast.Node),
tags: map[string]Tag{}, tags: map[string]Tag{},
vars: map[string]reflect.Value{}, vars: map[string]reflect.Value{},
macroMtx: &sync.Mutex{},
} }
performWhitespaceMutations(t.ast) performWhitespaceMutations(t.ast)
@ -67,10 +69,10 @@ func (n *Namespace) ParseWithName(name string, r io.Reader) (*Template, error) {
} }
// ParseFile parses the file at path as a salix template. It uses the path as the name. // ParseFile parses the file at path as a salix template. It uses the path as the name.
func (t *Namespace) ParseFile(path string) (*Template, error) { func (t *Namespace) ParseFile(path string) (Template, error) {
fl, err := os.Open(path) fl, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return Template{}, err
} }
defer fl.Close() defer fl.Close()
return t.Parse(fl) return t.Parse(fl)
@ -95,10 +97,10 @@ func (t *Namespace) ParseGlob(glob string) error {
} }
// ParseFile parses a file at the given path in a filesystem. It uses the path as the name. // ParseFile parses a file at the given path in a filesystem. It uses the path as the name.
func (t *Namespace) ParseFS(fsys fs.FS, path string) (*Template, error) { func (t *Namespace) ParseFS(fsys fs.FS, path string) (Template, error) {
fl, err := fsys.Open(path) fl, err := fsys.Open(path)
if err != nil { if err != nil {
return nil, err return Template{}, err
} }
defer fl.Close() defer fl.Close()
return t.ParseWithName(path, fl) return t.ParseWithName(path, fl)
@ -123,12 +125,12 @@ func (t *Namespace) ParseFSGlob(fsys fs.FS, glob string) error {
} }
// ParseString parses a string using the given filename. // ParseString parses a string using the given filename.
func (t *Namespace) ParseString(filename, tmpl string) (*Template, error) { func (t *Namespace) ParseString(filename, tmpl string) (Template, error) {
return t.ParseWithName(filename, strings.NewReader(tmpl)) return t.ParseWithName(filename, strings.NewReader(tmpl))
} }
// ParseString parses bytes using the given filename. // ParseString parses bytes using the given filename.
func (t *Namespace) ParseBytes(filename string, tmpl []byte) (*Template, error) { func (t *Namespace) ParseBytes(filename string, tmpl []byte) (Template, error) {
return t.ParseWithName(filename, bytes.NewReader(tmpl)) return t.ParseWithName(filename, bytes.NewReader(tmpl))
} }

View File

@ -63,86 +63,44 @@ type Template struct {
tags map[string]Tag tags map[string]Tag
vars map[string]reflect.Value vars map[string]reflect.Value
macroMtx sync.Mutex macroMtx *sync.Mutex
macros map[string][]ast.Node macros map[string][]ast.Node
} }
// WithVarMap returns a copy of the template with its variable map set to m. // WithVarMap returns a copy of the template with its variable map set to m.
func (t *Template) WithVarMap(m map[string]any) *Template { func (t Template) WithVarMap(m map[string]any) Template {
newTmpl := &Template{ t.vars = map[string]reflect.Value{}
ns: t.ns,
name: t.name,
ast: t.ast,
escapeHTML: t.escapeHTML,
tags: t.tags,
vars: map[string]reflect.Value{},
}
if m != nil { if m != nil {
for k, v := range m { for k, v := range m {
newTmpl.vars[k] = reflect.ValueOf(v) t.vars[k] = reflect.ValueOf(v)
} }
} }
return t
return newTmpl
} }
// WithTagMap returns a copy of the template with its tag map set to m. // WithTagMap returns a copy of the template with its tag map set to m.
func (t *Template) WithTagMap(m map[string]Tag) *Template { func (t Template) WithTagMap(m map[string]Tag) Template {
// Make sure the tag map is never nil to avoid panics // Make sure the tag map is never nil to avoid panics
if m == nil { if m == nil {
m = map[string]Tag{} m = map[string]Tag{}
} }
t.tags = m
return &Template{ return t
ns: t.ns,
name: t.name,
ast: t.ast,
escapeHTML: t.escapeHTML,
tags: m,
vars: t.vars,
}
} }
// WithEscapeHTML returns a copy of the template with HTML escaping enabled or disabled. // WithEscapeHTML returns a copy of the template with HTML escaping enabled or disabled.
// The HTML escaping functionality is NOT context-aware. // The HTML escaping functionality is NOT context-aware.
// Using the HTML type allows you to get around the escaping if needed. // Using the HTML type allows you to get around the escaping if needed.
func (t *Template) WithEscapeHTML(b bool) *Template { func (t Template) WithEscapeHTML(b bool) Template {
t.escapeHTML = true t.escapeHTML = true
return &Template{ return t
ns: t.ns,
name: t.name,
ast: t.ast,
escapeHTML: b,
tags: t.tags,
vars: t.vars,
}
} }
// Execute executes a parsed template and writes // Execute executes a parsed template and writes
// the result to w. // the result to w.
func (t *Template) Execute(w io.Writer) error { func (t Template) Execute(w io.Writer) error {
// Create a new template to give each execution t.macros = map[string][]ast.Node{}
// its own macros return t.execute(w, t.ast, nil)
tmpl := &Template{
ns: t.ns,
name: t.name,
ast: t.ast,
escapeHTML: t.escapeHTML,
tags: t.tags,
vars: t.vars,
macros: map[string][]ast.Node{},
}
return tmpl.execute(w, t.ast, nil)
} }
func (t *Template) execute(w io.Writer, nodes []ast.Node, local map[string]any) error { func (t *Template) execute(w io.Writer, nodes []ast.Node, local map[string]any) error {