hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }