mirror of
https://github.com/golang/go
synced 2024-11-24 22:47:58 -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) {
|
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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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("view.html")
|
t, _ := template.ParseFiles("view.html")
|
||||||
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+".html", nil)
|
t, _ := template.ParseFiles(tmpl+".html", 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+".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
|
||||||
@ -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{"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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\
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
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
|
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:
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
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
|
template2
|
||||||
|
{{define "y"}}y{{end}}
|
||||||
|
{{template "x"}}
|
||||||
|
Loading…
Reference in New Issue
Block a user