diff options
| author | Russ Cox <rsc@golang.org> | 2020-07-06 11:28:52 -0400 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2020-10-20 18:41:14 +0000 |
| commit | 2a9aa4dcac0c33f7fffefb94a1bc92a17fd7cfd3 (patch) | |
| tree | d7bfc3e8def31aeb5a692afaefaa862f28f5b287 /src/text/template | |
| parent | 1296ee6b4f9058be75c799513ccb488d2f2dd085 (diff) | |
| download | go-2a9aa4dcac0c33f7fffefb94a1bc92a17fd7cfd3.tar.xz | |
html/template, text/template: add ParseFS
Now templates can be parsed not just from operating system files
but from arbitrary file systems, including zip files.
For #41190.
Change-Id: I2172001388ddb1f13defa6c5e644e8ec8703ee80
Reviewed-on: https://go-review.googlesource.com/c/go/+/243938
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/text/template')
| -rw-r--r-- | src/text/template/helper.go | 59 | ||||
| -rw-r--r-- | src/text/template/multi_test.go | 30 |
2 files changed, 83 insertions, 6 deletions
diff --git a/src/text/template/helper.go b/src/text/template/helper.go index c9e890078c..8269fa28c5 100644 --- a/src/text/template/helper.go +++ b/src/text/template/helper.go @@ -8,7 +8,9 @@ package template import ( "fmt" + "io/fs" "io/ioutil" + "path" "path/filepath" ) @@ -35,7 +37,7 @@ func Must(t *Template, err error) *Template { // For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template // named "foo", while "a/foo" is unavailable. func ParseFiles(filenames ...string) (*Template, error) { - return parseFiles(nil, filenames...) + return parseFiles(nil, readFileOS, filenames...) } // ParseFiles parses the named files and associates the resulting templates with @@ -51,23 +53,22 @@ func ParseFiles(filenames ...string) (*Template, error) { // the last one mentioned will be the one that results. func (t *Template) ParseFiles(filenames ...string) (*Template, error) { t.init() - return parseFiles(t, filenames...) + return parseFiles(t, readFileOS, filenames...) } // parseFiles is the helper for the method and function. If the argument // template is nil, it is created from the first file. -func parseFiles(t *Template, filenames ...string) (*Template, error) { +func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) { if len(filenames) == 0 { // Not really a problem, but be consistent. return nil, fmt.Errorf("template: no files named in call to ParseFiles") } for _, filename := range filenames { - b, err := ioutil.ReadFile(filename) + name, b, err := readFile(filename) if err != nil { return nil, err } s := string(b) - name := filepath.Base(filename) // First template becomes return value if not already defined, // and we use that one for subsequent New calls to associate // all the templates together. Also, if this file has the same name @@ -126,5 +127,51 @@ func parseGlob(t *Template, pattern string) (*Template, error) { if len(filenames) == 0 { return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) } - return parseFiles(t, filenames...) + return parseFiles(t, readFileOS, filenames...) +} + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fsys +// instead of the host operating system's file system. +// It accepts a list of glob patterns. +// (Note that most file names serve as glob patterns matching only themselves.) +func ParseFS(fsys fs.FS, patterns ...string) (*Template, error) { + return parseFS(nil, fsys, patterns) +} + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fsys +// instead of the host operating system's file system. +// It accepts a list of glob patterns. +// (Note that most file names serve as glob patterns matching only themselves.) +func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error) { + t.init() + return parseFS(t, fsys, patterns) +} + +func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) { + var filenames []string + for _, pattern := range patterns { + list, err := fs.Glob(fsys, pattern) + if err != nil { + return nil, err + } + if len(list) == 0 { + return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) + } + filenames = append(filenames, list...) + } + return parseFiles(t, readFileFS(fsys), filenames...) +} + +func readFileOS(file string) (name string, b []byte, err error) { + name = filepath.Base(file) + b, err = ioutil.ReadFile(file) + return +} + +func readFileFS(fsys fs.FS) func(string) (string, []byte, error) { + return func(file string) (name string, b []byte, err error) { + name = path.Base(file) + b, err = fs.ReadFile(fsys, file) + return + } } diff --git a/src/text/template/multi_test.go b/src/text/template/multi_test.go index 34d2378e38..b543ab5c47 100644 --- a/src/text/template/multi_test.go +++ b/src/text/template/multi_test.go @@ -9,6 +9,7 @@ package template import ( "bytes" "fmt" + "os" "testing" "text/template/parse" ) @@ -153,6 +154,35 @@ func TestParseGlob(t *testing.T) { testExecute(multiExecTests, template, t) } +func TestParseFS(t *testing.T) { + fs := os.DirFS("testdata") + + { + _, err := ParseFS(fs, "DOES NOT EXIST") + if err == nil { + t.Error("expected error for non-existent file; got none") + } + } + + { + template := New("root") + _, err := template.ParseFS(fs, "file1.tmpl", "file2.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(multiExecTests, template, t) + } + + { + template := New("root") + _, err := template.ParseFS(fs, "file*.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(multiExecTests, template, t) + } +} + // In these tests, actual content (not just template definitions) comes from the parsed files. var templateFileExecTests = []execTest{ |
