mirror of
https://github.com/golang/go
synced 2024-11-21 10:34: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:
parent
af081cd43e
commit
f56db6f534
@ -68,7 +68,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||
t, err := template.ParseFile(tmpl + ".html")
|
||||
t, err := template.ParseFiles(tmpl + ".html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -33,14 +33,14 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
p = &Page{Title: title}
|
||||
}
|
||||
t, _ := template.ParseFile("edit.html")
|
||||
t, _ := template.ParseFiles("edit.html")
|
||||
t.Execute(w, p)
|
||||
}
|
||||
|
||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
title := r.URL.Path[lenPath:]
|
||||
p, _ := loadPage(title)
|
||||
t, _ := template.ParseFile("view.html")
|
||||
t, _ := template.ParseFiles("view.html")
|
||||
t.Execute(w, p)
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||
}
|
||||
|
||||
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 {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -51,7 +51,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ var templates = make(map[string]*template.Template)
|
||||
|
||||
func init() {
|
||||
for _, tmpl := range []string{"edit", "view"} {
|
||||
t := template.Must(template.ParseFile(tmpl + ".html"))
|
||||
t := template.Must(template.ParseFiles(tmpl + ".html"))
|
||||
templates[tmpl] = t
|
||||
}
|
||||
}
|
||||
|
@ -476,7 +476,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
p = &Page{Title: title}
|
||||
}
|
||||
t, _ := template.ParseFile("edit.html")
|
||||
t, _ := template.ParseFiles("edit.html")
|
||||
t.Execute(w, p)
|
||||
}
|
||||
</pre>
|
||||
@ -530,7 +530,7 @@ Modify <code>viewHandler</code> accordingly:
|
||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
title := r.URL.Path[lenPath:]
|
||||
p, _ := loadPage(title)
|
||||
t, _ := template.ParseFile("view.html")
|
||||
t, _ := template.ParseFiles("view.html")
|
||||
t.Execute(w, p)
|
||||
}
|
||||
</pre>
|
||||
@ -558,7 +558,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
</pre>
|
||||
@ -643,7 +643,7 @@ First, let's handle the errors in <code>renderTemplate</code>:
|
||||
|
||||
<pre>
|
||||
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 {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@ -719,7 +719,7 @@ can't be loaded the only sensible thing to do is exit the program.
|
||||
<pre>
|
||||
func init() {
|
||||
for _, tmpl := range []string{"edit", "view"} {
|
||||
t := template.Must(template.ParseFile(tmpl + ".html"))
|
||||
t := template.Must(template.ParseFiles(tmpl + ".html"))
|
||||
templates[tmpl] = t
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func main() {
|
||||
// Read and parse the input.
|
||||
name := flag.Args()[0]
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,6 @@ DIRS=\
|
||||
hash/crc64\
|
||||
hash/fnv\
|
||||
html\
|
||||
html/template\
|
||||
image\
|
||||
image/bmp\
|
||||
image/color\
|
||||
|
@ -10,7 +10,6 @@ GOFILES=\
|
||||
exec.go\
|
||||
funcs.go\
|
||||
helper.go\
|
||||
parse.go\
|
||||
set.go\
|
||||
template.go\
|
||||
|
||||
include ../../../Make.pkg
|
||||
|
@ -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.
|
||||
Actions may not span newlines, although comments can.
|
||||
|
||||
Once constructed, templates and template sets can be executed safely in
|
||||
parallel.
|
||||
Once constructed, a template may be executed safely in parallel.
|
||||
|
||||
Actions
|
||||
|
||||
@ -221,10 +220,9 @@ All produce the quoted word "output":
|
||||
|
||||
Functions
|
||||
|
||||
During execution functions are found in three function maps: first in the
|
||||
template, then in the "template set" (described below), and finally in the
|
||||
global function map. By default, no functions are defined in the template or
|
||||
the set but the Funcs methods can be used to add them.
|
||||
During execution functions are found in two function maps: first in the
|
||||
template, then in the global function map. By default, no functions are defined
|
||||
in the template but the Funcs methods can be used to add them.
|
||||
|
||||
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
|
||||
be true.
|
||||
|
||||
Template sets
|
||||
Associated templates
|
||||
|
||||
Each template is named by a string specified when it is created. A template may
|
||||
use a template invocation to instantiate another template directly or by its
|
||||
name; see the explanation of the template action above. The name is looked up
|
||||
in the template set associated with the template.
|
||||
Each template is named by a string specified when it is created. Also, each
|
||||
template is associated with zero or more other templates that it may invoke by
|
||||
name; such associations are transitive and form a name space of templates.
|
||||
|
||||
If no template invocation actions occur in the template, the issue of template
|
||||
sets can be ignored. If it does contain invocations, though, the template
|
||||
containing the invocations must be part of a template set in which to look up
|
||||
the names.
|
||||
A template may use a template invocation to instantiate another associated
|
||||
template; see the explanation of the "template" action above. The name must be
|
||||
that of a template associated with the template that contains the invocation.
|
||||
|
||||
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
|
||||
a single input defining multiple templates. The syntax of the definitions is to
|
||||
surround each template declaration with a define and end action.
|
||||
When parsing a template, another template may be defined and associated with the
|
||||
template being parsed. Template definitions must appear at the top level of the
|
||||
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
|
||||
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 "T2"}} definition of template T2 {{end}}
|
||||
{{define "T3"}} {{template "T1"}} {{template "T2"}} {{end}}`
|
||||
`{{define "T1"}}ONE{{end}}
|
||||
{{define "T2"}}TWO{{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
|
||||
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
|
||||
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.
|
||||
ONE TWO
|
||||
|
||||
Set.Parse may be called multiple times on different inputs to construct the set.
|
||||
Two sets may therefore be constructed with a common base set of templates plus,
|
||||
through a second Parse call each, specializations for some elements.
|
||||
By construction, a template may reside in only one association. If it's
|
||||
necessary to have a template addressable from multiple associations, the
|
||||
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
|
||||
named template from the set. To invoke our example above, we might write,
|
||||
Parse may be called multiple times to assemble the various associated templates;
|
||||
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 {
|
||||
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
|
||||
|
@ -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,
|
||||
// writing the output to wr.
|
||||
// and writes the output to wr.
|
||||
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||
defer errRecover(&err)
|
||||
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) {
|
||||
set := s.tmpl.set
|
||||
if set == nil {
|
||||
s.errorf("no set defined in which to invoke template named %q", t.Name)
|
||||
}
|
||||
tmpl := set.tmpl[t.Name]
|
||||
tmpl := s.tmpl.tmpl[t.Name]
|
||||
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.
|
||||
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 {
|
||||
function, ok := findFunction(name, s.tmpl, s.tmpl.set)
|
||||
function, ok := findFunction(name, s.tmpl)
|
||||
if !ok {
|
||||
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() {
|
||||
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)
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
// 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 (
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
|
@ -476,7 +476,7 @@ func vfunc(V, *V) string {
|
||||
return "vfunc"
|
||||
}
|
||||
|
||||
func testExecute(execTests []execTest, set *Set, t *testing.T) {
|
||||
func testExecute(execTests []execTest, template *Template, t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
funcs := FuncMap{
|
||||
"count": count,
|
||||
@ -486,12 +486,13 @@ func testExecute(execTests []execTest, set *Set, t *testing.T) {
|
||||
"zeroArgs": zeroArgs,
|
||||
}
|
||||
for _, test := range execTests {
|
||||
tmpl := New(test.name).Funcs(funcs)
|
||||
theSet := set
|
||||
if theSet == nil {
|
||||
theSet = new(Set)
|
||||
var tmpl *Template
|
||||
var err error
|
||||
if template == nil {
|
||||
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 {
|
||||
t.Errorf("%s: parse error: %s", test.name, err)
|
||||
continue
|
||||
@ -663,24 +664,34 @@ func TestTree(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
set := new(Set)
|
||||
_, err := set.Delims("(", ")").Parse(treeTemplate)
|
||||
tmpl, err := New("root").Delims("(", ")").Parse(treeTemplate)
|
||||
if err != nil {
|
||||
t.Fatal("parse error:", err)
|
||||
}
|
||||
var b bytes.Buffer
|
||||
err = set.Execute(&b, "tree", tree)
|
||||
if err != nil {
|
||||
t.Fatal("exec error:", err)
|
||||
}
|
||||
stripSpace := func(r rune) rune {
|
||||
if r == '\t' || r == '\n' {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}
|
||||
result := strings.Map(stripSpace, b.String())
|
||||
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 {
|
||||
t.Errorf("expected %q got %q", expect, result)
|
||||
}
|
||||
|
@ -17,8 +17,9 @@ import (
|
||||
|
||||
// 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
|
||||
// which the second has type error. If the second argument evaluates to non-nil
|
||||
// during execution, execution terminates and Execute returns an error.
|
||||
// which the second has type error. In that case, if the second (error)
|
||||
// argument evaluates to non-nil during execution, execution terminates and
|
||||
// Execute returns that error.
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
var builtins = FuncMap{
|
||||
@ -78,18 +79,13 @@ func goodFunc(typ reflect.Type) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// findFunction looks for a function in the template, set, and global map.
|
||||
func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) {
|
||||
if tmpl != nil {
|
||||
// findFunction looks for a function in the template, and global map.
|
||||
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||
if tmpl != nil && tmpl.common != nil {
|
||||
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
}
|
||||
if set != nil {
|
||||
if fn := set.execFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
}
|
||||
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
@ -310,7 +306,6 @@ func JSEscape(w io.Writer, b []byte) {
|
||||
if unicode.IsPrint(r) {
|
||||
w.Write(b[i : i+size])
|
||||
} else {
|
||||
// TODO(dsymonds): Do this without fmt?
|
||||
fmt.Fprintf(w, "\\u%04X", r)
|
||||
}
|
||||
i += size - 1
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// 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
|
||||
|
||||
@ -12,11 +12,11 @@ import (
|
||||
"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)
|
||||
// and panics if the error is non-nil. It is intended for use in variable initializations
|
||||
// such as
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
// var t = template.Must(template.New("name").Parse("text"))
|
||||
func Must(t *Template, err error) *Template {
|
||||
if err != nil {
|
||||
@ -25,217 +25,84 @@ func Must(t *Template, err error) *Template {
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseFile creates a new Template and parses the template definition from
|
||||
// the named file. The template name is the base name of the file.
|
||||
func ParseFile(filename string) (*Template, error) {
|
||||
t := New(filepath.Base(filename))
|
||||
return t.ParseFile(filename)
|
||||
// ParseFiles creates a new Template and parses the template definitions from
|
||||
// the named files. The returned template's name will have the (base) name and
|
||||
// (parsed) contents of the first file. There must be at least one file.
|
||||
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||
func ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(nil, filenames...)
|
||||
}
|
||||
|
||||
// parseFileInSet creates a new Template and parses the template
|
||||
// definition from the named file. The template name is the base name
|
||||
// of the file. It also adds the template to the set. Function bindings are
|
||||
// checked against those in the set.
|
||||
func parseFileInSet(filename string, set *Set) (*Template, error) {
|
||||
t := New(filepath.Base(filename))
|
||||
return t.parseFileInSet(filename, set)
|
||||
// ParseFiles parses the named files and associates the resulting templates with
|
||||
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||
// otherwise it is t. There must be at least one file.
|
||||
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
|
||||
// ParseFile reads the template definition from a file and parses it to
|
||||
// construct an internal representation of the template for execution.
|
||||
// The returned template will be nil if an error occurs.
|
||||
func (t *Template) ParseFile(filename string) (*Template, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// parseFiles is the helper for the method and function. If the argument
|
||||
// template is nil, it is created from the first file.
|
||||
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||
if len(filenames) == 0 {
|
||||
// Not really a problem, but be consistent.
|
||||
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||
}
|
||||
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 {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ParseSetFiles creates a new Set and parses the set definition from the
|
||||
// named files. Each file must be individually parseable.
|
||||
func ParseSetFiles(filenames ...string) (*Set, error) {
|
||||
s := new(Set)
|
||||
for _, filename := range filenames {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = s.Parse(string(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
// ParseGlob creates a new Template and parses the template definitions from the
|
||||
// files identified by the pattern, which must match at least one file. The
|
||||
// returned template will have the (base) name and (parsed) contents of the
|
||||
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||
// ParseFiles with the list of files matched by the pattern.
|
||||
func ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(nil, pattern)
|
||||
}
|
||||
|
||||
// ParseGlob 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.
|
||||
// If an error occurs, parsing stops and the returned set is nil.
|
||||
func (s *Set) ParseGlob(pattern string) (*Set, error) {
|
||||
// ParseGlob parses the template definitions in the files identified by the
|
||||
// pattern and associates the resulting templates with t. The pattern is
|
||||
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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...)
|
||||
}
|
||||
|
||||
// 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
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
|
251
src/pkg/text/template/multi_test.go
Normal file
251
src/pkg/text/template/multi_test.go
Normal 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())
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -97,6 +97,15 @@ func (t *Tree) expect(expected itemType, context string) item {
|
||||
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.
|
||||
func (t *Tree) unexpected(token item, context string) {
|
||||
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.parse(treeSet)
|
||||
t.stopParse()
|
||||
t.add(treeSet)
|
||||
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
|
||||
// as itemList except it also parses {{define}} actions.
|
||||
// It runs to EOF.
|
||||
@ -174,7 +192,7 @@ func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
||||
if t.peek().typ == itemLeftDelim {
|
||||
delim := t.next()
|
||||
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.parseDefinition(treeSet)
|
||||
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
|
||||
// been scanned.
|
||||
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||
if treeSet == nil {
|
||||
t.errorf("no set specified for template definition")
|
||||
}
|
||||
const context = "define clause"
|
||||
name := t.expect(itemString, context)
|
||||
name := t.expectOneOf(itemString, itemRawString, context)
|
||||
var err error
|
||||
t.Name, err = strconv.Unquote(name.val)
|
||||
if err != nil {
|
||||
@ -211,10 +226,7 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||
t.errorf("unexpected %s in %s", end, context)
|
||||
}
|
||||
t.stopParse()
|
||||
if _, present := treeSet[t.Name]; present {
|
||||
t.errorf("template: %q multiply defined", name)
|
||||
}
|
||||
treeSet[t.Name] = t
|
||||
t.add(treeSet)
|
||||
}
|
||||
|
||||
// itemList:
|
||||
|
@ -236,7 +236,7 @@ var builtins = map[string]interface{}{
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
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 {
|
||||
case err == nil && !test.ok:
|
||||
t.Errorf("%q: expected error; got none", test.name)
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
217
src/pkg/text/template/template.go
Normal file
217
src/pkg/text/template/template.go
Normal 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
|
||||
}
|
2
src/pkg/text/template/testdata/tmpl1.tmpl
vendored
2
src/pkg/text/template/testdata/tmpl1.tmpl
vendored
@ -1 +1,3 @@
|
||||
template1
|
||||
{{define "x"}}x{{end}}
|
||||
{{template "y"}}
|
||||
|
2
src/pkg/text/template/testdata/tmpl2.tmpl
vendored
2
src/pkg/text/template/testdata/tmpl2.tmpl
vendored
@ -1 +1,3 @@
|
||||
template2
|
||||
{{define "y"}}y{{end}}
|
||||
{{template "x"}}
|
||||
|
Loading…
Reference in New Issue
Block a user