diff options
Diffstat (limited to 'src/html')
-rw-r--r-- | src/html/template/clone_test.go | 14 | ||||
-rw-r--r-- | src/html/template/escape_test.go | 2 | ||||
-rw-r--r-- | src/html/template/exec_test.go | 10 | ||||
-rw-r--r-- | src/html/template/multi_test.go | 43 | ||||
-rw-r--r-- | src/html/template/template.go | 58 | ||||
-rw-r--r-- | src/html/template/testdata/fs.zip | bin | 0 -> 406 bytes |
6 files changed, 108 insertions, 19 deletions
diff --git a/src/html/template/clone_test.go b/src/html/template/clone_test.go index c9c619f0d4..7cb1b9ca06 100644 --- a/src/html/template/clone_test.go +++ b/src/html/template/clone_test.go @@ -8,7 +8,7 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" + "io" "strings" "sync" "testing" @@ -171,7 +171,7 @@ func TestCloneThenParse(t *testing.T) { t.Error("adding a template to a clone added it to the original") } // double check that the embedded template isn't available in the original - err := t0.ExecuteTemplate(ioutil.Discard, "a", nil) + err := t0.ExecuteTemplate(io.Discard, "a", nil) if err == nil { t.Error("expected 'no such template' error") } @@ -185,13 +185,13 @@ func TestFuncMapWorksAfterClone(t *testing.T) { // get the expected error output (no clone) uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - wantErr := uncloned.Execute(ioutil.Discard, nil) + wantErr := uncloned.Execute(io.Discard, nil) // toClone must be the same as uncloned. It has to be recreated from scratch, // since cloning cannot occur after execution. toClone := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) cloned := Must(toClone.Clone()) - gotErr := cloned.Execute(ioutil.Discard, nil) + gotErr := cloned.Execute(io.Discard, nil) if wantErr.Error() != gotErr.Error() { t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr) @@ -213,7 +213,7 @@ func TestTemplateCloneExecuteRace(t *testing.T) { go func() { defer wg.Done() for i := 0; i < 100; i++ { - if err := tmpl.Execute(ioutil.Discard, "data"); err != nil { + if err := tmpl.Execute(io.Discard, "data"); err != nil { panic(err) } } @@ -237,7 +237,7 @@ func TestCloneGrowth(t *testing.T) { tmpl = Must(tmpl.Clone()) Must(tmpl.Parse(`{{define "B"}}Text{{end}}`)) for i := 0; i < 10; i++ { - tmpl.Execute(ioutil.Discard, nil) + tmpl.Execute(io.Discard, nil) } if len(tmpl.DefinedTemplates()) > 200 { t.Fatalf("too many templates: %v", len(tmpl.DefinedTemplates())) @@ -257,7 +257,7 @@ func TestCloneRedefinedName(t *testing.T) { for i := 0; i < 2; i++ { t2 := Must(t1.Clone()) t2 = Must(t2.New(fmt.Sprintf("%d", i)).Parse(page)) - err := t2.Execute(ioutil.Discard, nil) + err := t2.Execute(io.Discard, nil) if err != nil { t.Fatal(err) } diff --git a/src/html/template/escape_test.go b/src/html/template/escape_test.go index fbc84a7592..b6031ea60a 100644 --- a/src/html/template/escape_test.go +++ b/src/html/template/escape_test.go @@ -243,7 +243,7 @@ func TestEscape(t *testing.T) { { "badMarshaler", `<button onclick='alert(1/{{.B}}in numbers)'>`, - `<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character 'f' looking for beginning of object key string */null in numbers)'>`, + `<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: json: invalid character 'f' looking for beginning of object key string */null in numbers)'>`, }, { "jsMarshaler", diff --git a/src/html/template/exec_test.go b/src/html/template/exec_test.go index ec2bfcccab..232945a0bb 100644 --- a/src/html/template/exec_test.go +++ b/src/html/template/exec_test.go @@ -11,7 +11,7 @@ import ( "errors" "flag" "fmt" - "io/ioutil" + "io" "reflect" "strings" "testing" @@ -1302,7 +1302,7 @@ func TestUnterminatedStringError(t *testing.T) { t.Fatal("expected error") } str := err.Error() - if !strings.Contains(str, "X:3: unexpected unterminated raw quoted string") { + if !strings.Contains(str, "X:3: unterminated raw quoted string") { t.Fatalf("unexpected error: %s", str) } } @@ -1335,7 +1335,7 @@ func TestExecuteGivesExecError(t *testing.T) { if err != nil { t.Fatal(err) } - err = tmpl.Execute(ioutil.Discard, 0) + err = tmpl.Execute(io.Discard, 0) if err == nil { t.Fatal("expected error; got none") } @@ -1481,7 +1481,7 @@ func TestEvalFieldErrors(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { tmpl := Must(New("tmpl").Parse(tc.src)) - err := tmpl.Execute(ioutil.Discard, tc.value) + err := tmpl.Execute(io.Discard, tc.value) got := "<nil>" if err != nil { got = err.Error() @@ -1498,7 +1498,7 @@ func TestMaxExecDepth(t *testing.T) { t.Skip("skipping in -short mode") } tmpl := Must(New("tmpl").Parse(`{{template "tmpl" .}}`)) - err := tmpl.Execute(ioutil.Discard, nil) + err := tmpl.Execute(io.Discard, nil) got := "<nil>" if err != nil { got = err.Error() diff --git a/src/html/template/multi_test.go b/src/html/template/multi_test.go index 50526c5b65..6535ab6c04 100644 --- a/src/html/template/multi_test.go +++ b/src/html/template/multi_test.go @@ -7,7 +7,9 @@ package template import ( + "archive/zip" "bytes" + "os" "testing" "text/template/parse" ) @@ -82,6 +84,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{ @@ -104,6 +135,18 @@ func TestParseGlobWithData(t *testing.T) { testExecute(templateFileExecTests, template, t) } +func TestParseZipFS(t *testing.T) { + z, err := zip.OpenReader("testdata/fs.zip") + if err != nil { + t.Fatalf("error parsing zip: %v", err) + } + template, err := New("root").ParseFS(z, "tmpl*.tmpl") + if err != nil { + t.Fatalf("error parsing files: %v", err) + } + testExecute(templateFileExecTests, template, t) +} + const ( cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}` cloneText2 = `{{define "b"}}b{{end}}` diff --git a/src/html/template/template.go b/src/html/template/template.go index 75437879e2..bc960afe5f 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -7,7 +7,9 @@ package template import ( "fmt" "io" + "io/fs" "io/ioutil" + "path" "path/filepath" "sync" "text/template" @@ -384,7 +386,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 @@ -396,12 +398,12 @@ func ParseFiles(filenames ...string) (*Template, error) { // // ParseFiles returns an error if t or any associated template has already been executed. func (t *Template) ParseFiles(filenames ...string) (*Template, error) { - 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 err := t.checkCanParse(); err != nil { return nil, err } @@ -411,12 +413,11 @@ func parseFiles(t *Template, filenames ...string) (*Template, error) { return nil, fmt.Errorf("html/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 @@ -479,7 +480,7 @@ func parseGlob(t *Template, pattern string) (*Template, error) { if len(filenames) == 0 { return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern) } - return parseFiles(t, filenames...) + return parseFiles(t, readFileOS, filenames...) } // IsTrue reports whether the value is 'true', in the sense of not the zero of its type, @@ -488,3 +489,48 @@ func parseGlob(t *Template, pattern string) (*Template, error) { func IsTrue(val interface{}) (truth, ok bool) { return template.IsTrue(val) } + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// 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(fs fs.FS, patterns ...string) (*Template, error) { + return parseFS(nil, fs, patterns) +} + +// ParseFS is like ParseFiles or ParseGlob but reads from the file system fs +// 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(fs fs.FS, patterns ...string) (*Template, error) { + return parseFS(t, fs, 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/html/template/testdata/fs.zip b/src/html/template/testdata/fs.zip Binary files differnew file mode 100644 index 0000000000..8581313ae3 --- /dev/null +++ b/src/html/template/testdata/fs.zip |