Source file src/text/template/helper.go

     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  // Helper functions to make constructing templates easier.
     6  
     7  package template
     8  
     9  import (
    10  	"fmt"
    11  	"io/fs"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  )
    16  
    17  // Functions and methods to parse templates.
    18  
    19  // Must is a helper that wraps a call to a function returning ([*Template], error)
    20  // and panics if the error is non-nil. It is intended for use in variable
    21  // initializations such as
    22  //
    23  //	var t = template.Must(template.New("name").Parse("text"))
    24  func Must(t *Template, err error) *Template {
    25  	if err != nil {
    26  		panic(err)
    27  	}
    28  	return t
    29  }
    30  
    31  // ParseFiles creates a new [Template] and parses the template definitions from
    32  // the named files. The returned template's name will have the base name and
    33  // parsed contents of the first file. There must be at least one file.
    34  // If an error occurs, parsing stops and the returned *Template is nil.
    35  //
    36  // When parsing multiple files with the same name in different directories,
    37  // the last one mentioned will be the one that results.
    38  // For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
    39  // named "foo", while "a/foo" is unavailable.
    40  func ParseFiles(filenames ...string) (*Template, error) {
    41  	return parseFiles(nil, readFileOS, filenames...)
    42  }
    43  
    44  // ParseFiles parses the named files and associates the resulting templates with
    45  // t. If an error occurs, parsing stops and the returned template is nil;
    46  // otherwise it is t. There must be at least one file.
    47  // Since the templates created by ParseFiles are named by the base
    48  // (see [filepath.Base]) names of the argument files, t should usually have the
    49  // name of one of the (base) names of the files. If it does not, depending on
    50  // t's contents before calling ParseFiles, t.Execute may fail. In that
    51  // case use t.ExecuteTemplate to execute a valid template.
    52  //
    53  // When parsing multiple files with the same name in different directories,
    54  // the last one mentioned will be the one that results.
    55  func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
    56  	t.init()
    57  	return parseFiles(t, readFileOS, filenames...)
    58  }
    59  
    60  // parseFiles is the helper for the method and function. If the argument
    61  // template is nil, it is created from the first file.
    62  func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
    63  	if len(filenames) == 0 {
    64  		// Not really a problem, but be consistent.
    65  		return nil, fmt.Errorf("template: no files named in call to ParseFiles")
    66  	}
    67  	for _, filename := range filenames {
    68  		name, b, err := readFile(filename)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  		s := string(b)
    73  		// First template becomes return value if not already defined,
    74  		// and we use that one for subsequent New calls to associate
    75  		// all the templates together. Also, if this file has the same name
    76  		// as t, this file becomes the contents of t, so
    77  		//  t, err := New(name).Funcs(xxx).ParseFiles(name)
    78  		// works. Otherwise we create a new template associated with t.
    79  		var tmpl *Template
    80  		if t == nil {
    81  			t = New(name)
    82  		}
    83  		if name == t.Name() {
    84  			tmpl = t
    85  		} else {
    86  			tmpl = t.New(name)
    87  		}
    88  		_, err = tmpl.Parse(s)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  	}
    93  	return t, nil
    94  }
    95  
    96  // ParseGlob creates a new [Template] and parses the template definitions from
    97  // the files identified by the pattern. The files are matched according to the
    98  // semantics of [filepath.Match], and the pattern must match at least one file.
    99  // The returned template will have the [filepath.Base] name and (parsed)
   100  // contents of the first file matched by the pattern. ParseGlob is equivalent to
   101  // calling [ParseFiles] with the list of files matched by the pattern.
   102  //
   103  // When parsing multiple files with the same name in different directories,
   104  // the last one mentioned will be the one that results.
   105  func ParseGlob(pattern string) (*Template, error) {
   106  	return parseGlob(nil, pattern)
   107  }
   108  
   109  // ParseGlob parses the template definitions in the files identified by the
   110  // pattern and associates the resulting templates with t. The files are matched
   111  // according to the semantics of [filepath.Match], and the pattern must match at
   112  // least one file. ParseGlob is equivalent to calling [Template.ParseFiles] with
   113  // the list of files matched by the pattern.
   114  //
   115  // When parsing multiple files with the same name in different directories,
   116  // the last one mentioned will be the one that results.
   117  func (t *Template) ParseGlob(pattern string) (*Template, error) {
   118  	t.init()
   119  	return parseGlob(t, pattern)
   120  }
   121  
   122  // parseGlob is the implementation of the function and method ParseGlob.
   123  func parseGlob(t *Template, pattern string) (*Template, error) {
   124  	filenames, err := filepath.Glob(pattern)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	if len(filenames) == 0 {
   129  		return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
   130  	}
   131  	return parseFiles(t, readFileOS, filenames...)
   132  }
   133  
   134  // ParseFS is like [Template.ParseFiles] or [Template.ParseGlob] but reads from the file system fsys
   135  // instead of the host operating system's file system.
   136  // It accepts a list of glob patterns (see [path.Match]).
   137  // (Note that most file names serve as glob patterns matching only themselves.)
   138  func ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
   139  	return parseFS(nil, fsys, patterns)
   140  }
   141  
   142  // ParseFS is like [Template.ParseFiles] or [Template.ParseGlob] but reads from the file system fsys
   143  // instead of the host operating system's file system.
   144  // It accepts a list of glob patterns (see [path.Match]).
   145  // (Note that most file names serve as glob patterns matching only themselves.)
   146  func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
   147  	t.init()
   148  	return parseFS(t, fsys, patterns)
   149  }
   150  
   151  func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
   152  	var filenames []string
   153  	for _, pattern := range patterns {
   154  		list, err := fs.Glob(fsys, pattern)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  		if len(list) == 0 {
   159  			return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
   160  		}
   161  		filenames = append(filenames, list...)
   162  	}
   163  	return parseFiles(t, readFileFS(fsys), filenames...)
   164  }
   165  
   166  func readFileOS(file string) (name string, b []byte, err error) {
   167  	name = filepath.Base(file)
   168  	b, err = os.ReadFile(file)
   169  	return
   170  }
   171  
   172  func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
   173  	return func(file string) (name string, b []byte, err error) {
   174  		name = path.Base(file)
   175  		b, err = fs.ReadFile(fsys, file)
   176  		return
   177  	}
   178  }
   179  

View as plain text