1
0
mirror of https://github.com/golang/go synced 2024-11-21 21:44:40 -07:00

text/template: new, simpler API

The Set type is gone. Instead, templates are automatically associated by
being parsed together; nested definitions implicitly create associations.
Only associated templates can invoke one another.

This approach dramatically reduces the breadth of the construction API.

For now, html/template is deleted from src/pkg/Makefile, so this can
be checked in. Nothing in the tree depends on it. It will be updated next.

R=dsymonds, adg, rsc, r, gri, mikesamuel, nigeltao
CC=golang-dev
https://golang.org/cl/5415060
This commit is contained in:
Rob Pike 2011-11-23 20:17:22 -08:00
parent af081cd43e
commit f56db6f534
23 changed files with 658 additions and 740 deletions

View File

@ -68,7 +68,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
} }
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
t, err := template.ParseFile(tmpl + ".html") t, err := template.ParseFiles(tmpl + ".html")
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -33,14 +33,14 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
p = &Page{Title: title} p = &Page{Title: title}
} }
t, _ := template.ParseFile("edit.html") t, _ := template.ParseFiles("edit.html")
t.Execute(w, p) t.Execute(w, p)
} }
func viewHandler(w http.ResponseWriter, r *http.Request) { func viewHandler(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[lenPath:] title := r.URL.Path[lenPath:]
p, _ := loadPage(title) p, _ := loadPage(title)
t, _ := template.ParseFile("view.html") t, _ := template.ParseFiles("view.html")
t.Execute(w, p) t.Execute(w, p)
} }

View File

@ -55,7 +55,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
} }
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
t, err := template.ParseFile(tmpl+".html", nil) t, err := template.ParseFiles(tmpl+".html", nil)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -51,7 +51,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
} }
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
t, _ := template.ParseFile(tmpl+".html", nil) t, _ := template.ParseFiles(tmpl+".html", nil)
t.Execute(w, p) t.Execute(w, p)
} }

View File

@ -58,7 +58,7 @@ var templates = make(map[string]*template.Template)
func init() { func init() {
for _, tmpl := range []string{"edit", "view"} { for _, tmpl := range []string{"edit", "view"} {
t := template.Must(template.ParseFile(tmpl + ".html")) t := template.Must(template.ParseFiles(tmpl + ".html"))
templates[tmpl] = t templates[tmpl] = t
} }
} }

View File

