template.go (17844B)
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package template
6
7 import (
8 "fmt"
9 "io"
10 "io/fs"
11 "os"
12 "path"
13 "path/filepath"
14 "sync"
15
16 template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
17 "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
18 )
19
20 // Template is a specialized Template from "text/template" that produces a safe
21 // HTML document fragment.
22 type Template struct {
23 // Sticky error if escaping fails, or escapeOK if succeeded.
24 escapeErr error
25 // We could embed the text/template field, but it's safer not to because
26 // we need to keep our version of the name space and the underlying
27 // template's in sync.
28 text *template.Template
29 // The underlying template's parse tree, updated to be HTML-safe.
30 Tree *parse.Tree
31 *nameSpace // common to all associated templates
32 }
33
34 // escapeOK is a sentinel value used to indicate valid escaping.
35 var escapeOK = fmt.Errorf("template escaped correctly")
36
37 // nameSpace is the data structure shared by all templates in an association.
38 type nameSpace struct {
39 mu sync.Mutex
40 set map[string]*Template
41 escaped bool
42 esc escaper
43 }
44
45 // Templates returns a slice of the templates associated with t, including t
46 // itself.
47 func (t *Template) Templates() []*Template {
48 ns := t.nameSpace
49 ns.mu.Lock()
50 defer ns.mu.Unlock()
51 // Return a slice so we don't expose the map.
52 m := make([]*Template, 0, len(ns.set))
53 for _, v := range ns.set {
54 m = append(m, v)
55 }
56 return m
57 }
58
59 // Option sets options for the template. Options are described by
60 // strings, either a simple string or "key=value". There can be at
61 // most one equals sign in an option string. If the option string
62 // is unrecognized or otherwise invalid, Option panics.
63 //
64 // Known options:
65 //
66 // missingkey: Control the behavior during execution if a map is
67 // indexed with a key that is not present in the map.
68 // "missingkey=default" or "missingkey=invalid"
69 // The default behavior: Do nothing and continue execution.
70 // If printed, the result of the index operation is the string
71 // "<no value>".
72 // "missingkey=zero"
73 // The operation returns the zero value for the map type's element.
74 // "missingkey=error"
75 // Execution stops immediately with an error.
76 //
77 func (t *Template) Option(opt ...string) *Template {
78 t.text.Option(opt...)
79 return t
80 }
81
82 // checkCanParse checks whether it is OK to parse templates.
83 // If not, it returns an error.
84 func (t *Template) checkCanParse() error {
85 if t == nil {
86 return nil
87 }
88 t.nameSpace.mu.Lock()
89 defer t.nameSpace.mu.Unlock()
90 if t.nameSpace.escaped {
91 return fmt.Errorf("html/template: cannot Parse after Execute")
92 }
93 return nil
94 }
95
96 // escape escapes all associated templates.
97 func (t *Template) escape() error {
98 t.nameSpace.mu.Lock()
99 defer t.nameSpace.mu.Unlock()
100 t.nameSpace.escaped = true
101 if t.escapeErr == nil {
102 if t.Tree == nil {
103 return fmt.Errorf("template: %q is an incomplete or empty template", t.Name())
104 }
105 if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil {
106 return err
107 }
108 } else if t.escapeErr != escapeOK {
109 return t.escapeErr
110 }
111 return nil
112 }
113
114 // Execute applies a parsed template to the specified data object,
115 // writing the output to wr.
116 // If an error occurs executing the template or writing its output,
117 // execution stops, but partial results may already have been written to
118 // the output writer.
119 // A template may be executed safely in parallel, although if parallel
120 // executions share a Writer the output may be interleaved.
121 func (t *Template) Execute(wr io.Writer, data any) error {
122 if err := t.escape(); err != nil {
123 return err
124 }
125 return t.text.Execute(wr, data)
126 }
127
128 // ExecuteTemplate applies the template associated with t that has the given
129 // name to the specified data object and writes the output to wr.
130 // If an error occurs executing the template or writing its output,
131 // execution stops, but partial results may already have been written to
132 // the output writer.
133 // A template may be executed safely in parallel, although if parallel
134 // executions share a Writer the output may be interleaved.
135 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error {
136 tmpl, err := t.lookupAndEscapeTemplate(name)
137 if err != nil {
138 return err
139 }
140 return tmpl.text.Execute(wr, data)
141 }
142
143 // lookupAndEscapeTemplate guarantees that the template with the given name
144 // is escaped, or returns an error if it cannot be. It returns the named
145 // template.
146 func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
147 t.nameSpace.mu.Lock()
148 defer t.nameSpace.mu.Unlock()
149 t.nameSpace.escaped = true
150 tmpl = t.set[name]
151 if tmpl == nil {
152 return nil, fmt.Errorf("html/template: %q is undefined", name)
153 }
154 if tmpl.escapeErr != nil && tmpl.escapeErr != escapeOK {
155 return nil, tmpl.escapeErr
156 }
157 if tmpl.text.Tree == nil || tmpl.text.Root == nil {
158 return nil, fmt.Errorf("html/template: %q is an incomplete template", name)
159 }
160 if t.text.Lookup(name) == nil {
161 panic("html/template internal error: template escaping out of sync")
162 }
163 if tmpl.escapeErr == nil {
164 err = escapeTemplate(tmpl, tmpl.text.Root, name)
165 }
166 return tmpl, err
167 }
168
169 // DefinedTemplates returns a string listing the defined templates,
170 // prefixed by the string "; defined templates are: ". If there are none,
171 // it returns the empty string. Used to generate an error message.
172 func (t *Template) DefinedTemplates() string {
173 return t.text.DefinedTemplates()
174 }
175
176 // Parse parses text as a template body for t.
177 // Named template definitions ({{define ...}} or {{block ...}} statements) in text
178 // define additional templates associated with t and are removed from the
179 // definition of t itself.
180 //
181 // Templates can be redefined in successive calls to Parse,
182 // before the first use of Execute on t or any associated template.
183 // A template definition with a body containing only white space and comments
184 // is considered empty and will not replace an existing template's body.
185 // This allows using Parse to add new named template definitions without
186 // overwriting the main template body.
187 func (t *Template) Parse(text string) (*Template, error) {
188 if err := t.checkCanParse(); err != nil {
189 return nil, err
190 }
191
192 ret, err := t.text.Parse(text)
193 if err != nil {
194 return nil, err
195 }
196
197 // In general, all the named templates might have changed underfoot.
198 // Regardless, some new ones may have been defined.
199 // The template.Template set has been updated; update ours.
200 t.nameSpace.mu.Lock()
201 defer t.nameSpace.mu.Unlock()
202 for _, v := range ret.Templates() {
203 name := v.Name()
204 tmpl := t.set[name]
205 if tmpl == nil {
206 tmpl = t.new(name)
207 }
208 tmpl.text = v
209 tmpl.Tree = v.Tree
210 }
211 return t, nil
212 }
213
214 // AddParseTree creates a new template with the name and parse tree
215 // and associates it with t.
216 //
217 // It returns an error if t or any associated template has already been executed.
218 func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
219 if err := t.checkCanParse(); err != nil {
220 return nil, err
221 }
222
223 t.nameSpace.mu.Lock()
224 defer t.nameSpace.mu.Unlock()
225 text, err := t.text.AddParseTree(name, tree)
226 if err != nil {
227 return nil, err
228 }
229 ret := &Template{
230 nil,
231 text,
232 text.Tree,
233 t.nameSpace,
234 }
235 t.set[name] = ret
236 return ret, nil
237 }
238
239 // Clone returns a duplicate of the template, including all associated
240 // templates. The actual representation is not copied, but the name space of
241 // associated templates is, so further calls to Parse in the copy will add
242 // templates to the copy but not to the original. Clone can be used to prepare
243 // common templates and use them with variant definitions for other templates
244 // by adding the variants after the clone is made.
245 //
246 // It returns an error if t has already been executed.
247 func (t *Template) Clone() (*Template, error) {
248 t.nameSpace.mu.Lock()
249 defer t.nameSpace.mu.Unlock()
250 if t.escapeErr != nil {
251 return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
252 }
253 textClone, err := t.text.Clone()
254 if err != nil {
255 return nil, err
256 }
257 ns := &nameSpace{set: make(map[string]*Template)}
258 ns.esc = makeEscaper(ns)
259 ret := &Template{
260 nil,
261 textClone,
262 textClone.Tree,
263 ns,
264 }
265 ret.set[ret.Name()] = ret
266 for _, x := range textClone.Templates() {
267 name := x.Name()
268 src := t.set[name]
269 if src == nil || src.escapeErr != nil {
270 return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
271 }
272 x.Tree = x.Tree.Copy()
273 ret.set[name] = &Template{
274 nil,
275 x,
276 x.Tree,
277 ret.nameSpace,
278 }
279 }
280 // Return the template associated with the name of this template.
281 return ret.set[ret.Name()], nil
282 }
283
284 // New allocates a new HTML template with the given name.
285 func New(name string) *Template {
286 ns := &nameSpace{set: make(map[string]*Template)}
287 ns.esc = makeEscaper(ns)
288 tmpl := &Template{
289 nil,
290 template.New(name),
291 nil,
292 ns,
293 }
294 tmpl.set[name] = tmpl
295 return tmpl
296 }
297
298 // New allocates a new HTML template associated with the given one
299 // and with the same delimiters. The association, which is transitive,
300 // allows one template to invoke another with a {{template}} action.
301 //
302 // If a template with the given name already exists, the new HTML template
303 // will replace it. The existing template will be reset and disassociated with
304 // t.
305 func (t *Template) New(name string) *Template {
306 t.nameSpace.mu.Lock()
307 defer t.nameSpace.mu.Unlock()
308 return t.new(name)
309 }
310
311 // new is the implementation of New, without the lock.
312 func (t *Template) new(name string) *Template {
313 tmpl := &Template{
314 nil,
315 t.text.New(name),
316 nil,
317 t.nameSpace,
318 }
319 if existing, ok := tmpl.set[name]; ok {
320 emptyTmpl := New(existing.Name())
321 *existing = *emptyTmpl
322 }
323 tmpl.set[name] = tmpl
324 return tmpl
325 }
326
327 // Name returns the name of the template.
328 func (t *Template) Name() string {
329 return t.text.Name()
330 }
331
332 // FuncMap is the type of the map defining the mapping from names to
333 // functions. Each function must have either a single return value, or two
334 // return values of which the second has type error. In that case, if the
335 // second (error) argument evaluates to non-nil during execution, execution
336 // terminates and Execute returns that error. FuncMap has the same base type
337 // as FuncMap in "text/template", copied here so clients need not import
338 // "text/template".
339 type FuncMap map[string]any
340
341 // Funcs adds the elements of the argument map to the template's function map.
342 // It must be called before the template is parsed.
343 // It panics if a value in the map is not a function with appropriate return
344 // type. However, it is legal to overwrite elements of the map. The return
345 // value is the template, so calls can be chained.
346 func (t *Template) Funcs(funcMap FuncMap) *Template {
347 t.text.Funcs(template.FuncMap(funcMap))
348 return t
349 }
350
351 // Delims sets the action delimiters to the specified strings, to be used in
352 // subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
353 // definitions will inherit the settings. An empty delimiter stands for the
354 // corresponding default: {{ or }}.
355 // The return value is the template, so calls can be chained.
356 func (t *Template) Delims(left, right string) *Template {
357 t.text.Delims(left, right)
358 return t
359 }
360
361 // Lookup returns the template with the given name that is associated with t,
362 // or nil if there is no such template.
363 func (t *Template) Lookup(name string) *Template {
364 t.nameSpace.mu.Lock()
365 defer t.nameSpace.mu.Unlock()
366 return t.set[name]
367 }
368
369 // Must is a helper that wraps a call to a function returning (*Template, error)
370 // and panics if the error is non-nil. It is intended for use in variable initializations
371 // such as
372 // var t = template.Must(template.New("name").Parse("html"))
373 func Must(t *Template, err error) *Template {
374 if err != nil {
375 panic(err)
376 }
377 return t
378 }
379
380 // ParseFiles creates a new Template and parses the template definitions from
381 // the named files. The returned template's name will have the (base) name and
382 // (parsed) contents of the first file. There must be at least one file.
383 // If an error occurs, parsing stops and the returned *Template is nil.
384 //
385 // When parsing multiple files with the same name in different directories,
386 // the last one mentioned will be the one that results.
387 // For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
388 // named "foo", while "a/foo" is unavailable.
389 func ParseFiles(filenames ...string) (*Template, error) {
390 return parseFiles(nil, readFileOS, filenames...)
391 }
392
393 // ParseFiles parses the named files and associates the resulting templates with
394 // t. If an error occurs, parsing stops and the returned template is nil;
395 // otherwise it is t. There must be at least one file.
396 //
397 // When parsing multiple files with the same name in different directories,
398 // the last one mentioned will be the one that results.
399 //
400 // ParseFiles returns an error if t or any associated template has already been executed.
401 func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
402 return parseFiles(t, readFileOS, filenames...)
403 }
404
405 // parseFiles is the helper for the method and function. If the argument
406 // template is nil, it is created from the first file.
407 func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
408 if err := t.checkCanParse(); err != nil {
409 return nil, err
410 }
411
412 if len(filenames) == 0 {
413 // Not really a problem, but be consistent.
414 return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
415 }
416 for _, filename := range filenames {
417 name, b, err := readFile(filename)
418 if err != nil {
419 return nil, err
420 }
421 s := string(b)
422 // First template becomes return value if not already defined,
423 // and we use that one for subsequent New calls to associate
424 // all the templates together. Also, if this file has the same name
425 // as t, this file becomes the contents of t, so
426 // t, err := New(name).Funcs(xxx).ParseFiles(name)
427 // works. Otherwise we create a new template associated with t.
428 var tmpl *Template
429 if t == nil {
430 t = New(name)
431 }
432 if name == t.Name() {
433 tmpl = t
434 } else {
435 tmpl = t.New(name)
436 }
437 _, err = tmpl.Parse(s)
438 if err != nil {
439 return nil, err
440 }
441 }
442 return t, nil
443 }
444
445 // ParseGlob creates a new Template and parses the template definitions from
446 // the files identified by the pattern. The files are matched according to the
447 // semantics of filepath.Match, and the pattern must match at least one file.
448 // The returned template will have the (base) name and (parsed) contents of the
449 // first file matched by the pattern. ParseGlob is equivalent to calling
450 // ParseFiles with the list of files matched by the pattern.
451 //
452 // When parsing multiple files with the same name in different directories,
453 // the last one mentioned will be the one that results.
454 func ParseGlob(pattern string) (*Template, error) {
455 return parseGlob(nil, pattern)
456 }
457
458 // ParseGlob parses the template definitions in the files identified by the
459 // pattern and associates the resulting templates with t. The files are matched
460 // according to the semantics of filepath.Match, and the pattern must match at
461 // least one file. ParseGlob is equivalent to calling t.ParseFiles with the
462 // list of files matched by the pattern.
463 //
464 // When parsing multiple files with the same name in different directories,
465 // the last one mentioned will be the one that results.
466 //
467 // ParseGlob returns an error if t or any associated template has already been executed.
468 func (t *Template) ParseGlob(pattern string) (*Template, error) {
469 return parseGlob(t, pattern)
470 }
471
472 // parseGlob is the implementation of the function and method ParseGlob.
473 func parseGlob(t *Template, pattern string) (*Template, error) {
474 if err := t.checkCanParse(); err != nil {
475 return nil, err
476 }
477 filenames, err := filepath.Glob(pattern)
478 if err != nil {
479 return nil, err
480 }
481 if len(filenames) == 0 {
482 return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern)
483 }
484 return parseFiles(t, readFileOS, filenames...)
485 }
486
487 // IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
488 // and whether the value has a meaningful truth value. This is the definition of
489 // truth used by if and other such actions.
490 func IsTrue(val any) (truth, ok bool) {
491 return template.IsTrue(val)
492 }
493
494 // ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
495 // instead of the host operating system's file system.
496 // It accepts a list of glob patterns.
497 // (Note that most file names serve as glob patterns matching only themselves.)
498 func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
499 return parseFS(nil, fs, patterns)
500 }
501
502 // ParseFS is like ParseFiles or ParseGlob but reads from the file system fs
503 // instead of the host operating system's file system.
504 // It accepts a list of glob patterns.
505 // (Note that most file names serve as glob patterns matching only themselves.)
506 func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
507 return parseFS(t, fs, patterns)
508 }
509
510 func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
511 var filenames []string
512 for _, pattern := range patterns {
513 list, err := fs.Glob(fsys, pattern)
514 if err != nil {
515 return nil, err
516 }
517 if len(list) == 0 {
518 return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
519 }
520 filenames = append(filenames, list...)
521 }
522 return parseFiles(t, readFileFS(fsys), filenames...)
523 }
524
525 func readFileOS(file string) (name string, b []byte, err error) {
526 name = filepath.Base(file)
527 b, err = os.ReadFile(file)
528 return
529 }
530
531 func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
532 return func(file string) (name string, b []byte, err error) {
533 name = path.Base(file)
534 b, err = fs.ReadFile(fsys, file)
535 return
536 }
537 }