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
7package template
8
9import (
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"))
24func 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.
40func 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.
55func (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.
62func 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.
105func 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.
117func (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.
123func 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.)
138func 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.)
146func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
147	t.init()
148	return parseFS(t, fsys, patterns)
149}
150
151func 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
166func readFileOS(file string) (name string, b []byte, err error) {
167	name = filepath.Base(file)
168	b, err = os.ReadFile(file)
169	return
170}
171
172func 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