@ -476,7 +476,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
p = &Page{Title: title} p = &Page{Title: title}
} }
t, _ := template.ParseFile("edit.html") t, _ := template.ParseFiles("edit.html")
t.Execute(w, p) t.Execute(w, p)
} }
</pre> </pre>
@ -530,7 +530,7 @@ Modify <code>viewHandler</code> accordingly:
func viewHandler(w http.ResponseWriter, r *http.Request) { func viewHandler(w http.ResponseWriter, r *http.Request) {
title := r.URL.Path[lenPath:] title := r.URL.Path[lenPath:]
p, _ := loadPage(title) p, _ := loadPage(title)
t, _ := template.ParseFile(&#34;view.html&#34;) t, _ := template.ParseFiles(&#34;view.html&#34;)
t.Execute(w, p) t.Execute(w, p)
} }
</pre> </pre>
@ -558,7 +558,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
} }
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
t, _ := template.ParseFile(tmpl+&#34;.html&#34;, nil) t, _ := template.ParseFiles(tmpl+&#34;.html&#34;, nil)
t.Execute(w, p) t.Execute(w, p)
} }
</pre> </pre>
@ -643,7 +643,7 @@ First, let's handle the errors in <code>renderTemplate</code>:
<pre> <pre>
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
t, err := template.ParseFile(tmpl+&#34;.html&#34;, nil) t, err := template.ParseFiles(tmpl+&#34;.html&#34;, nil)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -719,7 +719,7 @@ can't be loaded the only sensible thing to do is exit the program.
<pre> <pre>
func init() { func init() {
for _, tmpl := range []string{&#34;edit&#34;, &#34;view&#34;} { for _, tmpl := range []string{&#34;edit&#34;, &#34;view&#34;} {
t := template.Must(template.ParseFile(tmpl + &#34;.html&#34;)) t := template.Must(template.ParseFiles(tmpl + &#34;.html&#34;))
templates[tmpl] = t templates[tmpl] = t
} }
} }

View File

@ -45,7 +45,7 @@ func main() {
// Read and parse the input. // Read and parse the input.
name := flag.Args()[0] name := flag.Args()[0]
tmpl := template.New(name).Funcs(template.FuncMap{"code": code}) tmpl := template.New(name).Funcs(template.FuncMap{"code": code})
if _, err := tmpl.ParseFile(name); err != nil { if _, err := tmpl.ParseFiles(name); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -102,7 +102,6 @@ DIRS=\
hash/crc64\ hash/crc64\
hash/fnv\ hash/fnv\
html\ html\
html/template\
image\ image\
image/bmp\ image/bmp\
image/color\ image/color\

View File

@ -10,7 +10,6 @@ GOFILES=\
exec.go\ exec.go\
funcs.go\ funcs.go\
helper.go\ helper.go\
parse.go\ template.go\
set.go\
include ../../../Make.pkg include ../../../Make.pkg

View File

@ -18,8 +18,7 @@ The input text for a template is UTF-8-encoded text in any format.
"{{" and "}}"; all text outside actions is copied to the output unchanged. "{{" and "}}"; all text outside actions is copied to the output unchanged.
Actions may not span newlines, although comments can. Actions may not span newlines, although comments can.
Once constructed, templates and template sets can be executed safely in Once constructed, a template may be executed safely in parallel.
parallel.
Actions Actions
@ -221,10 +220,9 @@ All produce the quoted word "output":
Functions Functions
During execution functions are found in three function maps: first in the During execution functions are found in two function maps: first in the
template, then in the "template set" (described below), and finally in the template, then in the global function map. By default, no functions are defined
global function map. By default, no functions are defined in the template or in the template but the Funcs methods can be used to add them.
the set but the Funcs methods can be used to add them.
Predefined global functions are named as follows. Predefined global functions are named as follows.
@ -265,49 +263,63 @@ Predefined global functions are named as follows.
The boolean functions take any zero value to be false and a non-zero value to The boolean functions take any zero value to be false and a non-zero value to
be true. be true.
Template sets Associated templates
Each template is named by a string specified when it is created. A template may Each template is named by a string specified when it is created. Also, each
use a template invocation to instantiate another template directly or by its template is associated with zero or more other templates that it may invoke by
name; see the explanation of the template action above. The name is looked up name; such associations are transitive and form a name space of templates.
in the template set associated with the template.
If no template invocation actions occur in the template, the issue of template A template may use a template invocation to instantiate another associated
sets can be ignored. If it does contain invocations, though, the template template; see the explanation of the "template" action above. The name must be
containing the invocations must be part of a template set in which to look up that of a template associated with the template that contains the invocation.
the names.
There are two ways to construct template sets. Nested template definitions
The first is to use a Set's Parse method to create a set of named templates from When parsing a template, another template may be defined and associated with the
a single input defining multiple templates. The syntax of the definitions is to template being parsed. Template definitions must appear at the top level of the
surround each template declaration with a define and end action. template, much like global variables in a Go program.
The syntax of such definitions is to surround each template declaration with a
"define" and "end" action.
The define action names the template being created by providing a string The define action names the template being created by providing a string
constant. Here is a simple example of input to Set.Parse: constant. Here is a simple example:
`{{define "T1"}} definition of template T1 {{end}} `{{define "T1"}}ONE{{end}}
{{define "T2"}} definition of template T2 {{end}} {{define "T2"}}TWO{{end}}
{{define "T3"}} {{template "T1"}} {{template "T2"}} {{end}}` {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}`
This defines two templates, T1 and T2, and a third T3 that invokes the other two This defines two templates, T1 and T2, and a third T3 that invokes the other two
when it is executed. when it is executed. Finally it invokes T3. If executed this template will
produce the text
The second way to build a template set is to use Set's Add method to add a ONE TWO
parsed template to a set. A template may be bound to at most one set. If it's
necessary to have a template in multiple sets, the template definition must be
parsed multiple times to create distinct *Template values.
Set.Parse may be called multiple times on different inputs to construct the set. By construction, a template may reside in only one association. If it's
Two sets may therefore be constructed with a common base set of templates plus, necessary to have a template addressable from multiple associations, the
through a second Parse call each, specializations for some elements. template definition must be parsed multiple times to create distinct *Template
values.
A template may be executed directly or through Set.Execute, which executes a Parse may be called multiple times to assemble the various associated templates;
named template from the set. To invoke our example above, we might write, see the ParseFiles and ParseGlob functions and methods for simple ways to parse
related templates stored in files.
err := set.Execute(os.Stdout, "T3", "no data needed") A template may be executed directly or through ExecuteTemplate, which executes
an associated template identified by name. To invoke our example above, we
might write,
err := tmpl.Execute(os.Stdout, "no data needed")
if err != nil { if err != nil {
log.Fatalf("execution failed: %s", err) log.Fatalf("execution failed: %s", err)
} }
or to invoke a particular template explicitly by name,
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
if err != nil {
log.Fatalf("execution failed: %s", err)
}
*/ */
package template package template

View File

@ -85,8 +85,18 @@ func errRecover(errp *error) {
} }
} }
// ExecuteTemplate applies the template associated with t that has the given name
// to the specified data object and writes the output to wr.
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
tmpl := t.tmpl[name]
if tmpl == nil {
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
}
return tmpl.Execute(wr, data)
}
// Execute applies a parsed template to the specified data object, // Execute applies a parsed template to the specified data object,
// writing the output to wr. // and writes the output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) { func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
defer errRecover(&err) defer errRecover(&err)
value := reflect.ValueOf(data) value := reflect.ValueOf(data)
@ -251,13 +261,9 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
} }
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) { func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
set := s.tmpl.set tmpl := s.tmpl.tmpl[t.Name]
if set == nil {
s.errorf("no set defined in which to invoke template named %q", t.Name)
}
tmpl := set.tmpl[t.Name]
if tmpl == nil { if tmpl == nil {
s.errorf("template %q not in set", t.Name) s.errorf("template %q not defined", t.Name)
} }
// Variables declared by the pipeline persist. // Variables declared by the pipeline persist.
dot = s.evalPipeline(dot, t.Pipe) dot = s.evalPipeline(dot, t.Pipe)
@ -376,7 +382,7 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, ident []string, args
} }
func (s *state) evalFunction(dot reflect.Value, name string, args []parse.Node, final reflect.Value) reflect.Value { func (s *state) evalFunction(dot reflect.Value, name string, args []parse.Node, final reflect.Value) reflect.Value {
function, ok := findFunction(name, s.tmpl, s.tmpl.set) function, ok := findFunction(name, s.tmpl)
if !ok { if !ok {
s.errorf("%q is not a defined function", name) s.errorf("%q is not a defined function", name)
} }
@ -398,7 +404,7 @@ func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node
if ptr.Kind() != reflect.Interface && ptr.CanAddr() { if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
ptr = ptr.Addr() ptr = ptr.Addr()
} }
if method, ok := methodByName(ptr, fieldName); ok { if method := ptr.MethodByName(fieldName); method.IsValid() {
return s.evalCall(dot, method, fieldName, args, final) return s.evalCall(dot, method, fieldName, args, final)
} }
hasArgs := len(args) > 1 || final.IsValid() hasArgs := len(args) > 1 || final.IsValid()
@ -433,17 +439,6 @@ func (s *state) evalField(dot reflect.Value, fieldName string, args []parse.Node
panic("not reached") panic("not reached")
} }
// TODO: delete when reflect's own MethodByName is released.
func methodByName(receiver reflect.Value, name string) (reflect.Value, bool) {
typ := receiver.Type()
for i := 0; i < typ.NumMethod(); i++ {
if typ.Method(i).Name == name {
return receiver.Method(i), true // This value includes the receiver.
}
}
return zero, false
}
var ( var (
errorType = reflect.TypeOf((*error)(nil)).Elem() errorType = reflect.TypeOf((*error)(nil)).Elem()
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()

View File

@ -476,7 +476,7 @@ func vfunc(V, *V) string {
return "vfunc" return "vfunc"
} }
func testExecute(execTests []execTest, set *Set, t *testing.T) { func testExecute(execTests []execTest, template *Template, t *testing.T) {
b := new(bytes.Buffer) b := new(bytes.Buffer)
funcs := FuncMap{ funcs := FuncMap{
"count": count, "count": count,
@ -486,12 +486,13 @@ func testExecute(execTests []execTest, set *Set, t *testing.T) {
"zeroArgs": zeroArgs, "zeroArgs": zeroArgs,
} }
for _, test := range execTests { for _, test := range execTests {
tmpl := New(test.name).Funcs(funcs) var tmpl *Template
theSet := set var err error
if theSet == nil { if template == nil {
theSet = new(Set) tmpl, err = New(test.name).Funcs(funcs).Parse(test.input)
} else {
tmpl, err = template.New(test.name).Funcs(funcs).Parse(test.input)
} }
_, err := tmpl.ParseInSet(test.input, theSet)
if err != nil { if err != nil {
t.Errorf("%s: parse error: %s", test.name, err) t.Errorf("%s: parse error: %s", test.name, err)
continue continue
@ -663,24 +664,34 @@ func TestTree(t *testing.T) {
}, },
}, },
} }
set := new(Set) tmpl, err := New("root").Delims("(", ")").Parse(treeTemplate)
_, err := set.Delims("(", ")").Parse(treeTemplate)
if err != nil { if err != nil {
t.Fatal("parse error:", err) t.Fatal("parse error:", err)
} }
var b bytes.Buffer var b bytes.Buffer
err = set.Execute(&b, "tree", tree)
if err != nil {
t.Fatal("exec error:", err)
}
stripSpace := func(r rune) rune { stripSpace := func(r rune) rune {
if r == '\t' || r == '\n' { if r == '\t' || r == '\n' {
return -1 return -1
} }
return r return r
} }
result := strings.Map(stripSpace, b.String())
const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]" const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]"
// First by looking up the template.
err = tmpl.Template("tree").Execute(&b, tree)
if err != nil {
t.Fatal("exec error:", err)
}
result := strings.Map(stripSpace, b.String())
if result != expect {
t.Errorf("expected %q got %q", expect, result)
}
// Then direct to execution.
b.Reset()
err = tmpl.ExecuteTemplate(&b, "tree", tree)
if err != nil {
t.Fatal("exec error:", err)
}
result = strings.Map(stripSpace, b.String())
if result != expect { if result != expect {
t.Errorf("expected %q got %q", expect, result) t.Errorf("expected %q got %q", expect, result)
} }

View File

@ -17,8 +17,9 @@ import (
// FuncMap is the type of the map defining the mapping from names to functions. // FuncMap is the type of the map defining the mapping from names to functions.
// Each function must have either a single return value, or two return values of // Each function must have either a single return value, or two return values of
// which the second has type error. If the second argument evaluates to non-nil // which the second has type error. In that case, if the second (error)
// during execution, execution terminates and Execute returns an error. // argument evaluates to non-nil during execution, execution terminates and
// Execute returns that error.
type FuncMap map[string]interface{} type FuncMap map[string]interface{}
var builtins = FuncMap{ var builtins = FuncMap{
@ -78,18 +79,13 @@ func goodFunc(typ reflect.Type) bool {
return false return false
} }
// findFunction looks for a function in the template, set, and global map. // findFunction looks for a function in the template, and global map.
func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) { func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
if tmpl != nil { if tmpl != nil && tmpl.common != nil {
if fn := tmpl.execFuncs[name]; fn.IsValid() { if fn := tmpl.execFuncs[name]; fn.IsValid() {
return fn, true return fn, true
} }
} }
if set != nil {
if fn := set.execFuncs[name]; fn.IsValid() {
return fn, true
}
}
if fn := builtinFuncs[name]; fn.IsValid() { if fn := builtinFuncs[name]; fn.IsValid() {
return fn, true return fn, true
} }
@ -310,7 +306,6 @@ func JSEscape(w io.Writer, b []byte) {
if unicode.IsPrint(r) { if unicode.IsPrint(r) {
w.Write(b[i : i+size]) w.Write(b[i : i+size])
} else { } else {
// TODO(dsymonds): Do this without fmt?
fmt.Fprintf(w, "\\u%04X", r) fmt.Fprintf(w, "\\u%04X", r)
} }
i += size - 1 i += size - 1

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Helper functions to make constructing templates and sets easier. // Helper functions to make constructing templates easier.
package template package template
@ -12,11 +12,11 @@ import (
"path/filepath" "path/filepath"
) )
// Functions and methods to parse a single template. // Functions and methods to parse templates.
// Must is a helper that wraps a call to a function returning (*Template, error) // Must is a helper that wraps a call to a function returning (*Template, error)
// and panics if the error is non-nil. It is intended for use in variable initializations // and panics if the error is non-nil. It is intended for use in variable
// such as // initializations such as
// var t = template.Must(template.New("name").Parse("text")) // var t = template.Must(template.New("name").Parse("text"))
func Must(t *Template, err error) *Template { func Must(t *Template, err error) *Template {
if err != nil { if err != nil {
@ -25,217 +25,84 @@ func Must(t *Template, err error) *Template {
return t return t
} }
// ParseFile creates a new Template and parses the template definition from // ParseFiles creates a new Template and parses the template definitions from
// the named file. The template name is the base name of the file. // the named files. The returned template's name will have the (base) name and
func ParseFile(filename string) (*Template, error) { // (parsed) contents of the first file. There must be at least one file.
t := New(filepath.Base(filename)) // If an error occurs, parsing stops and the returned *Template is nil.
return t.ParseFile(filename) func ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(nil, filenames...)
} }
// parseFileInSet creates a new Template and parses the template // ParseFiles parses the named files and associates the resulting templates with
// definition from the named file. The template name is the base name // t. If an error occurs, parsing stops and the returned template is nil;
// of the file. It also adds the template to the set. Function bindings are // otherwise it is t. There must be at least one file.
// checked against those in the set. func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
func parseFileInSet(filename string, set *Set) (*Template, error) { return parseFiles(t, filenames...)
t := New(filepath.Base(filename))
return t.parseFileInSet(filename, set)
} }
// ParseFile reads the template definition from a file and parses it to // parseFiles is the helper for the method and function. If the argument
// construct an internal representation of the template for execution. // template is nil, it is created from the first file.
// The returned template will be nil if an error occurs. func parseFiles(t *Template, filenames ...string) (*Template, error) {
func (t *Template) ParseFile(filename string) (*Template, error) { if len(filenames) == 0 {
b, err := ioutil.ReadFile(filename) // Not really a problem, but be consistent.
if err != nil { return nil, fmt.Errorf("template: no files named in call to ParseFiles")
return nil, err
} }
return t.Parse(string(b))
}
// parseFileInSet is the same as ParseFile except that function bindings
// are checked against those in the set and the template is added
// to the set.
// The returned template will be nil if an error occurs.
func (t *Template) parseFileInSet(filename string, set *Set) (*Template, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return t.ParseInSet(string(b), set)
}
// Functions and methods to parse a set.
// SetMust is a helper that wraps a call to a function returning (*Set, error)
// and panics if the error is non-nil. It is intended for use in variable initializations
// such as
// var s = template.SetMust(template.ParseSetFiles("file"))
func SetMust(s *Set, err error) *Set {
if err != nil {
panic(err)
}
return s
}
// ParseFiles parses the named files into a set of named templates.
// Each file must be parseable by itself.
// If an error occurs, parsing stops and the returned set is nil.
func (s *Set) ParseFiles(filenames ...string) (*Set, error) {
for _, filename := range filenames { for _, filename := range filenames {
b, err := ioutil.ReadFile(filename) b, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = s.Parse(string(b)) s := string(b)
name := filepath.Base(filename)
// First template becomes return value if not already defined,
// and we use that one for subsequent New calls to associate
// all the templates together. Also, if this file has the same name
// as t, this file becomes the contents of t, so
// t, err := New(name).Funcs(xxx).ParseFiles(name)
// works. Otherwise we create a new template associated with t.
var tmpl *Template
if t == nil {
t = New(name)
}
if name == t.Name() {
tmpl = t
} else {
tmpl = t.New(name)
}
_, err = tmpl.Parse(s)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return s, nil return t, nil
} }
// ParseSetFiles creates a new Set and parses the set definition from the // ParseGlob creates a new Template and parses the template definitions from the
// named files. Each file must be individually parseable. // files identified by the pattern, which must match at least one file. The
func ParseSetFiles(filenames ...string) (*Set, error) { // returned template will have the (base) name and (parsed) contents of the
s := new(Set) // first file matched by the pattern. ParseGlob is equivalent to calling
for _, filename := range filenames { // ParseFiles with the list of files matched by the pattern.
b, err := ioutil.ReadFile(filename) func ParseGlob(pattern string) (*Template, error) {
if err != nil { return parseGlob(nil, pattern)
return nil, err
}
_, err = s.Parse(string(b))
if err != nil {
return nil, err
}
}
return s, nil
} }
// ParseGlob parses the set definition from the files identified by the // ParseGlob parses the template definitions in the files identified by the
// pattern. The pattern is processed by filepath.Glob and must match at // pattern and associates the resulting templates with t. The pattern is
// least one file. // processed by filepath.Glob and must match at least one file. ParseGlob is
// If an error occurs, parsing stops and the returned set is nil. // equivalent to calling t.ParseFiles with the list of files matched by the
func (s *Set) ParseGlob(pattern string) (*Set, error) { // pattern.
func (t *Template) ParseGlob(pattern string) (*Template, error) {
return parseGlob(t, pattern)
}
// parseGlob is the implementation of the function and method ParseGlob.
func parseGlob(t *Template, pattern string) (*Template, error) {
filenames, err := filepath.Glob(pattern) filenames, err := filepath.Glob(pattern)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(filenames) == 0 { if len(filenames) == 0 {
return nil, fmt.Errorf("pattern matches no files: %#q", pattern) return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
} }
return s.ParseFiles(filenames...) return parseFiles(t, filenames...)
}
// ParseSetGlob creates a new Set and parses the set definition from the
// files identified by the pattern. The pattern is processed by filepath.Glob
// and must match at least one file.
func ParseSetGlob(pattern string) (*Set, error) {
set, err := new(Set).ParseGlob(pattern)
if err != nil {
return nil, err
}
return set, nil
}
// Functions and methods to parse stand-alone template files into a set.
// ParseTemplateFiles parses the named template files and adds
// them to the set. Each template will be named the base name of
// its file.
// Unlike with ParseFiles, each file should be a stand-alone template
// definition suitable for Template.Parse (not Set.Parse); that is, the
// file does not contain {{define}} clauses. ParseTemplateFiles is
// therefore equivalent to calling the ParseFile function to create
// individual templates, which are then added to the set.
// Each file must be parseable by itself.
// If an error occurs, parsing stops and the returned set is nil.
func (s *Set) ParseTemplateFiles(filenames ...string) (*Set, error) {
for _, filename := range filenames {
_, err := parseFileInSet(filename, s)
if err != nil {
return nil, err
}
}
return s, nil
}
// ParseTemplateGlob parses the template files matched by the
// patern and adds them to the set. Each template will be named
// the base name of its file.
// Unlike with ParseGlob, each file should be a stand-alone template
// definition suitable for Template.Parse (not Set.Parse); that is, the
// file does not contain {{define}} clauses. ParseTemplateGlob is
// therefore equivalent to calling the ParseFile function to create
// individual templates, which are then added to the set.
// Each file must be parseable by itself.
// If an error occurs, parsing stops and the returned set is nil.
func (s *Set) ParseTemplateGlob(pattern string) (*Set, error) {
filenames, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
for _, filename := range filenames {
_, err := parseFileInSet(filename, s)
if err != nil {
return nil, err
}
}
return s, nil
}
// ParseTemplateFiles creates a set by parsing the named files,
// each of which defines a single template. Each template will be
// named the base name of its file.
// Unlike with ParseFiles, each file should be a stand-alone template
// definition suitable for Template.Parse (not Set.Parse); that is, the
// file does not contain {{define}} clauses. ParseTemplateFiles is
// therefore equivalent to calling the ParseFile function to create
// individual templates, which are then added to the set.
// Each file must be parseable by itself. Parsing stops if an error is
// encountered.
func ParseTemplateFiles(filenames ...string) (*Set, error) {
set := new(Set)
set.init()
for _, filename := range filenames {
t, err := ParseFile(filename)
if err != nil {
return nil, err
}
if err := set.add(t); err != nil {
return nil, err
}
}
return set, nil
}
// ParseTemplateGlob creates a set by parsing the files matched
// by the pattern, each of which defines a single template. The pattern
// is processed by filepath.Glob and must match at least one file. Each
// template will be named the base name of its file.
// Unlike with ParseGlob, each file should be a stand-alone template
// definition suitable for Template.Parse (not Set.Parse); that is, the
// file does not contain {{define}} clauses. ParseTemplateGlob is
// therefore equivalent to calling the ParseFile function to create
// individual templates, which are then added to the set.
// Each file must be parseable by itself. Parsing stops if an error is
// encountered.
func ParseTemplateGlob(pattern string) (*Set, error) {
set := new(Set)
filenames, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
if len(filenames) == 0 {
return nil, fmt.Errorf("pattern matches no files: %#q", pattern)
}
for _, filename := range filenames {
t, err := ParseFile(filename)
if err != nil {
return nil, err
}
if err := set.add(t); err != nil {
return nil, err
}
}
return set, nil
} }

View File

@ -0,0 +1,251 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
// Tests for mulitple-template parsing and execution.
import (
"bytes"
"fmt"
"testing"
)
type isEmptyTest struct {
name string
input string
empty bool
}
var isEmptyTests = []isEmptyTest{
{"empty", ``, true},
{"nonempty", `hello`, false},
{"spaces only", " \t\n \t\n", true},
{"definition", `{{define "x"}}something{{end}}`, true},
{"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true},
{"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n}}", false},
{"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false},
}
func TestIsEmpty(t *testing.T) {
for _, test := range isEmptyTests {
template, err := New("root").Parse(test.input)
if err != nil {
t.Errorf("%q: unexpected error: %v", test.name, err)
continue
}
if empty := isEmpty(template.Root); empty != test.empty {
t.Errorf("%q: expected %t got %t", test.name, test.empty, empty)
}
}
}
const (
noError = true
hasError = false
)
type multiParseTest struct {
name string
input string
ok bool
names []string
results []string
}
var multiParseTests = []multiParseTest{
{"empty", "", noError,
nil,
nil},
{"one", `{{define "foo"}} FOO {{end}}`, noError,
[]string{"foo"},
[]string{`[(text: " FOO ")]`}},
{"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
[]string{"foo", "bar"},
[]string{`[(text: " FOO ")]`, `[(text: " BAR ")]`}},
// errors
{"missing end", `{{define "foo"}} FOO `, hasError,
nil,
nil},
{"malformed name", `{{define "foo}} FOO `, hasError,
nil,
nil},
}
func TestMultiParse(t *testing.T) {
for _, test := range multiParseTests {
template, err := New("root").Parse(test.input)
switch {
case err == nil && !test.ok:
t.Errorf("%q: expected error; got none", test.name)
continue
case err != nil && test.ok:
t.Errorf("%q: unexpected error: %v", test.name, err)
continue
case err != nil && !test.ok:
// expected error, got one
if *debug {
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
}
continue
}
if template == nil {
continue
}
if len(template.tmpl) != len(test.names)+1 { // +1 for root
t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(template.tmpl))
continue
}
for i, name := range test.names {
tmpl, ok := template.tmpl[name]
if !ok {
t.Errorf("%s: can't find template %q", test.name, name)
continue
}
result := tmpl.Root.String()
if result != test.results[i] {
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
}
}
}
}
var multiExecTests = []execTest{
{"empty", "", "", nil, true},
{"text", "some text", "some text", nil, true},
{"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true},
{"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true},
{"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true},
{"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
{"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
{"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
{"variable declared by template", `{{template "nested" $x=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true},
// User-defined function: test argument evaluator.
{"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
{"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true},
}
// These strings are also in testdata/*.
const multiText1 = `
{{define "x"}}TEXT{{end}}
{{define "dotV"}}{{.V}}{{end}}
`
const multiText2 = `
{{define "dot"}}{{.}}{{end}}
{{define "nested"}}{{template "dot" .}}{{end}}
`
func TestMultiExecute(t *testing.T) {
// Declare a couple of templates first.
template, err := New("root").Parse(multiText1)
if err != nil {
t.Fatalf("parse error for 1: %s", err)
}
_, err = template.Parse(multiText2)
if err != nil {
t.Fatalf("parse error for 2: %s", err)
}
testExecute(multiExecTests, template, t)
}
func TestParseFiles(t *testing.T) {
_, err := ParseFiles("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
template := New("root")
_, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(multiExecTests, template, t)
}
func TestParseGlob(t *testing.T) {
_, err := ParseGlob("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
_, err = New("error").ParseGlob("[x")
if err == nil {
t.Error("expected error for bad pattern; got none")
}
template := New("root")
_, err = template.ParseGlob("testdata/file*.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(multiExecTests, template, t)
}
// In these tests, actual content (not just template definitions) comes from the parsed files.
var templateFileExecTests = []execTest{
{"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\n\ny\ntemplate2\n\nx\n", 0, true},
}
func TestParseFilesWithData(t *testing.T) {
template, err := New("root").ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(templateFileExecTests, template, t)
}
func TestParseGlobWithData(t *testing.T) {
template, err := New("root").ParseGlob("testdata/tmpl*.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(templateFileExecTests, template, t)
}
const (
cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}`
cloneText2 = `{{define "b"}}b{{end}}`
cloneText3 = `{{define "c"}}root{{end}}`
cloneText4 = `{{define "c"}}clone{{end}}`
)
func TestClone(t *testing.T) {
// Create some templates and clone the root.
root, err := New("root").Parse(cloneText1)
if err != nil {
t.Fatal(err)
}
_, err = root.Parse(cloneText2)
if err != nil {
t.Fatal(err)
}
clone := root.Clone()
// Add variants to both.
_, err = root.Parse(cloneText3)
if err != nil {
t.Fatal(err)
}
_, err = clone.Parse(cloneText4)
if err != nil {
t.Fatal(err)
}
// Execute root.
var b bytes.Buffer
err = root.ExecuteTemplate(&b, "a", 0)
if err != nil {
t.Fatal(err)
}
if b.String() != "broot" {
t.Errorf("expected %q got %q", "broot", b.String())
}
// Execute copy.
b.Reset()
err = clone.ExecuteTemplate(&b, "a", 0)
if err != nil {
t.Fatal(err)
}
if b.String() != "bclone" {
t.Errorf("expected %q got %q", "bclone", b.String())
}
}

View File

@ -1,83 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
import (
"reflect"
"text/template/parse"
)
// Template is the representation of a parsed template.
type Template struct {
name string
*parse.Tree
leftDelim string
rightDelim string
// We use two maps, one for parsing and one for execution.
// This separation makes the API cleaner since it doesn't
// expose reflection to the client.
parseFuncs FuncMap
execFuncs map[string]reflect.Value
set *Set // can be nil.
}
// Name returns the name of the template.
func (t *Template) Name() string {
return t.name
}
// Parsing.
// New allocates a new template with the given name.
func New(name string) *Template {
return &Template{
name: name,
parseFuncs: make(FuncMap),
execFuncs: make(map[string]reflect.Value),
}
}
// Delims sets the action delimiters, to be used in a subsequent
// parse, to the specified strings.
// An empty delimiter stands for the corresponding default: {{ or }}.
// The return value is the template, so calls can be chained.
func (t *Template) Delims(left, right string) *Template {
t.leftDelim = left
t.rightDelim = right
return t
}
// Funcs adds the elements of the argument map to the template's function
// map. It panics if a value in the map is not a function with appropriate
// return type.
// The return value is the template, so calls can be chained.
func (t *Template) Funcs(funcMap FuncMap) *Template {
addValueFuncs(t.execFuncs, funcMap)
addFuncs(t.parseFuncs, funcMap)
return t
}
// Parse parses the template definition string to construct an internal
// representation of the template for execution.
func (t *Template) Parse(s string) (tmpl *Template, err error) {
t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, nil, t.parseFuncs, builtins)
if err != nil {
return nil, err
}
return t, nil
}
// ParseInSet parses the template definition string to construct an internal
// representation of the template for execution. It also adds the template
// to the set, which must not be nil. It is an error if s is already defined in the set.
// Function bindings are checked against those in the set.
func (t *Template) ParseInSet(s string, set *Set) (tmpl *Template, err error) {
t.Tree, err = parse.New(t.name).Parse(s, t.leftDelim, t.rightDelim, set.trees, t.parseFuncs, set.parseFuncs, builtins)
if err != nil {
return nil, err
}
err = set.add(t)
return t, err
}

View File

@ -97,6 +97,15 @@ func (t *Tree) expect(expected itemType, context string) item {
return token return token
} }
// expectEither consumes the next token and guarantees it has one of the required types.
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
token := t.next()
if token.typ != expected1 && token.typ != expected2 {
t.errorf("expected %s or %s in %s; got %s", expected1, expected2, context, token)
}
return token
}
// unexpected complains about the token and terminates processing. // unexpected complains about the token and terminates processing.
func (t *Tree) unexpected(token item, context string) { func (t *Tree) unexpected(token item, context string) {
t.errorf("unexpected %s in %s", token, context) t.errorf("unexpected %s in %s", token, context)
@ -162,9 +171,18 @@ func (t *Tree) Parse(s, leftDelim, rightDelim string, treeSet map[string]*Tree,
t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim)) t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
t.parse(treeSet) t.parse(treeSet)
t.stopParse() t.stopParse()
t.add(treeSet)
return t, nil return t, nil
} }
// add adds tree to the treeSet.
func (t *Tree) add(treeSet map[string]*Tree) {
if _, present := treeSet[t.Name]; present {
t.errorf("template: multiple definition of template %q", t.Name)
}
treeSet[t.Name] = t
}
// parse is the top-level parser for a template, essentially the same // parse is the top-level parser for a template, essentially the same
// as itemList except it also parses {{define}} actions. // as itemList except it also parses {{define}} actions.
// It runs to EOF. // It runs to EOF.
@ -174,7 +192,7 @@ func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
if t.peek().typ == itemLeftDelim { if t.peek().typ == itemLeftDelim {
delim := t.next() delim := t.next()
if t.next().typ == itemDefine { if t.next().typ == itemDefine {
newT := New("new definition") // name will be updated once we know it. newT := New("definition") // name will be updated once we know it.
newT.startParse(t.funcs, t.lex) newT.startParse(t.funcs, t.lex)
newT.parseDefinition(treeSet) newT.parseDefinition(treeSet)
continue continue
@ -194,11 +212,8 @@ func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
// installs the definition in the treeSet map. The "define" keyword has already // installs the definition in the treeSet map. The "define" keyword has already
// been scanned. // been scanned.
func (t *Tree) parseDefinition(treeSet map[string]*Tree) { func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
if treeSet == nil {
t.errorf("no set specified for template definition")
}
const context = "define clause" const context = "define clause"
name := t.expect(itemString, context) name := t.expectOneOf(itemString, itemRawString, context)
var err error var err error
t.Name, err = strconv.Unquote(name.val) t.Name, err = strconv.Unquote(name.val)
if err != nil { if err != nil {
@ -211,10 +226,7 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
t.errorf("unexpected %s in %s", end, context) t.errorf("unexpected %s in %s", end, context)
} }
t.stopParse() t.stopParse()
if _, present := treeSet[t.Name]; present { t.add(treeSet)
t.errorf("template: %q multiply defined", name)
}
treeSet[t.Name] = t
} }
// itemList: // itemList:

View File

@ -236,7 +236,7 @@ var builtins = map[string]interface{}{
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
for _, test := range parseTests { for _, test := range parseTests {
tmpl, err := New(test.name).Parse(test.input, "", "", nil, builtins) tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
switch { switch {
case err == nil && !test.ok: case err == nil && !test.ok:
t.Errorf("%q: expected error; got none", test.name) t.Errorf("%q: expected error; got none", test.name)

View File

@ -1,122 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
import (
"fmt"
"io"
"reflect"
"text/template/parse"
)
// Set holds a set of related templates that can refer to one another by name.
// The zero value represents an empty set.
// A template may be a member of multiple sets.
type Set struct {
tmpl map[string]*Template
trees map[string]*parse.Tree // maintained by parse package
leftDelim string
rightDelim string
parseFuncs FuncMap
execFuncs map[string]reflect.Value
}
func (s *Set) init() {
if s.tmpl == nil {
s.tmpl = make(map[string]*Template)
s.parseFuncs = make(FuncMap)
s.execFuncs = make(map[string]reflect.Value)
}
}
// Delims sets the action delimiters, to be used in a subsequent
// parse, to the specified strings.
// An empty delimiter stands for the corresponding default: {{ or }}.
// The return value is the set, so calls can be chained.
func (s *Set) Delims(left, right string) *Set {
s.leftDelim = left
s.rightDelim = right
return s
}
// Funcs adds the elements of the argument map to the set's function map. It
// panics if a value in the map is not a function with appropriate return
// type.
// The return value is the set, so calls can be chained.
func (s *Set) Funcs(funcMap FuncMap) *Set {
s.init()
addValueFuncs(s.execFuncs, funcMap)
addFuncs(s.parseFuncs, funcMap)
return s
}
// Add adds the argument templates to the set. It panics if two templates
// with the same name are added or if a template is already a member of
// a set.
// The return value is the set, so calls can be chained.
func (s *Set) Add(templates ...*Template) *Set {
for _, t := range templates {
if err := s.add(t); err != nil {
panic(err)
}
}
return s
}
// add adds the argument template to the set.
func (s *Set) add(t *Template) error {
s.init()
if t.set != nil {
return fmt.Errorf("template: %q already in a set", t.name)
}
if _, ok := s.tmpl[t.name]; ok {
return fmt.Errorf("template: %q already defined in set", t.name)
}
s.tmpl[t.name] = t
t.set = s
return nil
}
// Template returns the template with the given name in the set,
// or nil if there is no such template.
func (s *Set) Template(name string) *Template {
return s.tmpl[name]
}
// FuncMap returns the set's function map.
func (s *Set) FuncMap() FuncMap {
return s.parseFuncs
}
// Execute applies the named template to the specified data object, writing
// the output to wr.
func (s *Set) Execute(wr io.Writer, name string, data interface{}) error {
tmpl := s.tmpl[name]
if tmpl == nil {
return fmt.Errorf("template: no template %q in set", name)
}
return tmpl.Execute(wr, data)
}
// Parse parses a string into a set of named templates. Parse may be called
// multiple times for a given set, adding the templates defined in the string
// to the set. It is an error if a template has a name already defined in the set.
func (s *Set) Parse(text string) (*Set, error) {
// TODO: "ROOT" is just a placeholder while we rejig the API.
trees, err := parse.Parse("ROOT", text, s.leftDelim, s.rightDelim, s.parseFuncs, builtins)
if err != nil {
return nil, err
}
s.init()
for name, tree := range trees {
tmpl := New(name)
tmpl.Tree = tree
err = s.add(tmpl)
if err != nil {
return s, err
}
}
return s, nil
}

View File

@ -1,239 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
import (
"fmt"
"testing"
)
const (
noError = true
hasError = false
)
type setParseTest struct {
name string
input string
ok bool
names []string
results []string
}
var setParseTests = []setParseTest{
{"empty", "", noError,
nil,
nil},
{"one", `{{define "foo"}} FOO {{end}}`, noError,
[]string{"foo"},
[]string{`[(text: " FOO ")]`}},
{"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
[]string{"foo", "bar"},
[]string{`[(text: " FOO ")]`, `[(text: " BAR ")]`}},
// errors
{"missing end", `{{define "foo"}} FOO `, hasError,
nil,
nil},
{"malformed name", `{{define "foo}} FOO `, hasError,
nil,
nil},
}
func TestSetParse(t *testing.T) {
for _, test := range setParseTests {
set, err := new(Set).Parse(test.input)
switch {
case err == nil && !test.ok:
t.Errorf("%q: expected error; got none", test.name)
continue
case err != nil && test.ok:
t.Errorf("%q: unexpected error: %v", test.name, err)
continue
case err != nil && !test.ok:
// expected error, got one
if *debug {
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
}
continue
}
if set == nil {
continue
}
if len(set.tmpl) != len(test.names) {
t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(set.tmpl))
continue
}
for i, name := range test.names {
tmpl, ok := set.tmpl[name]
if !ok {
t.Errorf("%s: can't find template %q", test.name, name)
continue
}
result := tmpl.Root.String()
if result != test.results[i] {
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
}
}
}
}
var setExecTests = []execTest{
{"empty", "", "", nil, true},
{"text", "some text", "some text", nil, true},
{"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true},
{"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true},
{"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true},
{"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
{"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
{"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
{"variable declared by template", `{{template "nested" $x=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true},
// User-defined function: test argument evaluator.
{"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
{"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true},
}
// These strings are also in testdata/*.
const setText1 = `
{{define "x"}}TEXT{{end}}
{{define "dotV"}}{{.V}}{{end}}
`
const setText2 = `
{{define "dot"}}{{.}}{{end}}
{{define "nested"}}{{template "dot" .}}{{end}}
`
func TestSetExecute(t *testing.T) {
// Declare a set with a couple of templates first.
set := new(Set)
_, err := set.Parse(setText1)
if err != nil {
t.Fatalf("error parsing set: %s", err)
}
_, err = set.Parse(setText2)
if err != nil {
t.Fatalf("error parsing set: %s", err)
}
testExecute(setExecTests, set, t)
}
func TestSetParseFiles(t *testing.T) {
set := new(Set)
_, err := set.ParseFiles("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
_, err = set.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(setExecTests, set, t)
}
func TestParseSetFiles(t *testing.T) {
set := new(Set)
_, err := ParseSetFiles("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
set, err = ParseSetFiles("testdata/file1.tmpl", "testdata/file2.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(setExecTests, set, t)
}
func TestSetParseGlob(t *testing.T) {
_, err := new(Set).ParseGlob("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
_, err = new(Set).ParseGlob("[x")
if err == nil {
t.Error("expected error for bad pattern; got none")
}
set, err := new(Set).ParseGlob("testdata/file*.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(setExecTests, set, t)
}
func TestParseSetGlob(t *testing.T) {
_, err := ParseSetGlob("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
_, err = ParseSetGlob("[x")
if err == nil {
t.Error("expected error for bad pattern; got none")
}
set, err := ParseSetGlob("testdata/file*.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(setExecTests, set, t)
}
var templateFileExecTests = []execTest{
{"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\ntemplate2\n", 0, true},
}
func TestSetParseTemplateFiles(t *testing.T) {
_, err := ParseTemplateFiles("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
set, err := new(Set).ParseTemplateFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(templateFileExecTests, set, t)
}
func TestParseTemplateFiles(t *testing.T) {
_, err := ParseTemplateFiles("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
set, err := new(Set).ParseTemplateFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(templateFileExecTests, set, t)
}
func TestSetParseTemplateGlob(t *testing.T) {
_, err := ParseTemplateGlob("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
_, err = new(Set).ParseTemplateGlob("[x")
if err == nil {
t.Error("expected error for bad pattern; got none")
}
set, err := new(Set).ParseTemplateGlob("testdata/tmpl*.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(templateFileExecTests, set, t)
}
func TestParseTemplateGlob(t *testing.T) {
_, err := ParseTemplateGlob("DOES NOT EXIST")
if err == nil {
t.Error("expected error for non-existent file; got none")
}
_, err = ParseTemplateGlob("[x")
if err == nil {
t.Error("expected error for bad pattern; got none")
}
set, err := ParseTemplateGlob("testdata/tmpl*.tmpl")
if err != nil {
t.Fatalf("error parsing files: %v", err)
}
testExecute(templateFileExecTests, set, t)
}

View File

@ -0,0 +1,217 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package template
import (
"bytes"
"fmt"
"reflect"
"text/template/parse"
)
// common holds the information shared by related templates.
type common struct {
tmpl map[string]*Template
// We use two maps, one for parsing and one for execution.
// This separation makes the API cleaner since it doesn't
// expose reflection to the client.
parseFuncs FuncMap
execFuncs map[string]reflect.Value
}
// Template is the representation of a parsed template. The *parse.Tree
// field is exported only for use by html/template and should be treated
// as unexported by all other clients.
type Template struct {
name string
*parse.Tree
*common
leftDelim string
rightDelim string
}
// New allocates a new template with the given name.
func New(name string) *Template {
return &Template{
name: name,
}
}
// Name returns the name of the template.
func (t *Template) Name() string {
return t.name
}
// New allocates a new template associated with the given one and with the same
// delimiters. The association, which is transitive, allows one template to
// invoke another with a {{template}} action.
func (t *Template) New(name string) *Template {
t.init()
return &Template{
name: name,
common: t.common,
leftDelim: t.leftDelim,
rightDelim: t.rightDelim,
}
}
func (t *Template) init() {
if t.common == nil {
t.common = new(common)
t.tmpl = make(map[string]*Template)
t.parseFuncs = make(FuncMap)
t.execFuncs = make(map[string]reflect.Value)
}
}
// Clone returns a duplicate of the template, including all associated
// templates. The actual representation is not copied, but the name space of
// associated templates is, so further calls to Parse in the copy will add
// templates to the copy but not to the original. Clone can be used to prepare
// common templates and use them with variant definitions for other templates by
// adding the variants after the clone is made.
func (t *Template) Clone() *Template {
nt := t.copy()
nt.init()
for k, v := range t.tmpl {
// The associated templates share nt's common structure.
tmpl := v.copy()
tmpl.common = nt.common
nt.tmpl[k] = tmpl
}
for k, v := range t.parseFuncs {
nt.parseFuncs[k] = v
}
for k, v := range t.execFuncs {
nt.execFuncs[k] = v
}
return nt
}
// copy returns a shallow copy of t, with common set to nil.
func (t *Template) copy() *Template {
nt := New(t.name)
nt.Tree = t.Tree
nt.leftDelim = t.leftDelim
nt.rightDelim = t.rightDelim
return nt
}
// Templates returns a slice of the templates associated with t, including t
// itself.
func (t *Template) Templates() []*Template {
// Return a slice so we don't expose the map.
m := make([]*Template, 0, len(t.tmpl))
for _, v := range t.tmpl {
m = append(m, v)
}
return m
}
// Delims sets the action delimiters to the specified strings, to be used in
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
// definitions will inherit the settings. An empty delimiter stands for the
// corresponding default: {{ or }}.
// The return value is the template, so calls can be chained.
func (t *Template) Delims(left, right string) *Template {
t.leftDelim = left
t.rightDelim = right
return t
}
// Funcs adds the elements of the argument map to the template's function map.
// It panics if a value in the map is not a function with appropriate return
// type. However, it is legal to overwrite elements of the map. The return
// value is the template, so calls can be chained.
func (t *Template) Funcs(funcMap FuncMap) *Template {
t.init()
addValueFuncs(t.execFuncs, funcMap)
addFuncs(t.parseFuncs, funcMap)
return t
}
// Template returns the template with the given name that is associated with t,
// or nil if there is no such template.
func (t *Template) Template(name string) *Template {
return t.tmpl[name]
}
// Parse parses a string into a template. Nested template definitions will be
// associated with the top-level template t. Parse may be called multiple times
// to parse definitions of templates to associate with t. It is an error if a
// resulting template is non-empty (contains content other than template
// definitions) and would replace a non-empty template with the same name.
// (In multiple calls to Parse with the same receiver template, only one call
// can contain text other than space, comments, and template definitions.)
func (t *Template) Parse(text string) (*Template, error) {
t.init()
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
if err != nil {
return nil, err
}
// Add the newly parsed trees, including the one for t, into our common structure.
for name, tree := range trees {
// If the name we parsed is the name of this template, overwrite this template.
// The associate method checks it's not a redefinition.
tmpl := t
if name != t.name {
tmpl = t.New(name)
}
// Even if t == tmpl, we need to install it in the common.tmpl map.
if err := t.associate(tmpl); err != nil {
return nil, err
}
tmpl.Tree = tree
tmpl.leftDelim = t.leftDelim
tmpl.rightDelim = t.rightDelim
}
return t, nil
}
// associate installs the new template into the group of templates associated
// with t. It is an error to reuse a name except to overwrite an empty
// template. The two are already known to share the common structure.
func (t *Template) associate(new *Template) error {
if new.common != t.common {
panic("internal error: associate not common")
}
name := new.name
if old := t.tmpl[name]; old != nil {
oldIsEmpty := isEmpty(old.Root)
newIsEmpty := isEmpty(new.Root)
if !oldIsEmpty && !newIsEmpty {
return fmt.Errorf("template: redefinition of template %q", name)
}
if newIsEmpty {
// Whether old is empty or not, new is empty; no reason to replace old.
return nil
}
}
t.tmpl[name] = new
return nil
}
// isEmpty reports whether this tree (node) is empty of everything but space.
func isEmpty(n parse.Node) bool {
switch n := n.(type) {
case *parse.ActionNode:
case *parse.IfNode:
case *parse.ListNode:
for _, node := range n.Nodes {
if !isEmpty(node) {
return false
}
}
return true
case *parse.RangeNode:
case *parse.TemplateNode:
case *parse.TextNode:
return len(bytes.TrimSpace(n.Text)) == 0
case *parse.WithNode:
default:
panic("unknown node: " + n.String())
}
return false
}

View File

@ -1 +1,3 @@
template1 template1
{{define "x"}}x{{end}}
{{template "y"}}

View File

@ -1 +1,3 @@
template2 template2
{{define "y"}}y{{end}}
{{template "x"}}