Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(2)

Side by Side Diff: src/pkg/text/template/template.go

Issue 5415060: code review 5415060: text/template: new, simpler API (Closed)
Patch Set: diff -r 75348d679279 https://go.googlecode.com/hg/ Created 13 years, 3 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/pkg/text/template/parse/parse_test.go ('k') | src/pkg/text/template/testdata/tmpl1.tmpl » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2011 The Go Authors. All rights reserved. 1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 package template 5 package template
6 6
7 import ( 7 import (
8 "bytes"
8 "fmt" 9 "fmt"
9 "io"
10 "reflect" 10 "reflect"
11 "text/template/parse" 11 "text/template/parse"
12 ) 12 )
13 13
14 // Set holds a set of related templates that can refer to one another by name. 14 // common holds the information shared by related templates.
15 // The zero value represents an empty set. 15 type common struct {
16 // A template may be a member of multiple sets. 16 » tmpl map[string]*Template
17 type Set struct { 17 » // We use two maps, one for parsing and one for execution.
18 » tmpl map[string]*Template 18 » // This separation makes the API cleaner since it doesn't
19 » trees map[string]*parse.Tree // maintained by parse package 19 » // expose reflection to the client.
20 » parseFuncs FuncMap
21 » execFuncs map[string]reflect.Value
22 }
23
24 // Template is the representation of a parsed template. The *parse.Tree
25 // field is exported only for use by html/template and should be treated
26 // as unexported by all other clients.
27 type Template struct {
28 » name string
29 » *parse.Tree
30 » *common
20 leftDelim string 31 leftDelim string
21 rightDelim string 32 rightDelim string
22 » parseFuncs FuncMap 33 }
23 » execFuncs map[string]reflect.Value 34
24 } 35 // New allocates a new template with the given name.
25 36 func New(name string) *Template {
26 func (s *Set) init() { 37 » return &Template{
27 » if s.tmpl == nil { 38 » » name: name,
28 » » s.tmpl = make(map[string]*Template) 39 » }
29 » » s.parseFuncs = make(FuncMap) 40 }
30 » » s.execFuncs = make(map[string]reflect.Value) 41
31 » } 42 // Name returns the name of the template.
32 } 43 func (t *Template) Name() string {
33 44 » return t.name
34 // Delims sets the action delimiters, to be used in a subsequent 45 }
35 // parse, to the specified strings. 46
36 // An empty delimiter stands for the corresponding default: {{ or }}. 47 // New allocates a new template associated with the given one and with the same
37 // The return value is the set, so calls can be chained. 48 // delimiters. The association, which is transitive, allows one template to
38 func (s *Set) Delims(left, right string) *Set { 49 // invoke another with a {{template}} action.
39 » s.leftDelim = left 50 func (t *Template) New(name string) *Template {
40 » s.rightDelim = right 51 » t.init()
41 » return s 52 » return &Template{
42 } 53 » » name: name,
43 54 » » common: t.common,
44 // Funcs adds the elements of the argument map to the set's function map. It 55 » » leftDelim: t.leftDelim,
45 // panics if a value in the map is not a function with appropriate return 56 » » rightDelim: t.rightDelim,
46 // type. 57 » }
47 // The return value is the set, so calls can be chained. 58 }
48 func (s *Set) Funcs(funcMap FuncMap) *Set { 59
49 » s.init() 60 func (t *Template) init() {
50 » addValueFuncs(s.execFuncs, funcMap) 61 » if t.common == nil {
51 » addFuncs(s.parseFuncs, funcMap) 62 » » t.common = new(common)
52 » return s 63 » » t.tmpl = make(map[string]*Template)
53 } 64 » » t.parseFuncs = make(FuncMap)
54 65 » » t.execFuncs = make(map[string]reflect.Value)
55 // Add adds the argument templates to the set. It panics if two templates 66 » }
56 // with the same name are added or if a template is already a member of 67 }
57 // a set. 68
58 // The return value is the set, so calls can be chained. 69 // Clone returns a duplicate of the template, including all associated
59 func (s *Set) Add(templates ...*Template) *Set { 70 // templates. The actual representation is not copied, but the name space of
60 » for _, t := range templates { 71 // associated templates is, so further calls to Parse in the copy will add
61 » » if err := s.add(t); err != nil { 72 // templates to the copy but not to the original. Clone can be used to prepare
62 » » » panic(err) 73 // common templates and use them with variant definitions for other templates by
63 » » } 74 // adding the variants after the clone is made.
64 » } 75 func (t *Template) Clone() *Template {
65 » return s 76 » nt := t.copy()
66 } 77 » nt.init()
67 78 » for k, v := range t.tmpl {
68 // add adds the argument template to the set. 79 » » // The associated templates share nt's common structure.
69 func (s *Set) add(t *Template) error { 80 » » tmpl := v.copy()
70 » s.init() 81 » » tmpl.common = nt.common
71 » if t.set != nil { 82 » » nt.tmpl[k] = tmpl
72 » » return fmt.Errorf("template: %q already in a set", t.name) 83 » }
73 » } 84 » for k, v := range t.parseFuncs {
74 » if _, ok := s.tmpl[t.name]; ok { 85 » » nt.parseFuncs[k] = v
75 » » return fmt.Errorf("template: %q already defined in set", t.name) 86 » }
76 » } 87 » for k, v := range t.execFuncs {
77 » s.tmpl[t.name] = t 88 » » nt.execFuncs[k] = v
78 » t.set = s 89 » }
79 » return nil 90 » return nt
80 } 91 }
81 92
82 // Template returns the template with the given name in the set, 93 // copy returns a shallow copy of t, with common set to nil.
94 func (t *Template) copy() *Template {
95 » nt := New(t.name)
96 » nt.Tree = t.Tree
97 » nt.leftDelim = t.leftDelim
98 » nt.rightDelim = t.rightDelim
99 » return nt
100 }
101
102 // Templates returns a slice of the templates associated with t, including t
103 // itself.
104 func (t *Template) Templates() []*Template {
105 » // Return a slice so we don't expose the map.
106 » m := make([]*Template, 0, len(t.tmpl))
107 » for _, v := range t.tmpl {
108 » » m = append(m, v)
109 » }
110 » return m
111 }
112
113 // Delims sets the action delimiters to the specified strings, to be used in
114 // subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
115 // definitions will inherit the settings. An empty delimiter stands for the
116 // corresponding default: {{ or }}.
117 // The return value is the template, so calls can be chained.
118 func (t *Template) Delims(left, right string) *Template {
119 » t.leftDelim = left
120 » t.rightDelim = right
121 » return t
122 }
123
124 // Funcs adds the elements of the argument map to the template's function map.
125 // It panics if a value in the map is not a function with appropriate return
126 // type. However, it is legal to overwrite elements of the map. The return
127 // value is the template, so calls can be chained.
128 func (t *Template) Funcs(funcMap FuncMap) *Template {
129 » t.init()
130 » addValueFuncs(t.execFuncs, funcMap)
131 » addFuncs(t.parseFuncs, funcMap)
132 » return t
133 }
134
135 // Template returns the template with the given name that is associated with t,
83 // or nil if there is no such template. 136 // or nil if there is no such template.
84 func (s *Set) Template(name string) *Template { 137 func (t *Template) Template(name string) *Template {
85 » return s.tmpl[name] 138 » return t.tmpl[name]
86 } 139 }
87 140
88 // FuncMap returns the set's function map. 141 // Parse parses a string into a template. Nested template definitions will be
89 func (s *Set) FuncMap() FuncMap { 142 // associated with the top-level template t. Parse may be called multiple times
90 » return s.parseFuncs 143 // to parse definitions of templates to associate with t. It is an error if a
91 } 144 // resulting template is non-empty (contains content other than template
92 145 // definitions) and would replace a non-empty template with the same name.
93 // Execute applies the named template to the specified data object, writing 146 // (In multiple calls to Parse with the same receiver template, only one call
94 // the output to wr. 147 // can contain text other than space, comments, and template definitions.)
95 func (s *Set) Execute(wr io.Writer, name string, data interface{}) error { 148 func (t *Template) Parse(text string) (*Template, error) {
96 » tmpl := s.tmpl[name] 149 » t.init()
97 » if tmpl == nil { 150 » trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.par seFuncs, builtins)
98 » » return fmt.Errorf("template: no template %q in set", name)
99 » }
100 » return tmpl.Execute(wr, data)
101 }
102
103 // Parse parses a string into a set of named templates. Parse may be called
104 // multiple times for a given set, adding the templates defined in the string
105 // to the set. It is an error if a template has a name already defined in the s et.
106 func (s *Set) Parse(text string) (*Set, error) {
107 » // TODO: "ROOT" is just a placeholder while we rejig the API.
108 » trees, err := parse.Parse("ROOT", text, s.leftDelim, s.rightDelim, s.par seFuncs, builtins)
109 if err != nil { 151 if err != nil {
110 return nil, err 152 return nil, err
111 } 153 }
112 » s.init() 154 » // Add the newly parsed trees, including the one for t, into our common structure.
113 for name, tree := range trees { 155 for name, tree := range trees {
114 » » tmpl := New(name) 156 » » // If the name we parsed is the name of this template, overwrite this template.
157 » » // The associate method checks it's not a redefinition.
158 » » tmpl := t
159 » » if name != t.name {
160 » » » tmpl = t.New(name)
161 » » }
162 » » // Even if t == tmpl, we need to install it in the common.tmpl m ap.
163 » » if err := t.associate(tmpl); err != nil {
164 » » » return nil, err
165 » » }
115 tmpl.Tree = tree 166 tmpl.Tree = tree
116 » » err = s.add(tmpl) 167 » » tmpl.leftDelim = t.leftDelim
117 » » if err != nil { 168 » » tmpl.rightDelim = t.rightDelim
118 » » » return s, err 169 » }
119 » » } 170 » return t, nil
120 » } 171 }
121 » return s, nil 172
122 } 173 // associate installs the new template into the group of templates associated
174 // with t. It is an error to reuse a name except to overwrite an empty
175 // template. The two are already known to share the common structure.
176 func (t *Template) associate(new *Template) error {
177 » if new.common != t.common {
178 » » panic("internal error: associate not common")
179 » }
180 » name := new.name
181 » if old := t.tmpl[name]; old != nil {
182 » » oldIsEmpty := isEmpty(old.Root)
183 » » newIsEmpty := isEmpty(new.Root)
184 » » if !oldIsEmpty && !newIsEmpty {
185 » » » return fmt.Errorf("template: redefinition of template %q ", name)
186 » » }
187 » » if newIsEmpty {
188 » » » // Whether old is empty or not, new is empty; no reason to replace old.
189 » » » return nil
190 » » }
191 » }
192 » t.tmpl[name] = new
193 » return nil
194 }
195
196 // isEmpty reports whether this tree (node) is empty of everything but space.
197 func isEmpty(n parse.Node) bool {
198 » switch n := n.(type) {
199 » case *parse.ActionNode:
200 » case *parse.IfNode:
201 » case *parse.ListNode:
202 » » for _, node := range n.Nodes {
203 » » » if !isEmpty(node) {
204 » » » » return false
205 » » » }
206 » » }
207 » » return true
208 » case *parse.RangeNode:
209 » case *parse.TemplateNode:
210 » case *parse.TextNode:
211 » » return len(bytes.TrimSpace(n.Text)) == 0
212 » case *parse.WithNode:
213 » default:
214 » » panic("unknown node: " + n.String())
215 » }
216 » return false
217 }
OLDNEW
« no previous file with comments | « src/pkg/text/template/parse/parse_test.go ('k') | src/pkg/text/template/testdata/tmpl1.tmpl » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b