Create lightweight forks of text/template and html/template
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 10 Dec 2019 07:02:15 +0000 (08:02 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 12 Dec 2019 08:59:34 +0000 (09:59 +0100)
This commit also removes support for Ace and Amber templates.

Updates #6594

82 files changed:
.gitignore
go.mod
go.sum
hugolib/case_insensitive_test.go
hugolib/template_engines_test.go
magefile.go
output/layout_base.go
scripts/fork_go_templates/.gitignore [new file with mode: 0644]
scripts/fork_go_templates/main.go [new file with mode: 0644]
tpl/cast/cast_test.go
tpl/collections/collections.go
tpl/collections/collections_test.go
tpl/internal/go_templates/fmtsort/export_test.go [new file with mode: 0644]
tpl/internal/go_templates/fmtsort/sort.go [new file with mode: 0644]
tpl/internal/go_templates/fmtsort/sort_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/attr.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/attr_string.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/clone_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/content.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/content_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/context.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/css.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/css_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/delim_string.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/doc.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/element_string.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/error.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/escape.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/escape_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/example_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/examplefiles_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/html.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/html_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/hugo_template.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/js.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/js_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/jsctx_string.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/state_string.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/template.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/template_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/transition.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/transition_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/url.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/url_test.go [new file with mode: 0644]
tpl/internal/go_templates/htmltemplate/urlpart_string.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/doc.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/example_test.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/examplefiles_test.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/examplefunc_test.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/exec.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/exec_test.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/funcs.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/helper.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/hugo_exec.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/hugo_template.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/multi_test.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/option.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/parse/lex.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/parse/lex_test.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/parse/node.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/parse/parse.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/parse/parse_test.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/template.go [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/testdata/file1.tmpl [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/testdata/file2.tmpl [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/testdata/tmpl1.tmpl [new file with mode: 0644]
tpl/internal/go_templates/texttemplate/testdata/tmpl2.tmpl [new file with mode: 0644]
tpl/partials/partials.go
tpl/safe/safe_test.go
tpl/strings/strings.go
tpl/strings/strings_test.go
tpl/strings/truncate.go
tpl/strings/truncate_test.go
tpl/template.go
tpl/template_test.go
tpl/tplimpl/ace.go [deleted file]
tpl/tplimpl/amber_compiler.go [deleted file]
tpl/tplimpl/template.go
tpl/tplimpl/template_ast_transformers.go
tpl/tplimpl/template_ast_transformers_test.go
tpl/transform/transform_test.go
tpl/urls/urls.go

index e71fe6c002b2b3c998dc2ba9fd1a74b17f5d4b9d..75d85e8d0d967d7f62b0f93a54d2064ef999efb1 100644 (file)
@@ -22,4 +22,5 @@ dist
 
 resources/sunset.jpg
 
-vendor
\ No newline at end of file
+vendor
+
diff --git a/go.mod b/go.mod
index ef28063230f31c9efa17784f76c4cd6045cedd21..163cf7b076080b1671a37f2261976cb63c11a379 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,6 @@ require (
        github.com/disintegration/gift v1.2.1
        github.com/dlclark/regexp2 v1.2.0 // indirect
        github.com/dustin/go-humanize v1.0.0
-       github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385
        github.com/fortytw2/leaktest v1.3.0
        github.com/frankban/quicktest v1.6.0
        github.com/fsnotify/fsnotify v1.4.7
@@ -53,8 +52,7 @@ require (
        github.com/spf13/pflag v1.0.3
        github.com/spf13/viper v1.4.0
        github.com/tdewolff/minify/v2 v2.6.1
-       github.com/yosssi/ace v0.0.5
-       github.com/yuin/goldmark v1.1.14
+       github.com/yuin/goldmark v1.1.11
        github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5
        go.opencensus.io v0.22.0 // indirect
        gocloud.dev v0.15.0
diff --git a/go.sum b/go.sum
index 85784880ee56e14a5332979b6a489477b13d1bbe..6911fbce38ff7444e9c2bd1f46c54addf8a41ac4 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -108,8 +108,6 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
-github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
 github.com/fortytw2/leaktest v1.2.0 h1:cj6GCiwJDH7l3tMHLjZDo0QqPtrXJiWSI9JgpeQKw+Q=
 github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -322,16 +320,10 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/tdewolff/minify/v2 v2.5.2 h1:If/q1brvT+91oWiWnIMEGuFcwWtpB6AtLTxba78tvMs=
-github.com/tdewolff/minify/v2 v2.5.2/go.mod h1:Q6mWHrmspbdRX0ZuUUoKIT8bDjVVXpIJ73ux7p7HZGg=
 github.com/tdewolff/minify/v2 v2.6.1 h1:UJLhbs2Q/iDrqA79EEyKE48uYHeAMPVdiUzdtKsatJ8=
 github.com/tdewolff/minify/v2 v2.6.1/go.mod h1:l9hbQnH096st77OkscoRUvKdd23oUM6pDZpYx381sPo=
-github.com/tdewolff/parse/v2 v2.3.9 h1:d8/K6XOLy5JVpLTG9Kx+SxA72rlm5OowFmVSVgtOlmM=
-github.com/tdewolff/parse/v2 v2.3.9/go.mod h1:HansaqmN4I/U7L6/tUp0NcwT2tFO0F4EAWYGSDzkYNk=
 github.com/tdewolff/parse/v2 v2.3.14 h1:Tzam5YoUXx7gybFEfR/zcuR74PXADnrfUqYUXL+K5oA=
 github.com/tdewolff/parse/v2 v2.3.14/go.mod h1:+V2lSZ93xpH2Csfs/vtNY1Fjr8kcFMsZKjyLoSkZbM0=
-github.com/tdewolff/test v1.0.0 h1:jOwzqCXr5ePXEPGJaq2ivoR6HOCi+D5TPfpoyg8yvmU=
-github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4=
 github.com/tdewolff/test v1.0.4 h1:ih38SXuQJ32Hng5EtSW32xqEsVeMnPp6nNNRPhBBDE8=
 github.com/tdewolff/test v1.0.4/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
 github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
@@ -349,24 +341,10 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe
 github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
-github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
-github.com/yuin/goldmark v1.1.5 h1:JJy3EDke+PMI2WcFIU6SdaeiP6FgRGK5NKAiPZHiOoE=
-github.com/yuin/goldmark v1.1.5/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.7 h1:XiwWADvxJeIM1JbXqthrEhDc19hTMui+o+QaY1hGXlk=
 github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.8 h1:d0m8Ac9JaetYjPZLC4P4W32ac7I0lpJpQbvxZtFqBoM=
-github.com/yuin/goldmark v1.1.8/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.10 h1:bg3TC1aj4DbjGdhvjSSffGfAgVUdBEIpccuCozwOYWo=
-github.com/yuin/goldmark v1.1.10/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.11 h1:OO08ilczi3F4swaYWPB99s08WRxP9DdLBemiLFQ6vCo=
 github.com/yuin/goldmark v1.1.11/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.14 h1:9/OvYI+gdtQ5EAZY0y4kuVnuKjlE03BRqTw/njWYRNo=
-github.com/yuin/goldmark v1.1.14/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark-highlighting v0.0.0-20191124122839-ede94e40cc3a h1:L7FTUnbc0WEBqGWgjbx4sPNAOX1/q5W/3KCD6g8XkKo=
-github.com/yuin/goldmark-highlighting v0.0.0-20191124122839-ede94e40cc3a/go.mod h1:1gshkGdH4gcrIH5MGSScGH42rOOCO+4Ks6acjAkA9C0=
-github.com/yuin/goldmark-highlighting v0.0.0-20191126180129-d7a4bf4d7ea4 h1:vI4Jv29V1cMPqetuLPMW1CMB9xNgxsHVBo8Mid6bwH8=
-github.com/yuin/goldmark-highlighting v0.0.0-20191126180129-d7a4bf4d7ea4/go.mod h1:4QGn5rJFOASBa2uK4Q2h3BRTyJqRfsAucPFIipSTcaM=
 github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5 h1:QbH7ca1qtgZHrzvcVAEoiJIwBqrXxMOfHYfwZIniIK0=
 github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5/go.mod h1:4QGn5rJFOASBa2uK4Q2h3BRTyJqRfsAucPFIipSTcaM=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
index 4a6b8f3a06d87b5347584b07fc655d16c313a424..a8616ab062348e0ec507d1f06bc0ab6c27c35f88 100644 (file)
@@ -16,7 +16,6 @@ package hugolib
 import (
        "fmt"
        "path/filepath"
-       "strings"
        "testing"
 
        "github.com/gohugoio/hugo/hugofs"
@@ -234,6 +233,7 @@ Page2: {{ $page2.Params.ColoR }}
        )
 }
 
+// TODO1
 func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
        t.Parallel()
 
@@ -241,23 +241,13 @@ func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
                return s
        }
 
-       amberFixer := func(s string) string {
-               fixed := strings.Replace(s, "{{ .Site.Params", "{{ Site.Params", -1)
-               fixed = strings.Replace(fixed, "{{ .Params", "{{ Params", -1)
-               fixed = strings.Replace(fixed, ".Content", "Content", -1)
-               fixed = strings.Replace(fixed, "{{", "#{", -1)
-               fixed = strings.Replace(fixed, "}}", "}", -1)
-
-               return fixed
-       }
-
        for _, config := range []struct {
                suffix        string
                templateFixer func(s string) string
        }{
-               {"amber", amberFixer},
+               //{"amber", amberFixer},
                {"html", noOp},
-               {"ace", noOp},
+               //{"ace", noOp},
        } {
                doTestCaseInsensitiveConfigurationForTemplateEngine(t, config.suffix, config.templateFixer)
 
index ebfb0b42862a57695b791cbcb25a4006c7801152..ea0fca0b69d2009f84b9a11d4afad1ac7873ae31 100644 (file)
@@ -18,34 +18,22 @@ import (
        "path/filepath"
        "testing"
 
-       "strings"
-
        "github.com/gohugoio/hugo/deps"
 )
 
+// TODO1
 func TestAllTemplateEngines(t *testing.T) {
        noOp := func(s string) string {
                return s
        }
 
-       amberFixer := func(s string) string {
-               fixed := strings.Replace(s, "{{ .Title", "{{ Title", -1)
-               fixed = strings.Replace(fixed, ".Content", "Content", -1)
-               fixed = strings.Replace(fixed, ".IsNamedParams", "IsNamedParams", -1)
-               fixed = strings.Replace(fixed, "{{", "#{", -1)
-               fixed = strings.Replace(fixed, "}}", "}", -1)
-               fixed = strings.Replace(fixed, `title "hello world"`, `title("hello world")`, -1)
-
-               return fixed
-       }
-
        for _, config := range []struct {
                suffix        string
                templateFixer func(s string) string
        }{
-               {"amber", amberFixer},
+               //{"amber", amberFixer},
                {"html", noOp},
-               {"ace", noOp},
+               //{"ace", noOp},
        } {
                config := config
                t.Run(config.suffix,
index a888fb78d9c7c90d9613199b47000e47891bc36c..38e8e057d0df409b709a0de168b13e9b83854729 100644 (file)
@@ -320,7 +320,7 @@ func runCmd(env map[string]string, cmd string, args ...string) error {
 }
 
 func isGoLatest() bool {
-       return strings.Contains(runtime.Version(), "1.12")
+       return strings.Contains(runtime.Version(), "1.13")
 }
 
 func isCI() bool {
index b8930df823270bb1d71bffbd103bab1a15ba8eb2..772002e6814a747f6b2206e2c48429e59b7da0e3 100644 (file)
@@ -26,8 +26,7 @@ const (
 )
 
 var (
-       aceTemplateInnerMarkers = [][]byte{[]byte("= content")}
-       goTemplateInnerMarkers  = [][]byte{[]byte("{{define"), []byte("{{ define"), []byte("{{- define"), []byte("{{-define")}
+       goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define"), []byte("{{- define"), []byte("{{-define")}
 )
 
 // TemplateNames represents a template naming scheme.
@@ -110,8 +109,8 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
                id.Name = "_text/" + id.Name
        }
 
-       // Ace and Go templates may have both a base and inner template.
-       if ext == "amber" || isShorthCodeOrPartial(name) {
+       // Go templates may have both a base and inner template.
+       if isShorthCodeOrPartial(name) {
                // No base template support
                return id, nil
        }
@@ -128,10 +127,6 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
                baseFilename = fmt.Sprintf("%s.%s", baseFileBase, ext)
        }
 
-       if ext == "ace" {
-               innerMarkers = aceTemplateInnerMarkers
-       }
-
        // This may be a view that shouldn't have base template
        // Have to look inside it to make sure
        needsBase, err := d.ContainsAny(d.RelPath, innerMarkers)
@@ -152,7 +147,7 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
                pathsToCheck := createPathsToCheck(pathDir, baseFilename, currBaseFilename)
 
                // We may have language code and/or "terms" in the template name. We want the most specific,
-               // but need to fall back to the baseof.html or baseof.ace if needed.
+               // but need to fall back to the baseof.html if needed.
                // E.g. list-baseof.en.html and list-baseof.terms.en.html
                // See #3893, #3856.
                baseBaseFilename, currBaseBaseFilename := helpers.Filename(baseFilename), helpers.Filename(currBaseFilename)
diff --git a/scripts/fork_go_templates/.gitignore b/scripts/fork_go_templates/.gitignore
new file mode 100644 (file)
index 0000000..81af73f
--- /dev/null
@@ -0,0 +1 @@
+fork_go_templates
diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go
new file mode 100644 (file)
index 0000000..1487858
--- /dev/null
@@ -0,0 +1,207 @@
+package main
+
+import (
+       "fmt"
+       "io/ioutil"
+       "log"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "regexp"
+       "strings"
+
+       "github.com/gohugoio/hugo/common/hugio"
+
+       "github.com/spf13/afero"
+)
+
+func main() {
+       // TODO(bep) git checkout tag
+       // The current is built with Go version 9341fe073e6f7742c9d61982084874560dac2014 / go1.13.5
+       fmt.Println("Forking ...")
+       defer fmt.Println("Done ...")
+
+       cleanFork()
+
+       htmlRoot := filepath.Join(forkRoot, "htmltemplate")
+
+       for _, pkg := range goPackages {
+               copyGoPackage(pkg.dstPkg, pkg.srcPkg)
+       }
+
+       for _, pkg := range goPackages {
+               doWithGoFiles(pkg.dstPkg, pkg.rewriter, pkg.replacer)
+       }
+
+       goimports(htmlRoot)
+       gofmt(forkRoot)
+
+}
+
+const (
+       // TODO(bep)
+       goSource = "/Users/bep/dev/go/dump/go/src"
+       forkRoot = "../../tpl/internal/go_templates"
+)
+
+type goPackage struct {
+       srcPkg   string
+       dstPkg   string
+       replacer func(name, content string) string
+       rewriter func(name string)
+}
+
+var (
+       textTemplateReplacers = strings.NewReplacer(
+               `"text/template/`, `"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/`,
+               `"internal/fmtsort"`, `"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"`,
+               // Rename types and function that we want to overload.
+               "type state struct", "type stateOld struct",
+       )
+
+       htmlTemplateReplacers = strings.NewReplacer(
+               `. "html/template"`, `. "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"`,
+               `"html/template"`, `template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"`,
+               "\"text/template\"\n", "template \"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate\"\n",
+               `"html/template"`, `htmltemplate "html/template"`,
+               `"fmt"`, `htmltemplate "html/template"`,
+       )
+)
+
+func commonReplace(name, content string) string {
+       if strings.HasSuffix(name, "_test.go") {
+               content = strings.Replace(content, "package template\n", `// +build go1.13,!windows
+
+package template
+`, 1)
+               content = strings.Replace(content, "package template_test\n", `// +build go1.13
+
+package template_test
+`, 1)
+
+               content = strings.Replace(content, "package parse\n", `// +build go1.13
+
+package parse
+`, 1)
+
+       }
+
+       return content
+
+}
+
+var goPackages = []goPackage{
+       goPackage{srcPkg: "text/template", dstPkg: "texttemplate",
+               replacer: func(name, content string) string { return textTemplateReplacers.Replace(commonReplace(name, content)) }},
+       goPackage{srcPkg: "html/template", dstPkg: "htmltemplate", replacer: func(name, content string) string {
+               if strings.HasSuffix(name, "content.go") {
+                       // Remove template.HTML types. We need to use the Go types.
+                       content = removeAll(`(?s)// Strings of content.*?\)\n`, content)
+               }
+
+               content = commonReplace(name, content)
+
+               return htmlTemplateReplacers.Replace(content)
+       },
+               rewriter: func(name string) {
+                       for _, s := range []string{"CSS", "HTML", "HTMLAttr", "JS", "JSStr", "URL", "Srcset"} {
+                               rewrite(name, fmt.Sprintf("%s -> htmltemplate.%s", s, s))
+                       }
+                       rewrite(name, `"text/template/parse" -> "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"`)
+               }},
+       goPackage{srcPkg: "internal/fmtsort", dstPkg: "fmtsort", rewriter: func(name string) {
+               rewrite(name, `"internal/fmtsort" -> "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"`)
+       }},
+}
+
+var fs = afero.NewOsFs()
+
+// Removes all non-Hugo files in the go_templates folder.
+func cleanFork() {
+       must(filepath.Walk(filepath.Join(forkRoot), func(path string, info os.FileInfo, err error) error {
+               if !info.IsDir() && len(path) > 10 && !strings.Contains(path, "hugo") {
+                       must(fs.Remove(path))
+               }
+               return nil
+       }))
+}
+
+func must(err error, what ...string) {
+       if err != nil {
+               log.Fatal(what, " ERROR: ", err)
+       }
+}
+
+func copyGoPackage(dst, src string) {
+       from := filepath.Join(goSource, src)
+       to := filepath.Join(forkRoot, dst)
+       fmt.Println("Copy", from, "to", to)
+       must(hugio.CopyDir(fs, from, to, func(s string) bool { return true }))
+}
+
+func doWithGoFiles(dir string,
+       rewrite func(name string),
+       transform func(name, in string) string) {
+       if rewrite == nil && transform == nil {
+               return
+       }
+       must(filepath.Walk(filepath.Join(forkRoot, dir), func(path string, info os.FileInfo, err error) error {
+               if info.IsDir() {
+                       return nil
+               }
+
+               if !strings.HasSuffix(path, ".go") || strings.Contains(path, "hugo_") {
+                       return nil
+               }
+
+               fmt.Println("Handle", path)
+
+               if rewrite != nil {
+                       rewrite(path)
+               }
+
+               if transform == nil {
+                       return nil
+               }
+
+               data, err := ioutil.ReadFile(path)
+               must(err)
+               f, err := os.Create(path)
+               must(err)
+               defer f.Close()
+               _, err = f.WriteString(transform(path, string(data)))
+               must(err)
+
+               return nil
+       }))
+}
+
+func removeAll(expression, content string) string {
+       re := regexp.MustCompile(expression)
+       return re.ReplaceAllString(content, "")
+
+}
+
+func rewrite(filename, rule string) {
+       cmf := exec.Command("gofmt", "-w", "-r", rule, filename)
+       out, err := cmf.CombinedOutput()
+       if err != nil {
+               log.Fatal("gofmt failed:", string(out))
+       }
+}
+
+func goimports(dir string) {
+       cmf := exec.Command("goimports", "-w", dir)
+       out, err := cmf.CombinedOutput()
+       if err != nil {
+               log.Fatal("goimports failed:", string(out))
+       }
+}
+
+func gofmt(dir string) {
+       cmf := exec.Command("gofmt", "-w", dir)
+       out, err := cmf.CombinedOutput()
+       if err != nil {
+               log.Fatal("gofmt failed:", string(out))
+       }
+}
index c6219728b0afb0549a36424d70af73b2aabd754f..d3f8d9733e185d9ac40aa106b8e4a54cd60fea66 100644 (file)
@@ -15,6 +15,7 @@ package cast
 
 import (
        "html/template"
+
        "testing"
 
        qt "github.com/frankban/quicktest"
index 8d89d6255b8d470152d6c408e29383bdaf5b592d..5b9d4a7009fb8eeecde9b2e94433582489af0a6d 100644 (file)
@@ -18,6 +18,7 @@ package collections
 import (
        "fmt"
        "html/template"
+
        "math/rand"
        "net/url"
        "reflect"
index 041a8e30c26796f70d9a30e57340557b6c4c8d3e..c98f4a527afa0200da31b2e8118b3f9aa67d78ae 100644 (file)
@@ -17,6 +17,7 @@ import (
        "errors"
        "fmt"
        "html/template"
+
        "math/rand"
        "reflect"
        "testing"
diff --git a/tpl/internal/go_templates/fmtsort/export_test.go b/tpl/internal/go_templates/fmtsort/export_test.go
new file mode 100644 (file)
index 0000000..25cbb5d
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmtsort
+
+import "reflect"
+
+func Compare(a, b reflect.Value) int {
+       return compare(a, b)
+}
diff --git a/tpl/internal/go_templates/fmtsort/sort.go b/tpl/internal/go_templates/fmtsort/sort.go
new file mode 100644 (file)
index 0000000..70a305a
--- /dev/null
@@ -0,0 +1,216 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package fmtsort provides a general stable ordering mechanism
+// for maps, on behalf of the fmt and text/template packages.
+// It is not guaranteed to be efficient and works only for types
+// that are valid map keys.
+package fmtsort
+
+import (
+       "reflect"
+       "sort"
+)
+
+// Note: Throughout this package we avoid calling reflect.Value.Interface as
+// it is not always legal to do so and it's easier to avoid the issue than to face it.
+
+// SortedMap represents a map's keys and values. The keys and values are
+// aligned in index order: Value[i] is the value in the map corresponding to Key[i].
+type SortedMap struct {
+       Key   []reflect.Value
+       Value []reflect.Value
+}
+
+func (o *SortedMap) Len() int           { return len(o.Key) }
+func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 }
+func (o *SortedMap) Swap(i, j int) {
+       o.Key[i], o.Key[j] = o.Key[j], o.Key[i]
+       o.Value[i], o.Value[j] = o.Value[j], o.Value[i]
+}
+
+// Sort accepts a map and returns a SortedMap that has the same keys and
+// values but in a stable sorted order according to the keys, modulo issues
+// raised by unorderable key values such as NaNs.
+//
+// The ordering rules are more general than with Go's < operator:
+//
+//  - when applicable, nil compares low
+//  - ints, floats, and strings order by <
+//  - NaN compares less than non-NaN floats
+//  - bool compares false before true
+//  - complex compares real, then imag
+//  - pointers compare by machine address
+//  - channel values compare by machine address
+//  - structs compare each field in turn
+//  - arrays compare each element in turn.
+//    Otherwise identical arrays compare by length.
+//  - interface values compare first by reflect.Type describing the concrete type
+//    and then by concrete value as described in the previous rules.
+//
+func Sort(mapValue reflect.Value) *SortedMap {
+       if mapValue.Type().Kind() != reflect.Map {
+               return nil
+       }
+       key := make([]reflect.Value, mapValue.Len())
+       value := make([]reflect.Value, len(key))
+       iter := mapValue.MapRange()
+       for i := 0; iter.Next(); i++ {
+               key[i] = iter.Key()
+               value[i] = iter.Value()
+       }
+       sorted := &SortedMap{
+               Key:   key,
+               Value: value,
+       }
+       sort.Stable(sorted)
+       return sorted
+}
+
+// compare compares two values of the same type. It returns -1, 0, 1
+// according to whether a > b (1), a == b (0), or a < b (-1).
+// If the types differ, it returns -1.
+// See the comment on Sort for the comparison rules.
+func compare(aVal, bVal reflect.Value) int {
+       aType, bType := aVal.Type(), bVal.Type()
+       if aType != bType {
+               return -1 // No good answer possible, but don't return 0: they're not equal.
+       }
+       switch aVal.Kind() {
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               a, b := aVal.Int(), bVal.Int()
+               switch {
+               case a < b:
+                       return -1
+               case a > b:
+                       return 1
+               default:
+                       return 0
+               }
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               a, b := aVal.Uint(), bVal.Uint()
+               switch {
+               case a < b:
+                       return -1
+               case a > b:
+                       return 1
+               default:
+                       return 0
+               }
+       case reflect.String:
+               a, b := aVal.String(), bVal.String()
+               switch {
+               case a < b:
+                       return -1
+               case a > b:
+                       return 1
+               default:
+                       return 0
+               }
+       case reflect.Float32, reflect.Float64:
+               return floatCompare(aVal.Float(), bVal.Float())
+       case reflect.Complex64, reflect.Complex128:
+               a, b := aVal.Complex(), bVal.Complex()
+               if c := floatCompare(real(a), real(b)); c != 0 {
+                       return c
+               }
+               return floatCompare(imag(a), imag(b))
+       case reflect.Bool:
+               a, b := aVal.Bool(), bVal.Bool()
+               switch {
+               case a == b:
+                       return 0
+               case a:
+                       return 1
+               default:
+                       return -1
+               }
+       case reflect.Ptr:
+               a, b := aVal.Pointer(), bVal.Pointer()
+               switch {
+               case a < b:
+                       return -1
+               case a > b:
+                       return 1
+               default:
+                       return 0
+               }
+       case reflect.Chan:
+               if c, ok := nilCompare(aVal, bVal); ok {
+                       return c
+               }
+               ap, bp := aVal.Pointer(), bVal.Pointer()
+               switch {
+               case ap < bp:
+                       return -1
+               case ap > bp:
+                       return 1
+               default:
+                       return 0
+               }
+       case reflect.Struct:
+               for i := 0; i < aVal.NumField(); i++ {
+                       if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 {
+                               return c
+                       }
+               }
+               return 0
+       case reflect.Array:
+               for i := 0; i < aVal.Len(); i++ {
+                       if c := compare(aVal.Index(i), bVal.Index(i)); c != 0 {
+                               return c
+                       }
+               }
+               return 0
+       case reflect.Interface:
+               if c, ok := nilCompare(aVal, bVal); ok {
+                       return c
+               }
+               c := compare(reflect.ValueOf(aVal.Elem().Type()), reflect.ValueOf(bVal.Elem().Type()))
+               if c != 0 {
+                       return c
+               }
+               return compare(aVal.Elem(), bVal.Elem())
+       default:
+               // Certain types cannot appear as keys (maps, funcs, slices), but be explicit.
+               panic("bad type in compare: " + aType.String())
+       }
+}
+
+// nilCompare checks whether either value is nil. If not, the boolean is false.
+// If either value is nil, the boolean is true and the integer is the comparison
+// value. The comparison is defined to be 0 if both are nil, otherwise the one
+// nil value compares low. Both arguments must represent a chan, func,
+// interface, map, pointer, or slice.
+func nilCompare(aVal, bVal reflect.Value) (int, bool) {
+       if aVal.IsNil() {
+               if bVal.IsNil() {
+                       return 0, true
+               }
+               return -1, true
+       }
+       if bVal.IsNil() {
+               return 1, true
+       }
+       return 0, false
+}
+
+// floatCompare compares two floating-point values. NaNs compare low.
+func floatCompare(a, b float64) int {
+       switch {
+       case isNaN(a):
+               return -1 // No good answer if b is a NaN so don't bother checking.
+       case isNaN(b):
+               return 1
+       case a < b:
+               return -1
+       case a > b:
+               return 1
+       }
+       return 0
+}
+
+func isNaN(a float64) bool {
+       return a != a
+}
diff --git a/tpl/internal/go_templates/fmtsort/sort_test.go b/tpl/internal/go_templates/fmtsort/sort_test.go
new file mode 100644 (file)
index 0000000..601ec9d
--- /dev/null
@@ -0,0 +1,246 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fmtsort_test
+
+import (
+       "fmt"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
+       "math"
+       "reflect"
+       "strings"
+       "testing"
+)
+
+var compareTests = [][]reflect.Value{
+       ct(reflect.TypeOf(int(0)), -1, 0, 1),
+       ct(reflect.TypeOf(int8(0)), -1, 0, 1),
+       ct(reflect.TypeOf(int16(0)), -1, 0, 1),
+       ct(reflect.TypeOf(int32(0)), -1, 0, 1),
+       ct(reflect.TypeOf(int64(0)), -1, 0, 1),
+       ct(reflect.TypeOf(uint(0)), 0, 1, 5),
+       ct(reflect.TypeOf(uint8(0)), 0, 1, 5),
+       ct(reflect.TypeOf(uint16(0)), 0, 1, 5),
+       ct(reflect.TypeOf(uint32(0)), 0, 1, 5),
+       ct(reflect.TypeOf(uint64(0)), 0, 1, 5),
+       ct(reflect.TypeOf(uintptr(0)), 0, 1, 5),
+       ct(reflect.TypeOf(string("")), "", "a", "ab"),
+       ct(reflect.TypeOf(float32(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)),
+       ct(reflect.TypeOf(float64(0)), math.NaN(), math.Inf(-1), -1e10, 0, 1e10, math.Inf(1)),
+       ct(reflect.TypeOf(complex64(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i),
+       ct(reflect.TypeOf(complex128(0+1i)), -1-1i, -1+0i, -1+1i, 0-1i, 0+0i, 0+1i, 1-1i, 1+0i, 1+1i),
+       ct(reflect.TypeOf(false), false, true),
+       ct(reflect.TypeOf(&ints[0]), &ints[0], &ints[1], &ints[2]),
+       ct(reflect.TypeOf(chans[0]), chans[0], chans[1], chans[2]),
+       ct(reflect.TypeOf(toy{}), toy{0, 1}, toy{0, 2}, toy{1, -1}, toy{1, 1}),
+       ct(reflect.TypeOf([2]int{}), [2]int{1, 1}, [2]int{1, 2}, [2]int{2, 0}),
+       ct(reflect.TypeOf(interface{}(interface{}(0))), iFace, 1, 2, 3),
+}
+
+var iFace interface{}
+
+func ct(typ reflect.Type, args ...interface{}) []reflect.Value {
+       value := make([]reflect.Value, len(args))
+       for i, v := range args {
+               x := reflect.ValueOf(v)
+               if !x.IsValid() { // Make it a typed nil.
+                       x = reflect.Zero(typ)
+               } else {
+                       x = x.Convert(typ)
+               }
+               value[i] = x
+       }
+       return value
+}
+
+func TestCompare(t *testing.T) {
+       for _, test := range compareTests {
+               for i, v0 := range test {
+                       for j, v1 := range test {
+                               c := fmtsort.Compare(v0, v1)
+                               var expect int
+                               switch {
+                               case i == j:
+                                       expect = 0
+                                       // NaNs are tricky.
+                                       if typ := v0.Type(); (typ.Kind() == reflect.Float32 || typ.Kind() == reflect.Float64) && math.IsNaN(v0.Float()) {
+                                               expect = -1
+                                       }
+                               case i < j:
+                                       expect = -1
+                               case i > j:
+                                       expect = 1
+                               }
+                               if c != expect {
+                                       t.Errorf("%s: compare(%v,%v)=%d; expect %d", v0.Type(), v0, v1, c, expect)
+                               }
+                       }
+               }
+       }
+}
+
+type sortTest struct {
+       data  interface{} // Always a map.
+       print string      // Printed result using our custom printer.
+}
+
+var sortTests = []sortTest{
+       {
+               map[int]string{7: "bar", -3: "foo"},
+               "-3:foo 7:bar",
+       },
+       {
+               map[uint8]string{7: "bar", 3: "foo"},
+               "3:foo 7:bar",
+       },
+       {
+               map[string]string{"7": "bar", "3": "foo"},
+               "3:foo 7:bar",
+       },
+       {
+               map[float64]string{7: "bar", -3: "foo", math.NaN(): "nan", math.Inf(0): "inf"},
+               "NaN:nan -3:foo 7:bar +Inf:inf",
+       },
+       {
+               map[complex128]string{7 + 2i: "bar2", 7 + 1i: "bar", -3: "foo", complex(math.NaN(), 0i): "nan", complex(math.Inf(0), 0i): "inf"},
+               "(NaN+0i):nan (-3+0i):foo (7+1i):bar (7+2i):bar2 (+Inf+0i):inf",
+       },
+       {
+               map[bool]string{true: "true", false: "false"},
+               "false:false true:true",
+       },
+       {
+               chanMap(),
+               "CHAN0:0 CHAN1:1 CHAN2:2",
+       },
+       {
+               pointerMap(),
+               "PTR0:0 PTR1:1 PTR2:2",
+       },
+       {
+               map[toy]string{toy{7, 2}: "72", toy{7, 1}: "71", toy{3, 4}: "34"},
+               "{3 4}:34 {7 1}:71 {7 2}:72",
+       },
+       {
+               map[[2]int]string{{7, 2}: "72", {7, 1}: "71", {3, 4}: "34"},
+               "[3 4]:34 [7 1]:71 [7 2]:72",
+       },
+}
+
+func sprint(data interface{}) string {
+       om := fmtsort.Sort(reflect.ValueOf(data))
+       if om == nil {
+               return "nil"
+       }
+       b := new(strings.Builder)
+       for i, key := range om.Key {
+               if i > 0 {
+                       b.WriteRune(' ')
+               }
+               b.WriteString(sprintKey(key))
+               b.WriteRune(':')
+               b.WriteString(fmt.Sprint(om.Value[i]))
+       }
+       return b.String()
+}
+
+// sprintKey formats a reflect.Value but gives reproducible values for some
+// problematic types such as pointers. Note that it only does special handling
+// for the troublesome types used in the test cases; it is not a general
+// printer.
+func sprintKey(key reflect.Value) string {
+       switch str := key.Type().String(); str {
+       case "*int":
+               ptr := key.Interface().(*int)
+               for i := range ints {
+                       if ptr == &ints[i] {
+                               return fmt.Sprintf("PTR%d", i)
+                       }
+               }
+               return "PTR???"
+       case "chan int":
+               c := key.Interface().(chan int)
+               for i := range chans {
+                       if c == chans[i] {
+                               return fmt.Sprintf("CHAN%d", i)
+                       }
+               }
+               return "CHAN???"
+       default:
+               return fmt.Sprint(key)
+       }
+}
+
+var (
+       ints  [3]int
+       chans = [3]chan int{make(chan int), make(chan int), make(chan int)}
+)
+
+func pointerMap() map[*int]string {
+       m := make(map[*int]string)
+       for i := 2; i >= 0; i-- {
+               m[&ints[i]] = fmt.Sprint(i)
+       }
+       return m
+}
+
+func chanMap() map[chan int]string {
+       m := make(map[chan int]string)
+       for i := 2; i >= 0; i-- {
+               m[chans[i]] = fmt.Sprint(i)
+       }
+       return m
+}
+
+type toy struct {
+       A int // Exported.
+       b int // Unexported.
+}
+
+func TestOrder(t *testing.T) {
+       for _, test := range sortTests {
+               got := sprint(test.data)
+               if got != test.print {
+                       t.Errorf("%s: got %q, want %q", reflect.TypeOf(test.data), got, test.print)
+               }
+       }
+}
+
+func TestInterface(t *testing.T) {
+       // A map containing multiple concrete types should be sorted by type,
+       // then value. However, the relative ordering of types is unspecified,
+       // so test this by checking the presence of sorted subgroups.
+       m := map[interface{}]string{
+               [2]int{1, 0}:             "",
+               [2]int{0, 1}:             "",
+               true:                     "",
+               false:                    "",
+               3.1:                      "",
+               2.1:                      "",
+               1.1:                      "",
+               math.NaN():               "",
+               3:                        "",
+               2:                        "",
+               1:                        "",
+               "c":                      "",
+               "b":                      "",
+               "a":                      "",
+               struct{ x, y int }{1, 0}: "",
+               struct{ x, y int }{0, 1}: "",
+       }
+       got := sprint(m)
+       typeGroups := []string{
+               "NaN: 1.1: 2.1: 3.1:", // float64
+               "false: true:",        // bool
+               "1: 2: 3:",            // int
+               "a: b: c:",            // string
+               "[0 1]: [1 0]:",       // [2]int
+               "{0 1}: {1 0}:",       // struct{ x int; y int }
+       }
+       for _, g := range typeGroups {
+               if !strings.Contains(got, g) {
+                       t.Errorf("sorted map should contain %q", g)
+               }
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/attr.go b/tpl/internal/go_templates/htmltemplate/attr.go
new file mode 100644 (file)
index 0000000..22922e6
--- /dev/null
@@ -0,0 +1,175 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "strings"
+)
+
+// attrTypeMap[n] describes the value of the given attribute.
+// If an attribute affects (or can mask) the encoding or interpretation of
+// other content, or affects the contents, idempotency, or credentials of a
+// network message, then the value in this map is contentTypeUnsafe.
+// This map is derived from HTML5, specifically
+// https://www.w3.org/TR/html5/Overview.html#attributes-1
+// as well as "%URI"-typed attributes from
+// https://www.w3.org/TR/html4/index/attributes.html
+var attrTypeMap = map[string]contentType{
+       "accept":          contentTypePlain,
+       "accept-charset":  contentTypeUnsafe,
+       "action":          contentTypeURL,
+       "alt":             contentTypePlain,
+       "archive":         contentTypeURL,
+       "async":           contentTypeUnsafe,
+       "autocomplete":    contentTypePlain,
+       "autofocus":       contentTypePlain,
+       "autoplay":        contentTypePlain,
+       "background":      contentTypeURL,
+       "border":          contentTypePlain,
+       "checked":         contentTypePlain,
+       "cite":            contentTypeURL,
+       "challenge":       contentTypeUnsafe,
+       "charset":         contentTypeUnsafe,
+       "class":           contentTypePlain,
+       "classid":         contentTypeURL,
+       "codebase":        contentTypeURL,
+       "cols":            contentTypePlain,
+       "colspan":         contentTypePlain,
+       "content":         contentTypeUnsafe,
+       "contenteditable": contentTypePlain,
+       "contextmenu":     contentTypePlain,
+       "controls":        contentTypePlain,
+       "coords":          contentTypePlain,
+       "crossorigin":     contentTypeUnsafe,
+       "data":            contentTypeURL,
+       "datetime":        contentTypePlain,
+       "default":         contentTypePlain,
+       "defer":           contentTypeUnsafe,
+       "dir":             contentTypePlain,
+       "dirname":         contentTypePlain,
+       "disabled":        contentTypePlain,
+       "draggable":       contentTypePlain,
+       "dropzone":        contentTypePlain,
+       "enctype":         contentTypeUnsafe,
+       "for":             contentTypePlain,
+       "form":            contentTypeUnsafe,
+       "formaction":      contentTypeURL,
+       "formenctype":     contentTypeUnsafe,
+       "formmethod":      contentTypeUnsafe,
+       "formnovalidate":  contentTypeUnsafe,
+       "formtarget":      contentTypePlain,
+       "headers":         contentTypePlain,
+       "height":          contentTypePlain,
+       "hidden":          contentTypePlain,
+       "high":            contentTypePlain,
+       "href":            contentTypeURL,
+       "hreflang":        contentTypePlain,
+       "http-equiv":      contentTypeUnsafe,
+       "icon":            contentTypeURL,
+       "id":              contentTypePlain,
+       "ismap":           contentTypePlain,
+       "keytype":         contentTypeUnsafe,
+       "kind":            contentTypePlain,
+       "label":           contentTypePlain,
+       "lang":            contentTypePlain,
+       "language":        contentTypeUnsafe,
+       "list":            contentTypePlain,
+       "longdesc":        contentTypeURL,
+       "loop":            contentTypePlain,
+       "low":             contentTypePlain,
+       "manifest":        contentTypeURL,
+       "max":             contentTypePlain,
+       "maxlength":       contentTypePlain,
+       "media":           contentTypePlain,
+       "mediagroup":      contentTypePlain,
+       "method":          contentTypeUnsafe,
+       "min":             contentTypePlain,
+       "multiple":        contentTypePlain,
+       "name":            contentTypePlain,
+       "novalidate":      contentTypeUnsafe,
+       // Skip handler names from
+       // https://www.w3.org/TR/html5/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects
+       // since we have special handling in attrType.
+       "open":        contentTypePlain,
+       "optimum":     contentTypePlain,
+       "pattern":     contentTypeUnsafe,
+       "placeholder": contentTypePlain,
+       "poster":      contentTypeURL,
+       "profile":     contentTypeURL,
+       "preload":     contentTypePlain,
+       "pubdate":     contentTypePlain,
+       "radiogroup":  contentTypePlain,
+       "readonly":    contentTypePlain,
+       "rel":         contentTypeUnsafe,
+       "required":    contentTypePlain,
+       "reversed":    contentTypePlain,
+       "rows":        contentTypePlain,
+       "rowspan":     contentTypePlain,
+       "sandbox":     contentTypeUnsafe,
+       "spellcheck":  contentTypePlain,
+       "scope":       contentTypePlain,
+       "scoped":      contentTypePlain,
+       "seamless":    contentTypePlain,
+       "selected":    contentTypePlain,
+       "shape":       contentTypePlain,
+       "size":        contentTypePlain,
+       "sizes":       contentTypePlain,
+       "span":        contentTypePlain,
+       "src":         contentTypeURL,
+       "srcdoc":      contentTypeHTML,
+       "srclang":     contentTypePlain,
+       "srcset":      contentTypeSrcset,
+       "start":       contentTypePlain,
+       "step":        contentTypePlain,
+       "style":       contentTypeCSS,
+       "tabindex":    contentTypePlain,
+       "target":      contentTypePlain,
+       "title":       contentTypePlain,
+       "type":        contentTypeUnsafe,
+       "usemap":      contentTypeURL,
+       "value":       contentTypeUnsafe,
+       "width":       contentTypePlain,
+       "wrap":        contentTypePlain,
+       "xmlns":       contentTypeURL,
+}
+
+// attrType returns a conservative (upper-bound on authority) guess at the
+// type of the lowercase named attribute.
+func attrType(name string) contentType {
+       if strings.HasPrefix(name, "data-") {
+               // Strip data- so that custom attribute heuristics below are
+               // widely applied.
+               // Treat data-action as URL below.
+               name = name[5:]
+       } else if colon := strings.IndexRune(name, ':'); colon != -1 {
+               if name[:colon] == "xmlns" {
+                       return contentTypeURL
+               }
+               // Treat svg:href and xlink:href as href below.
+               name = name[colon+1:]
+       }
+       if t, ok := attrTypeMap[name]; ok {
+               return t
+       }
+       // Treat partial event handler names as script.
+       if strings.HasPrefix(name, "on") {
+               return contentTypeJS
+       }
+
+       // Heuristics to prevent "javascript:..." injection in custom
+       // data attributes and custom attributes like g:tweetUrl.
+       // https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes
+       // "Custom data attributes are intended to store custom data
+       //  private to the page or application, for which there are no
+       //  more appropriate attributes or elements."
+       // Developers seem to store URL content in data URLs that start
+       // or end with "URI" or "URL".
+       if strings.Contains(name, "src") ||
+               strings.Contains(name, "uri") ||
+               strings.Contains(name, "url") {
+               return contentTypeURL
+       }
+       return contentTypePlain
+}
diff --git a/tpl/internal/go_templates/htmltemplate/attr_string.go b/tpl/internal/go_templates/htmltemplate/attr_string.go
new file mode 100644 (file)
index 0000000..babe70c
--- /dev/null
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type attr"; DO NOT EDIT.
+
+package template
+
+import "strconv"
+
+const _attr_name = "attrNoneattrScriptattrScriptTypeattrStyleattrURLattrSrcset"
+
+var _attr_index = [...]uint8{0, 8, 18, 32, 41, 48, 58}
+
+func (i attr) String() string {
+       if i >= attr(len(_attr_index)-1) {
+               return "attr(" + strconv.FormatInt(int64(i), 10) + ")"
+       }
+       return _attr_name[_attr_index[i]:_attr_index[i+1]]
+}
diff --git a/tpl/internal/go_templates/htmltemplate/clone_test.go b/tpl/internal/go_templates/htmltemplate/clone_test.go
new file mode 100644 (file)
index 0000000..2035e71
--- /dev/null
@@ -0,0 +1,282 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "io/ioutil"
+       "strings"
+       "sync"
+       "testing"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
+
+func TestAddParseTree(t *testing.T) {
+       root := Must(New("root").Parse(`{{define "a"}} {{.}} {{template "b"}} {{.}} "></a>{{end}}`))
+       tree, err := parse.Parse("t", `{{define "b"}}<a href="{{end}}`, "", "", nil, nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       added := Must(root.AddParseTree("b", tree["b"]))
+       b := new(bytes.Buffer)
+       err = added.ExecuteTemplate(b, "a", "1>0")
+       if err != nil {
+               t.Fatal(err)
+       }
+       if got, want := b.String(), ` 1&gt;0 <a href=" 1%3e0 "></a>`; got != want {
+               t.Errorf("got %q want %q", got, want)
+       }
+}
+
+func TestClone(t *testing.T) {
+       // The {{.}} will be executed with data "<i>*/" in different contexts.
+       // In the t0 template, it will be in a text context.
+       // In the t1 template, it will be in a URL context.
+       // In the t2 template, it will be in a JavaScript context.
+       // In the t3 template, it will be in a CSS context.
+       const tmpl = `{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}`
+       b := new(bytes.Buffer)
+
+       // Create an incomplete template t0.
+       t0 := Must(New("t0").Parse(tmpl))
+
+       // Clone t0 as t1.
+       t1 := Must(t0.Clone())
+       Must(t1.Parse(`{{define "lhs"}} <a href=" {{end}}`))
+       Must(t1.Parse(`{{define "rhs"}} "></a> {{end}}`))
+
+       // Execute t1.
+       b.Reset()
+       if err := t1.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
+               t.Fatal(err)
+       }
+       if got, want := b.String(), ` <a href=" %3ci%3e*/ "></a> `; got != want {
+               t.Errorf("t1: got %q want %q", got, want)
+       }
+
+       // Clone t0 as t2.
+       t2 := Must(t0.Clone())
+       Must(t2.Parse(`{{define "lhs"}} <p onclick="javascript: {{end}}`))
+       Must(t2.Parse(`{{define "rhs"}} "></p> {{end}}`))
+
+       // Execute t2.
+       b.Reset()
+       if err := t2.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
+               t.Fatal(err)
+       }
+       if got, want := b.String(), ` <p onclick="javascript: &#34;\u003ci\u003e*/&#34; "></p> `; got != want {
+               t.Errorf("t2: got %q want %q", got, want)
+       }
+
+       // Clone t0 as t3, but do not execute t3 yet.
+       t3 := Must(t0.Clone())
+       Must(t3.Parse(`{{define "lhs"}} <style> {{end}}`))
+       Must(t3.Parse(`{{define "rhs"}} </style> {{end}}`))
+
+       // Complete t0.
+       Must(t0.Parse(`{{define "lhs"}} ( {{end}}`))
+       Must(t0.Parse(`{{define "rhs"}} ) {{end}}`))
+
+       // Clone t0 as t4. Redefining the "lhs" template should not fail.
+       t4 := Must(t0.Clone())
+       if _, err := t4.Parse(`{{define "lhs"}} OK {{end}}`); err != nil {
+               t.Errorf(`redefine "lhs": got err %v want nil`, err)
+       }
+       // Cloning t1 should fail as it has been executed.
+       if _, err := t1.Clone(); err == nil {
+               t.Error("cloning t1: got nil err want non-nil")
+       }
+       // Redefining the "lhs" template in t1 should fail as it has been executed.
+       if _, err := t1.Parse(`{{define "lhs"}} OK {{end}}`); err == nil {
+               t.Error(`redefine "lhs": got nil err want non-nil`)
+       }
+
+       // Execute t0.
+       b.Reset()
+       if err := t0.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
+               t.Fatal(err)
+       }
+       if got, want := b.String(), ` ( &lt;i&gt;*/ ) `; got != want {
+               t.Errorf("t0: got %q want %q", got, want)
+       }
+
+       // Clone t0. This should fail, as t0 has already executed.
+       if _, err := t0.Clone(); err == nil {
+               t.Error(`t0.Clone(): got nil err want non-nil`)
+       }
+
+       // Similarly, cloning sub-templates should fail.
+       if _, err := t0.Lookup("a").Clone(); err == nil {
+               t.Error(`t0.Lookup("a").Clone(): got nil err want non-nil`)
+       }
+       if _, err := t0.Lookup("lhs").Clone(); err == nil {
+               t.Error(`t0.Lookup("lhs").Clone(): got nil err want non-nil`)
+       }
+
+       // Execute t3.
+       b.Reset()
+       if err := t3.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
+               t.Fatal(err)
+       }
+       if got, want := b.String(), ` <style> ZgotmplZ </style> `; got != want {
+               t.Errorf("t3: got %q want %q", got, want)
+       }
+}
+
+func TestTemplates(t *testing.T) {
+       names := []string{"t0", "a", "lhs", "rhs"}
+       // Some template definitions borrowed from TestClone.
+       const tmpl = `
+               {{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}
+               {{define "lhs"}} <a href=" {{end}}
+               {{define "rhs"}} "></a> {{end}}`
+       t0 := Must(New("t0").Parse(tmpl))
+       templates := t0.Templates()
+       if len(templates) != len(names) {
+               t.Errorf("expected %d templates; got %d", len(names), len(templates))
+       }
+       for _, name := range names {
+               found := false
+               for _, tmpl := range templates {
+                       if name == tmpl.text.Name() {
+                               found = true
+                               break
+                       }
+               }
+               if !found {
+                       t.Error("could not find template", name)
+               }
+       }
+}
+
+// This used to crash; https://golang.org/issue/3281
+func TestCloneCrash(t *testing.T) {
+       t1 := New("all")
+       Must(t1.New("t1").Parse(`{{define "foo"}}foo{{end}}`))
+       t1.Clone()
+}
+
+// Ensure that this guarantee from the docs is upheld:
+// "Further calls to Parse in the copy will add templates
+// to the copy but not to the original."
+func TestCloneThenParse(t *testing.T) {
+       t0 := Must(New("t0").Parse(`{{define "a"}}{{template "embedded"}}{{end}}`))
+       t1 := Must(t0.Clone())
+       Must(t1.Parse(`{{define "embedded"}}t1{{end}}`))
+       if len(t0.Templates())+1 != len(t1.Templates()) {
+               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)
+       if err == nil {
+               t.Error("expected 'no such template' error")
+       }
+}
+
+// https://golang.org/issue/5980
+func TestFuncMapWorksAfterClone(t *testing.T) {
+       funcs := FuncMap{"customFunc": func() (string, error) {
+               return "", errors.New("issue5980")
+       }}
+
+       // get the expected error output (no clone)
+       uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}"))
+       wantErr := uncloned.Execute(ioutil.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)
+
+       if wantErr.Error() != gotErr.Error() {
+               t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr)
+       }
+}
+
+// https://golang.org/issue/16101
+func TestTemplateCloneExecuteRace(t *testing.T) {
+       const (
+               input   = `<title>{{block "a" .}}a{{end}}</title><body>{{block "b" .}}b{{end}}<body>`
+               overlay = `{{define "b"}}A{{end}}`
+       )
+       outer := Must(New("outer").Parse(input))
+       tmpl := Must(Must(outer.Clone()).Parse(overlay))
+
+       var wg sync.WaitGroup
+       for i := 0; i < 10; i++ {
+               wg.Add(1)
+               go func() {
+                       defer wg.Done()
+                       for i := 0; i < 100; i++ {
+                               if err := tmpl.Execute(ioutil.Discard, "data"); err != nil {
+                                       panic(err)
+                               }
+                       }
+               }()
+       }
+       wg.Wait()
+}
+
+func TestTemplateCloneLookup(t *testing.T) {
+       // Template.escape makes an assumption that the template associated
+       // with t.Name() is t. Check that this holds.
+       tmpl := Must(New("x").Parse("a"))
+       tmpl = Must(tmpl.Clone())
+       if tmpl.Lookup(tmpl.Name()) != tmpl {
+               t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl")
+       }
+}
+
+func TestCloneGrowth(t *testing.T) {
+       tmpl := Must(New("root").Parse(`<title>{{block "B". }}Arg{{end}}</title>`))
+       tmpl = Must(tmpl.Clone())
+       Must(tmpl.Parse(`{{define "B"}}Text{{end}}`))
+       for i := 0; i < 10; i++ {
+               tmpl.Execute(ioutil.Discard, nil)
+       }
+       if len(tmpl.DefinedTemplates()) > 200 {
+               t.Fatalf("too many templates: %v", len(tmpl.DefinedTemplates()))
+       }
+}
+
+// https://golang.org/issue/17735
+func TestCloneRedefinedName(t *testing.T) {
+       const base = `
+{{ define "a" -}}<title>{{ template "b" . -}}</title>{{ end -}}
+{{ define "b" }}{{ end -}}
+`
+       const page = `{{ template "a" . }}`
+
+       t1 := Must(New("a").Parse(base))
+
+       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)
+               if err != nil {
+                       t.Fatal(err)
+               }
+       }
+}
+
+// Issue 24791.
+func TestClonePipe(t *testing.T) {
+       a := Must(New("a").Parse(`{{define "a"}}{{range $v := .A}}{{$v}}{{end}}{{end}}`))
+       data := struct{ A []string }{A: []string{"hi"}}
+       b := Must(a.Clone())
+       var buf strings.Builder
+       if err := b.Execute(&buf, &data); err != nil {
+               t.Fatal(err)
+       }
+       if got, want := buf.String(), "hi"; got != want {
+               t.Errorf("got %q want %q", got, want)
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/content.go b/tpl/internal/go_templates/htmltemplate/content.go
new file mode 100644 (file)
index 0000000..bc32dc8
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "fmt"
+       htmltemplate "html/template"
+       "reflect"
+)
+
+type contentType uint8
+
+const (
+       contentTypePlain contentType = iota
+       contentTypeCSS
+       contentTypeHTML
+       contentTypeHTMLAttr
+       contentTypeJS
+       contentTypeJSStr
+       contentTypeURL
+       contentTypeSrcset
+       // contentTypeUnsafe is used in attr.go for values that affect how
+       // embedded content and network messages are formed, vetted,
+       // or interpreted; or which credentials network messages carry.
+       contentTypeUnsafe
+)
+
+// indirect returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil).
+func indirect(a interface{}) interface{} {
+       if a == nil {
+               return nil
+       }
+       if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
+               // Avoid creating a reflect.Value if it's not a pointer.
+               return a
+       }
+       v := reflect.ValueOf(a)
+       for v.Kind() == reflect.Ptr && !v.IsNil() {
+               v = v.Elem()
+       }
+       return v.Interface()
+}
+
+var (
+       errorType       = reflect.TypeOf((*error)(nil)).Elem()
+       fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+)
+
+// indirectToStringerOrError returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
+// or error,
+func indirectToStringerOrError(a interface{}) interface{} {
+       if a == nil {
+               return nil
+       }
+       v := reflect.ValueOf(a)
+       for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+               v = v.Elem()
+       }
+       return v.Interface()
+}
+
+// stringify converts its arguments to a string and the type of the content.
+// All pointers are dereferenced, as in the text/template package.
+func stringify(args ...interface{}) (string, contentType) {
+       if len(args) == 1 {
+               switch s := indirect(args[0]).(type) {
+               case string:
+                       return s, contentTypePlain
+               case htmltemplate.CSS:
+                       return string(s), contentTypeCSS
+               case htmltemplate.HTML:
+                       return string(s), contentTypeHTML
+               case htmltemplate.HTMLAttr:
+                       return string(s), contentTypeHTMLAttr
+               case htmltemplate.JS:
+                       return string(s), contentTypeJS
+               case htmltemplate.JSStr:
+                       return string(s), contentTypeJSStr
+               case htmltemplate.URL:
+                       return string(s), contentTypeURL
+               case htmltemplate.Srcset:
+                       return string(s), contentTypeSrcset
+               }
+       }
+       i := 0
+       for _, arg := range args {
+               // We skip untyped nil arguments for backward compatibility.
+               // Without this they would be output as <nil>, escaped.
+               // See issue 25875.
+               if arg == nil {
+                       continue
+               }
+
+               args[i] = indirectToStringerOrError(arg)
+               i++
+       }
+       return fmt.Sprint(args[:i]...), contentTypePlain
+}
diff --git a/tpl/internal/go_templates/htmltemplate/content_test.go b/tpl/internal/go_templates/htmltemplate/content_test.go
new file mode 100644 (file)
index 0000000..2a1abfb
--- /dev/null
@@ -0,0 +1,461 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "bytes"
+       "fmt"
+       htmltemplate "html/template"
+       "strings"
+       "testing"
+)
+
+func TestTypedContent(t *testing.T) {
+       data := []interface{}{
+               `<b> "foo%" O'Reilly &bar;`,
+               htmltemplate.CSS(`a[href =~ "//example.com"]#foo`),
+               htmltemplate.HTML(`Hello, <b>World</b> &amp;tc!`),
+               htmltemplate.HTMLAttr(` dir="ltr"`),
+               htmltemplate.JS(`c && alert("Hello, World!");`),
+               htmltemplate.JSStr(`Hello, World & O'Reilly\x21`),
+               htmltemplate.URL(`greeting=H%69,&addressee=(World)`),
+               htmltemplate.Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`),
+               htmltemplate.URL(`,foo/,`),
+       }
+
+       // For each content sensitive escaper, see how it does on
+       // each of the typed strings above.
+       tests := []struct {
+               // A template containing a single {{.}}.
+               input string
+               want  []string
+       }{
+               {
+                       `<style>{{.}} { color: blue }</style>`,
+                       []string{
+                               `ZgotmplZ`,
+                               // Allowed but not escaped.
+                               `a[href =~ "//example.com"]#foo`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                       },
+               },
+               {
+                       `<div style="{{.}}">`,
+                       []string{
+                               `ZgotmplZ`,
+                               // Allowed and HTML escaped.
+                               `a[href =~ &#34;//example.com&#34;]#foo`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                       },
+               },
+               {
+                       `{{.}}`,
+                       []string{
+                               `&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
+                               `a[href =~ &#34;//example.com&#34;]#foo`,
+                               // Not escaped.
+                               `Hello, <b>World</b> &amp;tc!`,
+                               ` dir=&#34;ltr&#34;`,
+                               `c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
+                               `Hello, World &amp; O&#39;Reilly\x21`,
+                               `greeting=H%69,&amp;addressee=(World)`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `,foo/,`,
+                       },
+               },
+               {
+                       `<a{{.}}>`,
+                       []string{
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               // Allowed and HTML escaped.
+                               ` dir="ltr"`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                               `ZgotmplZ`,
+                       },
+               },
+               {
+                       `<a title={{.}}>`,
+                       []string{
+                               `&lt;b&gt;&#32;&#34;foo%&#34;&#32;O&#39;Reilly&#32;&amp;bar;`,
+                               `a[href&#32;&#61;~&#32;&#34;//example.com&#34;]#foo`,
+                               // Tags stripped, spaces escaped, entity not re-escaped.
+                               `Hello,&#32;World&#32;&amp;tc!`,
+                               `&#32;dir&#61;&#34;ltr&#34;`,
+                               `c&#32;&amp;&amp;&#32;alert(&#34;Hello,&#32;World!&#34;);`,
+                               `Hello,&#32;World&#32;&amp;&#32;O&#39;Reilly\x21`,
+                               `greeting&#61;H%69,&amp;addressee&#61;(World)`,
+                               `greeting&#61;H%69,&amp;addressee&#61;(World)&#32;2x,&#32;https://golang.org/favicon.ico&#32;500.5w`,
+                               `,foo/,`,
+                       },
+               },
+               {
+                       `<a title='{{.}}'>`,
+                       []string{
+                               `&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
+                               `a[href =~ &#34;//example.com&#34;]#foo`,
+                               // Tags stripped, entity not re-escaped.
+                               `Hello, World &amp;tc!`,
+                               ` dir=&#34;ltr&#34;`,
+                               `c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
+                               `Hello, World &amp; O&#39;Reilly\x21`,
+                               `greeting=H%69,&amp;addressee=(World)`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `,foo/,`,
+                       },
+               },
+               {
+                       `<textarea>{{.}}</textarea>`,
+                       []string{
+                               `&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
+                               `a[href =~ &#34;//example.com&#34;]#foo`,
+                               // Angle brackets escaped to prevent injection of close tags, entity not re-escaped.
+                               `Hello, &lt;b&gt;World&lt;/b&gt; &amp;tc!`,
+                               ` dir=&#34;ltr&#34;`,
+                               `c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
+                               `Hello, World &amp; O&#39;Reilly\x21`,
+                               `greeting=H%69,&amp;addressee=(World)`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `,foo/,`,
+                       },
+               },
+               {
+                       `<script>alert({{.}})</script>`,
+                       []string{
+                               `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
+                               `"a[href =~ \"//example.com\"]#foo"`,
+                               `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
+                               `" dir=\"ltr\""`,
+                               // Not escaped.
+                               `c && alert("Hello, World!");`,
+                               // Escape sequence not over-escaped.
+                               `"Hello, World & O'Reilly\x21"`,
+                               `"greeting=H%69,\u0026addressee=(World)"`,
+                               `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
+                               `",foo/,"`,
+                       },
+               },
+               {
+                       `<button onclick="alert({{.}})">`,
+                       []string{
+                               `&#34;\u003cb\u003e \&#34;foo%\&#34; O&#39;Reilly \u0026bar;&#34;`,
+                               `&#34;a[href =~ \&#34;//example.com\&#34;]#foo&#34;`,
+                               `&#34;Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!&#34;`,
+                               `&#34; dir=\&#34;ltr\&#34;&#34;`,
+                               // Not JS escaped but HTML escaped.
+                               `c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
+                               // Escape sequence not over-escaped.
+                               `&#34;Hello, World &amp; O&#39;Reilly\x21&#34;`,
+                               `&#34;greeting=H%69,\u0026addressee=(World)&#34;`,
+                               `&#34;greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w&#34;`,
+                               `&#34;,foo/,&#34;`,
+                       },
+               },
+               {
+                       `<script>alert("{{.}}")</script>`,
+                       []string{
+                               `\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
+                               `a[href =~ \x22\/\/example.com\x22]#foo`,
+                               `Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
+                               ` dir=\x22ltr\x22`,
+                               `c \x26\x26 alert(\x22Hello, World!\x22);`,
+                               // Escape sequence not over-escaped.
+                               `Hello, World \x26 O\x27Reilly\x21`,
+                               `greeting=H%69,\x26addressee=(World)`,
+                               `greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
+                               `,foo\/,`,
+                       },
+               },
+               {
+                       `<script type="text/javascript">alert("{{.}}")</script>`,
+                       []string{
+                               `\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
+                               `a[href =~ \x22\/\/example.com\x22]#foo`,
+                               `Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
+                               ` dir=\x22ltr\x22`,
+                               `c \x26\x26 alert(\x22Hello, World!\x22);`,
+                               // Escape sequence not over-escaped.
+                               `Hello, World \x26 O\x27Reilly\x21`,
+                               `greeting=H%69,\x26addressee=(World)`,
+                               `greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
+                               `,foo\/,`,
+                       },
+               },
+               {
+                       `<script type="text/javascript">alert({{.}})</script>`,
+                       []string{
+                               `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
+                               `"a[href =~ \"//example.com\"]#foo"`,
+                               `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
+                               `" dir=\"ltr\""`,
+                               // Not escaped.
+                               `c && alert("Hello, World!");`,
+                               // Escape sequence not over-escaped.
+                               `"Hello, World & O'Reilly\x21"`,
+                               `"greeting=H%69,\u0026addressee=(World)"`,
+                               `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
+                               `",foo/,"`,
+                       },
+               },
+               {
+                       // Not treated as JS. The output is same as for <div>{{.}}</div>
+                       `<script type="text/template">{{.}}</script>`,
+                       []string{
+                               `&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
+                               `a[href =~ &#34;//example.com&#34;]#foo`,
+                               // Not escaped.
+                               `Hello, <b>World</b> &amp;tc!`,
+                               ` dir=&#34;ltr&#34;`,
+                               `c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
+                               `Hello, World &amp; O&#39;Reilly\x21`,
+                               `greeting=H%69,&amp;addressee=(World)`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `,foo/,`,
+                       },
+               },
+               {
+                       `<button onclick='alert("{{.}}")'>`,
+                       []string{
+                               `\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
+                               `a[href =~ \x22\/\/example.com\x22]#foo`,
+                               `Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
+                               ` dir=\x22ltr\x22`,
+                               `c \x26\x26 alert(\x22Hello, World!\x22);`,
+                               // Escape sequence not over-escaped.
+                               `Hello, World \x26 O\x27Reilly\x21`,
+                               `greeting=H%69,\x26addressee=(World)`,
+                               `greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
+                               `,foo\/,`,
+                       },
+               },
+               {
+                       `<a href="?q={{.}}">`,
+                       []string{
+                               `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
+                               `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
+                               `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
+                               `%20dir%3d%22ltr%22`,
+                               `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
+                               `Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
+                               // Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
+                               `greeting=H%69,&amp;addressee=%28World%29`,
+                               `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
+                               `,foo/,`,
+                       },
+               },
+               {
+                       `<style>body { background: url('?img={{.}}') }</style>`,
+                       []string{
+                               `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
+                               `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
+                               `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
+                               `%20dir%3d%22ltr%22`,
+                               `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
+                               `Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
+                               // Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
+                               `greeting=H%69,&addressee=%28World%29`,
+                               `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
+                               `,foo/,`,
+                       },
+               },
+               {
+                       `<img srcset="{{.}}">`,
+                       []string{
+                               `#ZgotmplZ`,
+                               `#ZgotmplZ`,
+                               // Commas are not esacped
+                               `Hello,#ZgotmplZ`,
+                               // Leading spaces are not percent escapes.
+                               ` dir=%22ltr%22`,
+                               // Spaces after commas are not percent escaped.
+                               `#ZgotmplZ, World!%22%29;`,
+                               `Hello,#ZgotmplZ`,
+                               `greeting=H%69%2c&amp;addressee=%28World%29`,
+                               // Metadata is not escaped.
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `%2cfoo/%2c`,
+                       },
+               },
+               {
+                       `<img srcset={{.}}>`,
+                       []string{
+                               `#ZgotmplZ`,
+                               `#ZgotmplZ`,
+                               `Hello,#ZgotmplZ`,
+                               // Spaces are HTML escaped not %-escaped
+                               `&#32;dir&#61;%22ltr%22`,
+                               `#ZgotmplZ,&#32;World!%22%29;`,
+                               `Hello,#ZgotmplZ`,
+                               `greeting&#61;H%69%2c&amp;addressee&#61;%28World%29`,
+                               `greeting&#61;H%69,&amp;addressee&#61;(World)&#32;2x,&#32;https://golang.org/favicon.ico&#32;500.5w`,
+                               // Commas are escaped.
+                               `%2cfoo/%2c`,
+                       },
+               },
+               {
+                       `<img srcset="{{.}} 2x, https://golang.org/ 500.5w">`,
+                       []string{
+                               `#ZgotmplZ`,
+                               `#ZgotmplZ`,
+                               `Hello,#ZgotmplZ`,
+                               ` dir=%22ltr%22`,
+                               `#ZgotmplZ, World!%22%29;`,
+                               `Hello,#ZgotmplZ`,
+                               `greeting=H%69%2c&amp;addressee=%28World%29`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `%2cfoo/%2c`,
+                       },
+               },
+               {
+                       `<img srcset="http://godoc.org/ {{.}}, https://golang.org/ 500.5w">`,
+                       []string{
+                               `#ZgotmplZ`,
+                               `#ZgotmplZ`,
+                               `Hello,#ZgotmplZ`,
+                               ` dir=%22ltr%22`,
+                               `#ZgotmplZ, World!%22%29;`,
+                               `Hello,#ZgotmplZ`,
+                               `greeting=H%69%2c&amp;addressee=%28World%29`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `%2cfoo/%2c`,
+                       },
+               },
+               {
+                       `<img srcset="http://godoc.org/?q={{.}} 2x, https://golang.org/ 500.5w">`,
+                       []string{
+                               `#ZgotmplZ`,
+                               `#ZgotmplZ`,
+                               `Hello,#ZgotmplZ`,
+                               ` dir=%22ltr%22`,
+                               `#ZgotmplZ, World!%22%29;`,
+                               `Hello,#ZgotmplZ`,
+                               `greeting=H%69%2c&amp;addressee=%28World%29`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `%2cfoo/%2c`,
+                       },
+               },
+               {
+                       `<img srcset="http://godoc.org/ 2x, {{.}} 500.5w">`,
+                       []string{
+                               `#ZgotmplZ`,
+                               `#ZgotmplZ`,
+                               `Hello,#ZgotmplZ`,
+                               ` dir=%22ltr%22`,
+                               `#ZgotmplZ, World!%22%29;`,
+                               `Hello,#ZgotmplZ`,
+                               `greeting=H%69%2c&amp;addressee=%28World%29`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `%2cfoo/%2c`,
+                       },
+               },
+               {
+                       `<img srcset="http://godoc.org/ 2x, https://golang.org/ {{.}}">`,
+                       []string{
+                               `#ZgotmplZ`,
+                               `#ZgotmplZ`,
+                               `Hello,#ZgotmplZ`,
+                               ` dir=%22ltr%22`,
+                               `#ZgotmplZ, World!%22%29;`,
+                               `Hello,#ZgotmplZ`,
+                               `greeting=H%69%2c&amp;addressee=%28World%29`,
+                               `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+                               `%2cfoo/%2c`,
+                       },
+               },
+       }
+
+       for _, test := range tests {
+               tmpl := Must(New("x").Parse(test.input))
+               pre := strings.Index(test.input, "{{.}}")
+               post := len(test.input) - (pre + 5)
+               var b bytes.Buffer
+               for i, x := range data {
+                       b.Reset()
+                       if err := tmpl.Execute(&b, x); err != nil {
+                               t.Errorf("%q with %v: %s", test.input, x, err)
+                               continue
+                       }
+                       if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got {
+                               t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got)
+                               continue
+                       }
+               }
+       }
+}
+
+// Test that we print using the String method. Was issue 3073.
+type stringer struct {
+       v int
+}
+
+func (s *stringer) String() string {
+       return fmt.Sprintf("string=%d", s.v)
+}
+
+type errorer struct {
+       v int
+}
+
+func (s *errorer) Error() string {
+       return fmt.Sprintf("error=%d", s.v)
+}
+
+func TestStringer(t *testing.T) {
+       s := &stringer{3}
+       b := new(bytes.Buffer)
+       tmpl := Must(New("x").Parse("{{.}}"))
+       if err := tmpl.Execute(b, s); err != nil {
+               t.Fatal(err)
+       }
+       var expect = "string=3"
+       if b.String() != expect {
+               t.Errorf("expected %q got %q", expect, b.String())
+       }
+       e := &errorer{7}
+       b.Reset()
+       if err := tmpl.Execute(b, e); err != nil {
+               t.Fatal(err)
+       }
+       expect = "error=7"
+       if b.String() != expect {
+               t.Errorf("expected %q got %q", expect, b.String())
+       }
+}
+
+// https://golang.org/issue/5982
+func TestEscapingNilNonemptyInterfaces(t *testing.T) {
+       tmpl := Must(New("x").Parse("{{.E}}"))
+
+       got := new(bytes.Buffer)
+       testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
+       tmpl.Execute(got, testData)
+
+       // A non-empty interface should print like an empty interface.
+       want := new(bytes.Buffer)
+       data := struct{ E interface{} }{}
+       tmpl.Execute(want, data)
+
+       if !bytes.Equal(want.Bytes(), got.Bytes()) {
+               t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/context.go b/tpl/internal/go_templates/htmltemplate/context.go
new file mode 100644 (file)
index 0000000..006b870
--- /dev/null
@@ -0,0 +1,258 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import "fmt"
+
+// context describes the state an HTML parser must be in when it reaches the
+// portion of HTML produced by evaluating a particular template node.
+//
+// The zero value of type context is the start context for a template that
+// produces an HTML fragment as defined at
+// https://www.w3.org/TR/html5/syntax.html#the-end
+// where the context element is null.
+type context struct {
+       state   state
+       delim   delim
+       urlPart urlPart
+       jsCtx   jsCtx
+       attr    attr
+       element element
+       err     *Error
+}
+
+func (c context) String() string {
+       var err error
+       if c.err != nil {
+               err = c.err
+       }
+       return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, err)
+}
+
+// eq reports whether two contexts are equal.
+func (c context) eq(d context) bool {
+       return c.state == d.state &&
+               c.delim == d.delim &&
+               c.urlPart == d.urlPart &&
+               c.jsCtx == d.jsCtx &&
+               c.attr == d.attr &&
+               c.element == d.element &&
+               c.err == d.err
+}
+
+// mangle produces an identifier that includes a suffix that distinguishes it
+// from template names mangled with different contexts.
+func (c context) mangle(templateName string) string {
+       // The mangled name for the default context is the input templateName.
+       if c.state == stateText {
+               return templateName
+       }
+       s := templateName + "$htmltemplate_" + c.state.String()
+       if c.delim != delimNone {
+               s += "_" + c.delim.String()
+       }
+       if c.urlPart != urlPartNone {
+               s += "_" + c.urlPart.String()
+       }
+       if c.jsCtx != jsCtxRegexp {
+               s += "_" + c.jsCtx.String()
+       }
+       if c.attr != attrNone {
+               s += "_" + c.attr.String()
+       }
+       if c.element != elementNone {
+               s += "_" + c.element.String()
+       }
+       return s
+}
+
+// state describes a high-level HTML parser state.
+//
+// It bounds the top of the element stack, and by extension the HTML insertion
+// mode, but also contains state that does not correspond to anything in the
+// HTML5 parsing algorithm because a single token production in the HTML
+// grammar may contain embedded actions in a template. For instance, the quoted
+// HTML attribute produced by
+//     <div title="Hello {{.World}}">
+// is a single token in HTML's grammar but in a template spans several nodes.
+type state uint8
+
+//go:generate stringer -type state
+
+const (
+       // stateText is parsed character data. An HTML parser is in
+       // this state when its parse position is outside an HTML tag,
+       // directive, comment, and special element body.
+       stateText state = iota
+       // stateTag occurs before an HTML attribute or the end of a tag.
+       stateTag
+       // stateAttrName occurs inside an attribute name.
+       // It occurs between the ^'s in ` ^name^ = value`.
+       stateAttrName
+       // stateAfterName occurs after an attr name has ended but before any
+       // equals sign. It occurs between the ^'s in ` name^ ^= value`.
+       stateAfterName
+       // stateBeforeValue occurs after the equals sign but before the value.
+       // It occurs between the ^'s in ` name =^ ^value`.
+       stateBeforeValue
+       // stateHTMLCmt occurs inside an <!-- HTML comment -->.
+       stateHTMLCmt
+       // stateRCDATA occurs inside an RCDATA element (<textarea> or <title>)
+       // as described at https://www.w3.org/TR/html5/syntax.html#elements-0
+       stateRCDATA
+       // stateAttr occurs inside an HTML attribute whose content is text.
+       stateAttr
+       // stateURL occurs inside an HTML attribute whose content is a URL.
+       stateURL
+       // stateSrcset occurs inside an HTML srcset attribute.
+       stateSrcset
+       // stateJS occurs inside an event handler or script element.
+       stateJS
+       // stateJSDqStr occurs inside a JavaScript double quoted string.
+       stateJSDqStr
+       // stateJSSqStr occurs inside a JavaScript single quoted string.
+       stateJSSqStr
+       // stateJSRegexp occurs inside a JavaScript regexp literal.
+       stateJSRegexp
+       // stateJSBlockCmt occurs inside a JavaScript /* block comment */.
+       stateJSBlockCmt
+       // stateJSLineCmt occurs inside a JavaScript // line comment.
+       stateJSLineCmt
+       // stateCSS occurs inside a <style> element or style attribute.
+       stateCSS
+       // stateCSSDqStr occurs inside a CSS double quoted string.
+       stateCSSDqStr
+       // stateCSSSqStr occurs inside a CSS single quoted string.
+       stateCSSSqStr
+       // stateCSSDqURL occurs inside a CSS double quoted url("...").
+       stateCSSDqURL
+       // stateCSSSqURL occurs inside a CSS single quoted url('...').
+       stateCSSSqURL
+       // stateCSSURL occurs inside a CSS unquoted url(...).
+       stateCSSURL
+       // stateCSSBlockCmt occurs inside a CSS /* block comment */.
+       stateCSSBlockCmt
+       // stateCSSLineCmt occurs inside a CSS // line comment.
+       stateCSSLineCmt
+       // stateError is an infectious error state outside any valid
+       // HTML/CSS/JS construct.
+       stateError
+)
+
+// isComment is true for any state that contains content meant for template
+// authors & maintainers, not for end-users or machines.
+func isComment(s state) bool {
+       switch s {
+       case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt:
+               return true
+       }
+       return false
+}
+
+// isInTag return whether s occurs solely inside an HTML tag.
+func isInTag(s state) bool {
+       switch s {
+       case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr:
+               return true
+       }
+       return false
+}
+
+// delim is the delimiter that will end the current HTML attribute.
+type delim uint8
+
+//go:generate stringer -type delim
+
+const (
+       // delimNone occurs outside any attribute.
+       delimNone delim = iota
+       // delimDoubleQuote occurs when a double quote (") closes the attribute.
+       delimDoubleQuote
+       // delimSingleQuote occurs when a single quote (') closes the attribute.
+       delimSingleQuote
+       // delimSpaceOrTagEnd occurs when a space or right angle bracket (>)
+       // closes the attribute.
+       delimSpaceOrTagEnd
+)
+
+// urlPart identifies a part in an RFC 3986 hierarchical URL to allow different
+// encoding strategies.
+type urlPart uint8
+
+//go:generate stringer -type urlPart
+
+const (
+       // urlPartNone occurs when not in a URL, or possibly at the start:
+       // ^ in "^http://auth/path?k=v#frag".
+       urlPartNone urlPart = iota
+       // urlPartPreQuery occurs in the scheme, authority, or path; between the
+       // ^s in "h^ttp://auth/path^?k=v#frag".
+       urlPartPreQuery
+       // urlPartQueryOrFrag occurs in the query portion between the ^s in
+       // "http://auth/path?^k=v#frag^".
+       urlPartQueryOrFrag
+       // urlPartUnknown occurs due to joining of contexts both before and
+       // after the query separator.
+       urlPartUnknown
+)
+
+// jsCtx determines whether a '/' starts a regular expression literal or a
+// division operator.
+type jsCtx uint8
+
+//go:generate stringer -type jsCtx
+
+const (
+       // jsCtxRegexp occurs where a '/' would start a regexp literal.
+       jsCtxRegexp jsCtx = iota
+       // jsCtxDivOp occurs where a '/' would start a division operator.
+       jsCtxDivOp
+       // jsCtxUnknown occurs where a '/' is ambiguous due to context joining.
+       jsCtxUnknown
+)
+
+// element identifies the HTML element when inside a start tag or special body.
+// Certain HTML element (for example <script> and <style>) have bodies that are
+// treated differently from stateText so the element type is necessary to
+// transition into the correct context at the end of a tag and to identify the
+// end delimiter for the body.
+type element uint8
+
+//go:generate stringer -type element
+
+const (
+       // elementNone occurs outside a special tag or special element body.
+       elementNone element = iota
+       // elementScript corresponds to the raw text <script> element
+       // with JS MIME type or no type attribute.
+       elementScript
+       // elementStyle corresponds to the raw text <style> element.
+       elementStyle
+       // elementTextarea corresponds to the RCDATA <textarea> element.
+       elementTextarea
+       // elementTitle corresponds to the RCDATA <title> element.
+       elementTitle
+)
+
+//go:generate stringer -type attr
+
+// attr identifies the current HTML attribute when inside the attribute,
+// that is, starting from stateAttrName until stateTag/stateText (exclusive).
+type attr uint8
+
+const (
+       // attrNone corresponds to a normal attribute or no attribute.
+       attrNone attr = iota
+       // attrScript corresponds to an event handler attribute.
+       attrScript
+       // attrScriptType corresponds to the type attribute in script HTML element
+       attrScriptType
+       // attrStyle corresponds to the style attribute whose value is CSS.
+       attrStyle
+       // attrURL corresponds to an attribute whose value is a URL.
+       attrURL
+       // attrSrcset corresponds to a srcset attribute.
+       attrSrcset
+)
diff --git a/tpl/internal/go_templates/htmltemplate/css.go b/tpl/internal/go_templates/htmltemplate/css.go
new file mode 100644 (file)
index 0000000..eb92fc9
--- /dev/null
@@ -0,0 +1,260 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "fmt"
+       "strings"
+       "unicode"
+       "unicode/utf8"
+)
+
+// endsWithCSSKeyword reports whether b ends with an ident that
+// case-insensitively matches the lower-case kw.
+func endsWithCSSKeyword(b []byte, kw string) bool {
+       i := len(b) - len(kw)
+       if i < 0 {
+               // Too short.
+               return false
+       }
+       if i != 0 {
+               r, _ := utf8.DecodeLastRune(b[:i])
+               if isCSSNmchar(r) {
+                       // Too long.
+                       return false
+               }
+       }
+       // Many CSS keywords, such as "!important" can have characters encoded,
+       // but the URI production does not allow that according to
+       // https://www.w3.org/TR/css3-syntax/#TOK-URI
+       // This does not attempt to recognize encoded keywords. For example,
+       // given "\75\72\6c" and "url" this return false.
+       return string(bytes.ToLower(b[i:])) == kw
+}
+
+// isCSSNmchar reports whether rune is allowed anywhere in a CSS identifier.
+func isCSSNmchar(r rune) bool {
+       // Based on the CSS3 nmchar production but ignores multi-rune escape
+       // sequences.
+       // https://www.w3.org/TR/css3-syntax/#SUBTOK-nmchar
+       return 'a' <= r && r <= 'z' ||
+               'A' <= r && r <= 'Z' ||
+               '0' <= r && r <= '9' ||
+               r == '-' ||
+               r == '_' ||
+               // Non-ASCII cases below.
+               0x80 <= r && r <= 0xd7ff ||
+               0xe000 <= r && r <= 0xfffd ||
+               0x10000 <= r && r <= 0x10ffff
+}
+
+// decodeCSS decodes CSS3 escapes given a sequence of stringchars.
+// If there is no change, it returns the input, otherwise it returns a slice
+// backed by a new array.
+// https://www.w3.org/TR/css3-syntax/#SUBTOK-stringchar defines stringchar.
+func decodeCSS(s []byte) []byte {
+       i := bytes.IndexByte(s, '\\')
+       if i == -1 {
+               return s
+       }
+       // The UTF-8 sequence for a codepoint is never longer than 1 + the
+       // number hex digits need to represent that codepoint, so len(s) is an
+       // upper bound on the output length.
+       b := make([]byte, 0, len(s))
+       for len(s) != 0 {
+               i := bytes.IndexByte(s, '\\')
+               if i == -1 {
+                       i = len(s)
+               }
+               b, s = append(b, s[:i]...), s[i:]
+               if len(s) < 2 {
+                       break
+               }
+               // https://www.w3.org/TR/css3-syntax/#SUBTOK-escape
+               // escape ::= unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF]
+               if isHex(s[1]) {
+                       // https://www.w3.org/TR/css3-syntax/#SUBTOK-unicode
+                       //   unicode ::= '\' [0-9a-fA-F]{1,6} wc?
+                       j := 2
+                       for j < len(s) && j < 7 && isHex(s[j]) {
+                               j++
+                       }
+                       r := hexDecode(s[1:j])
+                       if r > unicode.MaxRune {
+                               r, j = r/16, j-1
+                       }
+                       n := utf8.EncodeRune(b[len(b):cap(b)], r)
+                       // The optional space at the end allows a hex
+                       // sequence to be followed by a literal hex.
+                       // string(decodeCSS([]byte(`\A B`))) == "\nB"
+                       b, s = b[:len(b)+n], skipCSSSpace(s[j:])
+               } else {
+                       // `\\` decodes to `\` and `\"` to `"`.
+                       _, n := utf8.DecodeRune(s[1:])
+                       b, s = append(b, s[1:1+n]...), s[1+n:]
+               }
+       }
+       return b
+}
+
+// isHex reports whether the given character is a hex digit.
+func isHex(c byte) bool {
+       return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
+}
+
+// hexDecode decodes a short hex digit sequence: "10" -> 16.
+func hexDecode(s []byte) rune {
+       n := '\x00'
+       for _, c := range s {
+               n <<= 4
+               switch {
+               case '0' <= c && c <= '9':
+                       n |= rune(c - '0')
+               case 'a' <= c && c <= 'f':
+                       n |= rune(c-'a') + 10
+               case 'A' <= c && c <= 'F':
+                       n |= rune(c-'A') + 10
+               default:
+                       panic(fmt.Sprintf("Bad hex digit in %q", s))
+               }
+       }
+       return n
+}
+
+// skipCSSSpace returns a suffix of c, skipping over a single space.
+func skipCSSSpace(c []byte) []byte {
+       if len(c) == 0 {
+               return c
+       }
+       // wc ::= #x9 | #xA | #xC | #xD | #x20
+       switch c[0] {
+       case '\t', '\n', '\f', ' ':
+               return c[1:]
+       case '\r':
+               // This differs from CSS3's wc production because it contains a
+               // probable spec error whereby wc contains all the single byte
+               // sequences in nl (newline) but not CRLF.
+               if len(c) >= 2 && c[1] == '\n' {
+                       return c[2:]
+               }
+               return c[1:]
+       }
+       return c
+}
+
+// isCSSSpace reports whether b is a CSS space char as defined in wc.
+func isCSSSpace(b byte) bool {
+       switch b {
+       case '\t', '\n', '\f', '\r', ' ':
+               return true
+       }
+       return false
+}
+
+// cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes.
+func cssEscaper(args ...interface{}) string {
+       s, _ := stringify(args...)
+       var b strings.Builder
+       r, w, written := rune(0), 0, 0
+       for i := 0; i < len(s); i += w {
+               // See comment in htmlEscaper.
+               r, w = utf8.DecodeRuneInString(s[i:])
+               var repl string
+               switch {
+               case int(r) < len(cssReplacementTable) && cssReplacementTable[r] != "":
+                       repl = cssReplacementTable[r]
+               default:
+                       continue
+               }
+               if written == 0 {
+                       b.Grow(len(s))
+               }
+               b.WriteString(s[written:i])
+               b.WriteString(repl)
+               written = i + w
+               if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) {
+                       b.WriteByte(' ')
+               }
+       }
+       if written == 0 {
+               return s
+       }
+       b.WriteString(s[written:])
+       return b.String()
+}
+
+var cssReplacementTable = []string{
+       0:    `\0`,
+       '\t': `\9`,
+       '\n': `\a`,
+       '\f': `\c`,
+       '\r': `\d`,
+       // Encode HTML specials as hex so the output can be embedded
+       // in HTML attributes without further encoding.
+       '"':  `\22`,
+       '&':  `\26`,
+       '\'': `\27`,
+       '(':  `\28`,
+       ')':  `\29`,
+       '+':  `\2b`,
+       '/':  `\2f`,
+       ':':  `\3a`,
+       ';':  `\3b`,
+       '<':  `\3c`,
+       '>':  `\3e`,
+       '\\': `\\`,
+       '{':  `\7b`,
+       '}':  `\7d`,
+}
+
+var expressionBytes = []byte("expression")
+var mozBindingBytes = []byte("mozbinding")
+
+// cssValueFilter allows innocuous CSS values in the output including CSS
+// quantities (10px or 25%), ID or class literals (#foo, .bar), keyword values
+// (inherit, blue), and colors (#888).
+// It filters out unsafe values, such as those that affect token boundaries,
+// and anything that might execute scripts.
+func cssValueFilter(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeCSS {
+               return s
+       }
+       b, id := decodeCSS([]byte(s)), make([]byte, 0, 64)
+
+       // CSS3 error handling is specified as honoring string boundaries per
+       // https://www.w3.org/TR/css3-syntax/#error-handling :
+       //     Malformed declarations. User agents must handle unexpected
+       //     tokens encountered while parsing a declaration by reading until
+       //     the end of the declaration, while observing the rules for
+       //     matching pairs of (), [], {}, "", and '', and correctly handling
+       //     escapes. For example, a malformed declaration may be missing a
+       //     property, colon (:) or value.
+       // So we need to make sure that values do not have mismatched bracket
+       // or quote characters to prevent the browser from restarting parsing
+       // inside a string that might embed JavaScript source.
+       for i, c := range b {
+               switch c {
+               case 0, '"', '\'', '(', ')', '/', ';', '@', '[', '\\', ']', '`', '{', '}':
+                       return filterFailsafe
+               case '-':
+                       // Disallow <!-- or -->.
+                       // -- should not appear in valid identifiers.
+                       if i != 0 && b[i-1] == '-' {
+                               return filterFailsafe
+                       }
+               default:
+                       if c < utf8.RuneSelf && isCSSNmchar(rune(c)) {
+                               id = append(id, c)
+                       }
+               }
+       }
+       id = bytes.ToLower(id)
+       if bytes.Contains(id, expressionBytes) || bytes.Contains(id, mozBindingBytes) {
+               return filterFailsafe
+       }
+       return string(b)
+}
diff --git a/tpl/internal/go_templates/htmltemplate/css_test.go b/tpl/internal/go_templates/htmltemplate/css_test.go
new file mode 100644 (file)
index 0000000..afed58c
--- /dev/null
@@ -0,0 +1,283 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "strconv"
+       "strings"
+       "testing"
+)
+
+func TestEndsWithCSSKeyword(t *testing.T) {
+       tests := []struct {
+               css, kw string
+               want    bool
+       }{
+               {"", "url", false},
+               {"url", "url", true},
+               {"URL", "url", true},
+               {"Url", "url", true},
+               {"url", "important", false},
+               {"important", "important", true},
+               {"image-url", "url", false},
+               {"imageurl", "url", false},
+               {"image url", "url", true},
+       }
+       for _, test := range tests {
+               got := endsWithCSSKeyword([]byte(test.css), test.kw)
+               if got != test.want {
+                       t.Errorf("want %t but got %t for css=%v, kw=%v", test.want, got, test.css, test.kw)
+               }
+       }
+}
+
+func TestIsCSSNmchar(t *testing.T) {
+       tests := []struct {
+               rune rune
+               want bool
+       }{
+               {0, false},
+               {'0', true},
+               {'9', true},
+               {'A', true},
+               {'Z', true},
+               {'a', true},
+               {'z', true},
+               {'_', true},
+               {'-', true},
+               {':', false},
+               {';', false},
+               {' ', false},
+               {0x7f, false},
+               {0x80, true},
+               {0x1234, true},
+               {0xd800, false},
+               {0xdc00, false},
+               {0xfffe, false},
+               {0x10000, true},
+               {0x110000, false},
+       }
+       for _, test := range tests {
+               got := isCSSNmchar(test.rune)
+               if got != test.want {
+                       t.Errorf("%q: want %t but got %t", string(test.rune), test.want, got)
+               }
+       }
+}
+
+func TestDecodeCSS(t *testing.T) {
+       tests := []struct {
+               css, want string
+       }{
+               {``, ``},
+               {`foo`, `foo`},
+               {`foo\`, `foo`},
+               {`foo\\`, `foo\`},
+               {`\`, ``},
+               {`\A`, "\n"},
+               {`\a`, "\n"},
+               {`\0a`, "\n"},
+               {`\00000a`, "\n"},
+               {`\000000a`, "\u0000a"},
+               {`\1234 5`, "\u1234" + "5"},
+               {`\1234\20 5`, "\u1234" + " 5"},
+               {`\1234\A 5`, "\u1234" + "\n5"},
+               {"\\1234\t5", "\u1234" + "5"},
+               {"\\1234\n5", "\u1234" + "5"},
+               {"\\1234\r\n5", "\u1234" + "5"},
+               {`\12345`, "\U00012345"},
+               {`\\`, `\`},
+               {`\\ `, `\ `},
+               {`\"`, `"`},
+               {`\'`, `'`},
+               {`\.`, `.`},
+               {`\. .`, `. .`},
+               {
+                       `The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e  fox jumps\2028over the \3c canine class=\22lazy\22 \3e dog\3c/canine\3e`,
+                       "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>",
+               },
+       }
+       for _, test := range tests {
+               got1 := string(decodeCSS([]byte(test.css)))
+               if got1 != test.want {
+                       t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.css, test.want, got1)
+               }
+               recoded := cssEscaper(got1)
+               if got2 := string(decodeCSS([]byte(recoded))); got2 != test.want {
+                       t.Errorf("%q: escape & decode not dual for %q", test.css, recoded)
+               }
+       }
+}
+
+func TestHexDecode(t *testing.T) {
+       for i := 0; i < 0x200000; i += 101 /* coprime with 16 */ {
+               s := strconv.FormatInt(int64(i), 16)
+               if got := int(hexDecode([]byte(s))); got != i {
+                       t.Errorf("%s: want %d but got %d", s, i, got)
+               }
+               s = strings.ToUpper(s)
+               if got := int(hexDecode([]byte(s))); got != i {
+                       t.Errorf("%s: want %d but got %d", s, i, got)
+               }
+       }
+}
+
+func TestSkipCSSSpace(t *testing.T) {
+       tests := []struct {
+               css, want string
+       }{
+               {"", ""},
+               {"foo", "foo"},
+               {"\n", ""},
+               {"\r\n", ""},
+               {"\r", ""},
+               {"\t", ""},
+               {" ", ""},
+               {"\f", ""},
+               {" foo", "foo"},
+               {"  foo", " foo"},
+               {`\20`, `\20`},
+       }
+       for _, test := range tests {
+               got := string(skipCSSSpace([]byte(test.css)))
+               if got != test.want {
+                       t.Errorf("%q: want %q but got %q", test.css, test.want, got)
+               }
+       }
+}
+
+func TestCSSEscaper(t *testing.T) {
+       input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
+               "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+               ` !"#$%&'()*+,-./` +
+               `0123456789:;<=>?` +
+               `@ABCDEFGHIJKLMNO` +
+               `PQRSTUVWXYZ[\]^_` +
+               "`abcdefghijklmno" +
+               "pqrstuvwxyz{|}~\x7f" +
+               "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
+
+       want := ("\\0\x01\x02\x03\x04\x05\x06\x07" +
+               "\x08\\9 \\a\x0b\\c \\d\x0E\x0F" +
+               "\x10\x11\x12\x13\x14\x15\x16\x17" +
+               "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+               ` !\22#$%\26\27\28\29*\2b,-.\2f ` +
+               `0123456789\3a\3b\3c=\3e?` +
+               `@ABCDEFGHIJKLMNO` +
+               `PQRSTUVWXYZ[\\]^_` +
+               "`abcdefghijklmno" +
+               `pqrstuvwxyz\7b|\7d~` + "\u007f" +
+               "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
+
+       got := cssEscaper(input)
+       if got != want {
+               t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got)
+       }
+
+       got = string(decodeCSS([]byte(got)))
+       if input != got {
+               t.Errorf("decode: want\n\t%q\nbut got\n\t%q", input, got)
+       }
+}
+
+func TestCSSValueFilter(t *testing.T) {
+       tests := []struct {
+               css, want string
+       }{
+               {"", ""},
+               {"foo", "foo"},
+               {"0", "0"},
+               {"0px", "0px"},
+               {"-5px", "-5px"},
+               {"1.25in", "1.25in"},
+               {"+.33em", "+.33em"},
+               {"100%", "100%"},
+               {"12.5%", "12.5%"},
+               {".foo", ".foo"},
+               {"#bar", "#bar"},
+               {"corner-radius", "corner-radius"},
+               {"-moz-corner-radius", "-moz-corner-radius"},
+               {"#000", "#000"},
+               {"#48f", "#48f"},
+               {"#123456", "#123456"},
+               {"U+00-FF, U+980-9FF", "U+00-FF, U+980-9FF"},
+               {"color: red", "color: red"},
+               {"<!--", "ZgotmplZ"},
+               {"-->", "ZgotmplZ"},
+               {"<![CDATA[", "ZgotmplZ"},
+               {"]]>", "ZgotmplZ"},
+               {"</style", "ZgotmplZ"},
+               {`"`, "ZgotmplZ"},
+               {`'`, "ZgotmplZ"},
+               {"`", "ZgotmplZ"},
+               {"\x00", "ZgotmplZ"},
+               {"/* foo */", "ZgotmplZ"},
+               {"//", "ZgotmplZ"},
+               {"[href=~", "ZgotmplZ"},
+               {"expression(alert(1337))", "ZgotmplZ"},
+               {"-expression(alert(1337))", "ZgotmplZ"},
+               {"expression", "ZgotmplZ"},
+               {"Expression", "ZgotmplZ"},
+               {"EXPRESSION", "ZgotmplZ"},
+               {"-moz-binding", "ZgotmplZ"},
+               {"-expr\x00ession(alert(1337))", "ZgotmplZ"},
+               {`-expr\0ession(alert(1337))`, "ZgotmplZ"},
+               {`-express\69on(alert(1337))`, "ZgotmplZ"},
+               {`-express\69 on(alert(1337))`, "ZgotmplZ"},
+               {`-exp\72 ession(alert(1337))`, "ZgotmplZ"},
+               {`-exp\52 ession(alert(1337))`, "ZgotmplZ"},
+               {`-exp\000052 ession(alert(1337))`, "ZgotmplZ"},
+               {`-expre\0000073sion`, "-expre\x073sion"},
+               {`@import url evil.css`, "ZgotmplZ"},
+       }
+       for _, test := range tests {
+               got := cssValueFilter(test.css)
+               if got != test.want {
+                       t.Errorf("%q: want %q but got %q", test.css, test.want, got)
+               }
+       }
+}
+
+func BenchmarkCSSEscaper(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               cssEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
+       }
+}
+
+func BenchmarkCSSEscaperNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               cssEscaper("The quick, brown fox jumps over the lazy dog.")
+       }
+}
+
+func BenchmarkDecodeCSS(b *testing.B) {
+       s := []byte(`The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3edog\3c/canine\3e`)
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               decodeCSS(s)
+       }
+}
+
+func BenchmarkDecodeCSSNoSpecials(b *testing.B) {
+       s := []byte("The quick, brown fox jumps over the lazy dog.")
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               decodeCSS(s)
+       }
+}
+
+func BenchmarkCSSValueFilter(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               cssValueFilter(`  e\78preS\0Sio/**/n(alert(1337))`)
+       }
+}
+
+func BenchmarkCSSValueFilterOk(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               cssValueFilter(`Times New Roman`)
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/delim_string.go b/tpl/internal/go_templates/htmltemplate/delim_string.go
new file mode 100644 (file)
index 0000000..6d80e09
--- /dev/null
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type delim"; DO NOT EDIT.
+
+package template
+
+import "strconv"
+
+const _delim_name = "delimNonedelimDoubleQuotedelimSingleQuotedelimSpaceOrTagEnd"
+
+var _delim_index = [...]uint8{0, 9, 25, 41, 59}
+
+func (i delim) String() string {
+       if i >= delim(len(_delim_index)-1) {
+               return "delim(" + strconv.FormatInt(int64(i), 10) + ")"
+       }
+       return _delim_name[_delim_index[i]:_delim_index[i+1]]
+}
diff --git a/tpl/internal/go_templates/htmltemplate/doc.go b/tpl/internal/go_templates/htmltemplate/doc.go
new file mode 100644 (file)
index 0000000..0a3004a
--- /dev/null
@@ -0,0 +1,196 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package template (html/template) implements data-driven templates for
+generating HTML output safe against code injection. It provides the
+same interface as package text/template and should be used instead of
+text/template whenever the output is HTML.
+
+The documentation here focuses on the security features of the package.
+For information about how to program the templates themselves, see the
+documentation for text/template.
+
+Introduction
+
+This package wraps package text/template so you can share its template API
+to parse and execute HTML templates safely.
+
+  tmpl, err := template.New("name").Parse(...)
+  // Error checking elided
+  err = tmpl.Execute(out, data)
+
+If successful, tmpl will now be injection-safe. Otherwise, err is an error
+defined in the docs for ErrorCode.
+
+HTML templates treat data values as plain text which should be encoded so they
+can be safely embedded in an HTML document. The escaping is contextual, so
+actions can appear within JavaScript, CSS, and URI contexts.
+
+The security model used by this package assumes that template authors are
+trusted, while Execute's data parameter is not. More details are
+provided below.
+
+Example
+
+  import template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+  ...
+  t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
+  err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
+
+produces
+
+  Hello, <script>alert('you have been pwned')</script>!
+
+but the contextual autoescaping in html/template
+
+  import template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+  ...
+  t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
+  err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
+
+produces safe, escaped HTML output
+
+  Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
+
+
+Contexts
+
+This package understands HTML, CSS, JavaScript, and URIs. It adds sanitizing
+functions to each simple action pipeline, so given the excerpt
+
+  <a href="/search?q={{.}}">{{.}}</a>
+
+At parse time each {{.}} is overwritten to add escaping functions as necessary.
+In this case it becomes
+
+  <a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>
+
+where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping
+functions.
+
+For these internal escaping functions, if an action pipeline evaluates to
+a nil interface value, it is treated as though it were an empty string.
+
+Errors
+
+See the documentation of ErrorCode for details.
+
+
+A fuller picture
+
+The rest of this package comment may be skipped on first reading; it includes
+details necessary to understand escaping contexts and error messages. Most users
+will not need to understand these details.
+
+
+Contexts
+
+Assuming {{.}} is `O'Reilly: How are <i>you</i>?`, the table below shows
+how {{.}} appears when used in the context to the left.
+
+  Context                          {{.}} After
+  {{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
+  <a title='{{.}}'>                O&#39;Reilly: How are you?
+  <a href="/{{.}}">                O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
+  <a href="?q={{.}}">              O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
+  <a onx='f("{{.}}")'>             O\x27Reilly: How are \x3ci\x3eyou...?
+  <a onx='f({{.}})'>               "O\x27Reilly: How are \x3ci\x3eyou...?"
+  <a onx='pattern = /{{.}}/;'>     O\x27Reilly: How are \x3ci\x3eyou...\x3f
+
+If used in an unsafe context, then the value might be filtered out:
+
+  Context                          {{.}} After
+  <a href="{{.}}">                 #ZgotmplZ
+
+since "O'Reilly:" is not an allowed protocol like "http:".
+
+
+If {{.}} is the innocuous word, `left`, then it can appear more widely,
+
+  Context                              {{.}} After
+  {{.}}                                left
+  <a title='{{.}}'>                    left
+  <a href='{{.}}'>                     left
+  <a href='/{{.}}'>                    left
+  <a href='?dir={{.}}'>                left
+  <a style="border-{{.}}: 4px">        left
+  <a style="align: {{.}}">             left
+  <a style="background: '{{.}}'>       left
+  <a style="background: url('{{.}}')>  left
+  <style>p.{{.}} {color:red}</style>   left
+
+Non-string values can be used in JavaScript contexts.
+If {{.}} is
+
+  struct{A,B string}{ "foo", "bar" }
+
+in the escaped template
+
+  <script>var pair = {{.}};</script>
+
+then the template output is
+
+  <script>var pair = {"A": "foo", "B": "bar"};</script>
+
+See package json to understand how non-string content is marshaled for
+embedding in JavaScript contexts.
+
+
+Typed Strings
+
+By default, this package assumes that all pipelines produce a plain text string.
+It adds escaping pipeline stages necessary to correctly and safely embed that
+plain text string in the appropriate context.
+
+When a data value is not plain text, you can make sure it is not over-escaped
+by marking it with its type.
+
+Types HTML, JS, URL, and others from content.go can carry safe content that is
+exempted from escaping.
+
+The template
+
+  Hello, {{.}}!
+
+can be invoked with
+
+  tmpl.Execute(out, template.HTML(`<b>World</b>`))
+
+to produce
+
+  Hello, <b>World</b>!
+
+instead of the
+
+  Hello, &lt;b&gt;World&lt;b&gt;!
+
+that would have been produced if {{.}} was a regular string.
+
+
+Security Model
+
+https://rawgit.com/mikesamuel/sanitized-jquery-templates/trunk/safetemplate.html#problem_definition defines "safe" as used by this package.
+
+This package assumes that template authors are trusted, that Execute's data
+parameter is not, and seeks to preserve the properties below in the face
+of untrusted data:
+
+Structure Preservation Property:
+"... when a template author writes an HTML tag in a safe templating language,
+the browser will interpret the corresponding portion of the output as a tag
+regardless of the values of untrusted data, and similarly for other structures
+such as attribute boundaries and JS and CSS string boundaries."
+
+Code Effect Property:
+"... only code specified by the template author should run as a result of
+injecting the template output into a page and all code specified by the
+template author should run as a result of the same."
+
+Least Surprise Property:
+"A developer (or code reviewer) familiar with HTML, CSS, and JavaScript, who
+knows that contextual autoescaping happens should be able to look at a {{.}}
+and correctly infer what sanitization happens."
+*/
+package template
diff --git a/tpl/internal/go_templates/htmltemplate/element_string.go b/tpl/internal/go_templates/htmltemplate/element_string.go
new file mode 100644 (file)
index 0000000..4573e08
--- /dev/null
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type element"; DO NOT EDIT.
+
+package template
+
+import "strconv"
+
+const _element_name = "elementNoneelementScriptelementStyleelementTextareaelementTitle"
+
+var _element_index = [...]uint8{0, 11, 24, 36, 51, 63}
+
+func (i element) String() string {
+       if i >= element(len(_element_index)-1) {
+               return "element(" + strconv.FormatInt(int64(i), 10) + ")"
+       }
+       return _element_name[_element_index[i]:_element_index[i+1]]
+}
diff --git a/tpl/internal/go_templates/htmltemplate/error.go b/tpl/internal/go_templates/htmltemplate/error.go
new file mode 100644 (file)
index 0000000..f8b3f1c
--- /dev/null
@@ -0,0 +1,234 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "fmt"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
+
+// Error describes a problem encountered during template Escaping.
+type Error struct {
+       // ErrorCode describes the kind of error.
+       ErrorCode ErrorCode
+       // Node is the node that caused the problem, if known.
+       // If not nil, it overrides Name and Line.
+       Node parse.Node
+       // Name is the name of the template in which the error was encountered.
+       Name string
+       // Line is the line number of the error in the template source or 0.
+       Line int
+       // Description is a human-readable description of the problem.
+       Description string
+}
+
+// ErrorCode is a code for a kind of error.
+type ErrorCode int
+
+// We define codes for each error that manifests while escaping templates, but
+// escaped templates may also fail at runtime.
+//
+// Output: "ZgotmplZ"
+// Example:
+//   <img src="{{.X}}">
+//   where {{.X}} evaluates to `javascript:...`
+// Discussion:
+//   "ZgotmplZ" is a special value that indicates that unsafe content reached a
+//   CSS or URL context at runtime. The output of the example will be
+//     <img src="#ZgotmplZ">
+//   If the data comes from a trusted source, use content types to exempt it
+//   from filtering: URL(`javascript:...`).
+const (
+       // OK indicates the lack of an error.
+       OK ErrorCode = iota
+
+       // ErrAmbigContext: "... appears in an ambiguous context within a URL"
+       // Example:
+       //   <a href="
+       //      {{if .C}}
+       //        /path/
+       //      {{else}}
+       //        /search?q=
+       //      {{end}}
+       //      {{.X}}
+       //   ">
+       // Discussion:
+       //   {{.X}} is in an ambiguous URL context since, depending on {{.C}},
+       //  it may be either a URL suffix or a query parameter.
+       //   Moving {{.X}} into the condition removes the ambiguity:
+       //   <a href="{{if .C}}/path/{{.X}}{{else}}/search?q={{.X}}">
+       ErrAmbigContext
+
+       // ErrBadHTML: "expected space, attr name, or end of tag, but got ...",
+       //   "... in unquoted attr", "... in attribute name"
+       // Example:
+       //   <a href = /search?q=foo>
+       //   <href=foo>
+       //   <form na<e=...>
+       //   <option selected<
+       // Discussion:
+       //   This is often due to a typo in an HTML element, but some runes
+       //   are banned in tag names, attribute names, and unquoted attribute
+       //   values because they can tickle parser ambiguities.
+       //   Quoting all attributes is the best policy.
+       ErrBadHTML
+
+       // ErrBranchEnd: "{{if}} branches end in different contexts"
+       // Example:
+       //   {{if .C}}<a href="{{end}}{{.X}}
+       // Discussion:
+       //   Package html/template statically examines each path through an
+       //   {{if}}, {{range}}, or {{with}} to escape any following pipelines.
+       //   The example is ambiguous since {{.X}} might be an HTML text node,
+       //   or a URL prefix in an HTML attribute. The context of {{.X}} is
+       //   used to figure out how to escape it, but that context depends on
+       //   the run-time value of {{.C}} which is not statically known.
+       //
+       //   The problem is usually something like missing quotes or angle
+       //   brackets, or can be avoided by refactoring to put the two contexts
+       //   into different branches of an if, range or with. If the problem
+       //   is in a {{range}} over a collection that should never be empty,
+       //   adding a dummy {{else}} can help.
+       ErrBranchEnd
+
+       // ErrEndContext: "... ends in a non-text context: ..."
+       // Examples:
+       //   <div
+       //   <div title="no close quote>
+       //   <script>f()
+       // Discussion:
+       //   Executed templates should produce a DocumentFragment of HTML.
+       //   Templates that end without closing tags will trigger this error.
+       //   Templates that should not be used in an HTML context or that
+       //   produce incomplete Fragments should not be executed directly.
+       //
+       //   {{define "main"}} <script>{{template "helper"}}</script> {{end}}
+       //   {{define "helper"}} document.write(' <div title=" ') {{end}}
+       //
+       //   "helper" does not produce a valid document fragment, so should
+       //   not be Executed directly.
+       ErrEndContext
+
+       // ErrNoSuchTemplate: "no such template ..."
+       // Examples:
+       //   {{define "main"}}<div {{template "attrs"}}>{{end}}
+       //   {{define "attrs"}}href="{{.URL}}"{{end}}
+       // Discussion:
+       //   Package html/template looks through template calls to compute the
+       //   context.
+       //   Here the {{.URL}} in "attrs" must be treated as a URL when called
+       //   from "main", but you will get this error if "attrs" is not defined
+       //   when "main" is parsed.
+       ErrNoSuchTemplate
+
+       // ErrOutputContext: "cannot compute output context for template ..."
+       // Examples:
+       //   {{define "t"}}{{if .T}}{{template "t" .T}}{{end}}{{.H}}",{{end}}
+       // Discussion:
+       //   A recursive template does not end in the same context in which it
+       //   starts, and a reliable output context cannot be computed.
+       //   Look for typos in the named template.
+       //   If the template should not be called in the named start context,
+       //   look for calls to that template in unexpected contexts.
+       //   Maybe refactor recursive templates to not be recursive.
+       ErrOutputContext
+
+       // ErrPartialCharset: "unfinished JS regexp charset in ..."
+       // Example:
+       //     <script>var pattern = /foo[{{.Chars}}]/</script>
+       // Discussion:
+       //   Package html/template does not support interpolation into regular
+       //   expression literal character sets.
+       ErrPartialCharset
+
+       // ErrPartialEscape: "unfinished escape sequence in ..."
+       // Example:
+       //   <script>alert("\{{.X}}")</script>
+       // Discussion:
+       //   Package html/template does not support actions following a
+       //   backslash.
+       //   This is usually an error and there are better solutions; for
+       //   example
+       //     <script>alert("{{.X}}")</script>
+       //   should work, and if {{.X}} is a partial escape sequence such as
+       //   "xA0", mark the whole sequence as safe content: JSStr(`\xA0`)
+       ErrPartialEscape
+
+       // ErrRangeLoopReentry: "on range loop re-entry: ..."
+       // Example:
+       //   <script>var x = [{{range .}}'{{.}},{{end}}]</script>
+       // Discussion:
+       //   If an iteration through a range would cause it to end in a
+       //   different context than an earlier pass, there is no single context.
+       //   In the example, there is missing a quote, so it is not clear
+       //   whether {{.}} is meant to be inside a JS string or in a JS value
+       //   context. The second iteration would produce something like
+       //
+       //     <script>var x = ['firstValue,'secondValue]</script>
+       ErrRangeLoopReentry
+
+       // ErrSlashAmbig: '/' could start a division or regexp.
+       // Example:
+       //   <script>
+       //     {{if .C}}var x = 1{{end}}
+       //     /-{{.N}}/i.test(x) ? doThis : doThat();
+       //   </script>
+       // Discussion:
+       //   The example above could produce `var x = 1/-2/i.test(s)...`
+       //   in which the first '/' is a mathematical division operator or it
+       //   could produce `/-2/i.test(s)` in which the first '/' starts a
+       //   regexp literal.
+       //   Look for missing semicolons inside branches, and maybe add
+       //   parentheses to make it clear which interpretation you intend.
+       ErrSlashAmbig
+
+       // ErrPredefinedEscaper: "predefined escaper ... disallowed in template"
+       // Example:
+       //   <div class={{. | html}}>Hello<div>
+       // Discussion:
+       //   Package html/template already contextually escapes all pipelines to
+       //   produce HTML output safe against code injection. Manually escaping
+       //   pipeline output using the predefined escapers "html" or "urlquery" is
+       //   unnecessary, and may affect the correctness or safety of the escaped
+       //   pipeline output in Go 1.8 and earlier.
+       //
+       //   In most cases, such as the given example, this error can be resolved by
+       //   simply removing the predefined escaper from the pipeline and letting the
+       //   contextual autoescaper handle the escaping of the pipeline. In other
+       //   instances, where the predefined escaper occurs in the middle of a
+       //   pipeline where subsequent commands expect escaped input, e.g.
+       //     {{.X | html | makeALink}}
+       //   where makeALink does
+       //     return `<a href="`+input+`">link</a>`
+       //   consider refactoring the surrounding template to make use of the
+       //   contextual autoescaper, i.e.
+       //     <a href="{{.X}}">link</a>
+       //
+       //   To ease migration to Go 1.9 and beyond, "html" and "urlquery" will
+       //   continue to be allowed as the last command in a pipeline. However, if the
+       //   pipeline occurs in an unquoted attribute value context, "html" is
+       //   disallowed. Avoid using "html" and "urlquery" entirely in new templates.
+       ErrPredefinedEscaper
+)
+
+func (e *Error) Error() string {
+       switch {
+       case e.Node != nil:
+               loc, _ := (*parse.Tree)(nil).ErrorContext(e.Node)
+               return fmt.Sprintf("html/template:%s: %s", loc, e.Description)
+       case e.Line != 0:
+               return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description)
+       case e.Name != "":
+               return fmt.Sprintf("html/template:%s: %s", e.Name, e.Description)
+       }
+       return "html/template: " + e.Description
+}
+
+// errorf creates an error given a format string f and args.
+// The template Name still needs to be supplied.
+func errorf(k ErrorCode, node parse.Node, line int, f string, args ...interface{}) *Error {
+       return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
+}
diff --git a/tpl/internal/go_templates/htmltemplate/escape.go b/tpl/internal/go_templates/htmltemplate/escape.go
new file mode 100644 (file)
index 0000000..53b8175
--- /dev/null
@@ -0,0 +1,891 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "fmt"
+       "html"
+       "io"
+
+       template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
+
+// escapeTemplate rewrites the named template, which must be
+// associated with t, to guarantee that the output of any of the named
+// templates is properly escaped. If no error is returned, then the named templates have
+// been modified. Otherwise the named templates have been rendered
+// unusable.
+func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
+       c, _ := tmpl.esc.escapeTree(context{}, node, name, 0)
+       var err error
+       if c.err != nil {
+               err, c.err.Name = c.err, name
+       } else if c.state != stateText {
+               err = &Error{ErrEndContext, nil, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)}
+       }
+       if err != nil {
+               // Prevent execution of unsafe templates.
+               if t := tmpl.set[name]; t != nil {
+                       t.escapeErr = err
+                       t.text.Tree = nil
+                       t.Tree = nil
+               }
+               return err
+       }
+       tmpl.esc.commit()
+       if t := tmpl.set[name]; t != nil {
+               t.escapeErr = escapeOK
+               t.Tree = t.text.Tree
+       }
+       return nil
+}
+
+// evalArgs formats the list of arguments into a string. It is equivalent to
+// fmt.Sprint(args...), except that it deferences all pointers.
+func evalArgs(args ...interface{}) string {
+       // Optimization for simple common case of a single string argument.
+       if len(args) == 1 {
+               if s, ok := args[0].(string); ok {
+                       return s
+               }
+       }
+       for i, arg := range args {
+               args[i] = indirectToStringerOrError(arg)
+       }
+       return fmt.Sprint(args...)
+}
+
+// funcMap maps command names to functions that render their inputs safe.
+var funcMap = template.FuncMap{
+       "_html_template_attrescaper":     attrEscaper,
+       "_html_template_commentescaper":  commentEscaper,
+       "_html_template_cssescaper":      cssEscaper,
+       "_html_template_cssvaluefilter":  cssValueFilter,
+       "_html_template_htmlnamefilter":  htmlNameFilter,
+       "_html_template_htmlescaper":     htmlEscaper,
+       "_html_template_jsregexpescaper": jsRegexpEscaper,
+       "_html_template_jsstrescaper":    jsStrEscaper,
+       "_html_template_jsvalescaper":    jsValEscaper,
+       "_html_template_nospaceescaper":  htmlNospaceEscaper,
+       "_html_template_rcdataescaper":   rcdataEscaper,
+       "_html_template_srcsetescaper":   srcsetFilterAndEscaper,
+       "_html_template_urlescaper":      urlEscaper,
+       "_html_template_urlfilter":       urlFilter,
+       "_html_template_urlnormalizer":   urlNormalizer,
+       "_eval_args_":                    evalArgs,
+}
+
+// escaper collects type inferences about templates and changes needed to make
+// templates injection safe.
+type escaper struct {
+       // ns is the nameSpace that this escaper is associated with.
+       ns *nameSpace
+       // output[templateName] is the output context for a templateName that
+       // has been mangled to include its input context.
+       output map[string]context
+       // derived[c.mangle(name)] maps to a template derived from the template
+       // named name templateName for the start context c.
+       derived map[string]*template.Template
+       // called[templateName] is a set of called mangled template names.
+       called map[string]bool
+       // xxxNodeEdits are the accumulated edits to apply during commit.
+       // Such edits are not applied immediately in case a template set
+       // executes a given template in different escaping contexts.
+       actionNodeEdits   map[*parse.ActionNode][]string
+       templateNodeEdits map[*parse.TemplateNode]string
+       textNodeEdits     map[*parse.TextNode][]byte
+}
+
+// makeEscaper creates a blank escaper for the given set.
+func makeEscaper(n *nameSpace) escaper {
+       return escaper{
+               n,
+               map[string]context{},
+               map[string]*template.Template{},
+               map[string]bool{},
+               map[*parse.ActionNode][]string{},
+               map[*parse.TemplateNode]string{},
+               map[*parse.TextNode][]byte{},
+       }
+}
+
+// filterFailsafe is an innocuous word that is emitted in place of unsafe values
+// by sanitizer functions. It is not a keyword in any programming language,
+// contains no special characters, is not empty, and when it appears in output
+// it is distinct enough that a developer can find the source of the problem
+// via a search engine.
+const filterFailsafe = "ZgotmplZ"
+
+// escape escapes a template node.
+func (e *escaper) escape(c context, n parse.Node) context {
+       switch n := n.(type) {
+       case *parse.ActionNode:
+               return e.escapeAction(c, n)
+       case *parse.IfNode:
+               return e.escapeBranch(c, &n.BranchNode, "if")
+       case *parse.ListNode:
+               return e.escapeList(c, n)
+       case *parse.RangeNode:
+               return e.escapeBranch(c, &n.BranchNode, "range")
+       case *parse.TemplateNode:
+               return e.escapeTemplate(c, n)
+       case *parse.TextNode:
+               return e.escapeText(c, n)
+       case *parse.WithNode:
+               return e.escapeBranch(c, &n.BranchNode, "with")
+       }
+       panic("escaping " + n.String() + " is unimplemented")
+}
+
+// escapeAction escapes an action template node.
+func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
+       if len(n.Pipe.Decl) != 0 {
+               // A local variable assignment, not an interpolation.
+               return c
+       }
+       c = nudge(c)
+       // Check for disallowed use of predefined escapers in the pipeline.
+       for pos, idNode := range n.Pipe.Cmds {
+               node, ok := idNode.Args[0].(*parse.IdentifierNode)
+               if !ok {
+                       // A predefined escaper "esc" will never be found as an identifier in a
+                       // Chain or Field node, since:
+                       // - "esc.x ..." is invalid, since predefined escapers return strings, and
+                       //   strings do not have methods, keys or fields.
+                       // - "... .esc" is invalid, since predefined escapers are global functions,
+                       //   not methods or fields of any types.
+                       // Therefore, it is safe to ignore these two node types.
+                       continue
+               }
+               ident := node.Ident
+               if _, ok := predefinedEscapers[ident]; ok {
+                       if pos < len(n.Pipe.Cmds)-1 ||
+                               c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" {
+                               return context{
+                                       state: stateError,
+                                       err:   errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident),
+                               }
+                       }
+               }
+       }
+       s := make([]string, 0, 3)
+       switch c.state {
+       case stateError:
+               return c
+       case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
+               switch c.urlPart {
+               case urlPartNone:
+                       s = append(s, "_html_template_urlfilter")
+                       fallthrough
+               case urlPartPreQuery:
+                       switch c.state {
+                       case stateCSSDqStr, stateCSSSqStr:
+                               s = append(s, "_html_template_cssescaper")
+                       default:
+                               s = append(s, "_html_template_urlnormalizer")
+                       }
+               case urlPartQueryOrFrag:
+                       s = append(s, "_html_template_urlescaper")
+               case urlPartUnknown:
+                       return context{
+                               state: stateError,
+                               err:   errorf(ErrAmbigContext, n, n.Line, "%s appears in an ambiguous context within a URL", n),
+                       }
+               default:
+                       panic(c.urlPart.String())
+               }
+       case stateJS:
+               s = append(s, "_html_template_jsvalescaper")
+               // A slash after a value starts a div operator.
+               c.jsCtx = jsCtxDivOp
+       case stateJSDqStr, stateJSSqStr:
+               s = append(s, "_html_template_jsstrescaper")
+       case stateJSRegexp:
+               s = append(s, "_html_template_jsregexpescaper")
+       case stateCSS:
+               s = append(s, "_html_template_cssvaluefilter")
+       case stateText:
+               s = append(s, "_html_template_htmlescaper")
+       case stateRCDATA:
+               s = append(s, "_html_template_rcdataescaper")
+       case stateAttr:
+               // Handled below in delim check.
+       case stateAttrName, stateTag:
+               c.state = stateAttrName
+               s = append(s, "_html_template_htmlnamefilter")
+       case stateSrcset:
+               s = append(s, "_html_template_srcsetescaper")
+       default:
+               if isComment(c.state) {
+                       s = append(s, "_html_template_commentescaper")
+               } else {
+                       panic("unexpected state " + c.state.String())
+               }
+       }
+       switch c.delim {
+       case delimNone:
+               // No extra-escaping needed for raw text content.
+       case delimSpaceOrTagEnd:
+               s = append(s, "_html_template_nospaceescaper")
+       default:
+               s = append(s, "_html_template_attrescaper")
+       }
+       e.editActionNode(n, s)
+       return c
+}
+
+// ensurePipelineContains ensures that the pipeline ends with the commands with
+// the identifiers in s in order. If the pipeline ends with a predefined escaper
+// (i.e. "html" or "urlquery"), merge it with the identifiers in s.
+func ensurePipelineContains(p *parse.PipeNode, s []string) {
+       if len(s) == 0 {
+               // Do not rewrite pipeline if we have no escapers to insert.
+               return
+       }
+       // Precondition: p.Cmds contains at most one predefined escaper and the
+       // escaper will be present at p.Cmds[len(p.Cmds)-1]. This precondition is
+       // always true because of the checks in escapeAction.
+       pipelineLen := len(p.Cmds)
+       if pipelineLen > 0 {
+               lastCmd := p.Cmds[pipelineLen-1]
+               if idNode, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok {
+                       if esc := idNode.Ident; predefinedEscapers[esc] {
+                               // Pipeline ends with a predefined escaper.
+                               if len(p.Cmds) == 1 && len(lastCmd.Args) > 1 {
+                                       // Special case: pipeline is of the form {{ esc arg1 arg2 ... argN }},
+                                       // where esc is the predefined escaper, and arg1...argN are its arguments.
+                                       // Convert this into the equivalent form
+                                       // {{ _eval_args_ arg1 arg2 ... argN | esc }}, so that esc can be easily
+                                       // merged with the escapers in s.
+                                       lastCmd.Args[0] = parse.NewIdentifier("_eval_args_").SetTree(nil).SetPos(lastCmd.Args[0].Position())
+                                       p.Cmds = appendCmd(p.Cmds, newIdentCmd(esc, p.Position()))
+                                       pipelineLen++
+                               }
+                               // If any of the commands in s that we are about to insert is equivalent
+                               // to the predefined escaper, use the predefined escaper instead.
+                               dup := false
+                               for i, escaper := range s {
+                                       if escFnsEq(esc, escaper) {
+                                               s[i] = idNode.Ident
+                                               dup = true
+                                       }
+                               }
+                               if dup {
+                                       // The predefined escaper will already be inserted along with the
+                                       // escapers in s, so do not copy it to the rewritten pipeline.
+                                       pipelineLen--
+                               }
+                       }
+               }
+       }
+       // Rewrite the pipeline, creating the escapers in s at the end of the pipeline.
+       newCmds := make([]*parse.CommandNode, pipelineLen, pipelineLen+len(s))
+       insertedIdents := make(map[string]bool)
+       for i := 0; i < pipelineLen; i++ {
+               cmd := p.Cmds[i]
+               newCmds[i] = cmd
+               if idNode, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
+                       insertedIdents[normalizeEscFn(idNode.Ident)] = true
+               }
+       }
+       for _, name := range s {
+               if !insertedIdents[normalizeEscFn(name)] {
+                       // When two templates share an underlying parse tree via the use of
+                       // AddParseTree and one template is executed after the other, this check
+                       // ensures that escapers that were already inserted into the pipeline on
+                       // the first escaping pass do not get inserted again.
+                       newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
+               }
+       }
+       p.Cmds = newCmds
+}
+
+// predefinedEscapers contains template predefined escapers that are equivalent
+// to some contextual escapers. Keep in sync with equivEscapers.
+var predefinedEscapers = map[string]bool{
+       "html":     true,
+       "urlquery": true,
+}
+
+// equivEscapers matches contextual escapers to equivalent predefined
+// template escapers.
+var equivEscapers = map[string]string{
+       // The following pairs of HTML escapers provide equivalent security
+       // guarantees, since they all escape '\000', '\'', '"', '&', '<', and '>'.
+       "_html_template_attrescaper":   "html",
+       "_html_template_htmlescaper":   "html",
+       "_html_template_rcdataescaper": "html",
+       // These two URL escapers produce URLs safe for embedding in a URL query by
+       // percent-encoding all the reserved characters specified in RFC 3986 Section
+       // 2.2
+       "_html_template_urlescaper": "urlquery",
+       // These two functions are not actually equivalent; urlquery is stricter as it
+       // escapes reserved characters (e.g. '#'), while _html_template_urlnormalizer
+       // does not. It is therefore only safe to replace _html_template_urlnormalizer
+       // with urlquery (this happens in ensurePipelineContains), but not the otherI've
+       // way around. We keep this entry around to preserve the behavior of templates
+       // written before Go 1.9, which might depend on this substitution taking place.
+       "_html_template_urlnormalizer": "urlquery",
+}
+
+// escFnsEq reports whether the two escaping functions are equivalent.
+func escFnsEq(a, b string) bool {
+       return normalizeEscFn(a) == normalizeEscFn(b)
+}
+
+// normalizeEscFn(a) is equal to normalizeEscFn(b) for any pair of names of
+// escaper functions a and b that are equivalent.
+func normalizeEscFn(e string) string {
+       if norm := equivEscapers[e]; norm != "" {
+               return norm
+       }
+       return e
+}
+
+// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
+// for all x.
+var redundantFuncs = map[string]map[string]bool{
+       "_html_template_commentescaper": {
+               "_html_template_attrescaper":    true,
+               "_html_template_nospaceescaper": true,
+               "_html_template_htmlescaper":    true,
+       },
+       "_html_template_cssescaper": {
+               "_html_template_attrescaper": true,
+       },
+       "_html_template_jsregexpescaper": {
+               "_html_template_attrescaper": true,
+       },
+       "_html_template_jsstrescaper": {
+               "_html_template_attrescaper": true,
+       },
+       "_html_template_urlescaper": {
+               "_html_template_urlnormalizer": true,
+       },
+}
+
+// appendCmd appends the given command to the end of the command pipeline
+// unless it is redundant with the last command.
+func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode {
+       if n := len(cmds); n != 0 {
+               last, okLast := cmds[n-1].Args[0].(*parse.IdentifierNode)
+               next, okNext := cmd.Args[0].(*parse.IdentifierNode)
+               if okLast && okNext && redundantFuncs[last.Ident][next.Ident] {
+                       return cmds
+               }
+       }
+       return append(cmds, cmd)
+}
+
+// newIdentCmd produces a command containing a single identifier node.
+func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode {
+       return &parse.CommandNode{
+               NodeType: parse.NodeCommand,
+               Args:     []parse.Node{parse.NewIdentifier(identifier).SetTree(nil).SetPos(pos)}, // TODO: SetTree.
+       }
+}
+
+// nudge returns the context that would result from following empty string
+// transitions from the input context.
+// For example, parsing:
+//     `<a href=`
+// will end in context{stateBeforeValue, attrURL}, but parsing one extra rune:
+//     `<a href=x`
+// will end in context{stateURL, delimSpaceOrTagEnd, ...}.
+// There are two transitions that happen when the 'x' is seen:
+// (1) Transition from a before-value state to a start-of-value state without
+//     consuming any character.
+// (2) Consume 'x' and transition past the first value character.
+// In this case, nudging produces the context after (1) happens.
+func nudge(c context) context {
+       switch c.state {
+       case stateTag:
+               // In `<foo {{.}}`, the action should emit an attribute.
+               c.state = stateAttrName
+       case stateBeforeValue:
+               // In `<foo bar={{.}}`, the action is an undelimited value.
+               c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone
+       case stateAfterName:
+               // In `<foo bar {{.}}`, the action is an attribute name.
+               c.state, c.attr = stateAttrName, attrNone
+       }
+       return c
+}
+
+// join joins the two contexts of a branch template node. The result is an
+// error context if either of the input contexts are error contexts, or if the
+// input contexts differ.
+func join(a, b context, node parse.Node, nodeName string) context {
+       if a.state == stateError {
+               return a
+       }
+       if b.state == stateError {
+               return b
+       }
+       if a.eq(b) {
+               return a
+       }
+
+       c := a
+       c.urlPart = b.urlPart
+       if c.eq(b) {
+               // The contexts differ only by urlPart.
+               c.urlPart = urlPartUnknown
+               return c
+       }
+
+       c = a
+       c.jsCtx = b.jsCtx
+       if c.eq(b) {
+               // The contexts differ only by jsCtx.
+               c.jsCtx = jsCtxUnknown
+               return c
+       }
+
+       // Allow a nudged context to join with an unnudged one.
+       // This means that
+       //   <p title={{if .C}}{{.}}{{end}}
+       // ends in an unquoted value state even though the else branch
+       // ends in stateBeforeValue.
+       if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) {
+               if e := join(c, d, node, nodeName); e.state != stateError {
+                       return e
+               }
+       }
+
+       return context{
+               state: stateError,
+               err:   errorf(ErrBranchEnd, node, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b),
+       }
+}
+
+// escapeBranch escapes a branch template node: "if", "range" and "with".
+func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
+       c0 := e.escapeList(c, n.List)
+       if nodeName == "range" && c0.state != stateError {
+               // The "true" branch of a "range" node can execute multiple times.
+               // We check that executing n.List once results in the same context
+               // as executing n.List twice.
+               c1, _ := e.escapeListConditionally(c0, n.List, nil)
+               c0 = join(c0, c1, n, nodeName)
+               if c0.state == stateError {
+                       // Make clear that this is a problem on loop re-entry
+                       // since developers tend to overlook that branch when
+                       // debugging templates.
+                       c0.err.Line = n.Line
+                       c0.err.Description = "on range loop re-entry: " + c0.err.Description
+                       return c0
+               }
+       }
+       c1 := e.escapeList(c, n.ElseList)
+       return join(c0, c1, n, nodeName)
+}
+
+// escapeList escapes a list template node.
+func (e *escaper) escapeList(c context, n *parse.ListNode) context {
+       if n == nil {
+               return c
+       }
+       for _, m := range n.Nodes {
+               c = e.escape(c, m)
+       }
+       return c
+}
+
+// escapeListConditionally escapes a list node but only preserves edits and
+// inferences in e if the inferences and output context satisfy filter.
+// It returns the best guess at an output context, and the result of the filter
+// which is the same as whether e was updated.
+func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
+       e1 := makeEscaper(e.ns)
+       // Make type inferences available to f.
+       for k, v := range e.output {
+               e1.output[k] = v
+       }
+       c = e1.escapeList(c, n)
+       ok := filter != nil && filter(&e1, c)
+       if ok {
+               // Copy inferences and edits from e1 back into e.
+               for k, v := range e1.output {
+                       e.output[k] = v
+               }
+               for k, v := range e1.derived {
+                       e.derived[k] = v
+               }
+               for k, v := range e1.called {
+                       e.called[k] = v
+               }
+               for k, v := range e1.actionNodeEdits {
+                       e.editActionNode(k, v)
+               }
+               for k, v := range e1.templateNodeEdits {
+                       e.editTemplateNode(k, v)
+               }
+               for k, v := range e1.textNodeEdits {
+                       e.editTextNode(k, v)
+               }
+       }
+       return c, ok
+}
+
+// escapeTemplate escapes a {{template}} call node.
+func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context {
+       c, name := e.escapeTree(c, n, n.Name, n.Line)
+       if name != n.Name {
+               e.editTemplateNode(n, name)
+       }
+       return c
+}
+
+// escapeTree escapes the named template starting in the given context as
+// necessary and returns its output context.
+func (e *escaper) escapeTree(c context, node parse.Node, name string, line int) (context, string) {
+       // Mangle the template name with the input context to produce a reliable
+       // identifier.
+       dname := c.mangle(name)
+       e.called[dname] = true
+       if out, ok := e.output[dname]; ok {
+               // Already escaped.
+               return out, dname
+       }
+       t := e.template(name)
+       if t == nil {
+               // Two cases: The template exists but is empty, or has never been mentioned at
+               // all. Distinguish the cases in the error messages.
+               if e.ns.set[name] != nil {
+                       return context{
+                               state: stateError,
+                               err:   errorf(ErrNoSuchTemplate, node, line, "%q is an incomplete or empty template", name),
+                       }, dname
+               }
+               return context{
+                       state: stateError,
+                       err:   errorf(ErrNoSuchTemplate, node, line, "no such template %q", name),
+               }, dname
+       }
+       if dname != name {
+               // Use any template derived during an earlier call to escapeTemplate
+               // with different top level templates, or clone if necessary.
+               dt := e.template(dname)
+               if dt == nil {
+                       dt = template.New(dname)
+                       dt.Tree = &parse.Tree{Name: dname, Root: t.Root.CopyList()}
+                       e.derived[dname] = dt
+               }
+               t = dt
+       }
+       return e.computeOutCtx(c, t), dname
+}
+
+// computeOutCtx takes a template and its start context and computes the output
+// context while storing any inferences in e.
+func (e *escaper) computeOutCtx(c context, t *template.Template) context {
+       // Propagate context over the body.
+       c1, ok := e.escapeTemplateBody(c, t)
+       if !ok {
+               // Look for a fixed point by assuming c1 as the output context.
+               if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 {
+                       c1, ok = c2, true
+               }
+               // Use c1 as the error context if neither assumption worked.
+       }
+       if !ok && c1.state != stateError {
+               return context{
+                       state: stateError,
+                       err:   errorf(ErrOutputContext, t.Tree.Root, 0, "cannot compute output context for template %s", t.Name()),
+               }
+       }
+       return c1
+}
+
+// escapeTemplateBody escapes the given template assuming the given output
+// context, and returns the best guess at the output context and whether the
+// assumption was correct.
+func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) {
+       filter := func(e1 *escaper, c1 context) bool {
+               if c1.state == stateError {
+                       // Do not update the input escaper, e.
+                       return false
+               }
+               if !e1.called[t.Name()] {
+                       // If t is not recursively called, then c1 is an
+                       // accurate output context.
+                       return true
+               }
+               // c1 is accurate if it matches our assumed output context.
+               return c.eq(c1)
+       }
+       // We need to assume an output context so that recursive template calls
+       // take the fast path out of escapeTree instead of infinitely recursing.
+       // Naively assuming that the input context is the same as the output
+       // works >90% of the time.
+       e.output[t.Name()] = c
+       return e.escapeListConditionally(c, t.Tree.Root, filter)
+}
+
+// delimEnds maps each delim to a string of characters that terminate it.
+var delimEnds = [...]string{
+       delimDoubleQuote: `"`,
+       delimSingleQuote: "'",
+       // Determined empirically by running the below in various browsers.
+       // var div = document.createElement("DIV");
+       // for (var i = 0; i < 0x10000; ++i) {
+       //   div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>";
+       //   if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0)
+       //     document.write("<p>U+" + i.toString(16));
+       // }
+       delimSpaceOrTagEnd: " \t\n\f\r>",
+}
+
+var doctypeBytes = []byte("<!DOCTYPE")
+
+// escapeText escapes a text template node.
+func (e *escaper) escapeText(c context, n *parse.TextNode) context {
+       s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
+       for i != len(s) {
+               c1, nread := contextAfterText(c, s[i:])
+               i1 := i + nread
+               if c.state == stateText || c.state == stateRCDATA {
+                       end := i1
+                       if c1.state != c.state {
+                               for j := end - 1; j >= i; j-- {
+                                       if s[j] == '<' {
+                                               end = j
+                                               break
+                                       }
+                               }
+                       }
+                       for j := i; j < end; j++ {
+                               if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) {
+                                       b.Write(s[written:j])
+                                       b.WriteString("&lt;")
+                                       written = j + 1
+                               }
+                       }
+               } else if isComment(c.state) && c.delim == delimNone {
+                       switch c.state {
+                       case stateJSBlockCmt:
+                               // https://es5.github.com/#x7.4:
+                               // "Comments behave like white space and are
+                               // discarded except that, if a MultiLineComment
+                               // contains a line terminator character, then
+                               // the entire comment is considered to be a
+                               // LineTerminator for purposes of parsing by
+                               // the syntactic grammar."
+                               if bytes.ContainsAny(s[written:i1], "\n\r\u2028\u2029") {
+                                       b.WriteByte('\n')
+                               } else {
+                                       b.WriteByte(' ')
+                               }
+                       case stateCSSBlockCmt:
+                               b.WriteByte(' ')
+                       }
+                       written = i1
+               }
+               if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone {
+                       // Preserve the portion between written and the comment start.
+                       cs := i1 - 2
+                       if c1.state == stateHTMLCmt {
+                               // "<!--" instead of "/*" or "//"
+                               cs -= 2
+                       }
+                       b.Write(s[written:cs])
+                       written = i1
+               }
+               if i == i1 && c.state == c1.state {
+                       panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:]))
+               }
+               c, i = c1, i1
+       }
+
+       if written != 0 && c.state != stateError {
+               if !isComment(c.state) || c.delim != delimNone {
+                       b.Write(n.Text[written:])
+               }
+               e.editTextNode(n, b.Bytes())
+       }
+       return c
+}
+
+// contextAfterText starts in context c, consumes some tokens from the front of
+// s, then returns the context after those tokens and the unprocessed suffix.
+func contextAfterText(c context, s []byte) (context, int) {
+       if c.delim == delimNone {
+               c1, i := tSpecialTagEnd(c, s)
+               if i == 0 {
+                       // A special end tag (`</script>`) has been seen and
+                       // all content preceding it has been consumed.
+                       return c1, 0
+               }
+               // Consider all content up to any end tag.
+               return transitionFunc[c.state](c, s[:i])
+       }
+
+       // We are at the beginning of an attribute value.
+
+       i := bytes.IndexAny(s, delimEnds[c.delim])
+       if i == -1 {
+               i = len(s)
+       }
+       if c.delim == delimSpaceOrTagEnd {
+               // https://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state
+               // lists the runes below as error characters.
+               // Error out because HTML parsers may differ on whether
+               // "<a id= onclick=f("     ends inside id's or onclick's value,
+               // "<a class=`foo "        ends inside a value,
+               // "<a style=font:'Arial'" needs open-quote fixup.
+               // IE treats '`' as a quotation character.
+               if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 {
+                       return context{
+                               state: stateError,
+                               err:   errorf(ErrBadHTML, nil, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]),
+                       }, len(s)
+               }
+       }
+       if i == len(s) {
+               // Remain inside the attribute.
+               // Decode the value so non-HTML rules can easily handle
+               //     <button onclick="alert(&quot;Hi!&quot;)">
+               // without having to entity decode token boundaries.
+               for u := []byte(html.UnescapeString(string(s))); len(u) != 0; {
+                       c1, i1 := transitionFunc[c.state](c, u)
+                       c, u = c1, u[i1:]
+               }
+               return c, len(s)
+       }
+
+       element := c.element
+
+       // If this is a non-JS "type" attribute inside "script" tag, do not treat the contents as JS.
+       if c.state == stateAttr && c.element == elementScript && c.attr == attrScriptType && !isJSType(string(s[:i])) {
+               element = elementNone
+       }
+
+       if c.delim != delimSpaceOrTagEnd {
+               // Consume any quote.
+               i++
+       }
+       // On exiting an attribute, we discard all state information
+       // except the state and element.
+       return context{state: stateTag, element: element}, i
+}
+
+// editActionNode records a change to an action pipeline for later commit.
+func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) {
+       if _, ok := e.actionNodeEdits[n]; ok {
+               panic(fmt.Sprintf("node %s shared between templates", n))
+       }
+       e.actionNodeEdits[n] = cmds
+}
+
+// editTemplateNode records a change to a {{template}} callee for later commit.
+func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) {
+       if _, ok := e.templateNodeEdits[n]; ok {
+               panic(fmt.Sprintf("node %s shared between templates", n))
+       }
+       e.templateNodeEdits[n] = callee
+}
+
+// editTextNode records a change to a text node for later commit.
+func (e *escaper) editTextNode(n *parse.TextNode, text []byte) {
+       if _, ok := e.textNodeEdits[n]; ok {
+               panic(fmt.Sprintf("node %s shared between templates", n))
+       }
+       e.textNodeEdits[n] = text
+}
+
+// commit applies changes to actions and template calls needed to contextually
+// autoescape content and adds any derived templates to the set.
+func (e *escaper) commit() {
+       for name := range e.output {
+               e.template(name).Funcs(funcMap)
+       }
+       // Any template from the name space associated with this escaper can be used
+       // to add derived templates to the underlying text/template name space.
+       tmpl := e.arbitraryTemplate()
+       for _, t := range e.derived {
+               if _, err := tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
+                       panic("error adding derived template")
+               }
+       }
+       for n, s := range e.actionNodeEdits {
+               ensurePipelineContains(n.Pipe, s)
+       }
+       for n, name := range e.templateNodeEdits {
+               n.Name = name
+       }
+       for n, s := range e.textNodeEdits {
+               n.Text = s
+       }
+       // Reset state that is specific to this commit so that the same changes are
+       // not re-applied to the template on subsequent calls to commit.
+       e.called = make(map[string]bool)
+       e.actionNodeEdits = make(map[*parse.ActionNode][]string)
+       e.templateNodeEdits = make(map[*parse.TemplateNode]string)
+       e.textNodeEdits = make(map[*parse.TextNode][]byte)
+}
+
+// template returns the named template given a mangled template name.
+func (e *escaper) template(name string) *template.Template {
+       // Any template from the name space associated with this escaper can be used
+       // to look up templates in the underlying text/template name space.
+       t := e.arbitraryTemplate().text.Lookup(name)
+       if t == nil {
+               t = e.derived[name]
+       }
+       return t
+}
+
+// arbitraryTemplate returns an arbitrary template from the name space
+// associated with e and panics if no templates are found.
+func (e *escaper) arbitraryTemplate() *Template {
+       for _, t := range e.ns.set {
+               return t
+       }
+       panic("no templates in name space")
+}
+
+// Forwarding functions so that clients need only import this package
+// to reach the general escaping functions of text/template.
+
+// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
+func HTMLEscape(w io.Writer, b []byte) {
+       template.HTMLEscape(w, b)
+}
+
+// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
+func HTMLEscapeString(s string) string {
+       return template.HTMLEscapeString(s)
+}
+
+// HTMLEscaper returns the escaped HTML equivalent of the textual
+// representation of its arguments.
+func HTMLEscaper(args ...interface{}) string {
+       return template.HTMLEscaper(args...)
+}
+
+// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
+func JSEscape(w io.Writer, b []byte) {
+       template.JSEscape(w, b)
+}
+
+// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
+func JSEscapeString(s string) string {
+       return template.JSEscapeString(s)
+}
+
+// JSEscaper returns the escaped JavaScript equivalent of the textual
+// representation of its arguments.
+func JSEscaper(args ...interface{}) string {
+       return template.JSEscaper(args...)
+}
+
+// URLQueryEscaper returns the escaped value of the textual representation of
+// its arguments in a form suitable for embedding in a URL query.
+func URLQueryEscaper(args ...interface{}) string {
+       return template.URLQueryEscaper(args...)
+}
diff --git a/tpl/internal/go_templates/htmltemplate/escape_test.go b/tpl/internal/go_templates/htmltemplate/escape_test.go
new file mode 100644 (file)
index 0000000..9e9db78
--- /dev/null
@@ -0,0 +1,1973 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "bytes"
+       "encoding/json"
+       "fmt"
+       htmltemplate "html/template"
+       "os"
+       "strings"
+       "testing"
+
+       template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
+
+type badMarshaler struct{}
+
+func (x *badMarshaler) MarshalJSON() ([]byte, error) {
+       // Keys in valid JSON must be double quoted as must all strings.
+       return []byte("{ foo: 'not quite valid JSON' }"), nil
+}
+
+type goodMarshaler struct{}
+
+func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
+       return []byte(`{ "<foo>": "O'Reilly" }`), nil
+}
+
+func TestEscape(t *testing.T) {
+       data := struct {
+               F, T    bool
+               C, G, H string
+               A, E    []string
+               B, M    json.Marshaler
+               N       int
+               U       interface{} // untyped nil
+               Z       *int        // typed nil
+               W       htmltemplate.HTML
+       }{
+               F: false,
+               T: true,
+               C: "<Cincinnati>",
+               G: "<Goodbye>",
+               H: "<Hello>",
+               A: []string{"<a>", "<b>"},
+               E: []string{},
+               N: 42,
+               B: &badMarshaler{},
+               M: &goodMarshaler{},
+               U: nil,
+               Z: nil,
+               W: htmltemplate.HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
+       }
+       pdata := &data
+
+       tests := []struct {
+               name   string
+               input  string
+               output string
+       }{
+               {
+                       "if",
+                       "{{if .T}}Hello{{end}}, {{.C}}!",
+                       "Hello, &lt;Cincinnati&gt;!",
+               },
+               {
+                       "else",
+                       "{{if .F}}{{.H}}{{else}}{{.G}}{{end}}!",
+                       "&lt;Goodbye&gt;!",
+               },
+               {
+                       "overescaping1",
+                       "Hello, {{.C | html}}!",
+                       "Hello, &lt;Cincinnati&gt;!",
+               },
+               {
+                       "overescaping2",
+                       "Hello, {{html .C}}!",
+                       "Hello, &lt;Cincinnati&gt;!",
+               },
+               {
+                       "overescaping3",
+                       "{{with .C}}{{$msg := .}}Hello, {{$msg}}!{{end}}",
+                       "Hello, &lt;Cincinnati&gt;!",
+               },
+               {
+                       "assignment",
+                       "{{if $x := .H}}{{$x}}{{end}}",
+                       "&lt;Hello&gt;",
+               },
+               {
+                       "withBody",
+                       "{{with .H}}{{.}}{{end}}",
+                       "&lt;Hello&gt;",
+               },
+               {
+                       "withElse",
+                       "{{with .E}}{{.}}{{else}}{{.H}}{{end}}",
+                       "&lt;Hello&gt;",
+               },
+               {
+                       "rangeBody",
+                       "{{range .A}}{{.}}{{end}}",
+                       "&lt;a&gt;&lt;b&gt;",
+               },
+               {
+                       "rangeElse",
+                       "{{range .E}}{{.}}{{else}}{{.H}}{{end}}",
+                       "&lt;Hello&gt;",
+               },
+               {
+                       "nonStringValue",
+                       "{{.T}}",
+                       "true",
+               },
+               {
+                       "untypedNilValue",
+                       "{{.U}}",
+                       "",
+               },
+               {
+                       "typedNilValue",
+                       "{{.Z}}",
+                       "&lt;nil&gt;",
+               },
+               {
+                       "constant",
+                       `<a href="/search?q={{"'a<b'"}}">`,
+                       `<a href="/search?q=%27a%3cb%27">`,
+               },
+               {
+                       "multipleAttrs",
+                       "<a b=1 c={{.H}}>",
+                       "<a b=1 c=&lt;Hello&gt;>",
+               },
+               {
+                       "urlStartRel",
+                       `<a href='{{"/foo/bar?a=b&c=d"}}'>`,
+                       `<a href='/foo/bar?a=b&amp;c=d'>`,
+               },
+               {
+                       "urlStartAbsOk",
+                       `<a href='{{"http://example.com/foo/bar?a=b&c=d"}}'>`,
+                       `<a href='http://example.com/foo/bar?a=b&amp;c=d'>`,
+               },
+               {
+                       "protocolRelativeURLStart",
+                       `<a href='{{"//example.com:8000/foo/bar?a=b&c=d"}}'>`,
+                       `<a href='//example.com:8000/foo/bar?a=b&amp;c=d'>`,
+               },
+               {
+                       "pathRelativeURLStart",
+                       `<a href="{{"/javascript:80/foo/bar"}}">`,
+                       `<a href="/javascript:80/foo/bar">`,
+               },
+               {
+                       "dangerousURLStart",
+                       `<a href='{{"javascript:alert(%22pwned%22)"}}'>`,
+                       `<a href='#ZgotmplZ'>`,
+               },
+               {
+                       "dangerousURLStart2",
+                       `<a href='  {{"javascript:alert(%22pwned%22)"}}'>`,
+                       `<a href='  #ZgotmplZ'>`,
+               },
+               {
+                       "nonHierURL",
+                       `<a href={{"mailto:Muhammed \"The Greatest\" Ali <m.ali@example.com>"}}>`,
+                       `<a href=mailto:Muhammed%20%22The%20Greatest%22%20Ali%20%3cm.ali@example.com%3e>`,
+               },
+               {
+                       "urlPath",
+                       `<a href='http://{{"javascript:80"}}/foo'>`,
+                       `<a href='http://javascript:80/foo'>`,
+               },
+               {
+                       "urlQuery",
+                       `<a href='/search?q={{.H}}'>`,
+                       `<a href='/search?q=%3cHello%3e'>`,
+               },
+               {
+                       "urlFragment",
+                       `<a href='/faq#{{.H}}'>`,
+                       `<a href='/faq#%3cHello%3e'>`,
+               },
+               {
+                       "urlBranch",
+                       `<a href="{{if .F}}/foo?a=b{{else}}/bar{{end}}">`,
+                       `<a href="/bar">`,
+               },
+               {
+                       "urlBranchConflictMoot",
+                       `<a href="{{if .T}}/foo?a={{else}}/bar#{{end}}{{.C}}">`,
+                       `<a href="/foo?a=%3cCincinnati%3e">`,
+               },
+               {
+                       "jsStrValue",
+                       "<button onclick='alert({{.H}})'>",
+                       `<button onclick='alert(&#34;\u003cHello\u003e&#34;)'>`,
+               },
+               {
+                       "jsNumericValue",
+                       "<button onclick='alert({{.N}})'>",
+                       `<button onclick='alert( 42 )'>`,
+               },
+               {
+                       "jsBoolValue",
+                       "<button onclick='alert({{.T}})'>",
+                       `<button onclick='alert( true )'>`,
+               },
+               {
+                       "jsNilValueTyped",
+                       "<button onclick='alert(typeof{{.Z}})'>",
+                       `<button onclick='alert(typeof null )'>`,
+               },
+               {
+                       "jsNilValueUntyped",
+                       "<button onclick='alert(typeof{{.U}})'>",
+                       `<button onclick='alert(typeof null )'>`,
+               },
+               {
+                       "jsObjValue",
+                       "<button onclick='alert({{.A}})'>",
+                       `<button onclick='alert([&#34;\u003ca\u003e&#34;,&#34;\u003cb\u003e&#34;])'>`,
+               },
+               {
+                       "jsObjValueScript",
+                       "<script>alert({{.A}})</script>",
+                       `<script>alert(["\u003ca\u003e","\u003cb\u003e"])</script>`,
+               },
+               {
+                       "jsObjValueNotOverEscaped",
+                       "<button onclick='alert({{.A | html}})'>",
+                       `<button onclick='alert([&#34;\u003ca\u003e&#34;,&#34;\u003cb\u003e&#34;])'>`,
+               },
+               {
+                       "jsStr",
+                       "<button onclick='alert(&quot;{{.H}}&quot;)'>",
+                       `<button onclick='alert(&quot;\x3cHello\x3e&quot;)'>`,
+               },
+               {
+                       "badMarshaler",
+                       `<button onclick='alert(1/{{.B}}in numbers)'>`,
+                       `<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character &#39;f&#39; looking for beginning of object key string */null in numbers)'>`,
+               },
+               {
+                       "jsMarshaler",
+                       `<button onclick='alert({{.M}})'>`,
+                       `<button onclick='alert({&#34;\u003cfoo\u003e&#34;:&#34;O&#39;Reilly&#34;})'>`,
+               },
+               {
+                       "jsStrNotUnderEscaped",
+                       "<button onclick='alert({{.C | urlquery}})'>",
+                       // URL escaped, then quoted for JS.
+                       `<button onclick='alert(&#34;%3CCincinnati%3E&#34;)'>`,
+               },
+               {
+                       "jsRe",
+                       `<button onclick='alert(/{{"foo+bar"}}/.test(""))'>`,
+                       `<button onclick='alert(/foo\x2bbar/.test(""))'>`,
+               },
+               {
+                       "jsReBlank",
+                       `<script>alert(/{{""}}/.test(""));</script>`,
+                       `<script>alert(/(?:)/.test(""));</script>`,
+               },
+               {
+                       "jsReAmbigOk",
+                       `<script>{{if true}}var x = 1{{end}}</script>`,
+                       // The {if} ends in an ambiguous jsCtx but there is
+                       // no slash following so we shouldn't care.
+                       `<script>var x = 1</script>`,
+               },
+               {
+                       "styleBidiKeywordPassed",
+                       `<p style="dir: {{"ltr"}}">`,
+                       `<p style="dir: ltr">`,
+               },
+               {
+                       "styleBidiPropNamePassed",
+                       `<p style="border-{{"left"}}: 0; border-{{"right"}}: 1in">`,
+                       `<p style="border-left: 0; border-right: 1in">`,
+               },
+               {
+                       "styleExpressionBlocked",
+                       `<p style="width: {{"expression(alert(1337))"}}">`,
+                       `<p style="width: ZgotmplZ">`,
+               },
+               {
+                       "styleTagSelectorPassed",
+                       `<style>{{"p"}} { color: pink }</style>`,
+                       `<style>p { color: pink }</style>`,
+               },
+               {
+                       "styleIDPassed",
+                       `<style>p{{"#my-ID"}} { font: Arial }</style>`,
+                       `<style>p#my-ID { font: Arial }</style>`,
+               },
+               {
+                       "styleClassPassed",
+                       `<style>p{{".my_class"}} { font: Arial }</style>`,
+                       `<style>p.my_class { font: Arial }</style>`,
+               },
+               {
+                       "styleQuantityPassed",
+                       `<a style="left: {{"2em"}}; top: {{0}}">`,
+                       `<a style="left: 2em; top: 0">`,
+               },
+               {
+                       "stylePctPassed",
+                       `<table style=width:{{"100%"}}>`,
+                       `<table style=width:100%>`,
+               },
+               {
+                       "styleColorPassed",
+                       `<p style="color: {{"#8ff"}}; background: {{"#000"}}">`,
+                       `<p style="color: #8ff; background: #000">`,
+               },
+               {
+                       "styleObfuscatedExpressionBlocked",
+                       `<p style="width: {{"  e\\78preS\x00Sio/**/n(alert(1337))"}}">`,
+                       `<p style="width: ZgotmplZ">`,
+               },
+               {
+                       "styleMozBindingBlocked",
+                       `<p style="{{"-moz-binding(alert(1337))"}}: ...">`,
+                       `<p style="ZgotmplZ: ...">`,
+               },
+               {
+                       "styleObfuscatedMozBindingBlocked",
+                       `<p style="{{"  -mo\\7a-B\x00I/**/nding(alert(1337))"}}: ...">`,
+                       `<p style="ZgotmplZ: ...">`,
+               },
+               {
+                       "styleFontNameString",
+                       `<p style='font-family: "{{"Times New Roman"}}"'>`,
+                       `<p style='font-family: "Times New Roman"'>`,
+               },
+               {
+                       "styleFontNameString",
+                       `<p style='font-family: "{{"Times New Roman"}}", "{{"sans-serif"}}"'>`,
+                       `<p style='font-family: "Times New Roman", "sans-serif"'>`,
+               },
+               {
+                       "styleFontNameUnquoted",
+                       `<p style='font-family: {{"Times New Roman"}}'>`,
+                       `<p style='font-family: Times New Roman'>`,
+               },
+               {
+                       "styleURLQueryEncoded",
+                       `<p style="background: url(/img?name={{"O'Reilly Animal(1)<2>.png"}})">`,
+                       `<p style="background: url(/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png)">`,
+               },
+               {
+                       "styleQuotedURLQueryEncoded",
+                       `<p style="background: url('/img?name={{"O'Reilly Animal(1)<2>.png"}}')">`,
+                       `<p style="background: url('/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png')">`,
+               },
+               {
+                       "styleStrQueryEncoded",
+                       `<p style="background: '/img?name={{"O'Reilly Animal(1)<2>.png"}}'">`,
+                       `<p style="background: '/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png'">`,
+               },
+               {
+                       "styleURLBadProtocolBlocked",
+                       `<a style="background: url('{{"javascript:alert(1337)"}}')">`,
+                       `<a style="background: url('#ZgotmplZ')">`,
+               },
+               {
+                       "styleStrBadProtocolBlocked",
+                       `<a style="background: '{{"vbscript:alert(1337)"}}'">`,
+                       `<a style="background: '#ZgotmplZ'">`,
+               },
+               {
+                       "styleStrEncodedProtocolEncoded",
+                       `<a style="background: '{{"javascript\\3a alert(1337)"}}'">`,
+                       // The CSS string 'javascript\\3a alert(1337)' does not contain a colon.
+                       `<a style="background: 'javascript\\3a alert\28 1337\29 '">`,
+               },
+               {
+                       "styleURLGoodProtocolPassed",
+                       `<a style="background: url('{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}')">`,
+                       `<a style="background: url('http://oreilly.com/O%27Reilly%20Animals%281%29%3c2%3e;%7b%7d.html')">`,
+               },
+               {
+                       "styleStrGoodProtocolPassed",
+                       `<a style="background: '{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}'">`,
+                       `<a style="background: 'http\3a\2f\2foreilly.com\2fO\27Reilly Animals\28 1\29\3c 2\3e\3b\7b\7d.html'">`,
+               },
+               {
+                       "styleURLEncodedForHTMLInAttr",
+                       `<a style="background: url('{{"/search?img=foo&size=icon"}}')">`,
+                       `<a style="background: url('/search?img=foo&amp;size=icon')">`,
+               },
+               {
+                       "styleURLNotEncodedForHTMLInCdata",
+                       `<style>body { background: url('{{"/search?img=foo&size=icon"}}') }</style>`,
+                       `<style>body { background: url('/search?img=foo&size=icon') }</style>`,
+               },
+               {
+                       "styleURLMixedCase",
+                       `<p style="background: URL(#{{.H}})">`,
+                       `<p style="background: URL(#%3cHello%3e)">`,
+               },
+               {
+                       "stylePropertyPairPassed",
+                       `<a style='{{"color: red"}}'>`,
+                       `<a style='color: red'>`,
+               },
+               {
+                       "styleStrSpecialsEncoded",
+                       `<a style="font-family: '{{"/**/'\";:// \\"}}', &quot;{{"/**/'\";:// \\"}}&quot;">`,
+                       `<a style="font-family: '\2f**\2f\27\22\3b\3a\2f\2f  \\', &quot;\2f**\2f\27\22\3b\3a\2f\2f  \\&quot;">`,
+               },
+               {
+                       "styleURLSpecialsEncoded",
+                       `<a style="border-image: url({{"/**/'\";:// \\"}}), url(&quot;{{"/**/'\";:// \\"}}&quot;), url('{{"/**/'\";:// \\"}}'), 'http://www.example.com/?q={{"/**/'\";:// \\"}}''">`,
+                       `<a style="border-image: url(/**/%27%22;://%20%5c), url(&quot;/**/%27%22;://%20%5c&quot;), url('/**/%27%22;://%20%5c'), 'http://www.example.com/?q=%2f%2a%2a%2f%27%22%3b%3a%2f%2f%20%5c''">`,
+               },
+               {
+                       "HTML comment",
+                       "<b>Hello, <!-- name of world -->{{.C}}</b>",
+                       "<b>Hello, &lt;Cincinnati&gt;</b>",
+               },
+               {
+                       "HTML comment not first < in text node.",
+                       "<<!-- -->!--",
+                       "&lt;!--",
+               },
+               {
+                       "HTML normalization 1",
+                       "a < b",
+                       "a &lt; b",
+               },
+               {
+                       "HTML normalization 2",
+                       "a << b",
+                       "a &lt;&lt; b",
+               },
+               {
+                       "HTML normalization 3",
+                       "a<<!-- --><!-- -->b",
+                       "a&lt;b",
+               },
+               {
+                       "HTML doctype not normalized",
+                       "<!DOCTYPE html>Hello, World!",
+                       "<!DOCTYPE html>Hello, World!",
+               },
+               {
+                       "HTML doctype not case-insensitive",
+                       "<!doCtYPE htMl>Hello, World!",
+                       "<!doCtYPE htMl>Hello, World!",
+               },
+               {
+                       "No doctype injection",
+                       `<!{{"DOCTYPE"}}`,
+                       "&lt;!DOCTYPE",
+               },
+               {
+                       "Split HTML comment",
+                       "<b>Hello, <!-- name of {{if .T}}city -->{{.C}}{{else}}world -->{{.W}}{{end}}</b>",
+                       "<b>Hello, &lt;Cincinnati&gt;</b>",
+               },
+               {
+                       "JS line comment",
+                       "<script>for (;;) { if (c()) break// foo not a label\n" +
+                               "foo({{.T}});}</script>",
+                       "<script>for (;;) { if (c()) break\n" +
+                               "foo( true );}</script>",
+               },
+               {
+                       "JS multiline block comment",
+                       "<script>for (;;) { if (c()) break/* foo not a label\n" +
+                               " */foo({{.T}});}</script>",
+                       // Newline separates break from call. If newline
+                       // removed, then break will consume label leaving
+                       // code invalid.
+                       "<script>for (;;) { if (c()) break\n" +
+                               "foo( true );}</script>",
+               },
+               {
+                       "JS single-line block comment",
+                       "<script>for (;;) {\n" +
+                               "if (c()) break/* foo a label */foo;" +
+                               "x({{.T}});}</script>",
+                       // Newline separates break from call. If newline
+                       // removed, then break will consume label leaving
+                       // code invalid.
+                       "<script>for (;;) {\n" +
+                               "if (c()) break foo;" +
+                               "x( true );}</script>",
+               },
+               {
+                       "JS block comment flush with mathematical division",
+                       "<script>var a/*b*//c\nd</script>",
+                       "<script>var a /c\nd</script>",
+               },
+               {
+                       "JS mixed comments",
+                       "<script>var a/*b*///c\nd</script>",
+                       "<script>var a \nd</script>",
+               },
+               {
+                       "CSS comments",
+                       "<style>p// paragraph\n" +
+                               `{border: 1px/* color */{{"#00f"}}}</style>`,
+                       "<style>p\n" +
+                               "{border: 1px #00f}</style>",
+               },
+               {
+                       "JS attr block comment",
+                       `<a onclick="f(&quot;&quot;); /* alert({{.H}}) */">`,
+                       // Attribute comment tests should pass if the comments
+                       // are successfully elided.
+                       `<a onclick="f(&quot;&quot;); /* alert() */">`,
+               },
+               {
+                       "JS attr line comment",
+                       `<a onclick="// alert({{.G}})">`,
+                       `<a onclick="// alert()">`,
+               },
+               {
+                       "CSS attr block comment",
+                       `<a style="/* color: {{.H}} */">`,
+                       `<a style="/* color:  */">`,
+               },
+               {
+                       "CSS attr line comment",
+                       `<a style="// color: {{.G}}">`,
+                       `<a style="// color: ">`,
+               },
+               {
+                       "HTML substitution commented out",
+                       "<p><!-- {{.H}} --></p>",
+                       "<p></p>",
+               },
+               {
+                       "Comment ends flush with start",
+                       "<!--{{.}}--><script>/*{{.}}*///{{.}}\n</script><style>/*{{.}}*///{{.}}\n</style><a onclick='/*{{.}}*///{{.}}' style='/*{{.}}*///{{.}}'>",
+                       "<script> \n</script><style> \n</style><a onclick='/**///' style='/**///'>",
+               },
+               {
+                       "typed HTML in text",
+                       `{{.W}}`,
+                       `&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`,
+               },
+               {
+                       "typed HTML in attribute",
+                       `<div title="{{.W}}">`,
+                       `<div title="&iexcl;Hello, O&#39;World!">`,
+               },
+               {
+                       "typed HTML in script",
+                       `<button onclick="alert({{.W}})">`,
+                       `<button onclick="alert(&#34;\u0026iexcl;\u003cb class=\&#34;foo\&#34;\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO&#39;World\u003c/textarea\u003e!&#34;)">`,
+               },
+               {
+                       "typed HTML in RCDATA",
+                       `<textarea>{{.W}}</textarea>`,
+                       `<textarea>&iexcl;&lt;b class=&#34;foo&#34;&gt;Hello&lt;/b&gt;, &lt;textarea&gt;O&#39;World&lt;/textarea&gt;!</textarea>`,
+               },
+               {
+                       "range in textarea",
+                       "<textarea>{{range .A}}{{.}}{{end}}</textarea>",
+                       "<textarea>&lt;a&gt;&lt;b&gt;</textarea>",
+               },
+               {
+                       "No tag injection",
+                       `{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`,
+                       `10$&lt;script src,evil.org/pwnd.js...`,
+               },
+               {
+                       "No comment injection",
+                       `<{{"!--"}}`,
+                       `&lt;!--`,
+               },
+               {
+                       "No RCDATA end tag injection",
+                       `<textarea><{{"/textarea "}}...</textarea>`,
+                       `<textarea>&lt;/textarea ...</textarea>`,
+               },
+               {
+                       "optional attrs",
+                       `<img class="{{"iconClass"}}"` +
+                               `{{if .T}} id="{{"<iconId>"}}"{{end}}` +
+                               // Double quotes inside if/else.
+                               ` src=` +
+                               `{{if .T}}"?{{"<iconPath>"}}"` +
+                               `{{else}}"images/cleardot.gif"{{end}}` +
+                               // Missing space before title, but it is not a
+                               // part of the src attribute.
+                               `{{if .T}}title="{{"<title>"}}"{{end}}` +
+                               // Quotes outside if/else.
+                               ` alt="` +
+                               `{{if .T}}{{"<alt>"}}` +
+                               `{{else}}{{if .F}}{{"<title>"}}{{end}}` +
+                               `{{end}}"` +
+                               `>`,
+                       `<img class="iconClass" id="&lt;iconId&gt;" src="?%3ciconPath%3e"title="&lt;title&gt;" alt="&lt;alt&gt;">`,
+               },
+               {
+                       "conditional valueless attr name",
+                       `<input{{if .T}} checked{{end}} name=n>`,
+                       `<input checked name=n>`,
+               },
+               {
+                       "conditional dynamic valueless attr name 1",
+                       `<input{{if .T}} {{"checked"}}{{end}} name=n>`,
+                       `<input checked name=n>`,
+               },
+               {
+                       "conditional dynamic valueless attr name 2",
+                       `<input {{if .T}}{{"checked"}} {{end}}name=n>`,
+                       `<input checked name=n>`,
+               },
+               {
+                       "dynamic attribute name",
+                       `<img on{{"load"}}="alert({{"loaded"}})">`,
+                       // Treated as JS since quotes are inserted.
+                       `<img onload="alert(&#34;loaded&#34;)">`,
+               },
+               {
+                       "bad dynamic attribute name 1",
+                       // Allow checked, selected, disabled, but not JS or
+                       // CSS attributes.
+                       `<input {{"onchange"}}="{{"doEvil()"}}">`,
+                       `<input ZgotmplZ="doEvil()">`,
+               },
+               {
+                       "bad dynamic attribute name 2",
+                       `<div {{"sTyle"}}="{{"color: expression(alert(1337))"}}">`,
+                       `<div ZgotmplZ="color: expression(alert(1337))">`,
+               },
+               {
+                       "bad dynamic attribute name 3",
+                       // Allow title or alt, but not a URL.
+                       `<img {{"src"}}="{{"javascript:doEvil()"}}">`,
+                       `<img ZgotmplZ="javascript:doEvil()">`,
+               },
+               {
+                       "bad dynamic attribute name 4",
+                       // Structure preservation requires values to associate
+                       // with a consistent attribute.
+                       `<input checked {{""}}="Whose value am I?">`,
+                       `<input checked ZgotmplZ="Whose value am I?">`,
+               },
+               {
+                       "dynamic element name",
+                       `<h{{3}}><table><t{{"head"}}>...</h{{3}}>`,
+                       `<h3><table><thead>...</h3>`,
+               },
+               {
+                       "bad dynamic element name",
+                       // Dynamic element names are typically used to switch
+                       // between (thead, tfoot, tbody), (ul, ol), (th, td),
+                       // and other replaceable sets.
+                       // We do not currently easily support (ul, ol).
+                       // If we do change to support that, this test should
+                       // catch failures to filter out special tag names which
+                       // would violate the structure preservation property --
+                       // if any special tag name could be substituted, then
+                       // the content could be raw text/RCDATA for some inputs
+                       // and regular HTML content for others.
+                       `<{{"script"}}>{{"doEvil()"}}</{{"script"}}>`,
+                       `&lt;script>doEvil()&lt;/script>`,
+               },
+               {
+                       "srcset bad URL in second position",
+                       `<img srcset="{{"/not-an-image#,javascript:alert(1)"}}">`,
+                       // The second URL is also filtered.
+                       `<img srcset="/not-an-image#,#ZgotmplZ">`,
+               },
+               {
+                       "srcset buffer growth",
+                       `<img srcset={{",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}}>`,
+                       `<img srcset=,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>`,
+               },
+       }
+
+       for _, test := range tests {
+               tmpl := New(test.name)
+               tmpl = Must(tmpl.Parse(test.input))
+               // Check for bug 6459: Tree field was not set in Parse.
+               if tmpl.Tree != tmpl.text.Tree {
+                       t.Errorf("%s: tree not set properly", test.name)
+                       continue
+               }
+               b := new(bytes.Buffer)
+               if err := tmpl.Execute(b, data); err != nil {
+                       t.Errorf("%s: template execution failed: %s", test.name, err)
+                       continue
+               }
+               if w, g := test.output, b.String(); w != g {
+                       t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
+                       continue
+               }
+               b.Reset()
+               if err := tmpl.Execute(b, pdata); err != nil {
+                       t.Errorf("%s: template execution failed for pointer: %s", test.name, err)
+                       continue
+               }
+               if w, g := test.output, b.String(); w != g {
+                       t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
+                       continue
+               }
+               if tmpl.Tree != tmpl.text.Tree {
+                       t.Errorf("%s: tree mismatch", test.name)
+                       continue
+               }
+       }
+}
+
+func TestEscapeMap(t *testing.T) {
+       data := map[string]string{
+               "html":     `<h1>Hi!</h1>`,
+               "urlquery": `http://www.foo.com/index.html?title=main`,
+       }
+       for _, test := range [...]struct {
+               desc, input, output string
+       }{
+               // covering issue 20323
+               {
+                       "field with predefined escaper name 1",
+                       `{{.html | print}}`,
+                       `&lt;h1&gt;Hi!&lt;/h1&gt;`,
+               },
+               // covering issue 20323
+               {
+                       "field with predefined escaper name 2",
+                       `{{.urlquery | print}}`,
+                       `http://www.foo.com/index.html?title=main`,
+               },
+       } {
+               tmpl := Must(New("").Parse(test.input))
+               b := new(bytes.Buffer)
+               if err := tmpl.Execute(b, data); err != nil {
+                       t.Errorf("%s: template execution failed: %s", test.desc, err)
+                       continue
+               }
+               if w, g := test.output, b.String(); w != g {
+                       t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.desc, w, g)
+                       continue
+               }
+       }
+}
+
+func TestEscapeSet(t *testing.T) {
+       type dataItem struct {
+               Children []*dataItem
+               X        string
+       }
+
+       data := dataItem{
+               Children: []*dataItem{
+                       {X: "foo"},
+                       {X: "<bar>"},
+                       {
+                               Children: []*dataItem{
+                                       {X: "baz"},
+                               },
+                       },
+               },
+       }
+
+       tests := []struct {
+               inputs map[string]string
+               want   string
+       }{
+               // The trivial set.
+               {
+                       map[string]string{
+                               "main": ``,
+                       },
+                       ``,
+               },
+               // A template called in the start context.
+               {
+                       map[string]string{
+                               "main": `Hello, {{template "helper"}}!`,
+                               // Not a valid top level HTML template.
+                               // "<b" is not a full tag.
+                               "helper": `{{"<World>"}}`,
+                       },
+                       `Hello, &lt;World&gt;!`,
+               },
+               // A template called in a context other than the start.
+               {
+                       map[string]string{
+                               "main": `<a onclick='a = {{template "helper"}};'>`,
+                               // Not a valid top level HTML template.
+                               // "<b" is not a full tag.
+                               "helper": `{{"<a>"}}<b`,
+                       },
+                       `<a onclick='a = &#34;\u003ca\u003e&#34;<b;'>`,
+               },
+               // A recursive template that ends in its start context.
+               {
+                       map[string]string{
+                               "main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`,
+                       },
+                       `foo &lt;bar&gt; baz `,
+               },
+               // A recursive helper template that ends in its start context.
+               {
+                       map[string]string{
+                               "main":   `{{template "helper" .}}`,
+                               "helper": `{{if .Children}}<ul>{{range .Children}}<li>{{template "main" .}}</li>{{end}}</ul>{{else}}{{.X}}{{end}}`,
+                       },
+                       `<ul><li>foo</li><li>&lt;bar&gt;</li><li><ul><li>baz</li></ul></li></ul>`,
+               },
+               // Co-recursive templates that end in its start context.
+               {
+                       map[string]string{
+                               "main":   `<blockquote>{{range .Children}}{{template "helper" .}}{{end}}</blockquote>`,
+                               "helper": `{{if .Children}}{{template "main" .}}{{else}}{{.X}}<br>{{end}}`,
+                       },
+                       `<blockquote>foo<br>&lt;bar&gt;<br><blockquote>baz<br></blockquote></blockquote>`,
+               },
+               // A template that is called in two different contexts.
+               {
+                       map[string]string{
+                               "main":   `<button onclick="title='{{template "helper"}}'; ...">{{template "helper"}}</button>`,
+                               "helper": `{{11}} of {{"<100>"}}`,
+                       },
+                       `<button onclick="title='11 of \x3c100\x3e'; ...">11 of &lt;100&gt;</button>`,
+               },
+               // A non-recursive template that ends in a different context.
+               // helper starts in jsCtxRegexp and ends in jsCtxDivOp.
+               {
+                       map[string]string{
+                               "main":   `<script>var x={{template "helper"}}/{{"42"}};</script>`,
+                               "helper": "{{126}}",
+                       },
+                       `<script>var x= 126 /"42";</script>`,
+               },
+               // A recursive template that ends in a similar context.
+               {
+                       map[string]string{
+                               "main":      `<script>var x=[{{template "countdown" 4}}];</script>`,
+                               "countdown": `{{.}}{{if .}},{{template "countdown" . | pred}}{{end}}`,
+                       },
+                       `<script>var x=[ 4 , 3 , 2 , 1 , 0 ];</script>`,
+               },
+               // A recursive template that ends in a different context.
+               /*
+                       {
+                               map[string]string{
+                                       "main":   `<a href="/foo{{template "helper" .}}">`,
+                                       "helper": `{{if .Children}}{{range .Children}}{{template "helper" .}}{{end}}{{else}}?x={{.X}}{{end}}`,
+                               },
+                               `<a href="/foo?x=foo?x=%3cbar%3e?x=baz">`,
+                       },
+               */
+       }
+
+       // pred is a template function that returns the predecessor of a
+       // natural number for testing recursive templates.
+       fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
+               if len(a) == 1 {
+                       if i, _ := a[0].(int); i > 0 {
+                               return i - 1, nil
+                       }
+               }
+               return nil, fmt.Errorf("undefined pred(%v)", a)
+       }}
+
+       for _, test := range tests {
+               source := ""
+               for name, body := range test.inputs {
+                       source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body)
+               }
+               tmpl, err := New("root").Funcs(fns).Parse(source)
+               if err != nil {
+                       t.Errorf("error parsing %q: %v", source, err)
+                       continue
+               }
+               var b bytes.Buffer
+
+               if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil {
+                       t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main"))
+                       continue
+               }
+               if got := b.String(); test.want != got {
+                       t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got)
+               }
+       }
+
+}
+
+func TestErrors(t *testing.T) {
+       tests := []struct {
+               input string
+               err   string
+       }{
+               // Non-error cases.
+               {
+                       "{{if .Cond}}<a>{{else}}<b>{{end}}",
+                       "",
+               },
+               {
+                       "{{if .Cond}}<a>{{end}}",
+                       "",
+               },
+               {
+                       "{{if .Cond}}{{else}}<b>{{end}}",
+                       "",
+               },
+               {
+                       "{{with .Cond}}<div>{{end}}",
+                       "",
+               },
+               {
+                       "{{range .Items}}<a>{{end}}",
+                       "",
+               },
+               {
+                       "<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>",
+                       "",
+               },
+               // Error cases.
+               {
+                       "{{if .Cond}}<a{{end}}",
+                       "z:1:5: {{if}} branches",
+               },
+               {
+                       "{{if .Cond}}\n{{else}}\n<a{{end}}",
+                       "z:1:5: {{if}} branches",
+               },
+               {
+                       // Missing quote in the else branch.
+                       `{{if .Cond}}<a href="foo">{{else}}<a href="bar>{{end}}`,
+                       "z:1:5: {{if}} branches",
+               },
+               {
+                       // Different kind of attribute: href implies a URL.
+                       "<a {{if .Cond}}href='{{else}}title='{{end}}{{.X}}'>",
+                       "z:1:8: {{if}} branches",
+               },
+               {
+                       "\n{{with .X}}<a{{end}}",
+                       "z:2:7: {{with}} branches",
+               },
+               {
+                       "\n{{with .X}}<a>{{else}}<a{{end}}",
+                       "z:2:7: {{with}} branches",
+               },
+               {
+                       "{{range .Items}}<a{{end}}",
+                       `z:1: on range loop re-entry: "<" in attribute name: "<a"`,
+               },
+               {
+                       "\n{{range .Items}} x='<a{{end}}",
+                       "z:2:8: on range loop re-entry: {{range}} branches",
+               },
+               {
+                       "<a b=1 c={{.H}}",
+                       "z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
+               },
+               {
+                       "<script>foo();",
+                       "z: ends in a non-text context: {stateJS",
+               },
+               {
+                       `<a href="{{if .F}}/foo?a={{else}}/bar/{{end}}{{.H}}">`,
+                       "z:1:47: {{.H}} appears in an ambiguous context within a URL",
+               },
+               {
+                       `<a onclick="alert('Hello \`,
+                       `unfinished escape sequence in JS string: "Hello \\"`,
+               },
+               {
+                       `<a onclick='alert("Hello\, World\`,
+                       `unfinished escape sequence in JS string: "Hello\\, World\\"`,
+               },
+               {
+                       `<a onclick='alert(/x+\`,
+                       `unfinished escape sequence in JS string: "x+\\"`,
+               },
+               {
+                       `<a onclick="/foo[\]/`,
+                       `unfinished JS regexp charset: "foo[\\]/"`,
+               },
+               {
+                       // It is ambiguous whether 1.5 should be 1\.5 or 1.5.
+                       // Either `var x = 1/- 1.5 /i.test(x)`
+                       // where `i.test(x)` is a method call of reference i,
+                       // or `/-1\.5/i.test(x)` which is a method call on a
+                       // case insensitive regular expression.
+                       `<script>{{if false}}var x = 1{{end}}/-{{"1.5"}}/i.test(x)</script>`,
+                       `'/' could start a division or regexp: "/-"`,
+               },
+               {
+                       `{{template "foo"}}`,
+                       "z:1:11: no such template \"foo\"",
+               },
+               {
+                       `<div{{template "y"}}>` +
+                               // Illegal starting in stateTag but not in stateText.
+                               `{{define "y"}} foo<b{{end}}`,
+                       `"<" in attribute name: " foo<b"`,
+               },
+               {
+                       `<script>reverseList = [{{template "t"}}]</script>` +
+                               // Missing " after recursive call.
+                               `{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`,
+                       `: cannot compute output context for template t$htmltemplate_stateJS_elementScript`,
+               },
+               {
+                       `<input type=button value=onclick=>`,
+                       `html/template:z: "=" in unquoted attr: "onclick="`,
+               },
+               {
+                       `<input type=button value= onclick=>`,
+                       `html/template:z: "=" in unquoted attr: "onclick="`,
+               },
+               {
+                       `<input type=button value= 1+1=2>`,
+                       `html/template:z: "=" in unquoted attr: "1+1=2"`,
+               },
+               {
+                       "<a class=`foo>",
+                       "html/template:z: \"`\" in unquoted attr: \"`foo\"",
+               },
+               {
+                       `<a style=font:'Arial'>`,
+                       `html/template:z: "'" in unquoted attr: "font:'Arial'"`,
+               },
+               {
+                       `<a=foo>`,
+                       `: expected space, attr name, or end of tag, but got "=foo>"`,
+               },
+               {
+                       `Hello, {{. | urlquery | print}}!`,
+                       // urlquery is disallowed if it is not the last command in the pipeline.
+                       `predefined escaper "urlquery" disallowed in template`,
+               },
+               {
+                       `Hello, {{. | html | print}}!`,
+                       // html is disallowed if it is not the last command in the pipeline.
+                       `predefined escaper "html" disallowed in template`,
+               },
+               {
+                       `Hello, {{html . | print}}!`,
+                       // A direct call to html is disallowed if it is not the last command in the pipeline.
+                       `predefined escaper "html" disallowed in template`,
+               },
+               {
+                       `<div class={{. | html}}>Hello<div>`,
+                       // html is disallowed in a pipeline that is in an unquoted attribute context,
+                       // even if it is the last command in the pipeline.
+                       `predefined escaper "html" disallowed in template`,
+               },
+               {
+                       `Hello, {{. | urlquery | html}}!`,
+                       // html is allowed since it is the last command in the pipeline, but urlquery is not.
+                       `predefined escaper "urlquery" disallowed in template`,
+               },
+       }
+       for _, test := range tests {
+               buf := new(bytes.Buffer)
+               tmpl, err := New("z").Parse(test.input)
+               if err != nil {
+                       t.Errorf("input=%q: unexpected parse error %s\n", test.input, err)
+                       continue
+               }
+               err = tmpl.Execute(buf, nil)
+               var got string
+               if err != nil {
+                       got = err.Error()
+               }
+               if test.err == "" {
+                       if got != "" {
+                               t.Errorf("input=%q: unexpected error %q", test.input, got)
+                       }
+                       continue
+               }
+               if !strings.Contains(got, test.err) {
+                       t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err)
+                       continue
+               }
+               // Check that we get the same error if we call Execute again.
+               if err := tmpl.Execute(buf, nil); err == nil || err.Error() != got {
+                       t.Errorf("input=%q: unexpected error on second call %q", test.input, err)
+
+               }
+       }
+}
+
+func TestEscapeText(t *testing.T) {
+       tests := []struct {
+               input  string
+               output context
+       }{
+               {
+                       ``,
+                       context{},
+               },
+               {
+                       `Hello, World!`,
+                       context{},
+               },
+               {
+                       // An orphaned "<" is OK.
+                       `I <3 Ponies!`,
+                       context{},
+               },
+               {
+                       `<a`,
+                       context{state: stateTag},
+               },
+               {
+                       `<a `,
+                       context{state: stateTag},
+               },
+               {
+                       `<a>`,
+                       context{state: stateText},
+               },
+               {
+                       `<a href`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a on`,
+                       context{state: stateAttrName, attr: attrScript},
+               },
+               {
+                       `<a href `,
+                       context{state: stateAfterName, attr: attrURL},
+               },
+               {
+                       `<a style  =  `,
+                       context{state: stateBeforeValue, attr: attrStyle},
+               },
+               {
+                       `<a href=`,
+                       context{state: stateBeforeValue, attr: attrURL},
+               },
+               {
+                       `<a href=x`,
+                       context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a href=x `,
+                       context{state: stateTag},
+               },
+               {
+                       `<a href=>`,
+                       context{state: stateText},
+               },
+               {
+                       `<a href=x>`,
+                       context{state: stateText},
+               },
+               {
+                       `<a href ='`,
+                       context{state: stateURL, delim: delimSingleQuote, attr: attrURL},
+               },
+               {
+                       `<a href=''`,
+                       context{state: stateTag},
+               },
+               {
+                       `<a href= "`,
+                       context{state: stateURL, delim: delimDoubleQuote, attr: attrURL},
+               },
+               {
+                       `<a href=""`,
+                       context{state: stateTag},
+               },
+               {
+                       `<a title="`,
+                       context{state: stateAttr, delim: delimDoubleQuote},
+               },
+               {
+                       `<a HREF='http:`,
+                       context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a Href='/`,
+                       context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a href='"`,
+                       context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a href="'`,
+                       context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a href='&apos;`,
+                       context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a href="&quot;`,
+                       context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a href="&#34;`,
+                       context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<a href=&quot;`,
+                       context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL},
+               },
+               {
+                       `<img alt="1">`,
+                       context{state: stateText},
+               },
+               {
+                       `<img alt="1>"`,
+                       context{state: stateTag},
+               },
+               {
+                       `<img alt="1>">`,
+                       context{state: stateText},
+               },
+               {
+                       `<input checked type="checkbox"`,
+                       context{state: stateTag},
+               },
+               {
+                       `<a onclick="`,
+                       context{state: stateJS, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="//foo`,
+                       context{state: stateJSLineCmt, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       "<a onclick='//\n",
+                       context{state: stateJS, delim: delimSingleQuote, attr: attrScript},
+               },
+               {
+                       "<a onclick='//\r\n",
+                       context{state: stateJS, delim: delimSingleQuote, attr: attrScript},
+               },
+               {
+                       "<a onclick='//\u2028",
+                       context{state: stateJS, delim: delimSingleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="/*`,
+                       context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="/*/`,
+                       context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="/**/`,
+                       context{state: stateJS, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onkeypress="&quot;`,
+                       context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick='&quot;foo&quot;`,
+                       context{state: stateJS, delim: delimSingleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
+               },
+               {
+                       `<a onclick=&#39;foo&#39;`,
+                       context{state: stateJS, delim: delimSpaceOrTagEnd, jsCtx: jsCtxDivOp, attr: attrScript},
+               },
+               {
+                       `<a onclick=&#39;foo`,
+                       context{state: stateJSSqStr, delim: delimSpaceOrTagEnd, attr: attrScript},
+               },
+               {
+                       `<a onclick="&quot;foo'`,
+                       context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="'foo&quot;`,
+                       context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<A ONCLICK="'`,
+                       context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="/`,
+                       context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="'foo'`,
+                       context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
+               },
+               {
+                       `<a onclick="'foo\'`,
+                       context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="'foo\'`,
+                       context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="/foo/`,
+                       context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
+               },
+               {
+                       `<script>/foo/ /=`,
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       `<a onclick="1 /foo`,
+                       context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
+               },
+               {
+                       `<a onclick="1 /*c*/ /foo`,
+                       context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
+               },
+               {
+                       `<a onclick="/foo[/]`,
+                       context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="/foo\/`,
+                       context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<a onclick="/foo/`,
+                       context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
+               },
+               {
+                       `<input checked style="`,
+                       context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="//`,
+                       context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="//</script>`,
+                       context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       "<a style='//\n",
+                       context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle},
+               },
+               {
+                       "<a style='//\r",
+                       context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="/*`,
+                       context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="/*/`,
+                       context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="/**/`,
+                       context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="background: '`,
+                       context{state: stateCSSSqStr, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="background: &quot;`,
+                       context{state: stateCSSDqStr, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="background: '/foo?img=`,
+                       context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle},
+               },
+               {
+                       `<a style="background: '/`,
+                       context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url(&#x22;/`,
+                       context{state: stateCSSDqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url('/`,
+                       context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url('/)`,
+                       context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url('/ `,
+                       context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url(/`,
+                       context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url( `,
+                       context{state: stateCSSURL, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url( /image?name=`,
+                       context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url(x)`,
+                       context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url('x'`,
+                       context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<a style="background: url( x `,
+                       context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
+               },
+               {
+                       `<!-- foo`,
+                       context{state: stateHTMLCmt},
+               },
+               {
+                       `<!-->`,
+                       context{state: stateHTMLCmt},
+               },
+               {
+                       `<!--->`,
+                       context{state: stateHTMLCmt},
+               },
+               {
+                       `<!-- foo -->`,
+                       context{state: stateText},
+               },
+               {
+                       `<script`,
+                       context{state: stateTag, element: elementScript},
+               },
+               {
+                       `<script `,
+                       context{state: stateTag, element: elementScript},
+               },
+               {
+                       `<script src="foo.js" `,
+                       context{state: stateTag, element: elementScript},
+               },
+               {
+                       `<script src='foo.js' `,
+                       context{state: stateTag, element: elementScript},
+               },
+               {
+                       `<script type=text/javascript `,
+                       context{state: stateTag, element: elementScript},
+               },
+               {
+                       `<script>`,
+                       context{state: stateJS, jsCtx: jsCtxRegexp, element: elementScript},
+               },
+               {
+                       `<script>foo`,
+                       context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
+               },
+               {
+                       `<script>foo</script>`,
+                       context{state: stateText},
+               },
+               {
+                       `<script>foo</script><!--`,
+                       context{state: stateHTMLCmt},
+               },
+               {
+                       `<script>document.write("<p>foo</p>");`,
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       `<script>document.write("<p>foo<\/script>");`,
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       `<script>document.write("<script>alert(1)</script>");`,
+                       context{state: stateText},
+               },
+               {
+                       `<script type="text/template">`,
+                       context{state: stateText},
+               },
+               // covering issue 19968
+               {
+                       `<script type="TEXT/JAVASCRIPT">`,
+                       context{state: stateJS, element: elementScript},
+               },
+               // covering issue 19965
+               {
+                       `<script TYPE="text/template">`,
+                       context{state: stateText},
+               },
+               {
+                       `<script type="notjs">`,
+                       context{state: stateText},
+               },
+               {
+                       `<Script>`,
+                       context{state: stateJS, element: elementScript},
+               },
+               {
+                       `<SCRIPT>foo`,
+                       context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
+               },
+               {
+                       `<textarea>value`,
+                       context{state: stateRCDATA, element: elementTextarea},
+               },
+               {
+                       `<textarea>value</TEXTAREA>`,
+                       context{state: stateText},
+               },
+               {
+                       `<textarea name=html><b`,
+                       context{state: stateRCDATA, element: elementTextarea},
+               },
+               {
+                       `<title>value`,
+                       context{state: stateRCDATA, element: elementTitle},
+               },
+               {
+                       `<style>value`,
+                       context{state: stateCSS, element: elementStyle},
+               },
+               {
+                       `<a xlink:href`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a xmlns`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a xmlns:foo`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a xmlnsxyz`,
+                       context{state: stateAttrName},
+               },
+               {
+                       `<a data-url`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a data-iconUri`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a data-urlItem`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a g:`,
+                       context{state: stateAttrName},
+               },
+               {
+                       `<a g:url`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a g:iconUri`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a g:urlItem`,
+                       context{state: stateAttrName, attr: attrURL},
+               },
+               {
+                       `<a g:value`,
+                       context{state: stateAttrName},
+               },
+               {
+                       `<a svg:style='`,
+                       context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle},
+               },
+               {
+                       `<svg:font-face`,
+                       context{state: stateTag},
+               },
+               {
+                       `<svg:a svg:onclick="`,
+                       context{state: stateJS, delim: delimDoubleQuote, attr: attrScript},
+               },
+               {
+                       `<svg:a svg:onclick="x()">`,
+                       context{},
+               },
+       }
+
+       for _, test := range tests {
+               b, e := []byte(test.input), makeEscaper(nil)
+               c := e.escapeText(context{}, &parse.TextNode{NodeType: parse.NodeText, Text: b})
+               if !test.output.eq(c) {
+                       t.Errorf("input %q: want context\n\t%v\ngot\n\t%v", test.input, test.output, c)
+                       continue
+               }
+               if test.input != string(b) {
+                       t.Errorf("input %q: text node was modified: want %q got %q", test.input, test.input, b)
+                       continue
+               }
+       }
+}
+
+func TestEnsurePipelineContains(t *testing.T) {
+       tests := []struct {
+               input, output string
+               ids           []string
+       }{
+               {
+                       "{{.X}}",
+                       ".X",
+                       []string{},
+               },
+               {
+                       "{{.X | html}}",
+                       ".X | html",
+                       []string{},
+               },
+               {
+                       "{{.X}}",
+                       ".X | html",
+                       []string{"html"},
+               },
+               {
+                       "{{html .X}}",
+                       "_eval_args_ .X | html | urlquery",
+                       []string{"html", "urlquery"},
+               },
+               {
+                       "{{html .X .Y .Z}}",
+                       "_eval_args_ .X .Y .Z | html | urlquery",
+                       []string{"html", "urlquery"},
+               },
+               {
+                       "{{.X | print}}",
+                       ".X | print | urlquery",
+                       []string{"urlquery"},
+               },
+               {
+                       "{{.X | print | urlquery}}",
+                       ".X | print | urlquery",
+                       []string{"urlquery"},
+               },
+               {
+                       "{{.X | urlquery}}",
+                       ".X | html | urlquery",
+                       []string{"html", "urlquery"},
+               },
+               {
+                       "{{.X | print 2 | .f 3}}",
+                       ".X | print 2 | .f 3 | urlquery | html",
+                       []string{"urlquery", "html"},
+               },
+               {
+                       // covering issue 10801
+                       "{{.X | println.x }}",
+                       ".X | println.x | urlquery | html",
+                       []string{"urlquery", "html"},
+               },
+               {
+                       // covering issue 10801
+                       "{{.X | (print 12 | println).x }}",
+                       ".X | (print 12 | println).x | urlquery | html",
+                       []string{"urlquery", "html"},
+               },
+               // The following test cases ensure that the merging of internal escapers
+               // with the predefined "html" and "urlquery" escapers is correct.
+               {
+                       "{{.X | urlquery}}",
+                       ".X | _html_template_urlfilter | urlquery",
+                       []string{"_html_template_urlfilter", "_html_template_urlnormalizer"},
+               },
+               {
+                       "{{.X | urlquery}}",
+                       ".X | urlquery | _html_template_urlfilter | _html_template_cssescaper",
+                       []string{"_html_template_urlfilter", "_html_template_cssescaper"},
+               },
+               {
+                       "{{.X | urlquery}}",
+                       ".X | urlquery",
+                       []string{"_html_template_urlnormalizer"},
+               },
+               {
+                       "{{.X | urlquery}}",
+                       ".X | urlquery",
+                       []string{"_html_template_urlescaper"},
+               },
+               {
+                       "{{.X | html}}",
+                       ".X | html",
+                       []string{"_html_template_htmlescaper"},
+               },
+               {
+                       "{{.X | html}}",
+                       ".X | html",
+                       []string{"_html_template_rcdataescaper"},
+               },
+       }
+       for i, test := range tests {
+               tmpl := template.Must(template.New("test").Parse(test.input))
+               action, ok := (tmpl.Tree.Root.Nodes[0].(*parse.ActionNode))
+               if !ok {
+                       t.Errorf("First node is not an action: %s", test.input)
+                       continue
+               }
+               pipe := action.Pipe
+               originalIDs := make([]string, len(test.ids))
+               copy(originalIDs, test.ids)
+               ensurePipelineContains(pipe, test.ids)
+               got := pipe.String()
+               if got != test.output {
+                       t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, test.input, originalIDs, test.output, got)
+               }
+       }
+}
+
+func TestEscapeMalformedPipelines(t *testing.T) {
+       tests := []string{
+               "{{ 0 | $ }}",
+               "{{ 0 | $ | urlquery }}",
+               "{{ 0 | (nil) }}",
+               "{{ 0 | (nil) | html }}",
+       }
+       for _, test := range tests {
+               var b bytes.Buffer
+               tmpl, err := New("test").Parse(test)
+               if err != nil {
+                       t.Errorf("failed to parse set: %q", err)
+               }
+               err = tmpl.Execute(&b, nil)
+               if err == nil {
+                       t.Errorf("Expected error for %q", test)
+               }
+       }
+}
+
+func TestEscapeErrorsNotIgnorable(t *testing.T) {
+       var b bytes.Buffer
+       tmpl, _ := New("dangerous").Parse("<a")
+       err := tmpl.Execute(&b, nil)
+       if err == nil {
+               t.Errorf("Expected error")
+       } else if b.Len() != 0 {
+               t.Errorf("Emitted output despite escaping failure")
+       }
+}
+
+func TestEscapeSetErrorsNotIgnorable(t *testing.T) {
+       var b bytes.Buffer
+       tmpl, err := New("root").Parse(`{{define "t"}}<a{{end}}`)
+       if err != nil {
+               t.Errorf("failed to parse set: %q", err)
+       }
+       err = tmpl.ExecuteTemplate(&b, "t", nil)
+       if err == nil {
+               t.Errorf("Expected error")
+       } else if b.Len() != 0 {
+               t.Errorf("Emitted output despite escaping failure")
+       }
+}
+
+func TestRedundantFuncs(t *testing.T) {
+       inputs := []interface{}{
+               "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
+                       "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+                       ` !"#$%&'()*+,-./` +
+                       `0123456789:;<=>?` +
+                       `@ABCDEFGHIJKLMNO` +
+                       `PQRSTUVWXYZ[\]^_` +
+                       "`abcdefghijklmno" +
+                       "pqrstuvwxyz{|}~\x7f" +
+                       "\u00A0\u0100\u2028\u2029\ufeff\ufdec\ufffd\uffff\U0001D11E" +
+                       "&amp;%22\\",
+               htmltemplate.CSS(`a[href =~ "//example.com"]#foo`),
+               htmltemplate.HTML(`Hello, <b>World</b> &amp;tc!`),
+               htmltemplate.HTMLAttr(` dir="ltr"`),
+               htmltemplate.JS(`c && alert("Hello, World!");`),
+               htmltemplate.JSStr(`Hello, World & O'Reilly\x21`),
+               htmltemplate.URL(`greeting=H%69&addressee=(World)`),
+       }
+
+       for n0, m := range redundantFuncs {
+               f0 := funcMap[n0].(func(...interface{}) string)
+               for n1 := range m {
+                       f1 := funcMap[n1].(func(...interface{}) string)
+                       for _, input := range inputs {
+                               want := f0(input)
+                               if got := f1(want); want != got {
+                                       t.Errorf("%s %s with %T %q: want\n\t%q,\ngot\n\t%q", n0, n1, input, input, want, got)
+                               }
+                       }
+               }
+       }
+}
+
+func TestIndirectPrint(t *testing.T) {
+       a := 3
+       ap := &a
+       b := "hello"
+       bp := &b
+       bpp := &bp
+       tmpl := Must(New("t").Parse(`{{.}}`))
+       var buf bytes.Buffer
+       err := tmpl.Execute(&buf, ap)
+       if err != nil {
+               t.Errorf("Unexpected error: %s", err)
+       } else if buf.String() != "3" {
+               t.Errorf(`Expected "3"; got %q`, buf.String())
+       }
+       buf.Reset()
+       err = tmpl.Execute(&buf, bpp)
+       if err != nil {
+               t.Errorf("Unexpected error: %s", err)
+       } else if buf.String() != "hello" {
+               t.Errorf(`Expected "hello"; got %q`, buf.String())
+       }
+}
+
+// This is a test for issue 3272.
+func TestEmptyTemplate(t *testing.T) {
+       page := Must(New("page").ParseFiles(os.DevNull))
+       if err := page.ExecuteTemplate(os.Stdout, "page", "nothing"); err == nil {
+               t.Fatal("expected error")
+       }
+}
+
+type Issue7379 int
+
+func (Issue7379) SomeMethod(x int) string {
+       return fmt.Sprintf("<%d>", x)
+}
+
+// This is a test for issue 7379: type assertion error caused panic, and then
+// the code to handle the panic breaks escaping. It's hard to see the second
+// problem once the first is fixed, but its fix is trivial so we let that go. See
+// the discussion for issue 7379.
+func TestPipeToMethodIsEscaped(t *testing.T) {
+       tmpl := Must(New("x").Parse("<html>{{0 | .SomeMethod}}</html>\n"))
+       tryExec := func() string {
+               defer func() {
+                       panicValue := recover()
+                       if panicValue != nil {
+                               t.Errorf("panicked: %v\n", panicValue)
+                       }
+               }()
+               var b bytes.Buffer
+               tmpl.Execute(&b, Issue7379(0))
+               return b.String()
+       }
+       for i := 0; i < 3; i++ {
+               str := tryExec()
+               const expect = "<html>&lt;0&gt;</html>\n"
+               if str != expect {
+                       t.Errorf("expected %q got %q", expect, str)
+               }
+       }
+}
+
+// Unlike text/template, html/template crashed if given an incomplete
+// template, that is, a template that had been named but not given any content.
+// This is issue #10204.
+func TestErrorOnUndefined(t *testing.T) {
+       tmpl := New("undefined")
+
+       err := tmpl.Execute(nil, nil)
+       if err == nil {
+               t.Error("expected error")
+       } else if !strings.Contains(err.Error(), "incomplete") {
+               t.Errorf("expected error about incomplete template; got %s", err)
+       }
+}
+
+// This covers issue #20842.
+func TestIdempotentExecute(t *testing.T) {
+       tmpl := Must(New("").
+               Parse(`{{define "main"}}<body>{{template "hello"}}</body>{{end}}`))
+       Must(tmpl.
+               Parse(`{{define "hello"}}Hello, {{"Ladies & Gentlemen!"}}{{end}}`))
+       got := new(bytes.Buffer)
+       var err error
+       // Ensure that "hello" produces the same output when executed twice.
+       want := "Hello, Ladies &amp; Gentlemen!"
+       for i := 0; i < 2; i++ {
+               err = tmpl.ExecuteTemplate(got, "hello", nil)
+               if err != nil {
+                       t.Errorf("unexpected error: %s", err)
+               }
+               if got.String() != want {
+                       t.Errorf("after executing template \"hello\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
+               }
+               got.Reset()
+       }
+       // Ensure that the implicit re-execution of "hello" during the execution of
+       // "main" does not cause the output of "hello" to change.
+       err = tmpl.ExecuteTemplate(got, "main", nil)
+       if err != nil {
+               t.Errorf("unexpected error: %s", err)
+       }
+       // If the HTML escaper is added again to the action {{"Ladies & Gentlemen!"}},
+       // we would expected to see the ampersand overescaped to "&amp;amp;".
+       want = "<body>Hello, Ladies &amp; Gentlemen!</body>"
+       if got.String() != want {
+               t.Errorf("after executing template \"main\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
+       }
+}
+
+func BenchmarkEscapedExecute(b *testing.B) {
+       tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))
+       var buf bytes.Buffer
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               tmpl.Execute(&buf, "foo & 'bar' & baz")
+               buf.Reset()
+       }
+}
+
+// Covers issue 22780.
+func TestOrphanedTemplate(t *testing.T) {
+       t1 := Must(New("foo").Parse(`<a href="{{.}}">link1</a>`))
+       t2 := Must(t1.New("foo").Parse(`bar`))
+
+       var b bytes.Buffer
+       const wantError = `template: "foo" is an incomplete or empty template`
+       if err := t1.Execute(&b, "javascript:alert(1)"); err == nil {
+               t.Fatal("expected error executing t1")
+       } else if gotError := err.Error(); gotError != wantError {
+               t.Fatalf("got t1 execution error:\n\t%s\nwant:\n\t%s", gotError, wantError)
+       }
+       b.Reset()
+       if err := t2.Execute(&b, nil); err != nil {
+               t.Fatalf("error executing t2: %s", err)
+       }
+       const want = "bar"
+       if got := b.String(); got != want {
+               t.Fatalf("t2 rendered %q, want %q", got, want)
+       }
+}
+
+// Covers issue 21844.
+func TestAliasedParseTreeDoesNotOverescape(t *testing.T) {
+       const (
+               tmplText = `{{.}}`
+               data     = `<baz>`
+               want     = `&lt;baz&gt;`
+       )
+       // Templates "foo" and "bar" both alias the same underlying parse tree.
+       tpl := Must(New("foo").Parse(tmplText))
+       if _, err := tpl.AddParseTree("bar", tpl.Tree); err != nil {
+               t.Fatalf("AddParseTree error: %v", err)
+       }
+       var b1, b2 bytes.Buffer
+       if err := tpl.ExecuteTemplate(&b1, "foo", data); err != nil {
+               t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err)
+       }
+       if err := tpl.ExecuteTemplate(&b2, "bar", data); err != nil {
+               t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err)
+       }
+       got1, got2 := b1.String(), b2.String()
+       if got1 != want {
+               t.Fatalf(`Template "foo" rendered %q, want %q`, got1, want)
+       }
+       if got1 != got2 {
+               t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2)
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/example_test.go b/tpl/internal/go_templates/htmltemplate/example_test.go
new file mode 100644 (file)
index 0000000..a3e7910
--- /dev/null
@@ -0,0 +1,184 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package template_test
+
+import (
+       "fmt"
+       "log"
+       "os"
+       "strings"
+
+       template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+)
+
+func Example() {
+       const tpl = `
+<!DOCTYPE html>
+<html>
+       <head>
+               <meta charset="UTF-8">
+               <title>{{.Title}}</title>
+       </head>
+       <body>
+               {{range .Items}}<div>{{ . }}</div>{{else}}<div><strong>no rows</strong></div>{{end}}
+       </body>
+</html>`
+
+       check := func(err error) {
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+       t, err := template.New("webpage").Parse(tpl)
+       check(err)
+
+       data := struct {
+               Title string
+               Items []string
+       }{
+               Title: "My page",
+               Items: []string{
+                       "My photos",
+                       "My blog",
+               },
+       }
+
+       err = t.Execute(os.Stdout, data)
+       check(err)
+
+       noItems := struct {
+               Title string
+               Items []string
+       }{
+               Title: "My another page",
+               Items: []string{},
+       }
+
+       err = t.Execute(os.Stdout, noItems)
+       check(err)
+
+       // Output:
+       // <!DOCTYPE html>
+       // <html>
+       //      <head>
+       //              <meta charset="UTF-8">
+       //              <title>My page</title>
+       //      </head>
+       //      <body>
+       //              <div>My photos</div><div>My blog</div>
+       //      </body>
+       // </html>
+       // <!DOCTYPE html>
+       // <html>
+       //      <head>
+       //              <meta charset="UTF-8">
+       //              <title>My another page</title>
+       //      </head>
+       //      <body>
+       //              <div><strong>no rows</strong></div>
+       //      </body>
+       // </html>
+
+}
+
+func Example_autoescaping() {
+       check := func(err error) {
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+       t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
+       check(err)
+       err = t.ExecuteTemplate(os.Stdout, "T", "<script>alert('you have been pwned')</script>")
+       check(err)
+       // Output:
+       // Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
+}
+
+func Example_escape() {
+       const s = `"Fran & Freddie's Diner" <tasty@example.com>`
+       v := []interface{}{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}
+
+       fmt.Println(template.HTMLEscapeString(s))
+       template.HTMLEscape(os.Stdout, []byte(s))
+       fmt.Fprintln(os.Stdout, "")
+       fmt.Println(template.HTMLEscaper(v...))
+
+       fmt.Println(template.JSEscapeString(s))
+       template.JSEscape(os.Stdout, []byte(s))
+       fmt.Fprintln(os.Stdout, "")
+       fmt.Println(template.JSEscaper(v...))
+
+       fmt.Println(template.URLQueryEscaper(v...))
+
+       // Output:
+       // &#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;
+       // &#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;
+       // &#34;Fran &amp; Freddie&#39;s Diner&#34;32&lt;tasty@example.com&gt;
+       // \"Fran & Freddie\'s Diner\" \x3Ctasty@example.com\x3E
+       // \"Fran & Freddie\'s Diner\" \x3Ctasty@example.com\x3E
+       // \"Fran & Freddie\'s Diner\"32\x3Ctasty@example.com\x3E
+       // %22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E
+
+}
+
+func ExampleTemplate_Delims() {
+       const text = "<<.Greeting>> {{.Name}}"
+
+       data := struct {
+               Greeting string
+               Name     string
+       }{
+               Greeting: "Hello",
+               Name:     "Joe",
+       }
+
+       t := template.Must(template.New("tpl").Delims("<<", ">>").Parse(text))
+
+       err := t.Execute(os.Stdout, data)
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       // Output:
+       // Hello {{.Name}}
+}
+
+// The following example is duplicated in text/template; keep them in sync.
+
+func ExampleTemplate_block() {
+       const (
+               master  = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
+               overlay = `{{define "list"}} {{join . ", "}}{{end}} `
+       )
+       var (
+               funcs     = template.FuncMap{"join": strings.Join}
+               guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
+       )
+       masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
+       if err != nil {
+               log.Fatal(err)
+       }
+       overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
+       if err != nil {
+               log.Fatal(err)
+       }
+       if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
+               log.Fatal(err)
+       }
+       if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
+               log.Fatal(err)
+       }
+       // Output:
+       // Names:
+       // - Gamora
+       // - Groot
+       // - Nebula
+       // - Rocket
+       // - Star-Lord
+       // Names: Gamora, Groot, Nebula, Rocket, Star-Lord
+}
diff --git a/tpl/internal/go_templates/htmltemplate/examplefiles_test.go b/tpl/internal/go_templates/htmltemplate/examplefiles_test.go
new file mode 100644 (file)
index 0000000..ae76318
--- /dev/null
@@ -0,0 +1,229 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package template_test
+
+import (
+       "io"
+       "io/ioutil"
+       "log"
+       "os"
+       "path/filepath"
+
+       template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+)
+
+// templateFile defines the contents of a template to be stored in a file, for testing.
+type templateFile struct {
+       name     string
+       contents string
+}
+
+func createTestDir(files []templateFile) string {
+       dir, err := ioutil.TempDir("", "template")
+       if err != nil {
+               log.Fatal(err)
+       }
+       for _, file := range files {
+               f, err := os.Create(filepath.Join(dir, file.name))
+               if err != nil {
+                       log.Fatal(err)
+               }
+               defer f.Close()
+               _, err = io.WriteString(f, file.contents)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+       return dir
+}
+
+// The following example is duplicated in text/template; keep them in sync.
+
+// Here we demonstrate loading a set of templates from a directory.
+func ExampleTemplate_glob() {
+       // Here we create a temporary directory and populate it with our sample
+       // template definition files; usually the template files would already
+       // exist in some location known to the program.
+       dir := createTestDir([]templateFile{
+               // T0.tmpl is a plain template file that just invokes T1.
+               {"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
+               // T1.tmpl defines a template, T1 that invokes T2.
+               {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+               // T2.tmpl defines a template T2.
+               {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
+       })
+       // Clean up after the test; another quirk of running as an example.
+       defer os.RemoveAll(dir)
+
+       // pattern is the glob pattern used to find all the template files.
+       pattern := filepath.Join(dir, "*.tmpl")
+
+       // Here starts the example proper.
+       // T0.tmpl is the first name matched, so it becomes the starting template,
+       // the value returned by ParseGlob.
+       tmpl := template.Must(template.ParseGlob(pattern))
+
+       err := tmpl.Execute(os.Stdout, nil)
+       if err != nil {
+               log.Fatalf("template execution: %s", err)
+       }
+       // Output:
+       // T0 invokes T1: (T1 invokes T2: (This is T2))
+}
+
+// Here we demonstrate loading a set of templates from files in different directories
+func ExampleTemplate_parsefiles() {
+       // Here we create different temporary directories and populate them with our sample
+       // template definition files; usually the template files would already
+       // exist in some location known to the program.
+       dir1 := createTestDir([]templateFile{
+               // T1.tmpl is a plain template file that just invokes T2.
+               {"T1.tmpl", `T1 invokes T2: ({{template "T2"}})`},
+       })
+
+       dir2 := createTestDir([]templateFile{
+               // T2.tmpl defines a template T2.
+               {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
+       })
+
+       // Clean up after the test; another quirk of running as an example.
+       defer func(dirs ...string) {
+               for _, dir := range dirs {
+                       os.RemoveAll(dir)
+               }
+       }(dir1, dir2)
+
+       // Here starts the example proper.
+       // Let's just parse only dir1/T0 and dir2/T2
+       paths := []string{
+               filepath.Join(dir1, "T1.tmpl"),
+               filepath.Join(dir2, "T2.tmpl"),
+       }
+       tmpl := template.Must(template.ParseFiles(paths...))
+
+       err := tmpl.Execute(os.Stdout, nil)
+       if err != nil {
+               log.Fatalf("template execution: %s", err)
+       }
+       // Output:
+       // T1 invokes T2: (This is T2)
+}
+
+// The following example is duplicated in text/template; keep them in sync.
+
+// This example demonstrates one way to share some templates
+// and use them in different contexts. In this variant we add multiple driver
+// templates by hand to an existing bundle of templates.
+func ExampleTemplate_helpers() {
+       // Here we create a temporary directory and populate it with our sample
+       // template definition files; usually the template files would already
+       // exist in some location known to the program.
+       dir := createTestDir([]templateFile{
+               // T1.tmpl defines a template, T1 that invokes T2.
+               {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+               // T2.tmpl defines a template T2.
+               {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
+       })
+       // Clean up after the test; another quirk of running as an example.
+       defer os.RemoveAll(dir)
+
+       // pattern is the glob pattern used to find all the template files.
+       pattern := filepath.Join(dir, "*.tmpl")
+
+       // Here starts the example proper.
+       // Load the helpers.
+       templates := template.Must(template.ParseGlob(pattern))
+       // Add one driver template to the bunch; we do this with an explicit template definition.
+       _, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
+       if err != nil {
+               log.Fatal("parsing driver1: ", err)
+       }
+       // Add another driver template.
+       _, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
+       if err != nil {
+               log.Fatal("parsing driver2: ", err)
+       }
+       // We load all the templates before execution. This package does not require
+       // that behavior but html/template's escaping does, so it's a good habit.
+       err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
+       if err != nil {
+               log.Fatalf("driver1 execution: %s", err)
+       }
+       err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
+       if err != nil {
+               log.Fatalf("driver2 execution: %s", err)
+       }
+       // Output:
+       // Driver 1 calls T1: (T1 invokes T2: (This is T2))
+       // Driver 2 calls T2: (This is T2)
+}
+
+// The following example is duplicated in text/template; keep them in sync.
+
+// This example demonstrates how to use one group of driver
+// templates with distinct sets of helper templates.
+func ExampleTemplate_share() {
+       // Here we create a temporary directory and populate it with our sample
+       // template definition files; usually the template files would already
+       // exist in some location known to the program.
+       dir := createTestDir([]templateFile{
+               // T0.tmpl is a plain template file that just invokes T1.
+               {"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
+               // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
+               {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+       })
+       // Clean up after the test; another quirk of running as an example.
+       defer os.RemoveAll(dir)
+
+       // pattern is the glob pattern used to find all the template files.
+       pattern := filepath.Join(dir, "*.tmpl")
+
+       // Here starts the example proper.
+       // Load the drivers.
+       drivers := template.Must(template.ParseGlob(pattern))
+
+       // We must define an implementation of the T2 template. First we clone
+       // the drivers, then add a definition of T2 to the template name space.
+
+       // 1. Clone the helper set to create a new name space from which to run them.
+       first, err := drivers.Clone()
+       if err != nil {
+               log.Fatal("cloning helpers: ", err)
+       }
+       // 2. Define T2, version A, and parse it.
+       _, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
+       if err != nil {
+               log.Fatal("parsing T2: ", err)
+       }
+
+       // Now repeat the whole thing, using a different version of T2.
+       // 1. Clone the drivers.
+       second, err := drivers.Clone()
+       if err != nil {
+               log.Fatal("cloning drivers: ", err)
+       }
+       // 2. Define T2, version B, and parse it.
+       _, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
+       if err != nil {
+               log.Fatal("parsing T2: ", err)
+       }
+
+       // Execute the templates in the reverse order to verify the
+       // first is unaffected by the second.
+       err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
+       if err != nil {
+               log.Fatalf("second execution: %s", err)
+       }
+       err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
+       if err != nil {
+               log.Fatalf("first: execution: %s", err)
+       }
+
+       // Output:
+       // T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
+       // T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
+}
diff --git a/tpl/internal/go_templates/htmltemplate/html.go b/tpl/internal/go_templates/htmltemplate/html.go
new file mode 100644 (file)
index 0000000..13a0cd0
--- /dev/null
@@ -0,0 +1,266 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "fmt"
+       "strings"
+       "unicode/utf8"
+)
+
+// htmlNospaceEscaper escapes for inclusion in unquoted attribute values.
+func htmlNospaceEscaper(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeHTML {
+               return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false)
+       }
+       return htmlReplacer(s, htmlNospaceReplacementTable, false)
+}
+
+// attrEscaper escapes for inclusion in quoted attribute values.
+func attrEscaper(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeHTML {
+               return htmlReplacer(stripTags(s), htmlNormReplacementTable, true)
+       }
+       return htmlReplacer(s, htmlReplacementTable, true)
+}
+
+// rcdataEscaper escapes for inclusion in an RCDATA element body.
+func rcdataEscaper(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeHTML {
+               return htmlReplacer(s, htmlNormReplacementTable, true)
+       }
+       return htmlReplacer(s, htmlReplacementTable, true)
+}
+
+// htmlEscaper escapes for inclusion in HTML text.
+func htmlEscaper(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeHTML {
+               return s
+       }
+       return htmlReplacer(s, htmlReplacementTable, true)
+}
+
+// htmlReplacementTable contains the runes that need to be escaped
+// inside a quoted attribute value or in a text node.
+var htmlReplacementTable = []string{
+       // https://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state
+       // U+0000 NULL Parse error. Append a U+FFFD REPLACEMENT
+       // CHARACTER character to the current attribute's value.
+       // "
+       // and similarly
+       // https://www.w3.org/TR/html5/syntax.html#before-attribute-value-state
+       0:    "\uFFFD",
+       '"':  "&#34;",
+       '&':  "&amp;",
+       '\'': "&#39;",
+       '+':  "&#43;",
+       '<':  "&lt;",
+       '>':  "&gt;",
+}
+
+// htmlNormReplacementTable is like htmlReplacementTable but without '&' to
+// avoid over-encoding existing entities.
+var htmlNormReplacementTable = []string{
+       0:    "\uFFFD",
+       '"':  "&#34;",
+       '\'': "&#39;",
+       '+':  "&#43;",
+       '<':  "&lt;",
+       '>':  "&gt;",
+}
+
+// htmlNospaceReplacementTable contains the runes that need to be escaped
+// inside an unquoted attribute value.
+// The set of runes escaped is the union of the HTML specials and
+// those determined by running the JS below in browsers:
+// <div id=d></div>
+// <script>(function () {
+// var a = [], d = document.getElementById("d"), i, c, s;
+// for (i = 0; i < 0x10000; ++i) {
+//   c = String.fromCharCode(i);
+//   d.innerHTML = "<span title=" + c + "lt" + c + "></span>"
+//   s = d.getElementsByTagName("SPAN")[0];
+//   if (!s || s.title !== c + "lt" + c) { a.push(i.toString(16)); }
+// }
+// document.write(a.join(", "));
+// })()</script>
+var htmlNospaceReplacementTable = []string{
+       0:    "&#xfffd;",
+       '\t': "&#9;",
+       '\n': "&#10;",
+       '\v': "&#11;",
+       '\f': "&#12;",
+       '\r': "&#13;",
+       ' ':  "&#32;",
+       '"':  "&#34;",
+       '&':  "&amp;",
+       '\'': "&#39;",
+       '+':  "&#43;",
+       '<':  "&lt;",
+       '=':  "&#61;",
+       '>':  "&gt;",
+       // A parse error in the attribute value (unquoted) and
+       // before attribute value states.
+       // Treated as a quoting character by IE.
+       '`': "&#96;",
+}
+
+// htmlNospaceNormReplacementTable is like htmlNospaceReplacementTable but
+// without '&' to avoid over-encoding existing entities.
+var htmlNospaceNormReplacementTable = []string{
+       0:    "&#xfffd;",
+       '\t': "&#9;",
+       '\n': "&#10;",
+       '\v': "&#11;",
+       '\f': "&#12;",
+       '\r': "&#13;",
+       ' ':  "&#32;",
+       '"':  "&#34;",
+       '\'': "&#39;",
+       '+':  "&#43;",
+       '<':  "&lt;",
+       '=':  "&#61;",
+       '>':  "&gt;",
+       // A parse error in the attribute value (unquoted) and
+       // before attribute value states.
+       // Treated as a quoting character by IE.
+       '`': "&#96;",
+}
+
+// htmlReplacer returns s with runes replaced according to replacementTable
+// and when badRunes is true, certain bad runes are allowed through unescaped.
+func htmlReplacer(s string, replacementTable []string, badRunes bool) string {
+       written, b := 0, new(strings.Builder)
+       r, w := rune(0), 0
+       for i := 0; i < len(s); i += w {
+               // Cannot use 'for range s' because we need to preserve the width
+               // of the runes in the input. If we see a decoding error, the input
+               // width will not be utf8.Runelen(r) and we will overrun the buffer.
+               r, w = utf8.DecodeRuneInString(s[i:])
+               if int(r) < len(replacementTable) {
+                       if repl := replacementTable[r]; len(repl) != 0 {
+                               if written == 0 {
+                                       b.Grow(len(s))
+                               }
+                               b.WriteString(s[written:i])
+                               b.WriteString(repl)
+                               written = i + w
+                       }
+               } else if badRunes {
+                       // No-op.
+                       // IE does not allow these ranges in unquoted attrs.
+               } else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff {
+                       if written == 0 {
+                               b.Grow(len(s))
+                       }
+                       fmt.Fprintf(b, "%s&#x%x;", s[written:i], r)
+                       written = i + w
+               }
+       }
+       if written == 0 {
+               return s
+       }
+       b.WriteString(s[written:])
+       return b.String()
+}
+
+// stripTags takes a snippet of HTML and returns only the text content.
+// For example, `<b>&iexcl;Hi!</b> <script>...</script>` -> `&iexcl;Hi! `.
+func stripTags(html string) string {
+       var b bytes.Buffer
+       s, c, i, allText := []byte(html), context{}, 0, true
+       // Using the transition funcs helps us avoid mangling
+       // `<div title="1>2">` or `I <3 Ponies!`.
+       for i != len(s) {
+               if c.delim == delimNone {
+                       st := c.state
+                       // Use RCDATA instead of parsing into JS or CSS styles.
+                       if c.element != elementNone && !isInTag(st) {
+                               st = stateRCDATA
+                       }
+                       d, nread := transitionFunc[st](c, s[i:])
+                       i1 := i + nread
+                       if c.state == stateText || c.state == stateRCDATA {
+                               // Emit text up to the start of the tag or comment.
+                               j := i1
+                               if d.state != c.state {
+                                       for j1 := j - 1; j1 >= i; j1-- {
+                                               if s[j1] == '<' {
+                                                       j = j1
+                                                       break
+                                               }
+                                       }
+                               }
+                               b.Write(s[i:j])
+                       } else {
+                               allText = false
+                       }
+                       c, i = d, i1
+                       continue
+               }
+               i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim])
+               if i1 < i {
+                       break
+               }
+               if c.delim != delimSpaceOrTagEnd {
+                       // Consume any quote.
+                       i1++
+               }
+               c, i = context{state: stateTag, element: c.element}, i1
+       }
+       if allText {
+               return html
+       } else if c.state == stateText || c.state == stateRCDATA {
+               b.Write(s[i:])
+       }
+       return b.String()
+}
+
+// htmlNameFilter accepts valid parts of an HTML attribute or tag name or
+// a known-safe HTML attribute.
+func htmlNameFilter(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeHTMLAttr {
+               return s
+       }
+       if len(s) == 0 {
+               // Avoid violation of structure preservation.
+               // <input checked {{.K}}={{.V}}>.
+               // Without this, if .K is empty then .V is the value of
+               // checked, but otherwise .V is the value of the attribute
+               // named .K.
+               return filterFailsafe
+       }
+       s = strings.ToLower(s)
+       if t := attrType(s); t != contentTypePlain {
+               // TODO: Split attr and element name part filters so we can whitelist
+               // attributes.
+               return filterFailsafe
+       }
+       for _, r := range s {
+               switch {
+               case '0' <= r && r <= '9':
+               case 'a' <= r && r <= 'z':
+               default:
+                       return filterFailsafe
+               }
+       }
+       return s
+}
+
+// commentEscaper returns the empty string regardless of input.
+// Comment content does not correspond to any parsed structure or
+// human-readable content, so the simplest and most secure policy is to drop
+// content interpolated into comments.
+// This approach is equally valid whether or not static comment content is
+// removed from the template.
+func commentEscaper(args ...interface{}) string {
+       return ""
+}
diff --git a/tpl/internal/go_templates/htmltemplate/html_test.go b/tpl/internal/go_templates/htmltemplate/html_test.go
new file mode 100644 (file)
index 0000000..9462218
--- /dev/null
@@ -0,0 +1,99 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "html"
+       "strings"
+       "testing"
+)
+
+func TestHTMLNospaceEscaper(t *testing.T) {
+       input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
+               "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+               ` !"#$%&'()*+,-./` +
+               `0123456789:;<=>?` +
+               `@ABCDEFGHIJKLMNO` +
+               `PQRSTUVWXYZ[\]^_` +
+               "`abcdefghijklmno" +
+               "pqrstuvwxyz{|}~\x7f" +
+               "\u00A0\u0100\u2028\u2029\ufeff\ufdec\U0001D11E" +
+               "erroneous\x960") // keep at the end
+
+       want := ("&#xfffd;\x01\x02\x03\x04\x05\x06\x07" +
+               "\x08&#9;&#10;&#11;&#12;&#13;\x0E\x0F" +
+               "\x10\x11\x12\x13\x14\x15\x16\x17" +
+               "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+               `&#32;!&#34;#$%&amp;&#39;()*&#43;,-./` +
+               `0123456789:;&lt;&#61;&gt;?` +
+               `@ABCDEFGHIJKLMNO` +
+               `PQRSTUVWXYZ[\]^_` +
+               `&#96;abcdefghijklmno` +
+               `pqrstuvwxyz{|}~` + "\u007f" +
+               "\u00A0\u0100\u2028\u2029\ufeff&#xfdec;\U0001D11E" +
+               "erroneous&#xfffd;0") // keep at the end
+
+       got := htmlNospaceEscaper(input)
+       if got != want {
+               t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got)
+       }
+
+       r := strings.NewReplacer("\x00", "\ufffd", "\x96", "\ufffd")
+       got, want = html.UnescapeString(got), r.Replace(input)
+       if want != got {
+               t.Errorf("decode: want\n\t%q\nbut got\n\t%q", want, got)
+       }
+}
+
+func TestStripTags(t *testing.T) {
+       tests := []struct {
+               input, want string
+       }{
+               {"", ""},
+               {"Hello, World!", "Hello, World!"},
+               {"foo&amp;bar", "foo&amp;bar"},
+               {`Hello <a href="www.example.com/">World</a>!`, "Hello World!"},
+               {"Foo <textarea>Bar</textarea> Baz", "Foo Bar Baz"},
+               {"Foo <!-- Bar --> Baz", "Foo  Baz"},
+               {"<", "<"},
+               {"foo < bar", "foo < bar"},
+               {`Foo<script type="text/javascript">alert(1337)</script>Bar`, "FooBar"},
+               {`Foo<div title="1>2">Bar`, "FooBar"},
+               {`I <3 Ponies!`, `I <3 Ponies!`},
+               {`<script>foo()</script>`, ``},
+       }
+
+       for _, test := range tests {
+               if got := stripTags(test.input); got != test.want {
+                       t.Errorf("%q: want %q, got %q", test.input, test.want, got)
+               }
+       }
+}
+
+func BenchmarkHTMLNospaceEscaper(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               htmlNospaceEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
+       }
+}
+
+func BenchmarkHTMLNospaceEscaperNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               htmlNospaceEscaper("The_quick,_brown_fox_jumps_over_the_lazy_dog.")
+       }
+}
+
+func BenchmarkStripTags(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               stripTags("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
+       }
+}
+
+func BenchmarkStripTagsNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               stripTags("The quick, brown fox jumps over the lazy dog.")
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/hugo_template.go b/tpl/internal/go_templates/htmltemplate/hugo_template.go
new file mode 100644 (file)
index 0000000..117d85e
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package template
+
+import (
+       template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+)
+
+/*
+
+This files contains the Hugo related addons. All the other files in this
+package is auto generated.
+
+*/
+
+// Prepare returns a template ready for execution.
+func (t *Template) Prepare() (*template.Template, error) {
+       if err := t.escape(); err != nil {
+               return nil, err
+       }
+       return t.text, nil
+}
diff --git a/tpl/internal/go_templates/htmltemplate/js.go b/tpl/internal/go_templates/htmltemplate/js.go
new file mode 100644 (file)
index 0000000..57622d1
--- /dev/null
@@ -0,0 +1,418 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "encoding/json"
+       "fmt"
+       htmltemplate "html/template"
+       "reflect"
+       "strings"
+       "unicode/utf8"
+)
+
+// nextJSCtx returns the context that determines whether a slash after the
+// given run of tokens starts a regular expression instead of a division
+// operator: / or /=.
+//
+// This assumes that the token run does not include any string tokens, comment
+// tokens, regular expression literal tokens, or division operators.
+//
+// This fails on some valid but nonsensical JavaScript programs like
+// "x = ++/foo/i" which is quite different than "x++/foo/i", but is not known to
+// fail on any known useful programs. It is based on the draft
+// JavaScript 2.0 lexical grammar and requires one token of lookbehind:
+// https://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html
+func nextJSCtx(s []byte, preceding jsCtx) jsCtx {
+       s = bytes.TrimRight(s, "\t\n\f\r \u2028\u2029")
+       if len(s) == 0 {
+               return preceding
+       }
+
+       // All cases below are in the single-byte UTF-8 group.
+       switch c, n := s[len(s)-1], len(s); c {
+       case '+', '-':
+               // ++ and -- are not regexp preceders, but + and - are whether
+               // they are used as infix or prefix operators.
+               start := n - 1
+               // Count the number of adjacent dashes or pluses.
+               for start > 0 && s[start-1] == c {
+                       start--
+               }
+               if (n-start)&1 == 1 {
+                       // Reached for trailing minus signs since "---" is the
+                       // same as "-- -".
+                       return jsCtxRegexp
+               }
+               return jsCtxDivOp
+       case '.':
+               // Handle "42."
+               if n != 1 && '0' <= s[n-2] && s[n-2] <= '9' {
+                       return jsCtxDivOp
+               }
+               return jsCtxRegexp
+       // Suffixes for all punctuators from section 7.7 of the language spec
+       // that only end binary operators not handled above.
+       case ',', '<', '>', '=', '*', '%', '&', '|', '^', '?':
+               return jsCtxRegexp
+       // Suffixes for all punctuators from section 7.7 of the language spec
+       // that are prefix operators not handled above.
+       case '!', '~':
+               return jsCtxRegexp
+       // Matches all the punctuators from section 7.7 of the language spec
+       // that are open brackets not handled above.
+       case '(', '[':
+               return jsCtxRegexp
+       // Matches all the punctuators from section 7.7 of the language spec
+       // that precede expression starts.
+       case ':', ';', '{':
+               return jsCtxRegexp
+       // CAVEAT: the close punctuators ('}', ']', ')') precede div ops and
+       // are handled in the default except for '}' which can precede a
+       // division op as in
+       //    ({ valueOf: function () { return 42 } } / 2
+       // which is valid, but, in practice, developers don't divide object
+       // literals, so our heuristic works well for code like
+       //    function () { ... }  /foo/.test(x) && sideEffect();
+       // The ')' punctuator can precede a regular expression as in
+       //     if (b) /foo/.test(x) && ...
+       // but this is much less likely than
+       //     (a + b) / c
+       case '}':
+               return jsCtxRegexp
+       default:
+               // Look for an IdentifierName and see if it is a keyword that
+               // can precede a regular expression.
+               j := n
+               for j > 0 && isJSIdentPart(rune(s[j-1])) {
+                       j--
+               }
+               if regexpPrecederKeywords[string(s[j:])] {
+                       return jsCtxRegexp
+               }
+       }
+       // Otherwise is a punctuator not listed above, or
+       // a string which precedes a div op, or an identifier
+       // which precedes a div op.
+       return jsCtxDivOp
+}
+
+// regexpPrecederKeywords is a set of reserved JS keywords that can precede a
+// regular expression in JS source.
+var regexpPrecederKeywords = map[string]bool{
+       "break":      true,
+       "case":       true,
+       "continue":   true,
+       "delete":     true,
+       "do":         true,
+       "else":       true,
+       "finally":    true,
+       "in":         true,
+       "instanceof": true,
+       "return":     true,
+       "throw":      true,
+       "try":        true,
+       "typeof":     true,
+       "void":       true,
+}
+
+var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
+
+// indirectToJSONMarshaler returns the value, after dereferencing as many times
+// as necessary to reach the base type (or nil) or an implementation of json.Marshal.
+func indirectToJSONMarshaler(a interface{}) interface{} {
+       // text/template now supports passing untyped nil as a func call
+       // argument, so we must support it. Otherwise we'd panic below, as one
+       // cannot call the Type or Interface methods on an invalid
+       // reflect.Value. See golang.org/issue/18716.
+       if a == nil {
+               return nil
+       }
+
+       v := reflect.ValueOf(a)
+       for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
+               v = v.Elem()
+       }
+       return v.Interface()
+}
+
+// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
+// neither side-effects nor free variables outside (NaN, Infinity).
+func jsValEscaper(args ...interface{}) string {
+       var a interface{}
+       if len(args) == 1 {
+               a = indirectToJSONMarshaler(args[0])
+               switch t := a.(type) {
+               case htmltemplate.JS:
+                       return string(t)
+               case htmltemplate.JSStr:
+                       // TODO: normalize quotes.
+                       return `"` + string(t) + `"`
+               case json.Marshaler:
+                       // Do not treat as a Stringer.
+               case fmt.Stringer:
+                       a = t.String()
+               }
+       } else {
+               for i, arg := range args {
+                       args[i] = indirectToJSONMarshaler(arg)
+               }
+               a = fmt.Sprint(args...)
+       }
+       // TODO: detect cycles before calling Marshal which loops infinitely on
+       // cyclic data. This may be an unacceptable DoS risk.
+
+       b, err := json.Marshal(a)
+       if err != nil {
+               // Put a space before comment so that if it is flush against
+               // a division operator it is not turned into a line comment:
+               //     x/{{y}}
+               // turning into
+               //     x//* error marshaling y:
+               //          second line of error message */null
+               return fmt.Sprintf(" /* %s */null ", strings.ReplaceAll(err.Error(), "*/", "* /"))
+       }
+
+       // TODO: maybe post-process output to prevent it from containing
+       // "<!--", "-->", "<![CDATA[", "]]>", or "</script"
+       // in case custom marshalers produce output containing those.
+
+       // TODO: Maybe abbreviate \u00ab to \xab to produce more compact output.
+       if len(b) == 0 {
+               // In, `x=y/{{.}}*z` a json.Marshaler that produces "" should
+               // not cause the output `x=y/*z`.
+               return " null "
+       }
+       first, _ := utf8.DecodeRune(b)
+       last, _ := utf8.DecodeLastRune(b)
+       var buf strings.Builder
+       // Prevent IdentifierNames and NumericLiterals from running into
+       // keywords: in, instanceof, typeof, void
+       pad := isJSIdentPart(first) || isJSIdentPart(last)
+       if pad {
+               buf.WriteByte(' ')
+       }
+       written := 0
+       // Make sure that json.Marshal escapes codepoints U+2028 & U+2029
+       // so it falls within the subset of JSON which is valid JS.
+       for i := 0; i < len(b); {
+               rune, n := utf8.DecodeRune(b[i:])
+               repl := ""
+               if rune == 0x2028 {
+                       repl = `\u2028`
+               } else if rune == 0x2029 {
+                       repl = `\u2029`
+               }
+               if repl != "" {
+                       buf.Write(b[written:i])
+                       buf.WriteString(repl)
+                       written = i + n
+               }
+               i += n
+       }
+       if buf.Len() != 0 {
+               buf.Write(b[written:])
+               if pad {
+                       buf.WriteByte(' ')
+               }
+               return buf.String()
+       }
+       return string(b)
+}
+
+// jsStrEscaper produces a string that can be included between quotes in
+// JavaScript source, in JavaScript embedded in an HTML5 <script> element,
+// or in an HTML5 event handler attribute such as onclick.
+func jsStrEscaper(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeJSStr {
+               return replace(s, jsStrNormReplacementTable)
+       }
+       return replace(s, jsStrReplacementTable)
+}
+
+// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
+// specials so the result is treated literally when included in a regular
+// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
+// the literal text of {{.X}} followed by the string "bar".
+func jsRegexpEscaper(args ...interface{}) string {
+       s, _ := stringify(args...)
+       s = replace(s, jsRegexpReplacementTable)
+       if s == "" {
+               // /{{.X}}/ should not produce a line comment when .X == "".
+               return "(?:)"
+       }
+       return s
+}
+
+// replace replaces each rune r of s with replacementTable[r], provided that
+// r < len(replacementTable). If replacementTable[r] is the empty string then
+// no replacement is made.
+// It also replaces runes U+2028 and U+2029 with the raw strings `\u2028` and
+// `\u2029`.
+func replace(s string, replacementTable []string) string {
+       var b strings.Builder
+       r, w, written := rune(0), 0, 0
+       for i := 0; i < len(s); i += w {
+               // See comment in htmlEscaper.
+               r, w = utf8.DecodeRuneInString(s[i:])
+               var repl string
+               switch {
+               case int(r) < len(replacementTable) && replacementTable[r] != "":
+                       repl = replacementTable[r]
+               case r == '\u2028':
+                       repl = `\u2028`
+               case r == '\u2029':
+                       repl = `\u2029`
+               default:
+                       continue
+               }
+               if written == 0 {
+                       b.Grow(len(s))
+               }
+               b.WriteString(s[written:i])
+               b.WriteString(repl)
+               written = i + w
+       }
+       if written == 0 {
+               return s
+       }
+       b.WriteString(s[written:])
+       return b.String()
+}
+
+var jsStrReplacementTable = []string{
+       0:    `\0`,
+       '\t': `\t`,
+       '\n': `\n`,
+       '\v': `\x0b`, // "\v" == "v" on IE 6.
+       '\f': `\f`,
+       '\r': `\r`,
+       // Encode HTML specials as hex so the output can be embedded
+       // in HTML attributes without further encoding.
+       '"':  `\x22`,
+       '&':  `\x26`,
+       '\'': `\x27`,
+       '+':  `\x2b`,
+       '/':  `\/`,
+       '<':  `\x3c`,
+       '>':  `\x3e`,
+       '\\': `\\`,
+}
+
+// jsStrNormReplacementTable is like jsStrReplacementTable but does not
+// overencode existing escapes since this table has no entry for `\`.
+var jsStrNormReplacementTable = []string{
+       0:    `\0`,
+       '\t': `\t`,
+       '\n': `\n`,
+       '\v': `\x0b`, // "\v" == "v" on IE 6.
+       '\f': `\f`,
+       '\r': `\r`,
+       // Encode HTML specials as hex so the output can be embedded
+       // in HTML attributes without further encoding.
+       '"':  `\x22`,
+       '&':  `\x26`,
+       '\'': `\x27`,
+       '+':  `\x2b`,
+       '/':  `\/`,
+       '<':  `\x3c`,
+       '>':  `\x3e`,
+}
+
+var jsRegexpReplacementTable = []string{
+       0:    `\0`,
+       '\t': `\t`,
+       '\n': `\n`,
+       '\v': `\x0b`, // "\v" == "v" on IE 6.
+       '\f': `\f`,
+       '\r': `\r`,
+       // Encode HTML specials as hex so the output can be embedded
+       // in HTML attributes without further encoding.
+       '"':  `\x22`,
+       '$':  `\$`,
+       '&':  `\x26`,
+       '\'': `\x27`,
+       '(':  `\(`,
+       ')':  `\)`,
+       '*':  `\*`,
+       '+':  `\x2b`,
+       '-':  `\-`,
+       '.':  `\.`,
+       '/':  `\/`,
+       '<':  `\x3c`,
+       '>':  `\x3e`,
+       '?':  `\?`,
+       '[':  `\[`,
+       '\\': `\\`,
+       ']':  `\]`,
+       '^':  `\^`,
+       '{':  `\{`,
+       '|':  `\|`,
+       '}':  `\}`,
+}
+
+// isJSIdentPart reports whether the given rune is a JS identifier part.
+// It does not handle all the non-Latin letters, joiners, and combining marks,
+// but it does handle every codepoint that can occur in a numeric literal or
+// a keyword.
+func isJSIdentPart(r rune) bool {
+       switch {
+       case r == '$':
+               return true
+       case '0' <= r && r <= '9':
+               return true
+       case 'A' <= r && r <= 'Z':
+               return true
+       case r == '_':
+               return true
+       case 'a' <= r && r <= 'z':
+               return true
+       }
+       return false
+}
+
+// isJSType reports whether the given MIME type should be considered JavaScript.
+//
+// It is used to determine whether a script tag with a type attribute is a javascript container.
+func isJSType(mimeType string) bool {
+       // per
+       //   https://www.w3.org/TR/html5/scripting-1.html#attr-script-type
+       //   https://tools.ietf.org/html/rfc7231#section-3.1.1
+       //   https://tools.ietf.org/html/rfc4329#section-3
+       //   https://www.ietf.org/rfc/rfc4627.txt
+       mimeType = strings.ToLower(mimeType)
+       // discard parameters
+       if i := strings.Index(mimeType, ";"); i >= 0 {
+               mimeType = mimeType[:i]
+       }
+       mimeType = strings.TrimSpace(mimeType)
+       switch mimeType {
+       case
+               "application/ecmascript",
+               "application/javascript",
+               "application/json",
+               "application/ld+json",
+               "application/x-ecmascript",
+               "application/x-javascript",
+               "module",
+               "text/ecmascript",
+               "text/javascript",
+               "text/javascript1.0",
+               "text/javascript1.1",
+               "text/javascript1.2",
+               "text/javascript1.3",
+               "text/javascript1.4",
+               "text/javascript1.5",
+               "text/jscript",
+               "text/livescript",
+               "text/x-ecmascript",
+               "text/x-javascript":
+               return true
+       default:
+               return false
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/js_test.go b/tpl/internal/go_templates/htmltemplate/js_test.go
new file mode 100644 (file)
index 0000000..0a6f332
--- /dev/null
@@ -0,0 +1,425 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "bytes"
+       "math"
+       "strings"
+       "testing"
+)
+
+func TestNextJsCtx(t *testing.T) {
+       tests := []struct {
+               jsCtx jsCtx
+               s     string
+       }{
+               // Statement terminators precede regexps.
+               {jsCtxRegexp, ";"},
+               // This is not airtight.
+               //     ({ valueOf: function () { return 1 } } / 2)
+               // is valid JavaScript but in practice, devs do not do this.
+               // A block followed by a statement starting with a RegExp is
+               // much more common:
+               //     while (x) {...} /foo/.test(x) || panic()
+               {jsCtxRegexp, "}"},
+               // But member, call, grouping, and array expression terminators
+               // precede div ops.
+               {jsCtxDivOp, ")"},
+               {jsCtxDivOp, "]"},
+               // At the start of a primary expression, array, or expression
+               // statement, expect a regexp.
+               {jsCtxRegexp, "("},
+               {jsCtxRegexp, "["},
+               {jsCtxRegexp, "{"},
+               // Assignment operators precede regexps as do all exclusively
+               // prefix and binary operators.
+               {jsCtxRegexp, "="},
+               {jsCtxRegexp, "+="},
+               {jsCtxRegexp, "*="},
+               {jsCtxRegexp, "*"},
+               {jsCtxRegexp, "!"},
+               // Whether the + or - is infix or prefix, it cannot precede a
+               // div op.
+               {jsCtxRegexp, "+"},
+               {jsCtxRegexp, "-"},
+               // An incr/decr op precedes a div operator.
+               // This is not airtight. In (g = ++/h/i) a regexp follows a
+               // pre-increment operator, but in practice devs do not try to
+               // increment or decrement regular expressions.
+               // (g++/h/i) where ++ is a postfix operator on g is much more
+               // common.
+               {jsCtxDivOp, "--"},
+               {jsCtxDivOp, "++"},
+               {jsCtxDivOp, "x--"},
+               // When we have many dashes or pluses, then they are grouped
+               // left to right.
+               {jsCtxRegexp, "x---"}, // A postfix -- then a -.
+               // return followed by a slash returns the regexp literal or the
+               // slash starts a regexp literal in an expression statement that
+               // is dead code.
+               {jsCtxRegexp, "return"},
+               {jsCtxRegexp, "return "},
+               {jsCtxRegexp, "return\t"},
+               {jsCtxRegexp, "return\n"},
+               {jsCtxRegexp, "return\u2028"},
+               // Identifiers can be divided and cannot validly be preceded by
+               // a regular expressions. Semicolon insertion cannot happen
+               // between an identifier and a regular expression on a new line
+               // because the one token lookahead for semicolon insertion has
+               // to conclude that it could be a div binary op and treat it as
+               // such.
+               {jsCtxDivOp, "x"},
+               {jsCtxDivOp, "x "},
+               {jsCtxDivOp, "x\t"},
+               {jsCtxDivOp, "x\n"},
+               {jsCtxDivOp, "x\u2028"},
+               {jsCtxDivOp, "preturn"},
+               // Numbers precede div ops.
+               {jsCtxDivOp, "0"},
+               // Dots that are part of a number are div preceders.
+               {jsCtxDivOp, "0."},
+       }
+
+       for _, test := range tests {
+               if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx {
+                       t.Errorf("want %s got %q", test.jsCtx, test.s)
+               }
+               if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx {
+                       t.Errorf("want %s got %q", test.jsCtx, test.s)
+               }
+       }
+
+       if nextJSCtx([]byte("   "), jsCtxRegexp) != jsCtxRegexp {
+               t.Error("Blank tokens")
+       }
+
+       if nextJSCtx([]byte("   "), jsCtxDivOp) != jsCtxDivOp {
+               t.Error("Blank tokens")
+       }
+}
+
+func TestJSValEscaper(t *testing.T) {
+       tests := []struct {
+               x  interface{}
+               js string
+       }{
+               {int(42), " 42 "},
+               {uint(42), " 42 "},
+               {int16(42), " 42 "},
+               {uint16(42), " 42 "},
+               {int32(-42), " -42 "},
+               {uint32(42), " 42 "},
+               {int16(-42), " -42 "},
+               {uint16(42), " 42 "},
+               {int64(-42), " -42 "},
+               {uint64(42), " 42 "},
+               {uint64(1) << 53, " 9007199254740992 "},
+               // ulp(1 << 53) > 1 so this loses precision in JS
+               // but it is still a representable integer literal.
+               {uint64(1)<<53 + 1, " 9007199254740993 "},
+               {float32(1.0), " 1 "},
+               {float32(-1.0), " -1 "},
+               {float32(0.5), " 0.5 "},
+               {float32(-0.5), " -0.5 "},
+               {float32(1.0) / float32(256), " 0.00390625 "},
+               {float32(0), " 0 "},
+               {math.Copysign(0, -1), " -0 "},
+               {float64(1.0), " 1 "},
+               {float64(-1.0), " -1 "},
+               {float64(0.5), " 0.5 "},
+               {float64(-0.5), " -0.5 "},
+               {float64(0), " 0 "},
+               {math.Copysign(0, -1), " -0 "},
+               {"", `""`},
+               {"foo", `"foo"`},
+               // Newlines.
+               {"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
+               // "\v" == "v" on IE 6 so use "\x0b" instead.
+               {"\t\x0b", `"\t\u000b"`},
+               {struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
+               {[]interface{}{}, "[]"},
+               {[]interface{}{42, "foo", nil}, `[42,"foo",null]`},
+               {[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
+               {"<!--", `"\u003c!--"`},
+               {"-->", `"--\u003e"`},
+               {"<![CDATA[", `"\u003c![CDATA["`},
+               {"]]>", `"]]\u003e"`},
+               {"</script", `"\u003c/script"`},
+               {"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
+               {nil, " null "},
+       }
+
+       for _, test := range tests {
+               if js := jsValEscaper(test.x); js != test.js {
+                       t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
+               }
+               // Make sure that escaping corner cases are not broken
+               // by nesting.
+               a := []interface{}{test.x}
+               want := "[" + strings.TrimSpace(test.js) + "]"
+               if js := jsValEscaper(a); js != want {
+                       t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
+               }
+       }
+}
+
+func TestJSStrEscaper(t *testing.T) {
+       tests := []struct {
+               x   interface{}
+               esc string
+       }{
+               {"", ``},
+               {"foo", `foo`},
+               {"\u0000", `\0`},
+               {"\t", `\t`},
+               {"\n", `\n`},
+               {"\r", `\r`},
+               {"\u2028", `\u2028`},
+               {"\u2029", `\u2029`},
+               {"\\", `\\`},
+               {"\\n", `\\n`},
+               {"foo\r\nbar", `foo\r\nbar`},
+               // Preserve attribute boundaries.
+               {`"`, `\x22`},
+               {`'`, `\x27`},
+               // Allow embedding in HTML without further escaping.
+               {`&amp;`, `\x26amp;`},
+               // Prevent breaking out of text node and element boundaries.
+               {"</script>", `\x3c\/script\x3e`},
+               {"<![CDATA[", `\x3c![CDATA[`},
+               {"]]>", `]]\x3e`},
+               // https://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
+               //   "The text in style, script, title, and textarea elements
+               //   must not have an escaping text span start that is not
+               //   followed by an escaping text span end."
+               // Furthermore, spoofing an escaping text span end could lead
+               // to different interpretation of a </script> sequence otherwise
+               // masked by the escaping text span, and spoofing a start could
+               // allow regular text content to be interpreted as script
+               // allowing script execution via a combination of a JS string
+               // injection followed by an HTML text injection.
+               {"<!--", `\x3c!--`},
+               {"-->", `--\x3e`},
+               // From https://code.google.com/p/doctype/wiki/ArticleUtf7
+               {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
+                       `\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`,
+               },
+               // Invalid UTF-8 sequence
+               {"foo\xA0bar", "foo\xA0bar"},
+               // Invalid unicode scalar value.
+               {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
+       }
+
+       for _, test := range tests {
+               esc := jsStrEscaper(test.x)
+               if esc != test.esc {
+                       t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
+               }
+       }
+}
+
+func TestJSRegexpEscaper(t *testing.T) {
+       tests := []struct {
+               x   interface{}
+               esc string
+       }{
+               {"", `(?:)`},
+               {"foo", `foo`},
+               {"\u0000", `\0`},
+               {"\t", `\t`},
+               {"\n", `\n`},
+               {"\r", `\r`},
+               {"\u2028", `\u2028`},
+               {"\u2029", `\u2029`},
+               {"\\", `\\`},
+               {"\\n", `\\n`},
+               {"foo\r\nbar", `foo\r\nbar`},
+               // Preserve attribute boundaries.
+               {`"`, `\x22`},
+               {`'`, `\x27`},
+               // Allow embedding in HTML without further escaping.
+               {`&amp;`, `\x26amp;`},
+               // Prevent breaking out of text node and element boundaries.
+               {"</script>", `\x3c\/script\x3e`},
+               {"<![CDATA[", `\x3c!\[CDATA\[`},
+               {"]]>", `\]\]\x3e`},
+               // Escaping text spans.
+               {"<!--", `\x3c!\-\-`},
+               {"-->", `\-\-\x3e`},
+               {"*", `\*`},
+               {"+", `\x2b`},
+               {"?", `\?`},
+               {"[](){}", `\[\]\(\)\{\}`},
+               {"$foo|x.y", `\$foo\|x\.y`},
+               {"x^y", `x\^y`},
+       }
+
+       for _, test := range tests {
+               esc := jsRegexpEscaper(test.x)
+               if esc != test.esc {
+                       t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
+               }
+       }
+}
+
+func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
+       input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
+               "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+               ` !"#$%&'()*+,-./` +
+               `0123456789:;<=>?` +
+               `@ABCDEFGHIJKLMNO` +
+               `PQRSTUVWXYZ[\]^_` +
+               "`abcdefghijklmno" +
+               "pqrstuvwxyz{|}~\x7f" +
+               "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
+
+       tests := []struct {
+               name    string
+               escaper func(...interface{}) string
+               escaped string
+       }{
+               {
+                       "jsStrEscaper",
+                       jsStrEscaper,
+                       "\\0\x01\x02\x03\x04\x05\x06\x07" +
+                               "\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
+                               "\x10\x11\x12\x13\x14\x15\x16\x17" +
+                               "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+                               ` !\x22#$%\x26\x27()*\x2b,-.\/` +
+                               `0123456789:;\x3c=\x3e?` +
+                               `@ABCDEFGHIJKLMNO` +
+                               `PQRSTUVWXYZ[\\]^_` +
+                               "`abcdefghijklmno" +
+                               "pqrstuvwxyz{|}~\x7f" +
+                               "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
+               },
+               {
+                       "jsRegexpEscaper",
+                       jsRegexpEscaper,
+                       "\\0\x01\x02\x03\x04\x05\x06\x07" +
+                               "\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
+                               "\x10\x11\x12\x13\x14\x15\x16\x17" +
+                               "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+                               ` !\x22#\$%\x26\x27\(\)\*\x2b,\-\.\/` +
+                               `0123456789:;\x3c=\x3e\?` +
+                               `@ABCDEFGHIJKLMNO` +
+                               `PQRSTUVWXYZ\[\\\]\^_` +
+                               "`abcdefghijklmno" +
+                               `pqrstuvwxyz\{\|\}~` + "\u007f" +
+                               "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
+               },
+       }
+
+       for _, test := range tests {
+               if s := test.escaper(input); s != test.escaped {
+                       t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
+                       continue
+               }
+
+               // Escape it rune by rune to make sure that any
+               // fast-path checking does not break escaping.
+               var buf bytes.Buffer
+               for _, c := range input {
+                       buf.WriteString(test.escaper(string(c)))
+               }
+
+               if s := buf.String(); s != test.escaped {
+                       t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
+                       continue
+               }
+       }
+}
+
+func TestIsJsMimeType(t *testing.T) {
+       tests := []struct {
+               in  string
+               out bool
+       }{
+               {"application/javascript;version=1.8", true},
+               {"application/javascript;version=1.8;foo=bar", true},
+               {"application/javascript/version=1.8", false},
+               {"text/javascript", true},
+               {"application/json", true},
+               {"application/ld+json", true},
+               {"module", true},
+       }
+
+       for _, test := range tests {
+               if isJSType(test.in) != test.out {
+                       t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
+               }
+       }
+}
+
+func BenchmarkJSValEscaperWithNum(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               jsValEscaper(3.141592654)
+       }
+}
+
+func BenchmarkJSValEscaperWithStr(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
+       }
+}
+
+func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               jsValEscaper("The quick, brown fox jumps over the lazy dog")
+       }
+}
+
+func BenchmarkJSValEscaperWithObj(b *testing.B) {
+       o := struct {
+               S string
+               N int
+       }{
+               "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
+               42,
+       }
+       for i := 0; i < b.N; i++ {
+               jsValEscaper(o)
+       }
+}
+
+func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
+       o := struct {
+               S string
+               N int
+       }{
+               "The quick, brown fox jumps over the lazy dog",
+               42,
+       }
+       for i := 0; i < b.N; i++ {
+               jsValEscaper(o)
+       }
+}
+
+func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
+       }
+}
+
+func BenchmarkJSStrEscaper(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
+       }
+}
+
+func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
+       }
+}
+
+func BenchmarkJSRegexpEscaper(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/jsctx_string.go b/tpl/internal/go_templates/htmltemplate/jsctx_string.go
new file mode 100644 (file)
index 0000000..dd1d87e
--- /dev/null
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type jsCtx"; DO NOT EDIT.
+
+package template
+
+import "strconv"
+
+const _jsCtx_name = "jsCtxRegexpjsCtxDivOpjsCtxUnknown"
+
+var _jsCtx_index = [...]uint8{0, 11, 21, 33}
+
+func (i jsCtx) String() string {
+       if i >= jsCtx(len(_jsCtx_index)-1) {
+               return "jsCtx(" + strconv.FormatInt(int64(i), 10) + ")"
+       }
+       return _jsCtx_name[_jsCtx_index[i]:_jsCtx_index[i+1]]
+}
diff --git a/tpl/internal/go_templates/htmltemplate/state_string.go b/tpl/internal/go_templates/htmltemplate/state_string.go
new file mode 100644 (file)
index 0000000..05104be
--- /dev/null
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type state"; DO NOT EDIT.
+
+package template
+
+import "strconv"
+
+const _state_name = "stateTextstateTagstateAttrNamestateAfterNamestateBeforeValuestateHTMLCmtstateRCDATAstateAttrstateURLstateSrcsetstateJSstateJSDqStrstateJSSqStrstateJSRegexpstateJSBlockCmtstateJSLineCmtstateCSSstateCSSDqStrstateCSSSqStrstateCSSDqURLstateCSSSqURLstateCSSURLstateCSSBlockCmtstateCSSLineCmtstateError"
+
+var _state_index = [...]uint16{0, 9, 17, 30, 44, 60, 72, 83, 92, 100, 111, 118, 130, 142, 155, 170, 184, 192, 205, 218, 231, 244, 255, 271, 286, 296}
+
+func (i state) String() string {
+       if i >= state(len(_state_index)-1) {
+               return "state(" + strconv.FormatInt(int64(i), 10) + ")"
+       }
+       return _state_name[_state_index[i]:_state_index[i+1]]
+}
diff --git a/tpl/internal/go_templates/htmltemplate/template.go b/tpl/internal/go_templates/htmltemplate/template.go
new file mode 100644 (file)
index 0000000..aa65d9c
--- /dev/null
@@ -0,0 +1,491 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "fmt"
+       "io"
+       "io/ioutil"
+       "path/filepath"
+       "sync"
+
+       template "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
+
+// Template is a specialized Template from "text/template" that produces a safe
+// HTML document fragment.
+type Template struct {
+       // Sticky error if escaping fails, or escapeOK if succeeded.
+       escapeErr error
+       // We could embed the text/template field, but it's safer not to because
+       // we need to keep our version of the name space and the underlying
+       // template's in sync.
+       text *template.Template
+       // The underlying template's parse tree, updated to be HTML-safe.
+       Tree       *parse.Tree
+       *nameSpace // common to all associated templates
+}
+
+// escapeOK is a sentinel value used to indicate valid escaping.
+var escapeOK = fmt.Errorf("template escaped correctly")
+
+// nameSpace is the data structure shared by all templates in an association.
+type nameSpace struct {
+       mu      sync.Mutex
+       set     map[string]*Template
+       escaped bool
+       esc     escaper
+}
+
+// Templates returns a slice of the templates associated with t, including t
+// itself.
+func (t *Template) Templates() []*Template {
+       ns := t.nameSpace
+       ns.mu.Lock()
+       defer ns.mu.Unlock()
+       // Return a slice so we don't expose the map.
+       m := make([]*Template, 0, len(ns.set))
+       for _, v := range ns.set {
+               m = append(m, v)
+       }
+       return m
+}
+
+// Option sets options for the template. Options are described by
+// strings, either a simple string or "key=value". There can be at
+// most one equals sign in an option string. If the option string
+// is unrecognized or otherwise invalid, Option panics.
+//
+// Known options:
+//
+// missingkey: Control the behavior during execution if a map is
+// indexed with a key that is not present in the map.
+//     "missingkey=default" or "missingkey=invalid"
+//             The default behavior: Do nothing and continue execution.
+//             If printed, the result of the index operation is the string
+//             "<no value>".
+//     "missingkey=zero"
+//             The operation returns the zero value for the map type's element.
+//     "missingkey=error"
+//             Execution stops immediately with an error.
+//
+func (t *Template) Option(opt ...string) *Template {
+       t.text.Option(opt...)
+       return t
+}
+
+// checkCanParse checks whether it is OK to parse templates.
+// If not, it returns an error.
+func (t *Template) checkCanParse() error {
+       if t == nil {
+               return nil
+       }
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       if t.nameSpace.escaped {
+               return fmt.Errorf("html/template: cannot Parse after Execute")
+       }
+       return nil
+}
+
+// escape escapes all associated templates.
+func (t *Template) escape() error {
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       t.nameSpace.escaped = true
+       if t.escapeErr == nil {
+               if t.Tree == nil {
+                       return fmt.Errorf("template: %q is an incomplete or empty template", t.Name())
+               }
+               if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil {
+                       return err
+               }
+       } else if t.escapeErr != escapeOK {
+               return t.escapeErr
+       }
+       return nil
+}
+
+// Execute applies a parsed template to the specified data object,
+// writing the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
+func (t *Template) Execute(wr io.Writer, data interface{}) error {
+       if err := t.escape(); err != nil {
+               return err
+       }
+       return t.text.Execute(wr, data)
+}
+
+// ExecuteTemplate applies the template associated with t that has the given
+// name to the specified data object and writes the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
+func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
+       tmpl, err := t.lookupAndEscapeTemplate(name)
+       if err != nil {
+               return err
+       }
+       return tmpl.text.Execute(wr, data)
+}
+
+// lookupAndEscapeTemplate guarantees that the template with the given name
+// is escaped, or returns an error if it cannot be. It returns the named
+// template.
+func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       t.nameSpace.escaped = true
+       tmpl = t.set[name]
+       if tmpl == nil {
+               return nil, fmt.Errorf("html/template: %q is undefined", name)
+       }
+       if tmpl.escapeErr != nil && tmpl.escapeErr != escapeOK {
+               return nil, tmpl.escapeErr
+       }
+       if tmpl.text.Tree == nil || tmpl.text.Root == nil {
+               return nil, fmt.Errorf("html/template: %q is an incomplete template", name)
+       }
+       if t.text.Lookup(name) == nil {
+               panic("html/template internal error: template escaping out of sync")
+       }
+       if tmpl.escapeErr == nil {
+               err = escapeTemplate(tmpl, tmpl.text.Root, name)
+       }
+       return tmpl, err
+}
+
+// DefinedTemplates returns a string listing the defined templates,
+// prefixed by the string "; defined templates are: ". If there are none,
+// it returns the empty string. Used to generate an error message.
+func (t *Template) DefinedTemplates() string {
+       return t.text.DefinedTemplates()
+}
+
+// Parse parses text as a template body for t.
+// Named template definitions ({{define ...}} or {{block ...}} statements) in text
+// define additional templates associated with t and are removed from the
+// definition of t itself.
+//
+// Templates can be redefined in successive calls to Parse,
+// before the first use of Execute on t or any associated template.
+// A template definition with a body containing only white space and comments
+// is considered empty and will not replace an existing template's body.
+// This allows using Parse to add new named template definitions without
+// overwriting the main template body.
+func (t *Template) Parse(text string) (*Template, error) {
+       if err := t.checkCanParse(); err != nil {
+               return nil, err
+       }
+
+       ret, err := t.text.Parse(text)
+       if err != nil {
+               return nil, err
+       }
+
+       // In general, all the named templates might have changed underfoot.
+       // Regardless, some new ones may have been defined.
+       // The template.Template set has been updated; update ours.
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       for _, v := range ret.Templates() {
+               name := v.Name()
+               tmpl := t.set[name]
+               if tmpl == nil {
+                       tmpl = t.new(name)
+               }
+               tmpl.text = v
+               tmpl.Tree = v.Tree
+       }
+       return t, nil
+}
+
+// AddParseTree creates a new template with the name and parse tree
+// and associates it with t.
+//
+// It returns an error if t or any associated template has already been executed.
+func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
+       if err := t.checkCanParse(); err != nil {
+               return nil, err
+       }
+
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       text, err := t.text.AddParseTree(name, tree)
+       if err != nil {
+               return nil, err
+       }
+       ret := &Template{
+               nil,
+               text,
+               text.Tree,
+               t.nameSpace,
+       }
+       t.set[name] = ret
+       return ret, nil
+}
+
+// Clone returns a duplicate of the template, including all associated
+// templates. The actual representation is not copied, but the name space of
+// associated templates is, so further calls to Parse in the copy will add
+// templates to the copy but not to the original. Clone can be used to prepare
+// common templates and use them with variant definitions for other templates
+// by adding the variants after the clone is made.
+//
+// It returns an error if t has already been executed.
+func (t *Template) Clone() (*Template, error) {
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       if t.escapeErr != nil {
+               return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
+       }
+       textClone, err := t.text.Clone()
+       if err != nil {
+               return nil, err
+       }
+       ns := &nameSpace{set: make(map[string]*Template)}
+       ns.esc = makeEscaper(ns)
+       ret := &Template{
+               nil,
+               textClone,
+               textClone.Tree,
+               ns,
+       }
+       ret.set[ret.Name()] = ret
+       for _, x := range textClone.Templates() {
+               name := x.Name()
+               src := t.set[name]
+               if src == nil || src.escapeErr != nil {
+                       return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
+               }
+               x.Tree = x.Tree.Copy()
+               ret.set[name] = &Template{
+                       nil,
+                       x,
+                       x.Tree,
+                       ret.nameSpace,
+               }
+       }
+       // Return the template associated with the name of this template.
+       return ret.set[ret.Name()], nil
+}
+
+// New allocates a new HTML template with the given name.
+func New(name string) *Template {
+       ns := &nameSpace{set: make(map[string]*Template)}
+       ns.esc = makeEscaper(ns)
+       tmpl := &Template{
+               nil,
+               template.New(name),
+               nil,
+               ns,
+       }
+       tmpl.set[name] = tmpl
+       return tmpl
+}
+
+// New allocates a new HTML template associated with the given one
+// and with the same delimiters. The association, which is transitive,
+// allows one template to invoke another with a {{template}} action.
+//
+// If a template with the given name already exists, the new HTML template
+// will replace it. The existing template will be reset and disassociated with
+// t.
+func (t *Template) New(name string) *Template {
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       return t.new(name)
+}
+
+// new is the implementation of New, without the lock.
+func (t *Template) new(name string) *Template {
+       tmpl := &Template{
+               nil,
+               t.text.New(name),
+               nil,
+               t.nameSpace,
+       }
+       if existing, ok := tmpl.set[name]; ok {
+               emptyTmpl := New(existing.Name())
+               *existing = *emptyTmpl
+       }
+       tmpl.set[name] = tmpl
+       return tmpl
+}
+
+// Name returns the name of the template.
+func (t *Template) Name() string {
+       return t.text.Name()
+}
+
+// FuncMap is the type of the map defining the mapping from names to
+// functions. Each function must have either a single return value, or two
+// return values of which the second has type error. In that case, if the
+// second (error) argument evaluates to non-nil during execution, execution
+// terminates and Execute returns that error. FuncMap has the same base type
+// as FuncMap in "text/template", copied here so clients need not import
+// "text/template".
+type FuncMap map[string]interface{}
+
+// Funcs adds the elements of the argument map to the template's function map.
+// It must be called before the template is parsed.
+// It panics if a value in the map is not a function with appropriate return
+// type. However, it is legal to overwrite elements of the map. The return
+// value is the template, so calls can be chained.
+func (t *Template) Funcs(funcMap FuncMap) *Template {
+       t.text.Funcs(template.FuncMap(funcMap))
+       return t
+}
+
+// Delims sets the action delimiters to the specified strings, to be used in
+// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
+// definitions will inherit the settings. An empty delimiter stands for the
+// corresponding default: {{ or }}.
+// The return value is the template, so calls can be chained.
+func (t *Template) Delims(left, right string) *Template {
+       t.text.Delims(left, right)
+       return t
+}
+
+// Lookup returns the template with the given name that is associated with t,
+// or nil if there is no such template.
+func (t *Template) Lookup(name string) *Template {
+       t.nameSpace.mu.Lock()
+       defer t.nameSpace.mu.Unlock()
+       return t.set[name]
+}
+
+// Must is a helper that wraps a call to a function returning (*Template, error)
+// and panics if the error is non-nil. It is intended for use in variable initializations
+// such as
+//     var t = template.Must(template.New("name").Parse("html"))
+func Must(t *Template, err error) *Template {
+       if err != nil {
+               panic(err)
+       }
+       return t
+}
+
+// ParseFiles creates a new Template and parses the template definitions from
+// the named files. The returned template's name will have the (base) name and
+// (parsed) contents of the first file. There must be at least one file.
+// If an error occurs, parsing stops and the returned *Template is nil.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+// 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...)
+}
+
+// ParseFiles parses the named files and associates the resulting templates with
+// t. If an error occurs, parsing stops and the returned template is nil;
+// otherwise it is t. There must be at least one file.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+//
+// 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...)
+}
+
+// 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) {
+       if err := t.checkCanParse(); err != nil {
+               return nil, err
+       }
+
+       if len(filenames) == 0 {
+               // Not really a problem, but be consistent.
+               return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
+       }
+       for _, filename := range filenames {
+               b, err := ioutil.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
+               // as t, this file becomes the contents of t, so
+               //  t, err := New(name).Funcs(xxx).ParseFiles(name)
+               // works. Otherwise we create a new template associated with t.
+               var tmpl *Template
+               if t == nil {
+                       t = New(name)
+               }
+               if name == t.Name() {
+                       tmpl = t
+               } else {
+                       tmpl = t.New(name)
+               }
+               _, err = tmpl.Parse(s)
+               if err != nil {
+                       return nil, err
+               }
+       }
+       return t, nil
+}
+
+// ParseGlob creates a new Template and parses the template definitions from
+// the files identified by the pattern. The files are matched according to the
+// semantics of filepath.Match, and the pattern must match at least one file.
+// The returned template will have the (base) name and (parsed) contents of the
+// first file matched by the pattern. ParseGlob is equivalent to calling
+// ParseFiles with the list of files matched by the pattern.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+func ParseGlob(pattern string) (*Template, error) {
+       return parseGlob(nil, pattern)
+}
+
+// ParseGlob parses the template definitions in the files identified by the
+// pattern and associates the resulting templates with t. The files are matched
+// according to the semantics of filepath.Match, and the pattern must match at
+// least one file. ParseGlob is equivalent to calling t.ParseFiles with the
+// list of files matched by the pattern.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+//
+// ParseGlob returns an error if t or any associated template has already been executed.
+func (t *Template) ParseGlob(pattern string) (*Template, error) {
+       return parseGlob(t, pattern)
+}
+
+// parseGlob is the implementation of the function and method ParseGlob.
+func parseGlob(t *Template, pattern string) (*Template, error) {
+       if err := t.checkCanParse(); err != nil {
+               return nil, err
+       }
+       filenames, err := filepath.Glob(pattern)
+       if err != nil {
+               return nil, err
+       }
+       if len(filenames) == 0 {
+               return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern)
+       }
+       return parseFiles(t, filenames...)
+}
+
+// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
+// and whether the value has a meaningful truth value. This is the definition of
+// truth used by if and other such actions.
+func IsTrue(val interface{}) (truth, ok bool) {
+       return template.IsTrue(val)
+}
diff --git a/tpl/internal/go_templates/htmltemplate/template_test.go b/tpl/internal/go_templates/htmltemplate/template_test.go
new file mode 100644 (file)
index 0000000..8adf332
--- /dev/null
@@ -0,0 +1,166 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package template_test
+
+import (
+       "bytes"
+       "strings"
+       "testing"
+
+       . "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+)
+
+func TestTemplateClone(t *testing.T) {
+       // https://golang.org/issue/12996
+       orig := New("name")
+       clone, err := orig.Clone()
+       if err != nil {
+               t.Fatal(err)
+       }
+       if len(clone.Templates()) != len(orig.Templates()) {
+               t.Fatalf("Invalid length of t.Clone().Templates()")
+       }
+
+       const want = "stuff"
+       parsed := Must(clone.Parse(want))
+       var buf bytes.Buffer
+       err = parsed.Execute(&buf, nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if got := buf.String(); got != want {
+               t.Fatalf("got %q; want %q", got, want)
+       }
+}
+
+func TestRedefineNonEmptyAfterExecution(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `foo`)
+       c.mustExecute(c.root, nil, "foo")
+       c.mustNotParse(c.root, `bar`)
+}
+
+func TestRedefineEmptyAfterExecution(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, ``)
+       c.mustExecute(c.root, nil, "")
+       c.mustNotParse(c.root, `foo`)
+       c.mustExecute(c.root, nil, "")
+}
+
+func TestRedefineAfterNonExecution(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `{{if .}}<{{template "X"}}>{{end}}{{define "X"}}foo{{end}}`)
+       c.mustExecute(c.root, 0, "")
+       c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
+       c.mustExecute(c.root, 1, "&lt;foo>")
+}
+
+func TestRedefineAfterNamedExecution(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `<{{template "X" .}}>{{define "X"}}foo{{end}}`)
+       c.mustExecute(c.root, nil, "&lt;foo>")
+       c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
+       c.mustExecute(c.root, nil, "&lt;foo>")
+}
+
+func TestRedefineNestedByNameAfterExecution(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
+       c.mustExecute(c.lookup("X"), nil, "foo")
+       c.mustNotParse(c.root, `{{define "X"}}bar{{end}}`)
+       c.mustExecute(c.lookup("X"), nil, "foo")
+}
+
+func TestRedefineNestedByTemplateAfterExecution(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `{{define "X"}}foo{{end}}`)
+       c.mustExecute(c.lookup("X"), nil, "foo")
+       c.mustNotParse(c.lookup("X"), `bar`)
+       c.mustExecute(c.lookup("X"), nil, "foo")
+}
+
+func TestRedefineSafety(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `<html><a href="{{template "X"}}">{{define "X"}}{{end}}`)
+       c.mustExecute(c.root, nil, `<html><a href="">`)
+       // Note: Every version of Go prior to Go 1.8 accepted the redefinition of "X"
+       // on the next line, but luckily kept it from being used in the outer template.
+       // Now we reject it, which makes clearer that we're not going to use it.
+       c.mustNotParse(c.root, `{{define "X"}}" bar="baz{{end}}`)
+       c.mustExecute(c.root, nil, `<html><a href="">`)
+}
+
+func TestRedefineTopUse(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `{{template "X"}}{{.}}{{define "X"}}{{end}}`)
+       c.mustExecute(c.root, 42, `42`)
+       c.mustNotParse(c.root, `{{define "X"}}<script>{{end}}`)
+       c.mustExecute(c.root, 42, `42`)
+}
+
+func TestRedefineOtherParsers(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, ``)
+       c.mustExecute(c.root, nil, ``)
+       if _, err := c.root.ParseFiles("no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
+               t.Errorf("ParseFiles: %v\nwanted error about already having Executed", err)
+       }
+       if _, err := c.root.ParseGlob("*.no.template"); err == nil || !strings.Contains(err.Error(), "Execute") {
+               t.Errorf("ParseGlob: %v\nwanted error about already having Executed", err)
+       }
+       if _, err := c.root.AddParseTree("t1", c.root.Tree); err == nil || !strings.Contains(err.Error(), "Execute") {
+               t.Errorf("AddParseTree: %v\nwanted error about already having Executed", err)
+       }
+}
+
+func TestNumbers(t *testing.T) {
+       c := newTestCase(t)
+       c.mustParse(c.root, `{{print 1_2.3_4}} {{print 0x0_1.e_0p+02}}`)
+       c.mustExecute(c.root, nil, "12.34 7.5")
+}
+
+type testCase struct {
+       t    *testing.T
+       root *Template
+}
+
+func newTestCase(t *testing.T) *testCase {
+       return &testCase{
+               t:    t,
+               root: New("root"),
+       }
+}
+
+func (c *testCase) lookup(name string) *Template {
+       return c.root.Lookup(name)
+}
+
+func (c *testCase) mustParse(t *Template, text string) {
+       _, err := t.Parse(text)
+       if err != nil {
+               c.t.Fatalf("parse: %v", err)
+       }
+}
+
+func (c *testCase) mustNotParse(t *Template, text string) {
+       _, err := t.Parse(text)
+       if err == nil {
+               c.t.Fatalf("parse: unexpected success")
+       }
+}
+
+func (c *testCase) mustExecute(t *Template, val interface{}, want string) {
+       var buf bytes.Buffer
+       err := t.Execute(&buf, val)
+       if err != nil {
+               c.t.Fatalf("execute: %v", err)
+       }
+       if buf.String() != want {
+               c.t.Fatalf("template output:\n%s\nwant:\n%s", buf.String(), want)
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/transition.go b/tpl/internal/go_templates/htmltemplate/transition.go
new file mode 100644 (file)
index 0000000..06df679
--- /dev/null
@@ -0,0 +1,592 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "strings"
+)
+
+// transitionFunc is the array of context transition functions for text nodes.
+// A transition function takes a context and template text input, and returns
+// the updated context and the number of bytes consumed from the front of the
+// input.
+var transitionFunc = [...]func(context, []byte) (context, int){
+       stateText:        tText,
+       stateTag:         tTag,
+       stateAttrName:    tAttrName,
+       stateAfterName:   tAfterName,
+       stateBeforeValue: tBeforeValue,
+       stateHTMLCmt:     tHTMLCmt,
+       stateRCDATA:      tSpecialTagEnd,
+       stateAttr:        tAttr,
+       stateURL:         tURL,
+       stateSrcset:      tURL,
+       stateJS:          tJS,
+       stateJSDqStr:     tJSDelimited,
+       stateJSSqStr:     tJSDelimited,
+       stateJSRegexp:    tJSDelimited,
+       stateJSBlockCmt:  tBlockCmt,
+       stateJSLineCmt:   tLineCmt,
+       stateCSS:         tCSS,
+       stateCSSDqStr:    tCSSStr,
+       stateCSSSqStr:    tCSSStr,
+       stateCSSDqURL:    tCSSStr,
+       stateCSSSqURL:    tCSSStr,
+       stateCSSURL:      tCSSStr,
+       stateCSSBlockCmt: tBlockCmt,
+       stateCSSLineCmt:  tLineCmt,
+       stateError:       tError,
+}
+
+var commentStart = []byte("<!--")
+var commentEnd = []byte("-->")
+
+// tText is the context transition function for the text state.
+func tText(c context, s []byte) (context, int) {
+       k := 0
+       for {
+               i := k + bytes.IndexByte(s[k:], '<')
+               if i < k || i+1 == len(s) {
+                       return c, len(s)
+               } else if i+4 <= len(s) && bytes.Equal(commentStart, s[i:i+4]) {
+                       return context{state: stateHTMLCmt}, i + 4
+               }
+               i++
+               end := false
+               if s[i] == '/' {
+                       if i+1 == len(s) {
+                               return c, len(s)
+                       }
+                       end, i = true, i+1
+               }
+               j, e := eatTagName(s, i)
+               if j != i {
+                       if end {
+                               e = elementNone
+                       }
+                       // We've found an HTML tag.
+                       return context{state: stateTag, element: e}, j
+               }
+               k = j
+       }
+}
+
+var elementContentType = [...]state{
+       elementNone:     stateText,
+       elementScript:   stateJS,
+       elementStyle:    stateCSS,
+       elementTextarea: stateRCDATA,
+       elementTitle:    stateRCDATA,
+}
+
+// tTag is the context transition function for the tag state.
+func tTag(c context, s []byte) (context, int) {
+       // Find the attribute name.
+       i := eatWhiteSpace(s, 0)
+       if i == len(s) {
+               return c, len(s)
+       }
+       if s[i] == '>' {
+               return context{
+                       state:   elementContentType[c.element],
+                       element: c.element,
+               }, i + 1
+       }
+       j, err := eatAttrName(s, i)
+       if err != nil {
+               return context{state: stateError, err: err}, len(s)
+       }
+       state, attr := stateTag, attrNone
+       if i == j {
+               return context{
+                       state: stateError,
+                       err:   errorf(ErrBadHTML, nil, 0, "expected space, attr name, or end of tag, but got %q", s[i:]),
+               }, len(s)
+       }
+
+       attrName := strings.ToLower(string(s[i:j]))
+       if c.element == elementScript && attrName == "type" {
+               attr = attrScriptType
+       } else {
+               switch attrType(attrName) {
+               case contentTypeURL:
+                       attr = attrURL
+               case contentTypeCSS:
+                       attr = attrStyle
+               case contentTypeJS:
+                       attr = attrScript
+               case contentTypeSrcset:
+                       attr = attrSrcset
+               }
+       }
+
+       if j == len(s) {
+               state = stateAttrName
+       } else {
+               state = stateAfterName
+       }
+       return context{state: state, element: c.element, attr: attr}, j
+}
+
+// tAttrName is the context transition function for stateAttrName.
+func tAttrName(c context, s []byte) (context, int) {
+       i, err := eatAttrName(s, 0)
+       if err != nil {
+               return context{state: stateError, err: err}, len(s)
+       } else if i != len(s) {
+               c.state = stateAfterName
+       }
+       return c, i
+}
+
+// tAfterName is the context transition function for stateAfterName.
+func tAfterName(c context, s []byte) (context, int) {
+       // Look for the start of the value.
+       i := eatWhiteSpace(s, 0)
+       if i == len(s) {
+               return c, len(s)
+       } else if s[i] != '=' {
+               // Occurs due to tag ending '>', and valueless attribute.
+               c.state = stateTag
+               return c, i
+       }
+       c.state = stateBeforeValue
+       // Consume the "=".
+       return c, i + 1
+}
+
+var attrStartStates = [...]state{
+       attrNone:       stateAttr,
+       attrScript:     stateJS,
+       attrScriptType: stateAttr,
+       attrStyle:      stateCSS,
+       attrURL:        stateURL,
+       attrSrcset:     stateSrcset,
+}
+
+// tBeforeValue is the context transition function for stateBeforeValue.
+func tBeforeValue(c context, s []byte) (context, int) {
+       i := eatWhiteSpace(s, 0)
+       if i == len(s) {
+               return c, len(s)
+       }
+       // Find the attribute delimiter.
+       delim := delimSpaceOrTagEnd
+       switch s[i] {
+       case '\'':
+               delim, i = delimSingleQuote, i+1
+       case '"':
+               delim, i = delimDoubleQuote, i+1
+       }
+       c.state, c.delim = attrStartStates[c.attr], delim
+       return c, i
+}
+
+// tHTMLCmt is the context transition function for stateHTMLCmt.
+func tHTMLCmt(c context, s []byte) (context, int) {
+       if i := bytes.Index(s, commentEnd); i != -1 {
+               return context{}, i + 3
+       }
+       return c, len(s)
+}
+
+// specialTagEndMarkers maps element types to the character sequence that
+// case-insensitively signals the end of the special tag body.
+var specialTagEndMarkers = [...][]byte{
+       elementScript:   []byte("script"),
+       elementStyle:    []byte("style"),
+       elementTextarea: []byte("textarea"),
+       elementTitle:    []byte("title"),
+}
+
+var (
+       specialTagEndPrefix = []byte("</")
+       tagEndSeparators    = []byte("> \t\n\f/")
+)
+
+// tSpecialTagEnd is the context transition function for raw text and RCDATA
+// element states.
+func tSpecialTagEnd(c context, s []byte) (context, int) {
+       if c.element != elementNone {
+               if i := indexTagEnd(s, specialTagEndMarkers[c.element]); i != -1 {
+                       return context{}, i
+               }
+       }
+       return c, len(s)
+}
+
+// indexTagEnd finds the index of a special tag end in a case insensitive way, or returns -1
+func indexTagEnd(s []byte, tag []byte) int {
+       res := 0
+       plen := len(specialTagEndPrefix)
+       for len(s) > 0 {
+               // Try to find the tag end prefix first
+               i := bytes.Index(s, specialTagEndPrefix)
+               if i == -1 {
+                       return i
+               }
+               s = s[i+plen:]
+               // Try to match the actual tag if there is still space for it
+               if len(tag) <= len(s) && bytes.EqualFold(tag, s[:len(tag)]) {
+                       s = s[len(tag):]
+                       // Check the tag is followed by a proper separator
+                       if len(s) > 0 && bytes.IndexByte(tagEndSeparators, s[0]) != -1 {
+                               return res + i
+                       }
+                       res += len(tag)
+               }
+               res += i + plen
+       }
+       return -1
+}
+
+// tAttr is the context transition function for the attribute state.
+func tAttr(c context, s []byte) (context, int) {
+       return c, len(s)
+}
+
+// tURL is the context transition function for the URL state.
+func tURL(c context, s []byte) (context, int) {
+       if bytes.ContainsAny(s, "#?") {
+               c.urlPart = urlPartQueryOrFrag
+       } else if len(s) != eatWhiteSpace(s, 0) && c.urlPart == urlPartNone {
+               // HTML5 uses "Valid URL potentially surrounded by spaces" for
+               // attrs: https://www.w3.org/TR/html5/index.html#attributes-1
+               c.urlPart = urlPartPreQuery
+       }
+       return c, len(s)
+}
+
+// tJS is the context transition function for the JS state.
+func tJS(c context, s []byte) (context, int) {
+       i := bytes.IndexAny(s, `"'/`)
+       if i == -1 {
+               // Entire input is non string, comment, regexp tokens.
+               c.jsCtx = nextJSCtx(s, c.jsCtx)
+               return c, len(s)
+       }
+       c.jsCtx = nextJSCtx(s[:i], c.jsCtx)
+       switch s[i] {
+       case '"':
+               c.state, c.jsCtx = stateJSDqStr, jsCtxRegexp
+       case '\'':
+               c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
+       case '/':
+               switch {
+               case i+1 < len(s) && s[i+1] == '/':
+                       c.state, i = stateJSLineCmt, i+1
+               case i+1 < len(s) && s[i+1] == '*':
+                       c.state, i = stateJSBlockCmt, i+1
+               case c.jsCtx == jsCtxRegexp:
+                       c.state = stateJSRegexp
+               case c.jsCtx == jsCtxDivOp:
+                       c.jsCtx = jsCtxRegexp
+               default:
+                       return context{
+                               state: stateError,
+                               err:   errorf(ErrSlashAmbig, nil, 0, "'/' could start a division or regexp: %.32q", s[i:]),
+                       }, len(s)
+               }
+       default:
+               panic("unreachable")
+       }
+       return c, i + 1
+}
+
+// tJSDelimited is the context transition function for the JS string and regexp
+// states.
+func tJSDelimited(c context, s []byte) (context, int) {
+       specials := `\"`
+       switch c.state {
+       case stateJSSqStr:
+               specials = `\'`
+       case stateJSRegexp:
+               specials = `\/[]`
+       }
+
+       k, inCharset := 0, false
+       for {
+               i := k + bytes.IndexAny(s[k:], specials)
+               if i < k {
+                       break
+               }
+               switch s[i] {
+               case '\\':
+                       i++
+                       if i == len(s) {
+                               return context{
+                                       state: stateError,
+                                       err:   errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in JS string: %q", s),
+                               }, len(s)
+                       }
+               case '[':
+                       inCharset = true
+               case ']':
+                       inCharset = false
+               default:
+                       // end delimiter
+                       if !inCharset {
+                               c.state, c.jsCtx = stateJS, jsCtxDivOp
+                               return c, i + 1
+                       }
+               }
+               k = i + 1
+       }
+
+       if inCharset {
+               // This can be fixed by making context richer if interpolation
+               // into charsets is desired.
+               return context{
+                       state: stateError,
+                       err:   errorf(ErrPartialCharset, nil, 0, "unfinished JS regexp charset: %q", s),
+               }, len(s)
+       }
+
+       return c, len(s)
+}
+
+var blockCommentEnd = []byte("*/")
+
+// tBlockCmt is the context transition function for /*comment*/ states.
+func tBlockCmt(c context, s []byte) (context, int) {
+       i := bytes.Index(s, blockCommentEnd)
+       if i == -1 {
+               return c, len(s)
+       }
+       switch c.state {
+       case stateJSBlockCmt:
+               c.state = stateJS
+       case stateCSSBlockCmt:
+               c.state = stateCSS
+       default:
+               panic(c.state.String())
+       }
+       return c, i + 2
+}
+
+// tLineCmt is the context transition function for //comment states.
+func tLineCmt(c context, s []byte) (context, int) {
+       var lineTerminators string
+       var endState state
+       switch c.state {
+       case stateJSLineCmt:
+               lineTerminators, endState = "\n\r\u2028\u2029", stateJS
+       case stateCSSLineCmt:
+               lineTerminators, endState = "\n\f\r", stateCSS
+               // Line comments are not part of any published CSS standard but
+               // are supported by the 4 major browsers.
+               // This defines line comments as
+               //     LINECOMMENT ::= "//" [^\n\f\d]*
+               // since https://www.w3.org/TR/css3-syntax/#SUBTOK-nl defines
+               // newlines:
+               //     nl ::= #xA | #xD #xA | #xD | #xC
+       default:
+               panic(c.state.String())
+       }
+
+       i := bytes.IndexAny(s, lineTerminators)
+       if i == -1 {
+               return c, len(s)
+       }
+       c.state = endState
+       // Per section 7.4 of EcmaScript 5 : https://es5.github.com/#x7.4
+       // "However, the LineTerminator at the end of the line is not
+       // considered to be part of the single-line comment; it is
+       // recognized separately by the lexical grammar and becomes part
+       // of the stream of input elements for the syntactic grammar."
+       return c, i
+}
+
+// tCSS is the context transition function for the CSS state.
+func tCSS(c context, s []byte) (context, int) {
+       // CSS quoted strings are almost never used except for:
+       // (1) URLs as in background: "/foo.png"
+       // (2) Multiword font-names as in font-family: "Times New Roman"
+       // (3) List separators in content values as in inline-lists:
+       //    <style>
+       //    ul.inlineList { list-style: none; padding:0 }
+       //    ul.inlineList > li { display: inline }
+       //    ul.inlineList > li:before { content: ", " }
+       //    ul.inlineList > li:first-child:before { content: "" }
+       //    </style>
+       //    <ul class=inlineList><li>One<li>Two<li>Three</ul>
+       // (4) Attribute value selectors as in a[href="http://example.com/"]
+       //
+       // We conservatively treat all strings as URLs, but make some
+       // allowances to avoid confusion.
+       //
+       // In (1), our conservative assumption is justified.
+       // In (2), valid font names do not contain ':', '?', or '#', so our
+       // conservative assumption is fine since we will never transition past
+       // urlPartPreQuery.
+       // In (3), our protocol heuristic should not be tripped, and there
+       // should not be non-space content after a '?' or '#', so as long as
+       // we only %-encode RFC 3986 reserved characters we are ok.
+       // In (4), we should URL escape for URL attributes, and for others we
+       // have the attribute name available if our conservative assumption
+       // proves problematic for real code.
+
+       k := 0
+       for {
+               i := k + bytes.IndexAny(s[k:], `("'/`)
+               if i < k {
+                       return c, len(s)
+               }
+               switch s[i] {
+               case '(':
+                       // Look for url to the left.
+                       p := bytes.TrimRight(s[:i], "\t\n\f\r ")
+                       if endsWithCSSKeyword(p, "url") {
+                               j := len(s) - len(bytes.TrimLeft(s[i+1:], "\t\n\f\r "))
+                               switch {
+                               case j != len(s) && s[j] == '"':
+                                       c.state, j = stateCSSDqURL, j+1
+                               case j != len(s) && s[j] == '\'':
+                                       c.state, j = stateCSSSqURL, j+1
+                               default:
+                                       c.state = stateCSSURL
+                               }
+                               return c, j
+                       }
+               case '/':
+                       if i+1 < len(s) {
+                               switch s[i+1] {
+                               case '/':
+                                       c.state = stateCSSLineCmt
+                                       return c, i + 2
+                               case '*':
+                                       c.state = stateCSSBlockCmt
+                                       return c, i + 2
+                               }
+                       }
+               case '"':
+                       c.state = stateCSSDqStr
+                       return c, i + 1
+               case '\'':
+                       c.state = stateCSSSqStr
+                       return c, i + 1
+               }
+               k = i + 1
+       }
+}
+
+// tCSSStr is the context transition function for the CSS string and URL states.
+func tCSSStr(c context, s []byte) (context, int) {
+       var endAndEsc string
+       switch c.state {
+       case stateCSSDqStr, stateCSSDqURL:
+               endAndEsc = `\"`
+       case stateCSSSqStr, stateCSSSqURL:
+               endAndEsc = `\'`
+       case stateCSSURL:
+               // Unquoted URLs end with a newline or close parenthesis.
+               // The below includes the wc (whitespace character) and nl.
+               endAndEsc = "\\\t\n\f\r )"
+       default:
+               panic(c.state.String())
+       }
+
+       k := 0
+       for {
+               i := k + bytes.IndexAny(s[k:], endAndEsc)
+               if i < k {
+                       c, nread := tURL(c, decodeCSS(s[k:]))
+                       return c, k + nread
+               }
+               if s[i] == '\\' {
+                       i++
+                       if i == len(s) {
+                               return context{
+                                       state: stateError,
+                                       err:   errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in CSS string: %q", s),
+                               }, len(s)
+                       }
+               } else {
+                       c.state = stateCSS
+                       return c, i + 1
+               }
+               c, _ = tURL(c, decodeCSS(s[:i+1]))
+               k = i + 1
+       }
+}
+
+// tError is the context transition function for the error state.
+func tError(c context, s []byte) (context, int) {
+       return c, len(s)
+}
+
+// eatAttrName returns the largest j such that s[i:j] is an attribute name.
+// It returns an error if s[i:] does not look like it begins with an
+// attribute name, such as encountering a quote mark without a preceding
+// equals sign.
+func eatAttrName(s []byte, i int) (int, *Error) {
+       for j := i; j < len(s); j++ {
+               switch s[j] {
+               case ' ', '\t', '\n', '\f', '\r', '=', '>':
+                       return j, nil
+               case '\'', '"', '<':
+                       // These result in a parse warning in HTML5 and are
+                       // indicative of serious problems if seen in an attr
+                       // name in a template.
+                       return -1, errorf(ErrBadHTML, nil, 0, "%q in attribute name: %.32q", s[j:j+1], s)
+               default:
+                       // No-op.
+               }
+       }
+       return len(s), nil
+}
+
+var elementNameMap = map[string]element{
+       "script":   elementScript,
+       "style":    elementStyle,
+       "textarea": elementTextarea,
+       "title":    elementTitle,
+}
+
+// asciiAlpha reports whether c is an ASCII letter.
+func asciiAlpha(c byte) bool {
+       return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'
+}
+
+// asciiAlphaNum reports whether c is an ASCII letter or digit.
+func asciiAlphaNum(c byte) bool {
+       return asciiAlpha(c) || '0' <= c && c <= '9'
+}
+
+// eatTagName returns the largest j such that s[i:j] is a tag name and the tag type.
+func eatTagName(s []byte, i int) (int, element) {
+       if i == len(s) || !asciiAlpha(s[i]) {
+               return i, elementNone
+       }
+       j := i + 1
+       for j < len(s) {
+               x := s[j]
+               if asciiAlphaNum(x) {
+                       j++
+                       continue
+               }
+               // Allow "x-y" or "x:y" but not "x-", "-y", or "x--y".
+               if (x == ':' || x == '-') && j+1 < len(s) && asciiAlphaNum(s[j+1]) {
+                       j += 2
+                       continue
+               }
+               break
+       }
+       return j, elementNameMap[strings.ToLower(string(s[i:j]))]
+}
+
+// eatWhiteSpace returns the largest j such that s[i:j] is white space.
+func eatWhiteSpace(s []byte, i int) int {
+       for j := i; j < len(s); j++ {
+               switch s[j] {
+               case ' ', '\t', '\n', '\f', '\r':
+                       // No-op.
+               default:
+                       return j
+               }
+       }
+       return len(s)
+}
diff --git a/tpl/internal/go_templates/htmltemplate/transition_test.go b/tpl/internal/go_templates/htmltemplate/transition_test.go
new file mode 100644 (file)
index 0000000..00b0ff6
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "bytes"
+       "strings"
+       "testing"
+)
+
+func TestFindEndTag(t *testing.T) {
+       tests := []struct {
+               s, tag string
+               want   int
+       }{
+               {"", "tag", -1},
+               {"hello </textarea> hello", "textarea", 6},
+               {"hello </TEXTarea> hello", "textarea", 6},
+               {"hello </textAREA>", "textarea", 6},
+               {"hello </textarea", "textareax", -1},
+               {"hello </textarea>", "tag", -1},
+               {"hello tag </textarea", "tag", -1},
+               {"hello </tag> </other> </textarea> <other>", "textarea", 22},
+               {"</textarea> <other>", "textarea", 0},
+               {"<div> </div> </TEXTAREA>", "textarea", 13},
+               {"<div> </div> </TEXTAREA\t>", "textarea", 13},
+               {"<div> </div> </TEXTAREA >", "textarea", 13},
+               {"<div> </div> </TEXTAREAfoo", "textarea", -1},
+               {"</TEXTAREAfoo </textarea>", "textarea", 14},
+               {"<</script >", "script", 1},
+               {"</script>", "textarea", -1},
+       }
+       for _, test := range tests {
+               if got := indexTagEnd([]byte(test.s), []byte(test.tag)); test.want != got {
+                       t.Errorf("%q/%q: want\n\t%d\nbut got\n\t%d", test.s, test.tag, test.want, got)
+               }
+       }
+}
+
+func BenchmarkTemplateSpecialTags(b *testing.B) {
+
+       r := struct {
+               Name, Gift string
+       }{"Aunt Mildred", "bone china tea set"}
+
+       h1 := "<textarea> Hello Hello Hello </textarea> "
+       h2 := "<textarea> <p> Dear {{.Name}},\n{{with .Gift}}Thank you for the lovely {{.}}. {{end}}\nBest wishes. </p>\n</textarea>"
+       html := strings.Repeat(h1, 100) + h2 + strings.Repeat(h1, 100) + h2
+
+       var buf bytes.Buffer
+       for i := 0; i < b.N; i++ {
+               tmpl := Must(New("foo").Parse(html))
+               if err := tmpl.Execute(&buf, r); err != nil {
+                       b.Fatal(err)
+               }
+               buf.Reset()
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/url.go b/tpl/internal/go_templates/htmltemplate/url.go
new file mode 100644 (file)
index 0000000..6f8185a
--- /dev/null
@@ -0,0 +1,219 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "fmt"
+       "strings"
+)
+
+// urlFilter returns its input unless it contains an unsafe scheme in which
+// case it defangs the entire URL.
+//
+// Schemes that cause unintended side effects that are irreversible without user
+// interaction are considered unsafe. For example, clicking on a "javascript:"
+// link can immediately trigger JavaScript code execution.
+//
+// This filter conservatively assumes that all schemes other than the following
+// are unsafe:
+//    * http:   Navigates to a new website, and may open a new window or tab.
+//              These side effects can be reversed by navigating back to the
+//              previous website, or closing the window or tab. No irreversible
+//              changes will take place without further user interaction with
+//              the new website.
+//    * https:  Same as http.
+//    * mailto: Opens an email program and starts a new draft. This side effect
+//              is not irreversible until the user explicitly clicks send; it
+//              can be undone by closing the email program.
+//
+// To allow URLs containing other schemes to bypass this filter, developers must
+// explicitly indicate that such a URL is expected and safe by encapsulating it
+// in a template.URL value.
+func urlFilter(args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeURL {
+               return s
+       }
+       if !isSafeURL(s) {
+               return "#" + filterFailsafe
+       }
+       return s
+}
+
+// isSafeURL is true if s is a relative URL or if URL has a protocol in
+// (http, https, mailto).
+func isSafeURL(s string) bool {
+       if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
+
+               protocol := s[:i]
+               if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
+                       return false
+               }
+       }
+       return true
+}
+
+// urlEscaper produces an output that can be embedded in a URL query.
+// The output can be embedded in an HTML attribute without further escaping.
+func urlEscaper(args ...interface{}) string {
+       return urlProcessor(false, args...)
+}
+
+// urlNormalizer normalizes URL content so it can be embedded in a quote-delimited
+// string or parenthesis delimited url(...).
+// The normalizer does not encode all HTML specials. Specifically, it does not
+// encode '&' so correct embedding in an HTML attribute requires escaping of
+// '&' to '&amp;'.
+func urlNormalizer(args ...interface{}) string {
+       return urlProcessor(true, args...)
+}
+
+// urlProcessor normalizes (when norm is true) or escapes its input to produce
+// a valid hierarchical or opaque URL part.
+func urlProcessor(norm bool, args ...interface{}) string {
+       s, t := stringify(args...)
+       if t == contentTypeURL {
+               norm = true
+       }
+       var b bytes.Buffer
+       if processURLOnto(s, norm, &b) {
+               return b.String()
+       }
+       return s
+}
+
+// processURLOnto appends a normalized URL corresponding to its input to b
+// and reports whether the appended content differs from s.
+func processURLOnto(s string, norm bool, b *bytes.Buffer) bool {
+       b.Grow(len(s) + 16)
+       written := 0
+       // The byte loop below assumes that all URLs use UTF-8 as the
+       // content-encoding. This is similar to the URI to IRI encoding scheme
+       // defined in section 3.1 of  RFC 3987, and behaves the same as the
+       // EcmaScript builtin encodeURIComponent.
+       // It should not cause any misencoding of URLs in pages with
+       // Content-type: text/html;charset=UTF-8.
+       for i, n := 0, len(s); i < n; i++ {
+               c := s[i]
+               switch c {
+               // Single quote and parens are sub-delims in RFC 3986, but we
+               // escape them so the output can be embedded in single
+               // quoted attributes and unquoted CSS url(...) constructs.
+               // Single quotes are reserved in URLs, but are only used in
+               // the obsolete "mark" rule in an appendix in RFC 3986
+               // so can be safely encoded.
+               case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
+                       if norm {
+                               continue
+                       }
+               // Unreserved according to RFC 3986 sec 2.3
+               // "For consistency, percent-encoded octets in the ranges of
+               // ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
+               // period (%2E), underscore (%5F), or tilde (%7E) should not be
+               // created by URI producers
+               case '-', '.', '_', '~':
+                       continue
+               case '%':
+                       // When normalizing do not re-encode valid escapes.
+                       if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) {
+                               continue
+                       }
+               default:
+                       // Unreserved according to RFC 3986 sec 2.3
+                       if 'a' <= c && c <= 'z' {
+                               continue
+                       }
+                       if 'A' <= c && c <= 'Z' {
+                               continue
+                       }
+                       if '0' <= c && c <= '9' {
+                               continue
+                       }
+               }
+               b.WriteString(s[written:i])
+               fmt.Fprintf(b, "%%%02x", c)
+               written = i + 1
+       }
+       b.WriteString(s[written:])
+       return written != 0
+}
+
+// Filters and normalizes srcset values which are comma separated
+// URLs followed by metadata.
+func srcsetFilterAndEscaper(args ...interface{}) string {
+       s, t := stringify(args...)
+       switch t {
+       case contentTypeSrcset:
+               return s
+       case contentTypeURL:
+               // Normalizing gets rid of all HTML whitespace
+               // which separate the image URL from its metadata.
+               var b bytes.Buffer
+               if processURLOnto(s, true, &b) {
+                       s = b.String()
+               }
+               // Additionally, commas separate one source from another.
+               return strings.ReplaceAll(s, ",", "%2c")
+       }
+
+       var b bytes.Buffer
+       written := 0
+       for i := 0; i < len(s); i++ {
+               if s[i] == ',' {
+                       filterSrcsetElement(s, written, i, &b)
+                       b.WriteString(",")
+                       written = i + 1
+               }
+       }
+       filterSrcsetElement(s, written, len(s), &b)
+       return b.String()
+}
+
+// Derived from https://play.golang.org/p/Dhmj7FORT5
+const htmlSpaceAndASCIIAlnumBytes = "\x00\x36\x00\x00\x01\x00\xff\x03\xfe\xff\xff\x07\xfe\xff\xff\x07"
+
+// isHTMLSpace is true iff c is a whitespace character per
+// https://infra.spec.whatwg.org/#ascii-whitespace
+func isHTMLSpace(c byte) bool {
+       return (c <= 0x20) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7)))
+}
+
+func isHTMLSpaceOrASCIIAlnum(c byte) bool {
+       return (c < 0x80) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7)))
+}
+
+func filterSrcsetElement(s string, left int, right int, b *bytes.Buffer) {
+       start := left
+       for start < right && isHTMLSpace(s[start]) {
+               start++
+       }
+       end := right
+       for i := start; i < right; i++ {
+               if isHTMLSpace(s[i]) {
+                       end = i
+                       break
+               }
+       }
+       if url := s[start:end]; isSafeURL(url) {
+               // If image metadata is only spaces or alnums then
+               // we don't need to URL normalize it.
+               metadataOk := true
+               for i := end; i < right; i++ {
+                       if !isHTMLSpaceOrASCIIAlnum(s[i]) {
+                               metadataOk = false
+                               break
+                       }
+               }
+               if metadataOk {
+                       b.WriteString(s[left:start])
+                       processURLOnto(url, true, b)
+                       b.WriteString(s[end:right])
+                       return
+               }
+       }
+       b.WriteString("#")
+       b.WriteString(filterFailsafe)
+}
diff --git a/tpl/internal/go_templates/htmltemplate/url_test.go b/tpl/internal/go_templates/htmltemplate/url_test.go
new file mode 100644 (file)
index 0000000..ff0459f
--- /dev/null
@@ -0,0 +1,171 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "testing"
+)
+
+func TestURLNormalizer(t *testing.T) {
+       tests := []struct {
+               url, want string
+       }{
+               {"", ""},
+               {
+                       "http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag",
+                       "http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag",
+               },
+               {" ", "%20"},
+               {"%7c", "%7c"},
+               {"%7C", "%7C"},
+               {"%2", "%252"},
+               {"%", "%25"},
+               {"%z", "%25z"},
+               {"/foo|bar/%5c\u1234", "/foo%7cbar/%5c%e1%88%b4"},
+       }
+       for _, test := range tests {
+               if got := urlNormalizer(test.url); test.want != got {
+                       t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.url, test.want, got)
+               }
+               if test.want != urlNormalizer(test.want) {
+                       t.Errorf("not idempotent: %q", test.want)
+               }
+       }
+}
+
+func TestURLFilters(t *testing.T) {
+       input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
+               "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
+               ` !"#$%&'()*+,-./` +
+               `0123456789:;<=>?` +
+               `@ABCDEFGHIJKLMNO` +
+               `PQRSTUVWXYZ[\]^_` +
+               "`abcdefghijklmno" +
+               "pqrstuvwxyz{|}~\x7f" +
+               "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
+
+       tests := []struct {
+               name    string
+               escaper func(...interface{}) string
+               escaped string
+       }{
+               {
+                       "urlEscaper",
+                       urlEscaper,
+                       "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" +
+                               "%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" +
+                               "%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f" +
+                               "0123456789%3a%3b%3c%3d%3e%3f" +
+                               "%40ABCDEFGHIJKLMNO" +
+                               "PQRSTUVWXYZ%5b%5c%5d%5e_" +
+                               "%60abcdefghijklmno" +
+                               "pqrstuvwxyz%7b%7c%7d~%7f" +
+                               "%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e",
+               },
+               {
+                       "urlNormalizer",
+                       urlNormalizer,
+                       "%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" +
+                               "%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" +
+                               "%20!%22#$%25&%27%28%29*+,-./" +
+                               "0123456789:;%3c=%3e?" +
+                               "@ABCDEFGHIJKLMNO" +
+                               "PQRSTUVWXYZ[%5c]%5e_" +
+                               "%60abcdefghijklmno" +
+                               "pqrstuvwxyz%7b%7c%7d~%7f" +
+                               "%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e",
+               },
+       }
+
+       for _, test := range tests {
+               if s := test.escaper(input); s != test.escaped {
+                       t.Errorf("%s: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
+                       continue
+               }
+       }
+}
+
+func TestSrcsetFilter(t *testing.T) {
+       tests := []struct {
+               name  string
+               input string
+               want  string
+       }{
+               {
+                       "one ok",
+                       "http://example.com/img.png",
+                       "http://example.com/img.png",
+               },
+               {
+                       "one ok with metadata",
+                       " /img.png 200w",
+                       " /img.png 200w",
+               },
+               {
+                       "one bad",
+                       "javascript:alert(1) 200w",
+                       "#ZgotmplZ",
+               },
+               {
+                       "two ok",
+                       "foo.png, bar.png",
+                       "foo.png, bar.png",
+               },
+               {
+                       "left bad",
+                       "javascript:alert(1), /foo.png",
+                       "#ZgotmplZ, /foo.png",
+               },
+               {
+                       "right bad",
+                       "/bogus#, javascript:alert(1)",
+                       "/bogus#,#ZgotmplZ",
+               },
+       }
+
+       for _, test := range tests {
+               if got := srcsetFilterAndEscaper(test.input); got != test.want {
+                       t.Errorf("%s: srcsetFilterAndEscaper(%q) want %q != %q", test.name, test.input, test.want, got)
+               }
+       }
+}
+
+func BenchmarkURLEscaper(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               urlEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
+       }
+}
+
+func BenchmarkURLEscaperNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               urlEscaper("TheQuickBrownFoxJumpsOverTheLazyDog.")
+       }
+}
+
+func BenchmarkURLNormalizer(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               urlNormalizer("The quick brown fox jumps over the lazy dog.\n")
+       }
+}
+
+func BenchmarkURLNormalizerNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               urlNormalizer("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
+       }
+}
+
+func BenchmarkSrcsetFilter(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               srcsetFilterAndEscaper(" /foo/bar.png 200w, /baz/boo(1).png")
+       }
+}
+
+func BenchmarkSrcsetFilterNoSpecials(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               srcsetFilterAndEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
+       }
+}
diff --git a/tpl/internal/go_templates/htmltemplate/urlpart_string.go b/tpl/internal/go_templates/htmltemplate/urlpart_string.go
new file mode 100644 (file)
index 0000000..813eea9
--- /dev/null
@@ -0,0 +1,16 @@
+// Code generated by "stringer -type urlPart"; DO NOT EDIT.
+
+package template
+
+import "strconv"
+
+const _urlPart_name = "urlPartNoneurlPartPreQueryurlPartQueryOrFragurlPartUnknown"
+
+var _urlPart_index = [...]uint8{0, 11, 26, 44, 58}
+
+func (i urlPart) String() string {
+       if i >= urlPart(len(_urlPart_index)-1) {
+               return "urlPart(" + strconv.FormatInt(int64(i), 10) + ")"
+       }
+       return _urlPart_name[_urlPart_index[i]:_urlPart_index[i+1]]
+}
diff --git a/tpl/internal/go_templates/texttemplate/doc.go b/tpl/internal/go_templates/texttemplate/doc.go
new file mode 100644 (file)
index 0000000..dbffaa4
--- /dev/null
@@ -0,0 +1,456 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package template implements data-driven templates for generating textual output.
+
+To generate HTML output, see package html/template, which has the same interface
+as this package but automatically secures HTML output against certain attacks.
+
+Templates are executed by applying them to a data structure. Annotations in the
+template refer to elements of the data structure (typically a field of a struct
+or a key in a map) to control execution and derive values to be displayed.
+Execution of the template walks the structure and sets the cursor, represented
+by a period '.' and called "dot", to the value at the current location in the
+structure as execution proceeds.
+
+The input text for a template is UTF-8-encoded text in any format.
+"Actions"--data evaluations or control structures--are delimited by
+"{{" and "}}"; all text outside actions is copied to the output unchanged.
+Except for raw strings, actions may not span newlines, although comments can.
+
+Once parsed, a template may be executed safely in parallel, although if parallel
+executions share a Writer the output may be interleaved.
+
+Here is a trivial example that prints "17 items are made of wool".
+
+       type Inventory struct {
+               Material string
+               Count    uint
+       }
+       sweaters := Inventory{"wool", 17}
+       tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
+       if err != nil { panic(err) }
+       err = tmpl.Execute(os.Stdout, sweaters)
+       if err != nil { panic(err) }
+
+More intricate examples appear below.
+
+Text and spaces
+
+By default, all text between actions is copied verbatim when the template is
+executed. For example, the string " items are made of " in the example above appears
+on standard output when the program is run.
+
+However, to aid in formatting template source code, if an action's left delimiter
+(by default "{{") is followed immediately by a minus sign and ASCII space character
+("{{- "), all trailing white space is trimmed from the immediately preceding text.
+Similarly, if the right delimiter ("}}") is preceded by a space and minus sign
+(" -}}"), all leading white space is trimmed from the immediately following text.
+In these trim markers, the ASCII space must be present; "{{-3}}" parses as an
+action containing the number -3.
+
+For instance, when executing the template whose source is
+
+       "{{23 -}} < {{- 45}}"
+
+the generated output would be
+
+       "23<45"
+
+For this trimming, the definition of white space characters is the same as in Go:
+space, horizontal tab, carriage return, and newline.
+
+Actions
+
+Here is the list of actions. "Arguments" and "pipelines" are evaluations of
+data, defined in detail in the corresponding sections that follow.
+
+*/
+//     {{/* a comment */}}
+//     {{- /* a comment with white space trimmed from preceding and following text */ -}}
+//             A comment; discarded. May contain newlines.
+//             Comments do not nest and must start and end at the
+//             delimiters, as shown here.
+/*
+
+       {{pipeline}}
+               The default textual representation (the same as would be
+               printed by fmt.Print) of the value of the pipeline is copied
+               to the output.
+
+       {{if pipeline}} T1 {{end}}
+               If the value of the pipeline is empty, no output is generated;
+               otherwise, T1 is executed. The empty values are false, 0, any
+               nil pointer or interface value, and any array, slice, map, or
+               string of length zero.
+               Dot is unaffected.
+
+       {{if pipeline}} T1 {{else}} T0 {{end}}
+               If the value of the pipeline is empty, T0 is executed;
+               otherwise, T1 is executed. Dot is unaffected.
+
+       {{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
+               To simplify the appearance of if-else chains, the else action
+               of an if may include another if directly; the effect is exactly
+               the same as writing
+                       {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
+
+       {{range pipeline}} T1 {{end}}
+               The value of the pipeline must be an array, slice, map, or channel.
+               If the value of the pipeline has length zero, nothing is output;
+               otherwise, dot is set to the successive elements of the array,
+               slice, or map and T1 is executed. If the value is a map and the
+               keys are of basic type with a defined order ("comparable"), the
+               elements will be visited in sorted key order.
+
+       {{range pipeline}} T1 {{else}} T0 {{end}}
+               The value of the pipeline must be an array, slice, map, or channel.
+               If the value of the pipeline has length zero, dot is unaffected and
+               T0 is executed; otherwise, dot is set to the successive elements
+               of the array, slice, or map and T1 is executed.
+
+       {{template "name"}}
+               The template with the specified name is executed with nil data.
+
+       {{template "name" pipeline}}
+               The template with the specified name is executed with dot set
+               to the value of the pipeline.
+
+       {{block "name" pipeline}} T1 {{end}}
+               A block is shorthand for defining a template
+                       {{define "name"}} T1 {{end}}
+               and then executing it in place
+                       {{template "name" pipeline}}
+               The typical use is to define a set of root templates that are
+               then customized by redefining the block templates within.
+
+       {{with pipeline}} T1 {{end}}
+               If the value of the pipeline is empty, no output is generated;
+               otherwise, dot is set to the value of the pipeline and T1 is
+               executed.
+
+       {{with pipeline}} T1 {{else}} T0 {{end}}
+               If the value of the pipeline is empty, dot is unaffected and T0
+               is executed; otherwise, dot is set to the value of the pipeline
+               and T1 is executed.
+
+Arguments
+
+An argument is a simple value, denoted by one of the following.
+
+       - A boolean, string, character, integer, floating-point, imaginary
+         or complex constant in Go syntax. These behave like Go's untyped
+         constants. Note that, as in Go, whether a large integer constant
+         overflows when assigned or passed to a function can depend on whether
+         the host machine's ints are 32 or 64 bits.
+       - The keyword nil, representing an untyped Go nil.
+       - The character '.' (period):
+               .
+         The result is the value of dot.
+       - A variable name, which is a (possibly empty) alphanumeric string
+         preceded by a dollar sign, such as
+               $piOver2
+         or
+               $
+         The result is the value of the variable.
+         Variables are described below.
+       - The name of a field of the data, which must be a struct, preceded
+         by a period, such as
+               .Field
+         The result is the value of the field. Field invocations may be
+         chained:
+           .Field1.Field2
+         Fields can also be evaluated on variables, including chaining:
+           $x.Field1.Field2
+       - The name of a key of the data, which must be a map, preceded
+         by a period, such as
+               .Key
+         The result is the map element value indexed by the key.
+         Key invocations may be chained and combined with fields to any
+         depth:
+           .Field1.Key1.Field2.Key2
+         Although the key must be an alphanumeric identifier, unlike with
+         field names they do not need to start with an upper case letter.
+         Keys can also be evaluated on variables, including chaining:
+           $x.key1.key2
+       - The name of a niladic method of the data, preceded by a period,
+         such as
+               .Method
+         The result is the value of invoking the method with dot as the
+         receiver, dot.Method(). Such a method must have one return value (of
+         any type) or two return values, the second of which is an error.
+         If it has two and the returned error is non-nil, execution terminates
+         and an error is returned to the caller as the value of Execute.
+         Method invocations may be chained and combined with fields and keys
+         to any depth:
+           .Field1.Key1.Method1.Field2.Key2.Method2
+         Methods can also be evaluated on variables, including chaining:
+           $x.Method1.Field
+       - The name of a niladic function, such as
+               fun
+         The result is the value of invoking the function, fun(). The return
+         types and values behave as in methods. Functions and function
+         names are described below.
+       - A parenthesized instance of one the above, for grouping. The result
+         may be accessed by a field or map key invocation.
+               print (.F1 arg1) (.F2 arg2)
+               (.StructValuedMethod "arg").Field
+
+Arguments may evaluate to any type; if they are pointers the implementation
+automatically indirects to the base type when required.
+If an evaluation yields a function value, such as a function-valued
+field of a struct, the function is not invoked automatically, but it
+can be used as a truth value for an if action and the like. To invoke
+it, use the call function, defined below.
+
+Pipelines
+
+A pipeline is a possibly chained sequence of "commands". A command is a simple
+value (argument) or a function or method call, possibly with multiple arguments:
+
+       Argument
+               The result is the value of evaluating the argument.
+       .Method [Argument...]
+               The method can be alone or the last element of a chain but,
+               unlike methods in the middle of a chain, it can take arguments.
+               The result is the value of calling the method with the
+               arguments:
+                       dot.Method(Argument1, etc.)
+       functionName [Argument...]
+               The result is the value of calling the function associated
+               with the name:
+                       function(Argument1, etc.)
+               Functions and function names are described below.
+
+A pipeline may be "chained" by separating a sequence of commands with pipeline
+characters '|'. In a chained pipeline, the result of each command is
+passed as the last argument of the following command. The output of the final
+command in the pipeline is the value of the pipeline.
+
+The output of a command will be either one value or two values, the second of
+which has type error. If that second value is present and evaluates to
+non-nil, execution terminates and the error is returned to the caller of
+Execute.
+
+Variables
+
+A pipeline inside an action may initialize a variable to capture the result.
+The initialization has syntax
+
+       $variable := pipeline
+
+where $variable is the name of the variable. An action that declares a
+variable produces no output.
+
+Variables previously declared can also be assigned, using the syntax
+
+       $variable = pipeline
+
+If a "range" action initializes a variable, the variable is set to the
+successive elements of the iteration. Also, a "range" may declare two
+variables, separated by a comma:
+
+       range $index, $element := pipeline
+
+in which case $index and $element are set to the successive values of the
+array/slice index or map key and element, respectively. Note that if there is
+only one variable, it is assigned the element; this is opposite to the
+convention in Go range clauses.
+
+A variable's scope extends to the "end" action of the control structure ("if",
+"with", or "range") in which it is declared, or to the end of the template if
+there is no such control structure. A template invocation does not inherit
+variables from the point of its invocation.
+
+When execution begins, $ is set to the data argument passed to Execute, that is,
+to the starting value of dot.
+
+Examples
+
+Here are some example one-line templates demonstrating pipelines and variables.
+All produce the quoted word "output":
+
+       {{"\"output\""}}
+               A string constant.
+       {{`"output"`}}
+               A raw string constant.
+       {{printf "%q" "output"}}
+               A function call.
+       {{"output" | printf "%q"}}
+               A function call whose final argument comes from the previous
+               command.
+       {{printf "%q" (print "out" "put")}}
+               A parenthesized argument.
+       {{"put" | printf "%s%s" "out" | printf "%q"}}
+               A more elaborate call.
+       {{"output" | printf "%s" | printf "%q"}}
+               A longer chain.
+       {{with "output"}}{{printf "%q" .}}{{end}}
+               A with action using dot.
+       {{with $x := "output" | printf "%q"}}{{$x}}{{end}}
+               A with action that creates and uses a variable.
+       {{with $x := "output"}}{{printf "%q" $x}}{{end}}
+               A with action that uses the variable in another action.
+       {{with $x := "output"}}{{$x | printf "%q"}}{{end}}
+               The same, but pipelined.
+
+Functions
+
+During execution functions are found in two function maps: first in the
+template, then in the global function map. By default, no functions are defined
+in the template but the Funcs method can be used to add them.
+
+Predefined global functions are named as follows.
+
+       and
+               Returns the boolean AND of its arguments by returning the
+               first empty argument or the last argument, that is,
+               "and x y" behaves as "if x then y else x". All the
+               arguments are evaluated.
+       call
+               Returns the result of calling the first argument, which
+               must be a function, with the remaining arguments as parameters.
+               Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
+               Y is a func-valued field, map entry, or the like.
+               The first argument must be the result of an evaluation
+               that yields a value of function type (as distinct from
+               a predefined function such as print). The function must
+               return either one or two result values, the second of which
+               is of type error. If the arguments don't match the function
+               or the returned error value is non-nil, execution stops.
+       html
+               Returns the escaped HTML equivalent of the textual
+               representation of its arguments. This function is unavailable
+               in html/template, with a few exceptions.
+       index
+               Returns the result of indexing its first argument by the
+               following arguments. Thus "index x 1 2 3" is, in Go syntax,
+               x[1][2][3]. Each indexed item must be a map, slice, or array.
+       slice
+               slice returns the result of slicing its first argument by the
+               remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2],
+               while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3"
+               is x[1:2:3]. The first argument must be a string, slice, or array.
+       js
+               Returns the escaped JavaScript equivalent of the textual
+               representation of its arguments.
+       len
+               Returns the integer length of its argument.
+       not
+               Returns the boolean negation of its single argument.
+       or
+               Returns the boolean OR of its arguments by returning the
+               first non-empty argument or the last argument, that is,
+               "or x y" behaves as "if x then x else y". All the
+               arguments are evaluated.
+       print
+               An alias for fmt.Sprint
+       printf
+               An alias for fmt.Sprintf
+       println
+               An alias for fmt.Sprintln
+       urlquery
+               Returns the escaped value of the textual representation of
+               its arguments in a form suitable for embedding in a URL query.
+               This function is unavailable in html/template, with a few
+               exceptions.
+
+The boolean functions take any zero value to be false and a non-zero
+value to be true.
+
+There is also a set of binary comparison operators defined as
+functions:
+
+       eq
+               Returns the boolean truth of arg1 == arg2
+       ne
+               Returns the boolean truth of arg1 != arg2
+       lt
+               Returns the boolean truth of arg1 < arg2
+       le
+               Returns the boolean truth of arg1 <= arg2
+       gt
+               Returns the boolean truth of arg1 > arg2
+       ge
+               Returns the boolean truth of arg1 >= arg2
+
+For simpler multi-way equality tests, eq (only) accepts two or more
+arguments and compares the second and subsequent to the first,
+returning in effect
+
+       arg1==arg2 || arg1==arg3 || arg1==arg4 ...
+
+(Unlike with || in Go, however, eq is a function call and all the
+arguments will be evaluated.)
+
+The comparison functions work on basic types only (or named basic
+types, such as "type Celsius float32"). They implement the Go rules
+for comparison of values, except that size and exact type are
+ignored, so any integer value, signed or unsigned, may be compared
+with any other integer value. (The arithmetic value is compared,
+not the bit pattern, so all negative integers are less than all
+unsigned integers.) However, as usual, one may not compare an int
+with a float32 and so on.
+
+Associated templates
+
+Each template is named by a string specified when it is created. Also, each
+template is associated with zero or more other templates that it may invoke by
+name; such associations are transitive and form a name space of templates.
+
+A template may use a template invocation to instantiate another associated
+template; see the explanation of the "template" action above. The name must be
+that of a template associated with the template that contains the invocation.
+
+Nested template definitions
+
+When parsing a template, another template may be defined and associated with the
+template being parsed. Template definitions must appear at the top level of the
+template, much like global variables in a Go program.
+
+The syntax of such definitions is to surround each template declaration with a
+"define" and "end" action.
+
+The define action names the template being created by providing a string
+constant. Here is a simple example:
+
+       `{{define "T1"}}ONE{{end}}
+       {{define "T2"}}TWO{{end}}
+       {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
+       {{template "T3"}}`
+
+This defines two templates, T1 and T2, and a third T3 that invokes the other two
+when it is executed. Finally it invokes T3. If executed this template will
+produce the text
+
+       ONE TWO
+
+By construction, a template may reside in only one association. If it's
+necessary to have a template addressable from multiple associations, the
+template definition must be parsed multiple times to create distinct *Template
+values, or must be copied with the Clone or AddParseTree method.
+
+Parse may be called multiple times to assemble the various associated templates;
+see the ParseFiles and ParseGlob functions and methods for simple ways to parse
+related templates stored in files.
+
+A template may be executed directly or through ExecuteTemplate, which executes
+an associated template identified by name. To invoke our example above, we
+might write,
+
+       err := tmpl.Execute(os.Stdout, "no data needed")
+       if err != nil {
+               log.Fatalf("execution failed: %s", err)
+       }
+
+or to invoke a particular template explicitly by name,
+
+       err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
+       if err != nil {
+               log.Fatalf("execution failed: %s", err)
+       }
+
+*/
+package template
diff --git a/tpl/internal/go_templates/texttemplate/example_test.go b/tpl/internal/go_templates/texttemplate/example_test.go
new file mode 100644 (file)
index 0000000..f192cac
--- /dev/null
@@ -0,0 +1,112 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package template_test
+
+import (
+       "log"
+       "os"
+       "strings"
+       "text/template"
+)
+
+func ExampleTemplate() {
+       // Define a template.
+       const letter = `
+Dear {{.Name}},
+{{if .Attended}}
+It was a pleasure to see you at the wedding.
+{{- else}}
+It is a shame you couldn't make it to the wedding.
+{{- end}}
+{{with .Gift -}}
+Thank you for the lovely {{.}}.
+{{end}}
+Best wishes,
+Josie
+`
+
+       // Prepare some data to insert into the template.
+       type Recipient struct {
+               Name, Gift string
+               Attended   bool
+       }
+       var recipients = []Recipient{
+               {"Aunt Mildred", "bone china tea set", true},
+               {"Uncle John", "moleskin pants", false},
+               {"Cousin Rodney", "", false},
+       }
+
+       // Create a new template and parse the letter into it.
+       t := template.Must(template.New("letter").Parse(letter))
+
+       // Execute the template for each recipient.
+       for _, r := range recipients {
+               err := t.Execute(os.Stdout, r)
+               if err != nil {
+                       log.Println("executing template:", err)
+               }
+       }
+
+       // Output:
+       // Dear Aunt Mildred,
+       //
+       // It was a pleasure to see you at the wedding.
+       // Thank you for the lovely bone china tea set.
+       //
+       // Best wishes,
+       // Josie
+       //
+       // Dear Uncle John,
+       //
+       // It is a shame you couldn't make it to the wedding.
+       // Thank you for the lovely moleskin pants.
+       //
+       // Best wishes,
+       // Josie
+       //
+       // Dear Cousin Rodney,
+       //
+       // It is a shame you couldn't make it to the wedding.
+       //
+       // Best wishes,
+       // Josie
+}
+
+// The following example is duplicated in html/template; keep them in sync.
+
+func ExampleTemplate_block() {
+       const (
+               master  = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
+               overlay = `{{define "list"}} {{join . ", "}}{{end}} `
+       )
+       var (
+               funcs     = template.FuncMap{"join": strings.Join}
+               guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
+       )
+       masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
+       if err != nil {
+               log.Fatal(err)
+       }
+       overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
+       if err != nil {
+               log.Fatal(err)
+       }
+       if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
+               log.Fatal(err)
+       }
+       if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
+               log.Fatal(err)
+       }
+       // Output:
+       // Names:
+       // - Gamora
+       // - Groot
+       // - Nebula
+       // - Rocket
+       // - Star-Lord
+       // Names: Gamora, Groot, Nebula, Rocket, Star-Lord
+}
diff --git a/tpl/internal/go_templates/texttemplate/examplefiles_test.go b/tpl/internal/go_templates/texttemplate/examplefiles_test.go
new file mode 100644 (file)
index 0000000..8a78a01
--- /dev/null
@@ -0,0 +1,184 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package template_test
+
+import (
+       "io"
+       "io/ioutil"
+       "log"
+       "os"
+       "path/filepath"
+       "text/template"
+)
+
+// templateFile defines the contents of a template to be stored in a file, for testing.
+type templateFile struct {
+       name     string
+       contents string
+}
+
+func createTestDir(files []templateFile) string {
+       dir, err := ioutil.TempDir("", "template")
+       if err != nil {
+               log.Fatal(err)
+       }
+       for _, file := range files {
+               f, err := os.Create(filepath.Join(dir, file.name))
+               if err != nil {
+                       log.Fatal(err)
+               }
+               defer f.Close()
+               _, err = io.WriteString(f, file.contents)
+               if err != nil {
+                       log.Fatal(err)
+               }
+       }
+       return dir
+}
+
+// Here we demonstrate loading a set of templates from a directory.
+func ExampleTemplate_glob() {
+       // Here we create a temporary directory and populate it with our sample
+       // template definition files; usually the template files would already
+       // exist in some location known to the program.
+       dir := createTestDir([]templateFile{
+               // T0.tmpl is a plain template file that just invokes T1.
+               {"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
+               // T1.tmpl defines a template, T1 that invokes T2.
+               {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+               // T2.tmpl defines a template T2.
+               {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
+       })
+       // Clean up after the test; another quirk of running as an example.
+       defer os.RemoveAll(dir)
+
+       // pattern is the glob pattern used to find all the template files.
+       pattern := filepath.Join(dir, "*.tmpl")
+
+       // Here starts the example proper.
+       // T0.tmpl is the first name matched, so it becomes the starting template,
+       // the value returned by ParseGlob.
+       tmpl := template.Must(template.ParseGlob(pattern))
+
+       err := tmpl.Execute(os.Stdout, nil)
+       if err != nil {
+               log.Fatalf("template execution: %s", err)
+       }
+       // Output:
+       // T0 invokes T1: (T1 invokes T2: (This is T2))
+}
+
+// This example demonstrates one way to share some templates
+// and use them in different contexts. In this variant we add multiple driver
+// templates by hand to an existing bundle of templates.
+func ExampleTemplate_helpers() {
+       // Here we create a temporary directory and populate it with our sample
+       // template definition files; usually the template files would already
+       // exist in some location known to the program.
+       dir := createTestDir([]templateFile{
+               // T1.tmpl defines a template, T1 that invokes T2.
+               {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+               // T2.tmpl defines a template T2.
+               {"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
+       })
+       // Clean up after the test; another quirk of running as an example.
+       defer os.RemoveAll(dir)
+
+       // pattern is the glob pattern used to find all the template files.
+       pattern := filepath.Join(dir, "*.tmpl")
+
+       // Here starts the example proper.
+       // Load the helpers.
+       templates := template.Must(template.ParseGlob(pattern))
+       // Add one driver template to the bunch; we do this with an explicit template definition.
+       _, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
+       if err != nil {
+               log.Fatal("parsing driver1: ", err)
+       }
+       // Add another driver template.
+       _, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
+       if err != nil {
+               log.Fatal("parsing driver2: ", err)
+       }
+       // We load all the templates before execution. This package does not require
+       // that behavior but html/template's escaping does, so it's a good habit.
+       err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
+       if err != nil {
+               log.Fatalf("driver1 execution: %s", err)
+       }
+       err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
+       if err != nil {
+               log.Fatalf("driver2 execution: %s", err)
+       }
+       // Output:
+       // Driver 1 calls T1: (T1 invokes T2: (This is T2))
+       // Driver 2 calls T2: (This is T2)
+}
+
+// This example demonstrates how to use one group of driver
+// templates with distinct sets of helper templates.
+func ExampleTemplate_share() {
+       // Here we create a temporary directory and populate it with our sample
+       // template definition files; usually the template files would already
+       // exist in some location known to the program.
+       dir := createTestDir([]templateFile{
+               // T0.tmpl is a plain template file that just invokes T1.
+               {"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
+               // T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
+               {"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
+       })
+       // Clean up after the test; another quirk of running as an example.
+       defer os.RemoveAll(dir)
+
+       // pattern is the glob pattern used to find all the template files.
+       pattern := filepath.Join(dir, "*.tmpl")
+
+       // Here starts the example proper.
+       // Load the drivers.
+       drivers := template.Must(template.ParseGlob(pattern))
+
+       // We must define an implementation of the T2 template. First we clone
+       // the drivers, then add a definition of T2 to the template name space.
+
+       // 1. Clone the helper set to create a new name space from which to run them.
+       first, err := drivers.Clone()
+       if err != nil {
+               log.Fatal("cloning helpers: ", err)
+       }
+       // 2. Define T2, version A, and parse it.
+       _, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
+       if err != nil {
+               log.Fatal("parsing T2: ", err)
+       }
+
+       // Now repeat the whole thing, using a different version of T2.
+       // 1. Clone the drivers.
+       second, err := drivers.Clone()
+       if err != nil {
+               log.Fatal("cloning drivers: ", err)
+       }
+       // 2. Define T2, version B, and parse it.
+       _, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
+       if err != nil {
+               log.Fatal("parsing T2: ", err)
+       }
+
+       // Execute the templates in the reverse order to verify the
+       // first is unaffected by the second.
+       err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
+       if err != nil {
+               log.Fatalf("second execution: %s", err)
+       }
+       err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
+       if err != nil {
+               log.Fatalf("first: execution: %s", err)
+       }
+
+       // Output:
+       // T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
+       // T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
+}
diff --git a/tpl/internal/go_templates/texttemplate/examplefunc_test.go b/tpl/internal/go_templates/texttemplate/examplefunc_test.go
new file mode 100644 (file)
index 0000000..62aab02
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package template_test
+
+import (
+       "log"
+       "os"
+       "strings"
+       "text/template"
+)
+
+// This example demonstrates a custom function to process template text.
+// It installs the strings.Title function and uses it to
+// Make Title Text Look Good In Our Template's Output.
+func ExampleTemplate_func() {
+       // First we create a FuncMap with which to register the function.
+       funcMap := template.FuncMap{
+               // The name "title" is what the function will be called in the template text.
+               "title": strings.Title,
+       }
+
+       // A simple template definition to test our function.
+       // We print the input text several ways:
+       // - the original
+       // - title-cased
+       // - title-cased and then printed with %q
+       // - printed with %q and then title-cased.
+       const templateText = `
+Input: {{printf "%q" .}}
+Output 0: {{title .}}
+Output 1: {{title . | printf "%q"}}
+Output 2: {{printf "%q" . | title}}
+`
+
+       // Create a template, add the function map, and parse the text.
+       tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText)
+       if err != nil {
+               log.Fatalf("parsing: %s", err)
+       }
+
+       // Run the template to verify the output.
+       err = tmpl.Execute(os.Stdout, "the go programming language")
+       if err != nil {
+               log.Fatalf("execution: %s", err)
+       }
+
+       // Output:
+       // Input: "the go programming language"
+       // Output 0: The Go Programming Language
+       // Output 1: "The Go Programming Language"
+       // Output 2: "The Go Programming Language"
+}
diff --git a/tpl/internal/go_templates/texttemplate/exec.go b/tpl/internal/go_templates/texttemplate/exec.go
new file mode 100644 (file)
index 0000000..d477933
--- /dev/null
@@ -0,0 +1,980 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "fmt"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+       "io"
+       "reflect"
+       "runtime"
+       "strings"
+)
+
+// maxExecDepth specifies the maximum stack depth of templates within
+// templates. This limit is only practically reached by accidentally
+// recursive template invocations. This limit allows us to return
+// an error instead of triggering a stack overflow.
+var maxExecDepth = initMaxExecDepth()
+
+func initMaxExecDepth() int {
+       if runtime.GOARCH == "wasm" {
+               return 1000
+       }
+       return 100000
+}
+
+// state represents the state of an execution. It's not part of the
+// template so that multiple executions of the same template
+// can execute in parallel.
+type stateOld struct {
+       tmpl  *Template
+       wr    io.Writer
+       node  parse.Node // current node, for errors
+       vars  []variable // push-down stack of variable values.
+       depth int        // the height of the stack of executing templates.
+}
+
+// variable holds the dynamic value of a variable such as $, $x etc.
+type variable struct {
+       name  string
+       value reflect.Value
+}
+
+// push pushes a new variable on the stack.
+func (s *state) push(name string, value reflect.Value) {
+       s.vars = append(s.vars, variable{name, value})
+}
+
+// mark returns the length of the variable stack.
+func (s *state) mark() int {
+       return len(s.vars)
+}
+
+// pop pops the variable stack up to the mark.
+func (s *state) pop(mark int) {
+       s.vars = s.vars[0:mark]
+}
+
+// setVar overwrites the last declared variable with the given name.
+// Used by variable assignments.
+func (s *state) setVar(name string, value reflect.Value) {
+       for i := s.mark() - 1; i >= 0; i-- {
+               if s.vars[i].name == name {
+                       s.vars[i].value = value
+                       return
+               }
+       }
+       s.errorf("undefined variable: %s", name)
+}
+
+// setTopVar overwrites the top-nth variable on the stack. Used by range iterations.
+func (s *state) setTopVar(n int, value reflect.Value) {
+       s.vars[len(s.vars)-n].value = value
+}
+
+// varValue returns the value of the named variable.
+func (s *state) varValue(name string) reflect.Value {
+       for i := s.mark() - 1; i >= 0; i-- {
+               if s.vars[i].name == name {
+                       return s.vars[i].value
+               }
+       }
+       s.errorf("undefined variable: %s", name)
+       return zero
+}
+
+var zero reflect.Value
+
+type missingValType struct{}
+
+var missingVal = reflect.ValueOf(missingValType{})
+
+// at marks the state to be on node n, for error reporting.
+func (s *state) at(node parse.Node) {
+       s.node = node
+}
+
+// doublePercent returns the string with %'s replaced by %%, if necessary,
+// so it can be used safely inside a Printf format string.
+func doublePercent(str string) string {
+       return strings.ReplaceAll(str, "%", "%%")
+}
+
+// TODO: It would be nice if ExecError was more broken down, but
+// the way ErrorContext embeds the template name makes the
+// processing too clumsy.
+
+// ExecError is the custom error type returned when Execute has an
+// error evaluating its template. (If a write error occurs, the actual
+// error is returned; it will not be of type ExecError.)
+type ExecError struct {
+       Name string // Name of template.
+       Err  error  // Pre-formatted error.
+}
+
+func (e ExecError) Error() string {
+       return e.Err.Error()
+}
+
+func (e ExecError) Unwrap() error {
+       return e.Err
+}
+
+// errorf records an ExecError and terminates processing.
+func (s *state) errorf(format string, args ...interface{}) {
+       name := doublePercent(s.tmpl.Name())
+       if s.node == nil {
+               format = fmt.Sprintf("template: %s: %s", name, format)
+       } else {
+               location, context := s.tmpl.ErrorContext(s.node)
+               format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
+       }
+       panic(ExecError{
+               Name: s.tmpl.Name(),
+               Err:  fmt.Errorf(format, args...),
+       })
+}
+
+// writeError is the wrapper type used internally when Execute has an
+// error writing to its output. We strip the wrapper in errRecover.
+// Note that this is not an implementation of error, so it cannot escape
+// from the package as an error value.
+type writeError struct {
+       Err error // Original error.
+}
+
+func (s *state) writeError(err error) {
+       panic(writeError{
+               Err: err,
+       })
+}
+
+// errRecover is the handler that turns panics into returns from the top
+// level of Parse.
+func errRecover(errp *error) {
+       e := recover()
+       if e != nil {
+               switch err := e.(type) {
+               case runtime.Error:
+                       panic(e)
+               case writeError:
+                       *errp = err.Err // Strip the wrapper.
+               case ExecError:
+                       *errp = err // Keep the wrapper.
+               default:
+                       panic(e)
+               }
+       }
+}
+
+// ExecuteTemplate applies the template associated with t that has the given name
+// to the specified data object and writes the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
+func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
+       var tmpl *Template
+       if t.common != nil {
+               tmpl = t.tmpl[name]
+       }
+       if tmpl == nil {
+               return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
+       }
+       return tmpl.Execute(wr, data)
+}
+
+// Execute applies a parsed template to the specified data object,
+// and writes the output to wr.
+// If an error occurs executing the template or writing its output,
+// execution stops, but partial results may already have been written to
+// the output writer.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
+//
+// If data is a reflect.Value, the template applies to the concrete
+// value that the reflect.Value holds, as in fmt.Print.
+func (t *Template) Execute(wr io.Writer, data interface{}) error {
+       return t.execute(wr, data)
+}
+
+func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
+       defer errRecover(&err)
+       value, ok := data.(reflect.Value)
+       if !ok {
+               value = reflect.ValueOf(data)
+       }
+       state := &state{
+               tmpl: t,
+               wr:   wr,
+               vars: []variable{{"$", value}},
+       }
+       if t.Tree == nil || t.Root == nil {
+               state.errorf("%q is an incomplete or empty template", t.Name())
+       }
+       state.walk(value, t.Root)
+       return
+}
+
+// DefinedTemplates returns a string listing the defined templates,
+// prefixed by the string "; defined templates are: ". If there are none,
+// it returns the empty string. For generating an error message here
+// and in html/template.
+func (t *Template) DefinedTemplates() string {
+       if t.common == nil {
+               return ""
+       }
+       var b bytes.Buffer
+       for name, tmpl := range t.tmpl {
+               if tmpl.Tree == nil || tmpl.Root == nil {
+                       continue
+               }
+               if b.Len() > 0 {
+                       b.WriteString(", ")
+               }
+               fmt.Fprintf(&b, "%q", name)
+       }
+       var s string
+       if b.Len() > 0 {
+               s = "; defined templates are: " + b.String()
+       }
+       return s
+}
+
+// Walk functions step through the major pieces of the template structure,
+// generating output as they go.
+func (s *state) walk(dot reflect.Value, node parse.Node) {
+       s.at(node)
+       switch node := node.(type) {
+       case *parse.ActionNode:
+               // Do not pop variables so they persist until next end.
+               // Also, if the action declares variables, don't print the result.
+               val := s.evalPipeline(dot, node.Pipe)
+               if len(node.Pipe.Decl) == 0 {
+                       s.printValue(node, val)
+               }
+       case *parse.IfNode:
+               s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
+       case *parse.ListNode:
+               for _, node := range node.Nodes {
+                       s.walk(dot, node)
+               }
+       case *parse.RangeNode:
+               s.walkRange(dot, node)
+       case *parse.TemplateNode:
+               s.walkTemplate(dot, node)
+       case *parse.TextNode:
+               if _, err := s.wr.Write(node.Text); err != nil {
+                       s.writeError(err)
+               }
+       case *parse.WithNode:
+               s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
+       default:
+               s.errorf("unknown node: %s", node)
+       }
+}
+
+// walkIfOrWith walks an 'if' or 'with' node. The two control structures
+// are identical in behavior except that 'with' sets dot.
+func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
+       defer s.pop(s.mark())
+       val := s.evalPipeline(dot, pipe)
+       truth, ok := isTrue(indirectInterface(val))
+       if !ok {
+               s.errorf("if/with can't use %v", val)
+       }
+       if truth {
+               if typ == parse.NodeWith {
+                       s.walk(val, list)
+               } else {
+                       s.walk(dot, list)
+               }
+       } else if elseList != nil {
+               s.walk(dot, elseList)
+       }
+}
+
+// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
+// and whether the value has a meaningful truth value. This is the definition of
+// truth used by if and other such actions.
+func IsTrue(val interface{}) (truth, ok bool) {
+       return isTrue(reflect.ValueOf(val))
+}
+
+func isTrue(val reflect.Value) (truth, ok bool) {
+       if !val.IsValid() {
+               // Something like var x interface{}, never set. It's a form of nil.
+               return false, true
+       }
+       switch val.Kind() {
+       case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+               truth = val.Len() > 0
+       case reflect.Bool:
+               truth = val.Bool()
+       case reflect.Complex64, reflect.Complex128:
+               truth = val.Complex() != 0
+       case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
+               truth = !val.IsNil()
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               truth = val.Int() != 0
+       case reflect.Float32, reflect.Float64:
+               truth = val.Float() != 0
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               truth = val.Uint() != 0
+       case reflect.Struct:
+               truth = true // Struct values are always true.
+       default:
+               return
+       }
+       return truth, true
+}
+
+func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
+       s.at(r)
+       defer s.pop(s.mark())
+       val, _ := indirect(s.evalPipeline(dot, r.Pipe))
+       // mark top of stack before any variables in the body are pushed.
+       mark := s.mark()
+       oneIteration := func(index, elem reflect.Value) {
+               // Set top var (lexically the second if there are two) to the element.
+               if len(r.Pipe.Decl) > 0 {
+                       s.setTopVar(1, elem)
+               }
+               // Set next var (lexically the first if there are two) to the index.
+               if len(r.Pipe.Decl) > 1 {
+                       s.setTopVar(2, index)
+               }
+               s.walk(elem, r.List)
+               s.pop(mark)
+       }
+       switch val.Kind() {
+       case reflect.Array, reflect.Slice:
+               if val.Len() == 0 {
+                       break
+               }
+               for i := 0; i < val.Len(); i++ {
+                       oneIteration(reflect.ValueOf(i), val.Index(i))
+               }
+               return
+       case reflect.Map:
+               if val.Len() == 0 {
+                       break
+               }
+               om := fmtsort.Sort(val)
+               for i, key := range om.Key {
+                       oneIteration(key, om.Value[i])
+               }
+               return
+       case reflect.Chan:
+               if val.IsNil() {
+                       break
+               }
+               i := 0
+               for ; ; i++ {
+                       elem, ok := val.Recv()
+                       if !ok {
+                               break
+                       }
+                       oneIteration(reflect.ValueOf(i), elem)
+               }
+               if i == 0 {
+                       break
+               }
+               return
+       case reflect.Invalid:
+               break // An invalid value is likely a nil map, etc. and acts like an empty map.
+       default:
+               s.errorf("range can't iterate over %v", val)
+       }
+       if r.ElseList != nil {
+               s.walk(dot, r.ElseList)
+       }
+}
+
+func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
+       s.at(t)
+       tmpl := s.tmpl.tmpl[t.Name]
+       if tmpl == nil {
+               s.errorf("template %q not defined", t.Name)
+       }
+       if s.depth == maxExecDepth {
+               s.errorf("exceeded maximum template depth (%v)", maxExecDepth)
+       }
+       // Variables declared by the pipeline persist.
+       dot = s.evalPipeline(dot, t.Pipe)
+       newState := *s
+       newState.depth++
+       newState.tmpl = tmpl
+       // No dynamic scoping: template invocations inherit no variables.
+       newState.vars = []variable{{"$", dot}}
+       newState.walk(dot, tmpl.Root)
+}
+
+// Eval functions evaluate pipelines, commands, and their elements and extract
+// values from the data structure by examining fields, calling methods, and so on.
+// The printing of those values happens only through walk functions.
+
+// evalPipeline returns the value acquired by evaluating a pipeline. If the
+// pipeline has a variable declaration, the variable will be pushed on the
+// stack. Callers should therefore pop the stack after they are finished
+// executing commands depending on the pipeline value.
+func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
+       if pipe == nil {
+               return
+       }
+       s.at(pipe)
+       value = missingVal
+       for _, cmd := range pipe.Cmds {
+               value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
+               // If the object has type interface{}, dig down one level to the thing inside.
+               if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
+                       value = reflect.ValueOf(value.Interface()) // lovely!
+               }
+       }
+       for _, variable := range pipe.Decl {
+               if pipe.IsAssign {
+                       s.setVar(variable.Ident[0], value)
+               } else {
+                       s.push(variable.Ident[0], value)
+               }
+       }
+       return value
+}
+
+func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
+       if len(args) > 1 || final != missingVal {
+               s.errorf("can't give argument to non-function %s", args[0])
+       }
+}
+
+func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
+       firstWord := cmd.Args[0]
+       switch n := firstWord.(type) {
+       case *parse.FieldNode:
+               return s.evalFieldNode(dot, n, cmd.Args, final)
+       case *parse.ChainNode:
+               return s.evalChainNode(dot, n, cmd.Args, final)
+       case *parse.IdentifierNode:
+               // Must be a function.
+               return s.evalFunction(dot, n, cmd, cmd.Args, final)
+       case *parse.PipeNode:
+               // Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
+               return s.evalPipeline(dot, n)
+       case *parse.VariableNode:
+               return s.evalVariableNode(dot, n, cmd.Args, final)
+       }
+       s.at(firstWord)
+       s.notAFunction(cmd.Args, final)
+       switch word := firstWord.(type) {
+       case *parse.BoolNode:
+               return reflect.ValueOf(word.True)
+       case *parse.DotNode:
+               return dot
+       case *parse.NilNode:
+               s.errorf("nil is not a command")
+       case *parse.NumberNode:
+               return s.idealConstant(word)
+       case *parse.StringNode:
+               return reflect.ValueOf(word.Text)
+       }
+       s.errorf("can't evaluate command %q", firstWord)
+       panic("not reached")
+}
+
+// idealConstant is called to return the value of a number in a context where
+// we don't know the type. In that case, the syntax of the number tells us
+// its type, and we use Go rules to resolve. Note there is no such thing as
+// a uint ideal constant in this situation - the value must be of int type.
+func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
+       // These are ideal constants but we don't know the type
+       // and we have no context.  (If it was a method argument,
+       // we'd know what we need.) The syntax guides us to some extent.
+       s.at(constant)
+       switch {
+       case constant.IsComplex:
+               return reflect.ValueOf(constant.Complex128) // incontrovertible.
+       case constant.IsFloat && !isHexInt(constant.Text) && strings.ContainsAny(constant.Text, ".eEpP"):
+               return reflect.ValueOf(constant.Float64)
+       case constant.IsInt:
+               n := int(constant.Int64)
+               if int64(n) != constant.Int64 {
+                       s.errorf("%s overflows int", constant.Text)
+               }
+               return reflect.ValueOf(n)
+       case constant.IsUint:
+               s.errorf("%s overflows int", constant.Text)
+       }
+       return zero
+}
+
+func isHexInt(s string) bool {
+       return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && !strings.ContainsAny(s, "pP")
+}
+
+func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
+       s.at(field)
+       return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
+}
+
+func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
+       s.at(chain)
+       if len(chain.Field) == 0 {
+               s.errorf("internal error: no fields in evalChainNode")
+       }
+       if chain.Node.Type() == parse.NodeNil {
+               s.errorf("indirection through explicit nil in %s", chain)
+       }
+       // (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
+       pipe := s.evalArg(dot, nil, chain.Node)
+       return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
+}
+
+func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
+       // $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
+       s.at(variable)
+       value := s.varValue(variable.Ident[0])
+       if len(variable.Ident) == 1 {
+               s.notAFunction(args, final)
+               return value
+       }
+       return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
+}
+
+// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
+// dot is the environment in which to evaluate arguments, while
+// receiver is the value being walked along the chain.
+func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
+       n := len(ident)
+       for i := 0; i < n-1; i++ {
+               receiver = s.evalField(dot, ident[i], node, nil, missingVal, receiver)
+       }
+       // Now if it's a method, it gets the arguments.
+       return s.evalField(dot, ident[n-1], node, args, final, receiver)
+}
+
+func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
+       s.at(node)
+       name := node.Ident
+       function, ok := findFunction(name, s.tmpl)
+       if !ok {
+               s.errorf("%q is not a defined function", name)
+       }
+       return s.evalCall(dot, function, cmd, name, args, final)
+}
+
+// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
+// The 'final' argument represents the return value from the preceding
+// value of the pipeline, if any.
+func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
+       if !receiver.IsValid() {
+               if s.tmpl.option.missingKey == mapError { // Treat invalid value as missing map key.
+                       s.errorf("nil data; no entry for key %q", fieldName)
+               }
+               return zero
+       }
+       typ := receiver.Type()
+       receiver, isNil := indirect(receiver)
+       if receiver.Kind() == reflect.Interface && isNil {
+               // Calling a method on a nil interface can't work. The
+               // MethodByName method call below would panic.
+               s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
+               return zero
+       }
+
+       // Unless it's an interface, need to get to a value of type *T to guarantee
+       // we see all methods of T and *T.
+       ptr := receiver
+       if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() {
+               ptr = ptr.Addr()
+       }
+       if method := ptr.MethodByName(fieldName); method.IsValid() {
+               return s.evalCall(dot, method, node, fieldName, args, final)
+       }
+       hasArgs := len(args) > 1 || final != missingVal
+       // It's not a method; must be a field of a struct or an element of a map.
+       switch receiver.Kind() {
+       case reflect.Struct:
+               tField, ok := receiver.Type().FieldByName(fieldName)
+               if ok {
+                       field := receiver.FieldByIndex(tField.Index)
+                       if tField.PkgPath != "" { // field is unexported
+                               s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
+                       }
+                       // If it's a function, we must call it.
+                       if hasArgs {
+                               s.errorf("%s has arguments but cannot be invoked as function", fieldName)
+                       }
+                       return field
+               }
+       case reflect.Map:
+               // If it's a map, attempt to use the field name as a key.
+               nameVal := reflect.ValueOf(fieldName)
+               if nameVal.Type().AssignableTo(receiver.Type().Key()) {
+                       if hasArgs {
+                               s.errorf("%s is not a method but has arguments", fieldName)
+                       }
+                       result := receiver.MapIndex(nameVal)
+                       if !result.IsValid() {
+                               switch s.tmpl.option.missingKey {
+                               case mapInvalid:
+                                       // Just use the invalid value.
+                               case mapZeroValue:
+                                       result = reflect.Zero(receiver.Type().Elem())
+                               case mapError:
+                                       s.errorf("map has no entry for key %q", fieldName)
+                               }
+                       }
+                       return result
+               }
+       case reflect.Ptr:
+               etyp := receiver.Type().Elem()
+               if etyp.Kind() == reflect.Struct {
+                       if _, ok := etyp.FieldByName(fieldName); !ok {
+                               // If there's no such field, say "can't evaluate"
+                               // instead of "nil pointer evaluating".
+                               break
+                       }
+               }
+               if isNil {
+                       s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
+               }
+       }
+       s.errorf("can't evaluate field %s in type %s", fieldName, typ)
+       panic("not reached")
+}
+
+var (
+       errorType        = reflect.TypeOf((*error)(nil)).Elem()
+       fmtStringerType  = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
+       reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem()
+)
+
+// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
+// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
+// as the function itself.
+func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
+       if args != nil {
+               args = args[1:] // Zeroth arg is function name/node; not passed to function.
+       }
+       typ := fun.Type()
+       numIn := len(args)
+       if final != missingVal {
+               numIn++
+       }
+       numFixed := len(args)
+       if typ.IsVariadic() {
+               numFixed = typ.NumIn() - 1 // last arg is the variadic one.
+               if numIn < numFixed {
+                       s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
+               }
+       } else if numIn != typ.NumIn() {
+               s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), numIn)
+       }
+       if !goodFunc(typ) {
+               // TODO: This could still be a confusing error; maybe goodFunc should provide info.
+               s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
+       }
+       // Build the arg list.
+       argv := make([]reflect.Value, numIn)
+       // Args must be evaluated. Fixed args first.
+       i := 0
+       for ; i < numFixed && i < len(args); i++ {
+               argv[i] = s.evalArg(dot, typ.In(i), args[i])
+       }
+       // Now the ... args.
+       if typ.IsVariadic() {
+               argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
+               for ; i < len(args); i++ {
+                       argv[i] = s.evalArg(dot, argType, args[i])
+               }
+       }
+       // Add final value if necessary.
+       if final != missingVal {
+               t := typ.In(typ.NumIn() - 1)
+               if typ.IsVariadic() {
+                       if numIn-1 < numFixed {
+                               // The added final argument corresponds to a fixed parameter of the function.
+                               // Validate against the type of the actual parameter.
+                               t = typ.In(numIn - 1)
+                       } else {
+                               // The added final argument corresponds to the variadic part.
+                               // Validate against the type of the elements of the variadic slice.
+                               t = t.Elem()
+                       }
+               }
+               argv[i] = s.validateType(final, t)
+       }
+       v, err := safeCall(fun, argv)
+       // If we have an error that is not nil, stop execution and return that
+       // error to the caller.
+       if err != nil {
+               s.at(node)
+               s.errorf("error calling %s: %v", name, err)
+       }
+       if v.Type() == reflectValueType {
+               v = v.Interface().(reflect.Value)
+       }
+       return v
+}
+
+// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
+func canBeNil(typ reflect.Type) bool {
+       switch typ.Kind() {
+       case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+               return true
+       case reflect.Struct:
+               return typ == reflectValueType
+       }
+       return false
+}
+
+// validateType guarantees that the value is valid and assignable to the type.
+func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
+       if !value.IsValid() {
+               if typ == nil {
+                       // An untyped nil interface{}. Accept as a proper nil value.
+                       return reflect.ValueOf(nil)
+               }
+               if canBeNil(typ) {
+                       // Like above, but use the zero value of the non-nil type.
+                       return reflect.Zero(typ)
+               }
+               s.errorf("invalid value; expected %s", typ)
+       }
+       if typ == reflectValueType && value.Type() != typ {
+               return reflect.ValueOf(value)
+       }
+       if typ != nil && !value.Type().AssignableTo(typ) {
+               if value.Kind() == reflect.Interface && !value.IsNil() {
+                       value = value.Elem()
+                       if value.Type().AssignableTo(typ) {
+                               return value
+                       }
+                       // fallthrough
+               }
+               // Does one dereference or indirection work? We could do more, as we
+               // do with method receivers, but that gets messy and method receivers
+               // are much more constrained, so it makes more sense there than here.
+               // Besides, one is almost always all you need.
+               switch {
+               case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
+                       value = value.Elem()
+                       if !value.IsValid() {
+                               s.errorf("dereference of nil pointer of type %s", typ)
+                       }
+               case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
+                       value = value.Addr()
+               default:
+                       s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
+               }
+       }
+       return value
+}
+
+func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
+       switch arg := n.(type) {
+       case *parse.DotNode:
+               return s.validateType(dot, typ)
+       case *parse.NilNode:
+               if canBeNil(typ) {
+                       return reflect.Zero(typ)
+               }
+               s.errorf("cannot assign nil to %s", typ)
+       case *parse.FieldNode:
+               return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, missingVal), typ)
+       case *parse.VariableNode:
+               return s.validateType(s.evalVariableNode(dot, arg, nil, missingVal), typ)
+       case *parse.PipeNode:
+               return s.validateType(s.evalPipeline(dot, arg), typ)
+       case *parse.IdentifierNode:
+               return s.validateType(s.evalFunction(dot, arg, arg, nil, missingVal), typ)
+       case *parse.ChainNode:
+               return s.validateType(s.evalChainNode(dot, arg, nil, missingVal), typ)
+       }
+       switch typ.Kind() {
+       case reflect.Bool:
+               return s.evalBool(typ, n)
+       case reflect.Complex64, reflect.Complex128:
+               return s.evalComplex(typ, n)
+       case reflect.Float32, reflect.Float64:
+               return s.evalFloat(typ, n)
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return s.evalInteger(typ, n)
+       case reflect.Interface:
+               if typ.NumMethod() == 0 {
+                       return s.evalEmptyInterface(dot, n)
+               }
+       case reflect.Struct:
+               if typ == reflectValueType {
+                       return reflect.ValueOf(s.evalEmptyInterface(dot, n))
+               }
+       case reflect.String:
+               return s.evalString(typ, n)
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return s.evalUnsignedInteger(typ, n)
+       }
+       s.errorf("can't handle %s for arg of type %s", n, typ)
+       panic("not reached")
+}
+
+func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
+       if n, ok := n.(*parse.BoolNode); ok {
+               value := reflect.New(typ).Elem()
+               value.SetBool(n.True)
+               return value
+       }
+       s.errorf("expected bool; found %s", n)
+       panic("not reached")
+}
+
+func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
+       if n, ok := n.(*parse.StringNode); ok {
+               value := reflect.New(typ).Elem()
+               value.SetString(n.Text)
+               return value
+       }
+       s.errorf("expected string; found %s", n)
+       panic("not reached")
+}
+
+func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
+       if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
+               value := reflect.New(typ).Elem()
+               value.SetInt(n.Int64)
+               return value
+       }
+       s.errorf("expected integer; found %s", n)
+       panic("not reached")
+}
+
+func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
+       if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
+               value := reflect.New(typ).Elem()
+               value.SetUint(n.Uint64)
+               return value
+       }
+       s.errorf("expected unsigned integer; found %s", n)
+       panic("not reached")
+}
+
+func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
+       s.at(n)
+       if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
+               value := reflect.New(typ).Elem()
+               value.SetFloat(n.Float64)
+               return value
+       }
+       s.errorf("expected float; found %s", n)
+       panic("not reached")
+}
+
+func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
+       if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
+               value := reflect.New(typ).Elem()
+               value.SetComplex(n.Complex128)
+               return value
+       }
+       s.errorf("expected complex; found %s", n)
+       panic("not reached")
+}
+
+func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
+       s.at(n)
+       switch n := n.(type) {
+       case *parse.BoolNode:
+               return reflect.ValueOf(n.True)
+       case *parse.DotNode:
+               return dot
+       case *parse.FieldNode:
+               return s.evalFieldNode(dot, n, nil, missingVal)
+       case *parse.IdentifierNode:
+               return s.evalFunction(dot, n, n, nil, missingVal)
+       case *parse.NilNode:
+               // NilNode is handled in evalArg, the only place that calls here.
+               s.errorf("evalEmptyInterface: nil (can't happen)")
+       case *parse.NumberNode:
+               return s.idealConstant(n)
+       case *parse.StringNode:
+               return reflect.ValueOf(n.Text)
+       case *parse.VariableNode:
+               return s.evalVariableNode(dot, n, nil, missingVal)
+       case *parse.PipeNode:
+               return s.evalPipeline(dot, n)
+       }
+       s.errorf("can't handle assignment of %s to empty interface argument", n)
+       panic("not reached")
+}
+
+// indirect returns the item at the end of indirection, and a bool to indicate
+// if it's nil. If the returned bool is true, the returned value's kind will be
+// either a pointer or interface.
+func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
+       for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
+               if v.IsNil() {
+                       return v, true
+               }
+       }
+       return v, false
+}
+
+// indirectInterface returns the concrete value in an interface value,
+// or else the zero reflect.Value.
+// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x):
+// the fact that x was an interface value is forgotten.
+func indirectInterface(v reflect.Value) reflect.Value {
+       if v.Kind() != reflect.Interface {
+               return v
+       }
+       if v.IsNil() {
+               return reflect.Value{}
+       }
+       return v.Elem()
+}
+
+// printValue writes the textual representation of the value to the output of
+// the template.
+func (s *state) printValue(n parse.Node, v reflect.Value) {
+       s.at(n)
+       iface, ok := printableValue(v)
+       if !ok {
+               s.errorf("can't print %s of type %s", n, v.Type())
+       }
+       _, err := fmt.Fprint(s.wr, iface)
+       if err != nil {
+               s.writeError(err)
+       }
+}
+
+// printableValue returns the, possibly indirected, interface value inside v that
+// is best for a call to formatted printer.
+func printableValue(v reflect.Value) (interface{}, bool) {
+       if v.Kind() == reflect.Ptr {
+               v, _ = indirect(v) // fmt.Fprint handles nil.
+       }
+       if !v.IsValid() {
+               return "<no value>", true
+       }
+
+       if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
+               if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
+                       v = v.Addr()
+               } else {
+                       switch v.Kind() {
+                       case reflect.Chan, reflect.Func:
+                               return nil, false
+                       }
+               }
+       }
+       return v.Interface(), true
+}
diff --git a/tpl/internal/go_templates/texttemplate/exec_test.go b/tpl/internal/go_templates/texttemplate/exec_test.go
new file mode 100644 (file)
index 0000000..504967d
--- /dev/null
@@ -0,0 +1,1624 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+import (
+       "bytes"
+       "errors"
+       "flag"
+       "fmt"
+       "io/ioutil"
+       "reflect"
+       "strings"
+       "testing"
+)
+
+var debug = flag.Bool("debug", false, "show the errors produced by the tests")
+
+// T has lots of interesting pieces to use to test execution.
+type T struct {
+       // Basics
+       True        bool
+       I           int
+       U16         uint16
+       X, S        string
+       FloatZero   float64
+       ComplexZero complex128
+       // Nested structs.
+       U *U
+       // Struct with String method.
+       V0     V
+       V1, V2 *V
+       // Struct with Error method.
+       W0     W
+       W1, W2 *W
+       // Slices
+       SI      []int
+       SICap   []int
+       SIEmpty []int
+       SB      []bool
+       // Arrays
+       AI [3]int
+       // Maps
+       MSI      map[string]int
+       MSIone   map[string]int // one element, for deterministic output
+       MSIEmpty map[string]int
+       MXI      map[interface{}]int
+       MII      map[int]int
+       MI32S    map[int32]string
+       MI64S    map[int64]string
+       MUI32S   map[uint32]string
+       MUI64S   map[uint64]string
+       MI8S     map[int8]string
+       MUI8S    map[uint8]string
+       SMSI     []map[string]int
+       // Empty interfaces; used to see if we can dig inside one.
+       Empty0 interface{} // nil
+       Empty1 interface{}
+       Empty2 interface{}
+       Empty3 interface{}
+       Empty4 interface{}
+       // Non-empty interfaces.
+       NonEmptyInterface         I
+       NonEmptyInterfacePtS      *I
+       NonEmptyInterfaceNil      I
+       NonEmptyInterfaceTypedNil I
+       // Stringer.
+       Str fmt.Stringer
+       Err error
+       // Pointers
+       PI  *int
+       PS  *string
+       PSI *[]int
+       NIL *int
+       // Function (not method)
+       BinaryFunc      func(string, string) string
+       VariadicFunc    func(...string) string
+       VariadicFuncInt func(int, ...string) string
+       NilOKFunc       func(*int) bool
+       ErrFunc         func() (string, error)
+       PanicFunc       func() string
+       // Template to test evaluation of templates.
+       Tmpl *Template
+       // Unexported field; cannot be accessed by template.
+       unexported int
+}
+
+type S []string
+
+func (S) Method0() string {
+       return "M0"
+}
+
+type U struct {
+       V string
+}
+
+type V struct {
+       j int
+}
+
+func (v *V) String() string {
+       if v == nil {
+               return "nilV"
+       }
+       return fmt.Sprintf("<%d>", v.j)
+}
+
+type W struct {
+       k int
+}
+
+func (w *W) Error() string {
+       if w == nil {
+               return "nilW"
+       }
+       return fmt.Sprintf("[%d]", w.k)
+}
+
+var siVal = I(S{"a", "b"})
+
+var tVal = &T{
+       True:   true,
+       I:      17,
+       U16:    16,
+       X:      "x",
+       S:      "xyz",
+       U:      &U{"v"},
+       V0:     V{6666},
+       V1:     &V{7777}, // leave V2 as nil
+       W0:     W{888},
+       W1:     &W{999}, // leave W2 as nil
+       SI:     []int{3, 4, 5},
+       SICap:  make([]int, 5, 10),
+       AI:     [3]int{3, 4, 5},
+       SB:     []bool{true, false},
+       MSI:    map[string]int{"one": 1, "two": 2, "three": 3},
+       MSIone: map[string]int{"one": 1},
+       MXI:    map[interface{}]int{"one": 1},
+       MII:    map[int]int{1: 1},
+       MI32S:  map[int32]string{1: "one", 2: "two"},
+       MI64S:  map[int64]string{2: "i642", 3: "i643"},
+       MUI32S: map[uint32]string{2: "u322", 3: "u323"},
+       MUI64S: map[uint64]string{2: "ui642", 3: "ui643"},
+       MI8S:   map[int8]string{2: "i82", 3: "i83"},
+       MUI8S:  map[uint8]string{2: "u82", 3: "u83"},
+       SMSI: []map[string]int{
+               {"one": 1, "two": 2},
+               {"eleven": 11, "twelve": 12},
+       },
+       Empty1:                    3,
+       Empty2:                    "empty2",
+       Empty3:                    []int{7, 8},
+       Empty4:                    &U{"UinEmpty"},
+       NonEmptyInterface:         &T{X: "x"},
+       NonEmptyInterfacePtS:      &siVal,
+       NonEmptyInterfaceTypedNil: (*T)(nil),
+       Str:                       bytes.NewBuffer([]byte("foozle")),
+       Err:                       errors.New("erroozle"),
+       PI:                        newInt(23),
+       PS:                        newString("a string"),
+       PSI:                       newIntSlice(21, 22, 23),
+       BinaryFunc:                func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
+       VariadicFunc:              func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
+       VariadicFuncInt:           func(a int, s ...string) string { return fmt.Sprint(a, "=<", strings.Join(s, "+"), ">") },
+       NilOKFunc:                 func(s *int) bool { return s == nil },
+       ErrFunc:                   func() (string, error) { return "bla", nil },
+       PanicFunc:                 func() string { panic("test panic") },
+       Tmpl:                      Must(New("x").Parse("test template")), // "x" is the value of .X
+}
+
+var tSliceOfNil = []*T{nil}
+
+// A non-empty interface.
+type I interface {
+       Method0() string
+}
+
+var iVal I = tVal
+
+// Helpers for creation.
+func newInt(n int) *int {
+       return &n
+}
+
+func newString(s string) *string {
+       return &s
+}
+
+func newIntSlice(n ...int) *[]int {
+       p := new([]int)
+       *p = make([]int, len(n))
+       copy(*p, n)
+       return p
+}
+
+// Simple methods with and without arguments.
+func (t *T) Method0() string {
+       return "M0"
+}
+
+func (t *T) Method1(a int) int {
+       return a
+}
+
+func (t *T) Method2(a uint16, b string) string {
+       return fmt.Sprintf("Method2: %d %s", a, b)
+}
+
+func (t *T) Method3(v interface{}) string {
+       return fmt.Sprintf("Method3: %v", v)
+}
+
+func (t *T) Copy() *T {
+       n := new(T)
+       *n = *t
+       return n
+}
+
+func (t *T) MAdd(a int, b []int) []int {
+       v := make([]int, len(b))
+       for i, x := range b {
+               v[i] = x + a
+       }
+       return v
+}
+
+var myError = errors.New("my error")
+
+// MyError returns a value and an error according to its argument.
+func (t *T) MyError(error bool) (bool, error) {
+       if error {
+               return true, myError
+       }
+       return false, nil
+}
+
+// A few methods to test chaining.
+func (t *T) GetU() *U {
+       return t.U
+}
+
+func (u *U) TrueFalse(b bool) string {
+       if b {
+               return "true"
+       }
+       return ""
+}
+
+func typeOf(arg interface{}) string {
+       return fmt.Sprintf("%T", arg)
+}
+
+type execTest struct {
+       name   string
+       input  string
+       output string
+       data   interface{}
+       ok     bool
+}
+
+// bigInt and bigUint are hex string representing numbers either side
+// of the max int boundary.
+// We do it this way so the test doesn't depend on ints being 32 bits.
+var (
+       bigInt  = fmt.Sprintf("0x%x", int(1<<uint(reflect.TypeOf(0).Bits()-1)-1))
+       bigUint = fmt.Sprintf("0x%x", uint(1<<uint(reflect.TypeOf(0).Bits()-1)))
+)
+
+var execTests = []execTest{
+       // Trivial cases.
+       {"empty", "", "", nil, true},
+       {"text", "some text", "some text", nil, true},
+       {"nil action", "{{nil}}", "", nil, false},
+
+       // Ideal constants.
+       {"ideal int", "{{typeOf 3}}", "int", 0, true},
+       {"ideal float", "{{typeOf 1.0}}", "float64", 0, true},
+       {"ideal exp float", "{{typeOf 1e1}}", "float64", 0, true},
+       {"ideal complex", "{{typeOf 1i}}", "complex128", 0, true},
+       {"ideal int", "{{typeOf " + bigInt + "}}", "int", 0, true},
+       {"ideal too big", "{{typeOf " + bigUint + "}}", "", 0, false},
+       {"ideal nil without type", "{{nil}}", "", 0, false},
+
+       // Fields of structs.
+       {".X", "-{{.X}}-", "-x-", tVal, true},
+       {".U.V", "-{{.U.V}}-", "-v-", tVal, true},
+       {".unexported", "{{.unexported}}", "", tVal, false},
+
+       // Fields on maps.
+       {"map .one", "{{.MSI.one}}", "1", tVal, true},
+       {"map .two", "{{.MSI.two}}", "2", tVal, true},
+       {"map .NO", "{{.MSI.NO}}", "<no value>", tVal, true},
+       {"map .one interface", "{{.MXI.one}}", "1", tVal, true},
+       {"map .WRONG args", "{{.MSI.one 1}}", "", tVal, false},
+       {"map .WRONG type", "{{.MII.one}}", "", tVal, false},
+
+       // Dots of all kinds to test basic evaluation.
+       {"dot int", "<{{.}}>", "<13>", 13, true},
+       {"dot uint", "<{{.}}>", "<14>", uint(14), true},
+       {"dot float", "<{{.}}>", "<15.1>", 15.1, true},
+       {"dot bool", "<{{.}}>", "<true>", true, true},
+       {"dot complex", "<{{.}}>", "<(16.2-17i)>", 16.2 - 17i, true},
+       {"dot string", "<{{.}}>", "<hello>", "hello", true},
+       {"dot slice", "<{{.}}>", "<[-1 -2 -3]>", []int{-1, -2, -3}, true},
+       {"dot map", "<{{.}}>", "<map[two:22]>", map[string]int{"two": 22}, true},
+       {"dot struct", "<{{.}}>", "<{7 seven}>", struct {
+               a int
+               b string
+       }{7, "seven"}, true},
+
+       // Variables.
+       {"$ int", "{{$}}", "123", 123, true},
+       {"$.I", "{{$.I}}", "17", tVal, true},
+       {"$.U.V", "{{$.U.V}}", "v", tVal, true},
+       {"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
+       {"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
+       {"nested assignment",
+               "{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
+               "3", tVal, true},
+       {"nested assignment changes the last declaration",
+               "{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
+               "1", tVal, true},
+
+       // Type with String method.
+       {"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true},
+       {"&V{7777}.String()", "-{{.V1}}-", "-<7777>-", tVal, true},
+       {"(*V)(nil).String()", "-{{.V2}}-", "-nilV-", tVal, true},
+
+       // Type with Error method.
+       {"W{888}.Error()", "-{{.W0}}-", "-[888]-", tVal, true},
+       {"&W{999}.Error()", "-{{.W1}}-", "-[999]-", tVal, true},
+       {"(*W)(nil).Error()", "-{{.W2}}-", "-nilW-", tVal, true},
+
+       // Pointers.
+       {"*int", "{{.PI}}", "23", tVal, true},
+       {"*string", "{{.PS}}", "a string", tVal, true},
+       {"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true},
+       {"*[]int[1]", "{{index .PSI 1}}", "22", tVal, true},
+       {"NIL", "{{.NIL}}", "<nil>", tVal, true},
+
+       // Empty interfaces holding values.
+       {"empty nil", "{{.Empty0}}", "<no value>", tVal, true},
+       {"empty with int", "{{.Empty1}}", "3", tVal, true},
+       {"empty with string", "{{.Empty2}}", "empty2", tVal, true},
+       {"empty with slice", "{{.Empty3}}", "[7 8]", tVal, true},
+       {"empty with struct", "{{.Empty4}}", "{UinEmpty}", tVal, true},
+       {"empty with struct, field", "{{.Empty4.V}}", "UinEmpty", tVal, true},
+
+       // Edge cases with <no value> with an interface value
+       {"field on interface", "{{.foo}}", "<no value>", nil, true},
+       {"field on parenthesized interface", "{{(.).foo}}", "<no value>", nil, true},
+
+       // Method calls.
+       {".Method0", "-{{.Method0}}-", "-M0-", tVal, true},
+       {".Method1(1234)", "-{{.Method1 1234}}-", "-1234-", tVal, true},
+       {".Method1(.I)", "-{{.Method1 .I}}-", "-17-", tVal, true},
+       {".Method2(3, .X)", "-{{.Method2 3 .X}}-", "-Method2: 3 x-", tVal, true},
+       {".Method2(.U16, `str`)", "-{{.Method2 .U16 `str`}}-", "-Method2: 16 str-", tVal, true},
+       {".Method2(.U16, $x)", "{{if $x := .X}}-{{.Method2 .U16 $x}}{{end}}-", "-Method2: 16 x-", tVal, true},
+       {".Method3(nil constant)", "-{{.Method3 nil}}-", "-Method3: <nil>-", tVal, true},
+       {".Method3(nil value)", "-{{.Method3 .MXI.unset}}-", "-Method3: <nil>-", tVal, true},
+       {"method on var", "{{if $x := .}}-{{$x.Method2 .U16 $x.X}}{{end}}-", "-Method2: 16 x-", tVal, true},
+       {"method on chained var",
+               "{{range .MSIone}}{{if $.U.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
+               "true", tVal, true},
+       {"chained method",
+               "{{range .MSIone}}{{if $.GetU.TrueFalse $.True}}{{$.U.TrueFalse $.True}}{{else}}WRONG{{end}}{{end}}",
+               "true", tVal, true},
+       {"chained method on variable",
+               "{{with $x := .}}{{with .SI}}{{$.GetU.TrueFalse $.True}}{{end}}{{end}}",
+               "true", tVal, true},
+       {".NilOKFunc not nil", "{{call .NilOKFunc .PI}}", "false", tVal, true},
+       {".NilOKFunc nil", "{{call .NilOKFunc nil}}", "true", tVal, true},
+       {"method on nil value from slice", "-{{range .}}{{.Method1 1234}}{{end}}-", "-1234-", tSliceOfNil, true},
+       {"method on typed nil interface value", "{{.NonEmptyInterfaceTypedNil.Method0}}", "M0", tVal, true},
+
+       // Function call builtin.
+       {".BinaryFunc", "{{call .BinaryFunc `1` `2`}}", "[1=2]", tVal, true},
+       {".VariadicFunc0", "{{call .VariadicFunc}}", "<>", tVal, true},
+       {".VariadicFunc2", "{{call .VariadicFunc `he` `llo`}}", "<he+llo>", tVal, true},
+       {".VariadicFuncInt", "{{call .VariadicFuncInt 33 `he` `llo`}}", "33=<he+llo>", tVal, true},
+       {"if .BinaryFunc call", "{{ if .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{end}}", "[1=2]", tVal, true},
+       {"if not .BinaryFunc call", "{{ if not .BinaryFunc}}{{call .BinaryFunc `1` `2`}}{{else}}No{{end}}", "No", tVal, true},
+       {"Interface Call", `{{stringer .S}}`, "foozle", map[string]interface{}{"S": bytes.NewBufferString("foozle")}, true},
+       {".ErrFunc", "{{call .ErrFunc}}", "bla", tVal, true},
+       {"call nil", "{{call nil}}", "", tVal, false},
+
+       // Erroneous function calls (check args).
+       {".BinaryFuncTooFew", "{{call .BinaryFunc `1`}}", "", tVal, false},
+       {".BinaryFuncTooMany", "{{call .BinaryFunc `1` `2` `3`}}", "", tVal, false},
+       {".BinaryFuncBad0", "{{call .BinaryFunc 1 3}}", "", tVal, false},
+       {".BinaryFuncBad1", "{{call .BinaryFunc `1` 3}}", "", tVal, false},
+       {".VariadicFuncBad0", "{{call .VariadicFunc 3}}", "", tVal, false},
+       {".VariadicFuncIntBad0", "{{call .VariadicFuncInt}}", "", tVal, false},
+       {".VariadicFuncIntBad`", "{{call .VariadicFuncInt `x`}}", "", tVal, false},
+       {".VariadicFuncNilBad", "{{call .VariadicFunc nil}}", "", tVal, false},
+
+       // Pipelines.
+       {"pipeline", "-{{.Method0 | .Method2 .U16}}-", "-Method2: 16 M0-", tVal, true},
+       {"pipeline func", "-{{call .VariadicFunc `llo` | call .VariadicFunc `he` }}-", "-<he+<llo>>-", tVal, true},
+
+       // Nil values aren't missing arguments.
+       {"nil pipeline", "{{ .Empty0 | call .NilOKFunc }}", "true", tVal, true},
+       {"nil call arg", "{{ call .NilOKFunc .Empty0 }}", "true", tVal, true},
+       {"bad nil pipeline", "{{ .Empty0 | .VariadicFunc }}", "", tVal, false},
+
+       // Parenthesized expressions
+       {"parens in pipeline", "{{printf `%d %d %d` (1) (2 | add 3) (add 4 (add 5 6))}}", "1 5 15", tVal, true},
+
+       // Parenthesized expressions with field accesses
+       {"parens: $ in paren", "{{($).X}}", "x", tVal, true},
+       {"parens: $.GetU in paren", "{{($.GetU).V}}", "v", tVal, true},
+       {"parens: $ in paren in pipe", "{{($ | echo).X}}", "x", tVal, true},
+       {"parens: spaces and args", `{{(makemap "up" "down" "left" "right").left}}`, "right", tVal, true},
+
+       // If.
+       {"if true", "{{if true}}TRUE{{end}}", "TRUE", tVal, true},
+       {"if false", "{{if false}}TRUE{{else}}FALSE{{end}}", "FALSE", tVal, true},
+       {"if nil", "{{if nil}}TRUE{{end}}", "", tVal, false},
+       {"if on typed nil interface value", "{{if .NonEmptyInterfaceTypedNil}}TRUE{{ end }}", "", tVal, true},
+       {"if 1", "{{if 1}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
+       {"if 0", "{{if 0}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
+       {"if 1.5", "{{if 1.5}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
+       {"if 0.0", "{{if .FloatZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
+       {"if 1.5i", "{{if 1.5i}}NON-ZERO{{else}}ZERO{{end}}", "NON-ZERO", tVal, true},
+       {"if 0.0i", "{{if .ComplexZero}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
+       {"if emptystring", "{{if ``}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"if string", "{{if `notempty`}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
+       {"if emptyslice", "{{if .SIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
+       {"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
+       {"if map unset", "{{if .MXI.none}}NON-ZERO{{else}}ZERO{{end}}", "ZERO", tVal, true},
+       {"if map not unset", "{{if not .MXI.none}}ZERO{{else}}NON-ZERO{{end}}", "ZERO", tVal, true},
+       {"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true},
+       {"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true},
+       {"if else if", "{{if false}}FALSE{{else if true}}TRUE{{end}}", "TRUE", tVal, true},
+       {"if else chain", "{{if eq 1 3}}1{{else if eq 2 3}}2{{else if eq 3 3}}3{{end}}", "3", tVal, true},
+
+       // Print etc.
+       {"print", `{{print "hello, print"}}`, "hello, print", tVal, true},
+       {"print 123", `{{print 1 2 3}}`, "1 2 3", tVal, true},
+       {"print nil", `{{print nil}}`, "<nil>", tVal, true},
+       {"println", `{{println 1 2 3}}`, "1 2 3\n", tVal, true},
+       {"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true},
+       {"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true},
+       {"printf complex", `{{printf "%g" 1+7i}}`, "(1+7i)", tVal, true},
+       {"printf string", `{{printf "%s" "hello"}}`, "hello", tVal, true},
+       {"printf function", `{{printf "%#q" zeroArgs}}`, "`zeroArgs`", tVal, true},
+       {"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true},
+       {"printf method", `{{printf "%s" .Method0}}`, "M0", tVal, true},
+       {"printf dot", `{{with .I}}{{printf "%d" .}}{{end}}`, "17", tVal, true},
+       {"printf var", `{{with $x := .I}}{{printf "%d" $x}}{{end}}`, "17", tVal, true},
+       {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) M0", tVal, true},
+
+       // HTML.
+       {"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
+               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
+       {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
+               "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
+       {"html", `{{html .PS}}`, "a string", tVal, true},
+       {"html typed nil", `{{html .NIL}}`, "&lt;nil&gt;", tVal, true},
+       {"html untyped nil", `{{html .Empty0}}`, "&lt;no value&gt;", tVal, true},
+
+       // JavaScript.
+       {"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
+
+       // URL query.
+       {"urlquery", `{{"http://www.example.org/"|urlquery}}`, "http%3A%2F%2Fwww.example.org%2F", nil, true},
+
+       // Booleans
+       {"not", "{{not true}} {{not false}}", "false true", nil, true},
+       {"and", "{{and false 0}} {{and 1 0}} {{and 0 true}} {{and 1 1}}", "false 0 0 1", nil, true},
+       {"or", "{{or 0 0}} {{or 1 0}} {{or 0 true}} {{or 1 1}}", "0 1 true 1", nil, true},
+       {"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true},
+       {"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
+
+       // Indexing.
+       {"slice[0]", "{{index .SI 0}}", "3", tVal, true},
+       {"slice[1]", "{{index .SI 1}}", "4", tVal, true},
+       {"slice[HUGE]", "{{index .SI 10}}", "", tVal, false},
+       {"slice[WRONG]", "{{index .SI `hello`}}", "", tVal, false},
+       {"slice[nil]", "{{index .SI nil}}", "", tVal, false},
+       {"map[one]", "{{index .MSI `one`}}", "1", tVal, true},
+       {"map[two]", "{{index .MSI `two`}}", "2", tVal, true},
+       {"map[NO]", "{{index .MSI `XXX`}}", "0", tVal, true},
+       {"map[nil]", "{{index .MSI nil}}", "", tVal, false},
+       {"map[``]", "{{index .MSI ``}}", "0", tVal, true},
+       {"map[WRONG]", "{{index .MSI 10}}", "", tVal, false},
+       {"double index", "{{index .SMSI 1 `eleven`}}", "11", tVal, true},
+       {"nil[1]", "{{index nil 1}}", "", tVal, false},
+       {"map MI64S", "{{index .MI64S 2}}", "i642", tVal, true},
+       {"map MI32S", "{{index .MI32S 2}}", "two", tVal, true},
+       {"map MUI64S", "{{index .MUI64S 3}}", "ui643", tVal, true},
+       {"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true},
+       {"map MUI8S", "{{index .MUI8S 2}}", "u82", tVal, true},
+
+       // Slicing.
+       {"slice[:]", "{{slice .SI}}", "[3 4 5]", tVal, true},
+       {"slice[1:]", "{{slice .SI 1}}", "[4 5]", tVal, true},
+       {"slice[1:2]", "{{slice .SI 1 2}}", "[4]", tVal, true},
+       {"slice[-1:]", "{{slice .SI -1}}", "", tVal, false},
+       {"slice[1:-2]", "{{slice .SI 1 -2}}", "", tVal, false},
+       {"slice[1:2:-1]", "{{slice .SI 1 2 -1}}", "", tVal, false},
+       {"slice[2:1]", "{{slice .SI 2 1}}", "", tVal, false},
+       {"slice[2:2:1]", "{{slice .SI 2 2 1}}", "", tVal, false},
+       {"out of range", "{{slice .SI 4 5}}", "", tVal, false},
+       {"out of range", "{{slice .SI 2 2 5}}", "", tVal, false},
+       {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10}}", "[0 0 0 0]", tVal, true},
+       {"len(s) < indexes < cap(s)", "{{slice .SICap 6 10 10}}", "[0 0 0 0]", tVal, true},
+       {"indexes > cap(s)", "{{slice .SICap 10 11}}", "", tVal, false},
+       {"indexes > cap(s)", "{{slice .SICap 6 10 11}}", "", tVal, false},
+       {"array[:]", "{{slice .AI}}", "[3 4 5]", tVal, true},
+       {"array[1:]", "{{slice .AI 1}}", "[4 5]", tVal, true},
+       {"array[1:2]", "{{slice .AI 1 2}}", "[4]", tVal, true},
+       {"string[:]", "{{slice .S}}", "xyz", tVal, true},
+       {"string[0:1]", "{{slice .S 0 1}}", "x", tVal, true},
+       {"string[1:]", "{{slice .S 1}}", "yz", tVal, true},
+       {"string[1:2]", "{{slice .S 1 2}}", "y", tVal, true},
+       {"out of range", "{{slice .S 1 5}}", "", tVal, false},
+       {"3-index slice of string", "{{slice .S 1 2 2}}", "", tVal, false},
+
+       // Len.
+       {"slice", "{{len .SI}}", "3", tVal, true},
+       {"map", "{{len .MSI }}", "3", tVal, true},
+       {"len of int", "{{len 3}}", "", tVal, false},
+       {"len of nothing", "{{len .Empty0}}", "", tVal, false},
+
+       // With.
+       {"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
+       {"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
+       {"with 1", "{{with 1}}{{.}}{{else}}ZERO{{end}}", "1", tVal, true},
+       {"with 0", "{{with 0}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
+       {"with 1.5", "{{with 1.5}}{{.}}{{else}}ZERO{{end}}", "1.5", tVal, true},
+       {"with 0.0", "{{with .FloatZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
+       {"with 1.5i", "{{with 1.5i}}{{.}}{{else}}ZERO{{end}}", "(0+1.5i)", tVal, true},
+       {"with 0.0i", "{{with .ComplexZero}}{{.}}{{else}}ZERO{{end}}", "ZERO", tVal, true},
+       {"with emptystring", "{{with ``}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"with string", "{{with `notempty`}}{{.}}{{else}}EMPTY{{end}}", "notempty", tVal, true},
+       {"with emptyslice", "{{with .SIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"with slice", "{{with .SI}}{{.}}{{else}}EMPTY{{end}}", "[3 4 5]", tVal, true},
+       {"with emptymap", "{{with .MSIEmpty}}{{.}}{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"with map", "{{with .MSIone}}{{.}}{{else}}EMPTY{{end}}", "map[one:1]", tVal, true},
+       {"with empty interface, struct field", "{{with .Empty4}}{{.V}}{{end}}", "UinEmpty", tVal, true},
+       {"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true},
+       {"with $x struct.U.V", "{{with $x := $}}{{$x.U.V}}{{end}}", "v", tVal, true},
+       {"with variable and action", "{{with $x := $}}{{$y := $.U.V}}{{$y}}{{end}}", "v", tVal, true},
+       {"with on typed nil interface value", "{{with .NonEmptyInterfaceTypedNil}}TRUE{{ end }}", "", tVal, true},
+
+       // Range.
+       {"range []int", "{{range .SI}}-{{.}}-{{end}}", "-3--4--5-", tVal, true},
+       {"range empty no else", "{{range .SIEmpty}}-{{.}}-{{end}}", "", tVal, true},
+       {"range []int else", "{{range .SI}}-{{.}}-{{else}}EMPTY{{end}}", "-3--4--5-", tVal, true},
+       {"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
+       {"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
+       {"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
+       {"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true},
+       {"range map else", "{{range .MSI}}-{{.}}-{{else}}EMPTY{{end}}", "-1--3--2-", tVal, true},
+       {"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
+       {"range empty interface", "{{range .Empty3}}-{{.}}-{{else}}EMPTY{{end}}", "-7--8-", tVal, true},
+       {"range empty nil", "{{range .Empty0}}-{{.}}-{{end}}", "", tVal, true},
+       {"range $x SI", "{{range $x := .SI}}<{{$x}}>{{end}}", "<3><4><5>", tVal, true},
+       {"range $x $y SI", "{{range $x, $y := .SI}}<{{$x}}={{$y}}>{{end}}", "<0=3><1=4><2=5>", tVal, true},
+       {"range $x MSIone", "{{range $x := .MSIone}}<{{$x}}>{{end}}", "<1>", tVal, true},
+       {"range $x $y MSIone", "{{range $x, $y := .MSIone}}<{{$x}}={{$y}}>{{end}}", "<one=1>", tVal, true},
+       {"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true},
+       {"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true},
+       {"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true},
+       {"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true},
+
+       // Cute examples.
+       {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
+       {"or as if false", `{{or .SIEmpty "slice is empty"}}`, "slice is empty", tVal, true},
+
+       // Error handling.
+       {"error method, error", "{{.MyError true}}", "", tVal, false},
+       {"error method, no error", "{{.MyError false}}", "false", tVal, true},
+
+       // Numbers
+       {"decimal", "{{print 1234}}", "1234", tVal, true},
+       {"decimal _", "{{print 12_34}}", "1234", tVal, true},
+       {"binary", "{{print 0b101}}", "5", tVal, true},
+       {"binary _", "{{print 0b_1_0_1}}", "5", tVal, true},
+       {"BINARY", "{{print 0B101}}", "5", tVal, true},
+       {"octal0", "{{print 0377}}", "255", tVal, true},
+       {"octal", "{{print 0o377}}", "255", tVal, true},
+       {"octal _", "{{print 0o_3_7_7}}", "255", tVal, true},
+       {"OCTAL", "{{print 0O377}}", "255", tVal, true},
+       {"hex", "{{print 0x123}}", "291", tVal, true},
+       {"hex _", "{{print 0x1_23}}", "291", tVal, true},
+       {"HEX", "{{print 0X123ABC}}", "1194684", tVal, true},
+       {"float", "{{print 123.4}}", "123.4", tVal, true},
+       {"float _", "{{print 0_0_1_2_3.4}}", "123.4", tVal, true},
+       {"hex float", "{{print +0x1.ep+2}}", "7.5", tVal, true},
+       {"hex float _", "{{print +0x_1.e_0p+0_2}}", "7.5", tVal, true},
+       {"HEX float", "{{print +0X1.EP+2}}", "7.5", tVal, true},
+       {"print multi", "{{print 1_2_3_4 7.5_00_00_00}}", "1234 7.5", tVal, true},
+       {"print multi2", "{{print 1234 0x0_1.e_0p+02}}", "1234 7.5", tVal, true},
+
+       // Fixed bugs.
+       // Must separate dot and receiver; otherwise args are evaluated with dot set to variable.
+       {"bug0", "{{range .MSIone}}{{if $.Method1 .}}X{{end}}{{end}}", "X", tVal, true},
+       // Do not loop endlessly in indirect for non-empty interfaces.
+       // The bug appears with *interface only; looped forever.
+       {"bug1", "{{.Method0}}", "M0", &iVal, true},
+       // Was taking address of interface field, so method set was empty.
+       {"bug2", "{{$.NonEmptyInterface.Method0}}", "M0", tVal, true},
+       // Struct values were not legal in with - mere oversight.
+       {"bug3", "{{with $}}{{.Method0}}{{end}}", "M0", tVal, true},
+       // Nil interface values in if.
+       {"bug4", "{{if .Empty0}}non-nil{{else}}nil{{end}}", "nil", tVal, true},
+       // Stringer.
+       {"bug5", "{{.Str}}", "foozle", tVal, true},
+       {"bug5a", "{{.Err}}", "erroozle", tVal, true},
+       // Args need to be indirected and dereferenced sometimes.
+       {"bug6a", "{{vfunc .V0 .V1}}", "vfunc", tVal, true},
+       {"bug6b", "{{vfunc .V0 .V0}}", "vfunc", tVal, true},
+       {"bug6c", "{{vfunc .V1 .V0}}", "vfunc", tVal, true},
+       {"bug6d", "{{vfunc .V1 .V1}}", "vfunc", tVal, true},
+       // Legal parse but illegal execution: non-function should have no arguments.
+       {"bug7a", "{{3 2}}", "", tVal, false},
+       {"bug7b", "{{$x := 1}}{{$x 2}}", "", tVal, false},
+       {"bug7c", "{{$x := 1}}{{3 | $x}}", "", tVal, false},
+       // Pipelined arg was not being type-checked.
+       {"bug8a", "{{3|oneArg}}", "", tVal, false},
+       {"bug8b", "{{4|dddArg 3}}", "", tVal, false},
+       // A bug was introduced that broke map lookups for lower-case names.
+       {"bug9", "{{.cause}}", "neglect", map[string]string{"cause": "neglect"}, true},
+       // Field chain starting with function did not work.
+       {"bug10", "{{mapOfThree.three}}-{{(mapOfThree).three}}", "3-3", 0, true},
+       // Dereferencing nil pointer while evaluating function arguments should not panic. Issue 7333.
+       {"bug11", "{{valueString .PS}}", "", T{}, false},
+       // 0xef gave constant type float64. Issue 8622.
+       {"bug12xe", "{{printf `%T` 0xef}}", "int", T{}, true},
+       {"bug12xE", "{{printf `%T` 0xEE}}", "int", T{}, true},
+       {"bug12Xe", "{{printf `%T` 0Xef}}", "int", T{}, true},
+       {"bug12XE", "{{printf `%T` 0XEE}}", "int", T{}, true},
+       // Chained nodes did not work as arguments. Issue 8473.
+       {"bug13", "{{print (.Copy).I}}", "17", tVal, true},
+       // Didn't protect against nil or literal values in field chains.
+       {"bug14a", "{{(nil).True}}", "", tVal, false},
+       {"bug14b", "{{$x := nil}}{{$x.anything}}", "", tVal, false},
+       {"bug14c", `{{$x := (1.0)}}{{$y := ("hello")}}{{$x.anything}}{{$y.true}}`, "", tVal, false},
+       // Didn't call validateType on function results. Issue 10800.
+       {"bug15", "{{valueString returnInt}}", "", tVal, false},
+       // Variadic function corner cases. Issue 10946.
+       {"bug16a", "{{true|printf}}", "", tVal, false},
+       {"bug16b", "{{1|printf}}", "", tVal, false},
+       {"bug16c", "{{1.1|printf}}", "", tVal, false},
+       {"bug16d", "{{'x'|printf}}", "", tVal, false},
+       {"bug16e", "{{0i|printf}}", "", tVal, false},
+       {"bug16f", "{{true|twoArgs \"xxx\"}}", "", tVal, false},
+       {"bug16g", "{{\"aaa\" |twoArgs \"bbb\"}}", "twoArgs=bbbaaa", tVal, true},
+       {"bug16h", "{{1|oneArg}}", "", tVal, false},
+       {"bug16i", "{{\"aaa\"|oneArg}}", "oneArg=aaa", tVal, true},
+       {"bug16j", "{{1+2i|printf \"%v\"}}", "(1+2i)", tVal, true},
+       {"bug16k", "{{\"aaa\"|printf }}", "aaa", tVal, true},
+       {"bug17a", "{{.NonEmptyInterface.X}}", "x", tVal, true},
+       {"bug17b", "-{{.NonEmptyInterface.Method1 1234}}-", "-1234-", tVal, true},
+       {"bug17c", "{{len .NonEmptyInterfacePtS}}", "2", tVal, true},
+       {"bug17d", "{{index .NonEmptyInterfacePtS 0}}", "a", tVal, true},
+       {"bug17e", "{{range .NonEmptyInterfacePtS}}-{{.}}-{{end}}", "-a--b-", tVal, true},
+}
+
+func zeroArgs() string {
+       return "zeroArgs"
+}
+
+func oneArg(a string) string {
+       return "oneArg=" + a
+}
+
+func twoArgs(a, b string) string {
+       return "twoArgs=" + a + b
+}
+
+func dddArg(a int, b ...string) string {
+       return fmt.Sprintln(a, b)
+}
+
+// count returns a channel that will deliver n sequential 1-letter strings starting at "a"
+func count(n int) chan string {
+       if n == 0 {
+               return nil
+       }
+       c := make(chan string)
+       go func() {
+               for i := 0; i < n; i++ {
+                       c <- "abcdefghijklmnop"[i : i+1]
+               }
+               close(c)
+       }()
+       return c
+}
+
+// vfunc takes a *V and a V
+func vfunc(V, *V) string {
+       return "vfunc"
+}
+
+// valueString takes a string, not a pointer.
+func valueString(v string) string {
+       return "value is ignored"
+}
+
+// returnInt returns an int
+func returnInt() int {
+       return 7
+}
+
+func add(args ...int) int {
+       sum := 0
+       for _, x := range args {
+               sum += x
+       }
+       return sum
+}
+
+func echo(arg interface{}) interface{} {
+       return arg
+}
+
+func makemap(arg ...string) map[string]string {
+       if len(arg)%2 != 0 {
+               panic("bad makemap")
+       }
+       m := make(map[string]string)
+       for i := 0; i < len(arg); i += 2 {
+               m[arg[i]] = arg[i+1]
+       }
+       return m
+}
+
+func stringer(s fmt.Stringer) string {
+       return s.String()
+}
+
+func mapOfThree() interface{} {
+       return map[string]int{"three": 3}
+}
+
+func testExecute(execTests []execTest, template *Template, t *testing.T) {
+       b := new(bytes.Buffer)
+       funcs := FuncMap{
+               "add":         add,
+               "count":       count,
+               "dddArg":      dddArg,
+               "echo":        echo,
+               "makemap":     makemap,
+               "mapOfThree":  mapOfThree,
+               "oneArg":      oneArg,
+               "returnInt":   returnInt,
+               "stringer":    stringer,
+               "twoArgs":     twoArgs,
+               "typeOf":      typeOf,
+               "valueString": valueString,
+               "vfunc":       vfunc,
+               "zeroArgs":    zeroArgs,
+       }
+       for _, test := range execTests {
+               var tmpl *Template
+               var err error
+               if template == nil {
+                       tmpl, err = New(test.name).Funcs(funcs).Parse(test.input)
+               } else {
+                       tmpl, err = template.New(test.name).Funcs(funcs).Parse(test.input)
+               }
+               if err != nil {
+                       t.Errorf("%s: parse error: %s", test.name, err)
+                       continue
+               }
+               b.Reset()
+               err = tmpl.Execute(b, test.data)
+               switch {
+               case !test.ok && err == nil:
+                       t.Errorf("%s: expected error; got none", test.name)
+                       continue
+               case test.ok && err != nil:
+                       t.Errorf("%s: unexpected execute error: %s", test.name, err)
+                       continue
+               case !test.ok && err != nil:
+                       // expected error, got one
+                       if *debug {
+                               fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
+                       }
+               }
+               result := b.String()
+               if result != test.output {
+                       t.Errorf("%s: expected\n\t%q\ngot\n\t%q", test.name, test.output, result)
+               }
+       }
+}
+
+func TestExecute(t *testing.T) {
+       testExecute(execTests, nil, t)
+}
+
+var delimPairs = []string{
+       "", "", // default
+       "{{", "}}", // same as default
+       "<<", ">>", // distinct
+       "|", "|", // same
+       "(日)", "(本)", // peculiar
+}
+
+func TestDelims(t *testing.T) {
+       const hello = "Hello, world"
+       var value = struct{ Str string }{hello}
+       for i := 0; i < len(delimPairs); i += 2 {
+               text := ".Str"
+               left := delimPairs[i+0]
+               trueLeft := left
+               right := delimPairs[i+1]
+               trueRight := right
+               if left == "" { // default case
+                       trueLeft = "{{"
+               }
+               if right == "" { // default case
+                       trueRight = "}}"
+               }
+               text = trueLeft + text + trueRight
+               // Now add a comment
+               text += trueLeft + "/*comment*/" + trueRight
+               // Now add  an action containing a string.
+               text += trueLeft + `"` + trueLeft + `"` + trueRight
+               // At this point text looks like `{{.Str}}{{/*comment*/}}{{"{{"}}`.
+               tmpl, err := New("delims").Delims(left, right).Parse(text)
+               if err != nil {
+                       t.Fatalf("delim %q text %q parse err %s", left, text, err)
+               }
+               var b = new(bytes.Buffer)
+               err = tmpl.Execute(b, value)
+               if err != nil {
+                       t.Fatalf("delim %q exec err %s", left, err)
+               }
+               if b.String() != hello+trueLeft {
+                       t.Errorf("expected %q got %q", hello+trueLeft, b.String())
+               }
+       }
+}
+
+// Check that an error from a method flows back to the top.
+func TestExecuteError(t *testing.T) {
+       b := new(bytes.Buffer)
+       tmpl := New("error")
+       _, err := tmpl.Parse("{{.MyError true}}")
+       if err != nil {
+               t.Fatalf("parse error: %s", err)
+       }
+       err = tmpl.Execute(b, tVal)
+       if err == nil {
+               t.Errorf("expected error; got none")
+       } else if !strings.Contains(err.Error(), myError.Error()) {
+               if *debug {
+                       fmt.Printf("test execute error: %s\n", err)
+               }
+               t.Errorf("expected myError; got %s", err)
+       }
+}
+
+const execErrorText = `line 1
+line 2
+line 3
+{{template "one" .}}
+{{define "one"}}{{template "two" .}}{{end}}
+{{define "two"}}{{template "three" .}}{{end}}
+{{define "three"}}{{index "hi" $}}{{end}}`
+
+// Check that an error from a nested template contains all the relevant information.
+func TestExecError(t *testing.T) {
+       tmpl, err := New("top").Parse(execErrorText)
+       if err != nil {
+               t.Fatal("parse error:", err)
+       }
+       var b bytes.Buffer
+       err = tmpl.Execute(&b, 5) // 5 is out of range indexing "hi"
+       if err == nil {
+               t.Fatal("expected error")
+       }
+       const want = `template: top:7:20: executing "three" at <index "hi" $>: error calling index: index out of range: 5`
+       got := err.Error()
+       if got != want {
+               t.Errorf("expected\n%q\ngot\n%q", want, got)
+       }
+}
+
+func TestJSEscaping(t *testing.T) {
+       testCases := []struct {
+               in, exp string
+       }{
+               {`a`, `a`},
+               {`'foo`, `\'foo`},
+               {`Go "jump" \`, `Go \"jump\" \\`},
+               {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`},
+               {"unprintable \uFDFF", `unprintable \uFDFF`},
+               {`<html>`, `\x3Chtml\x3E`},
+       }
+       for _, tc := range testCases {
+               s := JSEscapeString(tc.in)
+               if s != tc.exp {
+                       t.Errorf("JS escaping [%s] got [%s] want [%s]", tc.in, s, tc.exp)
+               }
+       }
+}
+
+// A nice example: walk a binary tree.
+
+type Tree struct {
+       Val         int
+       Left, Right *Tree
+}
+
+// Use different delimiters to test Set.Delims.
+// Also test the trimming of leading and trailing spaces.
+const treeTemplate = `
+       (- define "tree" -)
+       [
+               (- .Val -)
+               (- with .Left -)
+                       (template "tree" . -)
+               (- end -)
+               (- with .Right -)
+                       (- template "tree" . -)
+               (- end -)
+       ]
+       (- end -)
+`
+
+func TestTree(t *testing.T) {
+       var tree = &Tree{
+               1,
+               &Tree{
+                       2, &Tree{
+                               3,
+                               &Tree{
+                                       4, nil, nil,
+                               },
+                               nil,
+                       },
+                       &Tree{
+                               5,
+                               &Tree{
+                                       6, nil, nil,
+                               },
+                               nil,
+                       },
+               },
+               &Tree{
+                       7,
+                       &Tree{
+                               8,
+                               &Tree{
+                                       9, nil, nil,
+                               },
+                               nil,
+                       },
+                       &Tree{
+                               10,
+                               &Tree{
+                                       11, nil, nil,
+                               },
+                               nil,
+                       },
+               },
+       }
+       tmpl, err := New("root").Delims("(", ")").Parse(treeTemplate)
+       if err != nil {
+               t.Fatal("parse error:", err)
+       }
+       var b bytes.Buffer
+       const expect = "[1[2[3[4]][5[6]]][7[8[9]][10[11]]]]"
+       // First by looking up the template.
+       err = tmpl.Lookup("tree").Execute(&b, tree)
+       if err != nil {
+               t.Fatal("exec error:", err)
+       }
+       result := b.String()
+       if result != expect {
+               t.Errorf("expected %q got %q", expect, result)
+       }
+       // Then direct to execution.
+       b.Reset()
+       err = tmpl.ExecuteTemplate(&b, "tree", tree)
+       if err != nil {
+               t.Fatal("exec error:", err)
+       }
+       result = b.String()
+       if result != expect {
+               t.Errorf("expected %q got %q", expect, result)
+       }
+}
+
+func TestExecuteOnNewTemplate(t *testing.T) {
+       // This is issue 3872.
+       New("Name").Templates()
+       // This is issue 11379.
+       new(Template).Templates()
+       new(Template).Parse("")
+       new(Template).New("abc").Parse("")
+       new(Template).Execute(nil, nil)                // returns an error (but does not crash)
+       new(Template).ExecuteTemplate(nil, "XXX", nil) // returns an error (but does not crash)
+}
+
+const testTemplates = `{{define "one"}}one{{end}}{{define "two"}}two{{end}}`
+
+func TestMessageForExecuteEmpty(t *testing.T) {
+       // Test a truly empty template.
+       tmpl := New("empty")
+       var b bytes.Buffer
+       err := tmpl.Execute(&b, 0)
+       if err == nil {
+               t.Fatal("expected initial error")
+       }
+       got := err.Error()
+       want := `template: empty: "empty" is an incomplete or empty template`
+       if got != want {
+               t.Errorf("expected error %s got %s", want, got)
+       }
+       // Add a non-empty template to check that the error is helpful.
+       tests, err := New("").Parse(testTemplates)
+       if err != nil {
+               t.Fatal(err)
+       }
+       tmpl.AddParseTree("secondary", tests.Tree)
+       err = tmpl.Execute(&b, 0)
+       if err == nil {
+               t.Fatal("expected second error")
+       }
+       got = err.Error()
+       want = `template: empty: "empty" is an incomplete or empty template`
+       if got != want {
+               t.Errorf("expected error %s got %s", want, got)
+       }
+       // Make sure we can execute the secondary.
+       err = tmpl.ExecuteTemplate(&b, "secondary", 0)
+       if err != nil {
+               t.Fatal(err)
+       }
+}
+
+func TestFinalForPrintf(t *testing.T) {
+       tmpl, err := New("").Parse(`{{"x" | printf}}`)
+       if err != nil {
+               t.Fatal(err)
+       }
+       var b bytes.Buffer
+       err = tmpl.Execute(&b, 0)
+       if err != nil {
+               t.Fatal(err)
+       }
+}
+
+type cmpTest struct {
+       expr  string
+       truth string
+       ok    bool
+}
+
+var cmpTests = []cmpTest{
+       {"eq true true", "true", true},
+       {"eq true false", "false", true},
+       {"eq 1+2i 1+2i", "true", true},
+       {"eq 1+2i 1+3i", "false", true},
+       {"eq 1.5 1.5", "true", true},
+       {"eq 1.5 2.5", "false", true},
+       {"eq 1 1", "true", true},
+       {"eq 1 2", "false", true},
+       {"eq `xy` `xy`", "true", true},
+       {"eq `xy` `xyz`", "false", true},
+       {"eq .Uthree .Uthree", "true", true},
+       {"eq .Uthree .Ufour", "false", true},
+       {"eq 3 4 5 6 3", "true", true},
+       {"eq 3 4 5 6 7", "false", true},
+       {"ne true true", "false", true},
+       {"ne true false", "true", true},
+       {"ne 1+2i 1+2i", "false", true},
+       {"ne 1+2i 1+3i", "true", true},
+       {"ne 1.5 1.5", "false", true},
+       {"ne 1.5 2.5", "true", true},
+       {"ne 1 1", "false", true},
+       {"ne 1 2", "true", true},
+       {"ne `xy` `xy`", "false", true},
+       {"ne `xy` `xyz`", "true", true},
+       {"ne .Uthree .Uthree", "false", true},
+       {"ne .Uthree .Ufour", "true", true},
+       {"lt 1.5 1.5", "false", true},
+       {"lt 1.5 2.5", "true", true},
+       {"lt 1 1", "false", true},
+       {"lt 1 2", "true", true},
+       {"lt `xy` `xy`", "false", true},
+       {"lt `xy` `xyz`", "true", true},
+       {"lt .Uthree .Uthree", "false", true},
+       {"lt .Uthree .Ufour", "true", true},
+       {"le 1.5 1.5", "true", true},
+       {"le 1.5 2.5", "true", true},
+       {"le 2.5 1.5", "false", true},
+       {"le 1 1", "true", true},
+       {"le 1 2", "true", true},
+       {"le 2 1", "false", true},
+       {"le `xy` `xy`", "true", true},
+       {"le `xy` `xyz`", "true", true},
+       {"le `xyz` `xy`", "false", true},
+       {"le .Uthree .Uthree", "true", true},
+       {"le .Uthree .Ufour", "true", true},
+       {"le .Ufour .Uthree", "false", true},
+       {"gt 1.5 1.5", "false", true},
+       {"gt 1.5 2.5", "false", true},
+       {"gt 1 1", "false", true},
+       {"gt 2 1", "true", true},
+       {"gt 1 2", "false", true},
+       {"gt `xy` `xy`", "false", true},
+       {"gt `xy` `xyz`", "false", true},
+       {"gt .Uthree .Uthree", "false", true},
+       {"gt .Uthree .Ufour", "false", true},
+       {"gt .Ufour .Uthree", "true", true},
+       {"ge 1.5 1.5", "true", true},
+       {"ge 1.5 2.5", "false", true},
+       {"ge 2.5 1.5", "true", true},
+       {"ge 1 1", "true", true},
+       {"ge 1 2", "false", true},
+       {"ge 2 1", "true", true},
+       {"ge `xy` `xy`", "true", true},
+       {"ge `xy` `xyz`", "false", true},
+       {"ge `xyz` `xy`", "true", true},
+       {"ge .Uthree .Uthree", "true", true},
+       {"ge .Uthree .Ufour", "false", true},
+       {"ge .Ufour .Uthree", "true", true},
+       // Mixing signed and unsigned integers.
+       {"eq .Uthree .Three", "true", true},
+       {"eq .Three .Uthree", "true", true},
+       {"le .Uthree .Three", "true", true},
+       {"le .Three .Uthree", "true", true},
+       {"ge .Uthree .Three", "true", true},
+       {"ge .Three .Uthree", "true", true},
+       {"lt .Uthree .Three", "false", true},
+       {"lt .Three .Uthree", "false", true},
+       {"gt .Uthree .Three", "false", true},
+       {"gt .Three .Uthree", "false", true},
+       {"eq .Ufour .Three", "false", true},
+       {"lt .Ufour .Three", "false", true},
+       {"gt .Ufour .Three", "true", true},
+       {"eq .NegOne .Uthree", "false", true},
+       {"eq .Uthree .NegOne", "false", true},
+       {"ne .NegOne .Uthree", "true", true},
+       {"ne .Uthree .NegOne", "true", true},
+       {"lt .NegOne .Uthree", "true", true},
+       {"lt .Uthree .NegOne", "false", true},
+       {"le .NegOne .Uthree", "true", true},
+       {"le .Uthree .NegOne", "false", true},
+       {"gt .NegOne .Uthree", "false", true},
+       {"gt .Uthree .NegOne", "true", true},
+       {"ge .NegOne .Uthree", "false", true},
+       {"ge .Uthree .NegOne", "true", true},
+       {"eq (index `x` 0) 'x'", "true", true}, // The example that triggered this rule.
+       {"eq (index `x` 0) 'y'", "false", true},
+       // Errors
+       {"eq `xy` 1", "", false},    // Different types.
+       {"eq 2 2.0", "", false},     // Different types.
+       {"lt true true", "", false}, // Unordered types.
+       {"lt 1+0i 1+0i", "", false}, // Unordered types.
+}
+
+func TestComparison(t *testing.T) {
+       b := new(bytes.Buffer)
+       var cmpStruct = struct {
+               Uthree, Ufour uint
+               NegOne, Three int
+       }{3, 4, -1, 3}
+       for _, test := range cmpTests {
+               text := fmt.Sprintf("{{if %s}}true{{else}}false{{end}}", test.expr)
+               tmpl, err := New("empty").Parse(text)
+               if err != nil {
+                       t.Fatalf("%q: %s", test.expr, err)
+               }
+               b.Reset()
+               err = tmpl.Execute(b, &cmpStruct)
+               if test.ok && err != nil {
+                       t.Errorf("%s errored incorrectly: %s", test.expr, err)
+                       continue
+               }
+               if !test.ok && err == nil {
+                       t.Errorf("%s did not error", test.expr)
+                       continue
+               }
+               if b.String() != test.truth {
+                       t.Errorf("%s: want %s; got %s", test.expr, test.truth, b.String())
+               }
+       }
+}
+
+func TestMissingMapKey(t *testing.T) {
+       data := map[string]int{
+               "x": 99,
+       }
+       tmpl, err := New("t1").Parse("{{.x}} {{.y}}")
+       if err != nil {
+               t.Fatal(err)
+       }
+       var b bytes.Buffer
+       // By default, just get "<no value>"
+       err = tmpl.Execute(&b, data)
+       if err != nil {
+               t.Fatal(err)
+       }
+       want := "99 <no value>"
+       got := b.String()
+       if got != want {
+               t.Errorf("got %q; expected %q", got, want)
+       }
+       // Same if we set the option explicitly to the default.
+       tmpl.Option("missingkey=default")
+       b.Reset()
+       err = tmpl.Execute(&b, data)
+       if err != nil {
+               t.Fatal("default:", err)
+       }
+       want = "99 <no value>"
+       got = b.String()
+       if got != want {
+               t.Errorf("got %q; expected %q", got, want)
+       }
+       // Next we ask for a zero value
+       tmpl.Option("missingkey=zero")
+       b.Reset()
+       err = tmpl.Execute(&b, data)
+       if err != nil {
+               t.Fatal("zero:", err)
+       }
+       want = "99 0"
+       got = b.String()
+       if got != want {
+               t.Errorf("got %q; expected %q", got, want)
+       }
+       // Now we ask for an error.
+       tmpl.Option("missingkey=error")
+       err = tmpl.Execute(&b, data)
+       if err == nil {
+               t.Errorf("expected error; got none")
+       }
+       // same Option, but now a nil interface: ask for an error
+       err = tmpl.Execute(&b, nil)
+       t.Log(err)
+       if err == nil {
+               t.Errorf("expected error for nil-interface; got none")
+       }
+}
+
+// Test that the error message for multiline unterminated string
+// refers to the line number of the opening quote.
+func TestUnterminatedStringError(t *testing.T) {
+       _, err := New("X").Parse("hello\n\n{{`unterminated\n\n\n\n}}\n some more\n\n")
+       if err == nil {
+               t.Fatal("expected error")
+       }
+       str := err.Error()
+       if !strings.Contains(str, "X:3: unexpected unterminated raw quoted string") {
+               t.Fatalf("unexpected error: %s", str)
+       }
+}
+
+const alwaysErrorText = "always be failing"
+
+var alwaysError = errors.New(alwaysErrorText)
+
+type ErrorWriter int
+
+func (e ErrorWriter) Write(p []byte) (int, error) {
+       return 0, alwaysError
+}
+
+func TestExecuteGivesExecError(t *testing.T) {
+       // First, a non-execution error shouldn't be an ExecError.
+       tmpl, err := New("X").Parse("hello")
+       if err != nil {
+               t.Fatal(err)
+       }
+       err = tmpl.Execute(ErrorWriter(0), 0)
+       if err == nil {
+               t.Fatal("expected error; got none")
+       }
+       if err.Error() != alwaysErrorText {
+               t.Errorf("expected %q error; got %q", alwaysErrorText, err)
+       }
+       // This one should be an ExecError.
+       tmpl, err = New("X").Parse("hello, {{.X.Y}}")
+       if err != nil {
+               t.Fatal(err)
+       }
+       err = tmpl.Execute(ioutil.Discard, 0)
+       if err == nil {
+               t.Fatal("expected error; got none")
+       }
+       eerr, ok := err.(ExecError)
+       if !ok {
+               t.Fatalf("did not expect ExecError %s", eerr)
+       }
+       expect := "field X in type int"
+       if !strings.Contains(err.Error(), expect) {
+               t.Errorf("expected %q; got %q", expect, err)
+       }
+}
+
+func funcNameTestFunc() int {
+       return 0
+}
+
+func TestGoodFuncNames(t *testing.T) {
+       names := []string{
+               "_",
+               "a",
+               "a1",
+               "a1",
+               "Ӵ",
+       }
+       for _, name := range names {
+               tmpl := New("X").Funcs(
+                       FuncMap{
+                               name: funcNameTestFunc,
+                       },
+               )
+               if tmpl == nil {
+                       t.Fatalf("nil result for %q", name)
+               }
+       }
+}
+
+func TestBadFuncNames(t *testing.T) {
+       names := []string{
+               "",
+               "2",
+               "a-b",
+       }
+       for _, name := range names {
+               testBadFuncName(name, t)
+       }
+}
+
+func testBadFuncName(name string, t *testing.T) {
+       t.Helper()
+       defer func() {
+               recover()
+       }()
+       New("X").Funcs(
+               FuncMap{
+                       name: funcNameTestFunc,
+               },
+       )
+       // If we get here, the name did not cause a panic, which is how Funcs
+       // reports an error.
+       t.Errorf("%q succeeded incorrectly as function name", name)
+}
+
+func TestBlock(t *testing.T) {
+       const (
+               input   = `a({{block "inner" .}}bar({{.}})baz{{end}})b`
+               want    = `a(bar(hello)baz)b`
+               overlay = `{{define "inner"}}foo({{.}})bar{{end}}`
+               want2   = `a(foo(goodbye)bar)b`
+       )
+       tmpl, err := New("outer").Parse(input)
+       if err != nil {
+               t.Fatal(err)
+       }
+       tmpl2, err := Must(tmpl.Clone()).Parse(overlay)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       var buf bytes.Buffer
+       if err := tmpl.Execute(&buf, "hello"); err != nil {
+               t.Fatal(err)
+       }
+       if got := buf.String(); got != want {
+               t.Errorf("got %q, want %q", got, want)
+       }
+
+       buf.Reset()
+       if err := tmpl2.Execute(&buf, "goodbye"); err != nil {
+               t.Fatal(err)
+       }
+       if got := buf.String(); got != want2 {
+               t.Errorf("got %q, want %q", got, want2)
+       }
+}
+
+func TestEvalFieldErrors(t *testing.T) {
+       tests := []struct {
+               name, src string
+               value     interface{}
+               want      string
+       }{
+               {
+                       // Check that calling an invalid field on nil pointer
+                       // prints a field error instead of a distracting nil
+                       // pointer error. https://golang.org/issue/15125
+                       "MissingFieldOnNil",
+                       "{{.MissingField}}",
+                       (*T)(nil),
+                       "can't evaluate field MissingField in type *template.T",
+               },
+               {
+                       "MissingFieldOnNonNil",
+                       "{{.MissingField}}",
+                       &T{},
+                       "can't evaluate field MissingField in type *template.T",
+               },
+               {
+                       "ExistingFieldOnNil",
+                       "{{.X}}",
+                       (*T)(nil),
+                       "nil pointer evaluating *template.T.X",
+               },
+               {
+                       "MissingKeyOnNilMap",
+                       "{{.MissingKey}}",
+                       (*map[string]string)(nil),
+                       "nil pointer evaluating *map[string]string.MissingKey",
+               },
+               {
+                       "MissingKeyOnNilMapPtr",
+                       "{{.MissingKey}}",
+                       (*map[string]string)(nil),
+                       "nil pointer evaluating *map[string]string.MissingKey",
+               },
+               {
+                       "MissingKeyOnMapPtrToNil",
+                       "{{.MissingKey}}",
+                       &map[string]string{},
+                       "<nil>",
+               },
+       }
+       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)
+                       got := "<nil>"
+                       if err != nil {
+                               got = err.Error()
+                       }
+                       if !strings.HasSuffix(got, tc.want) {
+                               t.Fatalf("got error %q, want %q", got, tc.want)
+                       }
+               })
+       }
+}
+
+func TestMaxExecDepth(t *testing.T) {
+       if testing.Short() {
+               t.Skip("skipping in -short mode")
+       }
+       tmpl := Must(New("tmpl").Parse(`{{template "tmpl" .}}`))
+       err := tmpl.Execute(ioutil.Discard, nil)
+       got := "<nil>"
+       if err != nil {
+               got = err.Error()
+       }
+       const want = "exceeded maximum template depth"
+       if !strings.Contains(got, want) {
+               t.Errorf("got error %q; want %q", got, want)
+       }
+}
+
+func TestAddrOfIndex(t *testing.T) {
+       // golang.org/issue/14916.
+       // Before index worked on reflect.Values, the .String could not be
+       // found on the (incorrectly unaddressable) V value,
+       // in contrast to range, which worked fine.
+       // Also testing that passing a reflect.Value to tmpl.Execute works.
+       texts := []string{
+               `{{range .}}{{.String}}{{end}}`,
+               `{{with index . 0}}{{.String}}{{end}}`,
+       }
+       for _, text := range texts {
+               tmpl := Must(New("tmpl").Parse(text))
+               var buf bytes.Buffer
+               err := tmpl.Execute(&buf, reflect.ValueOf([]V{{1}}))
+               if err != nil {
+                       t.Fatalf("%s: Execute: %v", text, err)
+               }
+               if buf.String() != "<1>" {
+                       t.Fatalf("%s: template output = %q, want %q", text, &buf, "<1>")
+               }
+       }
+}
+
+func TestInterfaceValues(t *testing.T) {
+       // golang.org/issue/17714.
+       // Before index worked on reflect.Values, interface values
+       // were always implicitly promoted to the underlying value,
+       // except that nil interfaces were promoted to the zero reflect.Value.
+       // Eliminating a round trip to interface{} and back to reflect.Value
+       // eliminated this promotion, breaking these cases.
+       tests := []struct {
+               text string
+               out  string
+       }{
+               {`{{index .Nil 1}}`, "ERROR: index of untyped nil"},
+               {`{{index .Slice 2}}`, "2"},
+               {`{{index .Slice .Two}}`, "2"},
+               {`{{call .Nil 1}}`, "ERROR: call of nil"},
+               {`{{call .PlusOne 1}}`, "2"},
+               {`{{call .PlusOne .One}}`, "2"},
+               {`{{and (index .Slice 0) true}}`, "0"},
+               {`{{and .Zero true}}`, "0"},
+               {`{{and (index .Slice 1) false}}`, "false"},
+               {`{{and .One false}}`, "false"},
+               {`{{or (index .Slice 0) false}}`, "false"},
+               {`{{or .Zero false}}`, "false"},
+               {`{{or (index .Slice 1) true}}`, "1"},
+               {`{{or .One true}}`, "1"},
+               {`{{not (index .Slice 0)}}`, "true"},
+               {`{{not .Zero}}`, "true"},
+               {`{{not (index .Slice 1)}}`, "false"},
+               {`{{not .One}}`, "false"},
+               {`{{eq (index .Slice 0) .Zero}}`, "true"},
+               {`{{eq (index .Slice 1) .One}}`, "true"},
+               {`{{ne (index .Slice 0) .Zero}}`, "false"},
+               {`{{ne (index .Slice 1) .One}}`, "false"},
+               {`{{ge (index .Slice 0) .One}}`, "false"},
+               {`{{ge (index .Slice 1) .Zero}}`, "true"},
+               {`{{gt (index .Slice 0) .One}}`, "false"},
+               {`{{gt (index .Slice 1) .Zero}}`, "true"},
+               {`{{le (index .Slice 0) .One}}`, "true"},
+               {`{{le (index .Slice 1) .Zero}}`, "false"},
+               {`{{lt (index .Slice 0) .One}}`, "true"},
+               {`{{lt (index .Slice 1) .Zero}}`, "false"},
+       }
+
+       for _, tt := range tests {
+               tmpl := Must(New("tmpl").Parse(tt.text))
+               var buf bytes.Buffer
+               err := tmpl.Execute(&buf, map[string]interface{}{
+                       "PlusOne": func(n int) int {
+                               return n + 1
+                       },
+                       "Slice": []int{0, 1, 2, 3},
+                       "One":   1,
+                       "Two":   2,
+                       "Nil":   nil,
+                       "Zero":  0,
+               })
+               if strings.HasPrefix(tt.out, "ERROR:") {
+                       e := strings.TrimSpace(strings.TrimPrefix(tt.out, "ERROR:"))
+                       if err == nil || !strings.Contains(err.Error(), e) {
+                               t.Errorf("%s: Execute: %v, want error %q", tt.text, err, e)
+                       }
+                       continue
+               }
+               if err != nil {
+                       t.Errorf("%s: Execute: %v", tt.text, err)
+                       continue
+               }
+               if buf.String() != tt.out {
+                       t.Errorf("%s: template output = %q, want %q", tt.text, &buf, tt.out)
+               }
+       }
+}
+
+// Check that panics during calls are recovered and returned as errors.
+func TestExecutePanicDuringCall(t *testing.T) {
+       funcs := map[string]interface{}{
+               "doPanic": func() string {
+                       panic("custom panic string")
+               },
+       }
+       tests := []struct {
+               name    string
+               input   string
+               data    interface{}
+               wantErr string
+       }{
+               {
+                       "direct func call panics",
+                       "{{doPanic}}", (*T)(nil),
+                       `template: t:1:2: executing "t" at <doPanic>: error calling doPanic: custom panic string`,
+               },
+               {
+                       "indirect func call panics",
+                       "{{call doPanic}}", (*T)(nil),
+                       `template: t:1:7: executing "t" at <doPanic>: error calling doPanic: custom panic string`,
+               },
+               {
+                       "direct method call panics",
+                       "{{.GetU}}", (*T)(nil),
+                       `template: t:1:2: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`,
+               },
+               {
+                       "indirect method call panics",
+                       "{{call .GetU}}", (*T)(nil),
+                       `template: t:1:7: executing "t" at <.GetU>: error calling GetU: runtime error: invalid memory address or nil pointer dereference`,
+               },
+               {
+                       "func field call panics",
+                       "{{call .PanicFunc}}", tVal,
+                       `template: t:1:2: executing "t" at <call .PanicFunc>: error calling call: test panic`,
+               },
+               {
+                       "method call on nil interface",
+                       "{{.NonEmptyInterfaceNil.Method0}}", tVal,
+                       `template: t:1:23: executing "t" at <.NonEmptyInterfaceNil.Method0>: nil pointer evaluating template.I.Method0`,
+               },
+       }
+       for _, tc := range tests {
+               b := new(bytes.Buffer)
+               tmpl, err := New("t").Funcs(funcs).Parse(tc.input)
+               if err != nil {
+                       t.Fatalf("parse error: %s", err)
+               }
+               err = tmpl.Execute(b, tc.data)
+               if err == nil {
+                       t.Errorf("%s: expected error; got none", tc.name)
+               } else if !strings.Contains(err.Error(), tc.wantErr) {
+                       if *debug {
+                               fmt.Printf("%s: test execute error: %s\n", tc.name, err)
+                       }
+                       t.Errorf("%s: expected error:\n%s\ngot:\n%s", tc.name, tc.wantErr, err)
+               }
+       }
+}
diff --git a/tpl/internal/go_templates/texttemplate/funcs.go b/tpl/internal/go_templates/texttemplate/funcs.go
new file mode 100644 (file)
index 0000000..248dbcf
--- /dev/null
@@ -0,0 +1,741 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "io"
+       "net/url"
+       "reflect"
+       "strings"
+       "unicode"
+       "unicode/utf8"
+)
+
+// FuncMap is the type of the map defining the mapping from names to functions.
+// Each function must have either a single return value, or two return values of
+// which the second has type error. In that case, if the second (error)
+// return value evaluates to non-nil during execution, execution terminates and
+// Execute returns that error.
+//
+// When template execution invokes a function with an argument list, that list
+// must be assignable to the function's parameter types. Functions meant to
+// apply to arguments of arbitrary type can use parameters of type interface{} or
+// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
+// type can return interface{} or reflect.Value.
+type FuncMap map[string]interface{}
+
+var builtins = FuncMap{
+       "and":      and,
+       "call":     call,
+       "html":     HTMLEscaper,
+       "index":    index,
+       "slice":    slice,
+       "js":       JSEscaper,
+       "len":      length,
+       "not":      not,
+       "or":       or,
+       "print":    fmt.Sprint,
+       "printf":   fmt.Sprintf,
+       "println":  fmt.Sprintln,
+       "urlquery": URLQueryEscaper,
+
+       // Comparisons
+       "eq": eq, // ==
+       "ge": ge, // >=
+       "gt": gt, // >
+       "le": le, // <=
+       "lt": lt, // <
+       "ne": ne, // !=
+}
+
+var builtinFuncs = createValueFuncs(builtins)
+
+// createValueFuncs turns a FuncMap into a map[string]reflect.Value
+func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
+       m := make(map[string]reflect.Value)
+       addValueFuncs(m, funcMap)
+       return m
+}
+
+// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
+func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
+       for name, fn := range in {
+               if !goodName(name) {
+                       panic(fmt.Errorf("function name %q is not a valid identifier", name))
+               }
+               v := reflect.ValueOf(fn)
+               if v.Kind() != reflect.Func {
+                       panic("value for " + name + " not a function")
+               }
+               if !goodFunc(v.Type()) {
+                       panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
+               }
+               out[name] = v
+       }
+}
+
+// addFuncs adds to values the functions in funcs. It does no checking of the input -
+// call addValueFuncs first.
+func addFuncs(out, in FuncMap) {
+       for name, fn := range in {
+               out[name] = fn
+       }
+}
+
+// goodFunc reports whether the function or method has the right result signature.
+func goodFunc(typ reflect.Type) bool {
+       // We allow functions with 1 result or 2 results where the second is an error.
+       switch {
+       case typ.NumOut() == 1:
+               return true
+       case typ.NumOut() == 2 && typ.Out(1) == errorType:
+               return true
+       }
+       return false
+}
+
+// goodName reports whether the function name is a valid identifier.
+func goodName(name string) bool {
+       if name == "" {
+               return false
+       }
+       for i, r := range name {
+               switch {
+               case r == '_':
+               case i == 0 && !unicode.IsLetter(r):
+                       return false
+               case !unicode.IsLetter(r) && !unicode.IsDigit(r):
+                       return false
+               }
+       }
+       return true
+}
+
+// findFunction looks for a function in the template, and global map.
+func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
+       if tmpl != nil && tmpl.common != nil {
+               tmpl.muFuncs.RLock()
+               defer tmpl.muFuncs.RUnlock()
+               if fn := tmpl.execFuncs[name]; fn.IsValid() {
+                       return fn, true
+               }
+       }
+       if fn := builtinFuncs[name]; fn.IsValid() {
+               return fn, true
+       }
+       return reflect.Value{}, false
+}
+
+// prepareArg checks if value can be used as an argument of type argType, and
+// converts an invalid value to appropriate zero if possible.
+func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) {
+       if !value.IsValid() {
+               if !canBeNil(argType) {
+                       return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType)
+               }
+               value = reflect.Zero(argType)
+       }
+       if value.Type().AssignableTo(argType) {
+               return value, nil
+       }
+       if intLike(value.Kind()) && intLike(argType.Kind()) && value.Type().ConvertibleTo(argType) {
+               value = value.Convert(argType)
+               return value, nil
+       }
+       return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
+}
+
+func intLike(typ reflect.Kind) bool {
+       switch typ {
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return true
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return true
+       }
+       return false
+}
+
+// indexArg checks if a reflect.Value can be used as an index, and converts it to int if possible.
+func indexArg(index reflect.Value, cap int) (int, error) {
+       var x int64
+       switch index.Kind() {
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               x = index.Int()
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               x = int64(index.Uint())
+       case reflect.Invalid:
+               return 0, fmt.Errorf("cannot index slice/array with nil")
+       default:
+               return 0, fmt.Errorf("cannot index slice/array with type %s", index.Type())
+       }
+       if x < 0 || int(x) < 0 || int(x) > cap {
+               return 0, fmt.Errorf("index out of range: %d", x)
+       }
+       return int(x), nil
+}
+
+// Indexing.
+
+// index returns the result of indexing its first argument by the following
+// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
+// indexed item must be a map, slice, or array.
+func index(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) {
+       v := indirectInterface(item)
+       if !v.IsValid() {
+               return reflect.Value{}, fmt.Errorf("index of untyped nil")
+       }
+       for _, i := range indexes {
+               index := indirectInterface(i)
+               var isNil bool
+               if v, isNil = indirect(v); isNil {
+                       return reflect.Value{}, fmt.Errorf("index of nil pointer")
+               }
+               switch v.Kind() {
+               case reflect.Array, reflect.Slice, reflect.String:
+                       x, err := indexArg(index, v.Len())
+                       if err != nil {
+                               return reflect.Value{}, err
+                       }
+                       v = v.Index(x)
+               case reflect.Map:
+                       index, err := prepareArg(index, v.Type().Key())
+                       if err != nil {
+                               return reflect.Value{}, err
+                       }
+                       if x := v.MapIndex(index); x.IsValid() {
+                               v = x
+                       } else {
+                               v = reflect.Zero(v.Type().Elem())
+                       }
+               case reflect.Invalid:
+                       // the loop holds invariant: v.IsValid()
+                       panic("unreachable")
+               default:
+                       return reflect.Value{}, fmt.Errorf("can't index item of type %s", v.Type())
+               }
+       }
+       return v, nil
+}
+
+// Slicing.
+
+// slice returns the result of slicing its first argument by the remaining
+// arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x"
+// is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first
+// argument must be a string, slice, or array.
+func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) {
+       var (
+               cap int
+               v   = indirectInterface(item)
+       )
+       if !v.IsValid() {
+               return reflect.Value{}, fmt.Errorf("slice of untyped nil")
+       }
+       if len(indexes) > 3 {
+               return reflect.Value{}, fmt.Errorf("too many slice indexes: %d", len(indexes))
+       }
+       switch v.Kind() {
+       case reflect.String:
+               if len(indexes) == 3 {
+                       return reflect.Value{}, fmt.Errorf("cannot 3-index slice a string")
+               }
+               cap = v.Len()
+       case reflect.Array, reflect.Slice:
+               cap = v.Cap()
+       default:
+               return reflect.Value{}, fmt.Errorf("can't slice item of type %s", v.Type())
+       }
+       // set default values for cases item[:], item[i:].
+       idx := [3]int{0, v.Len()}
+       for i, index := range indexes {
+               x, err := indexArg(index, cap)
+               if err != nil {
+                       return reflect.Value{}, err
+               }
+               idx[i] = x
+       }
+       // given item[i:j], make sure i <= j.
+       if idx[0] > idx[1] {
+               return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[0], idx[1])
+       }
+       if len(indexes) < 3 {
+               return item.Slice(idx[0], idx[1]), nil
+       }
+       // given item[i:j:k], make sure i <= j <= k.
+       if idx[1] > idx[2] {
+               return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[1], idx[2])
+       }
+       return item.Slice3(idx[0], idx[1], idx[2]), nil
+}
+
+// Length
+
+// length returns the length of the item, with an error if it has no defined length.
+func length(item interface{}) (int, error) {
+       v := reflect.ValueOf(item)
+       if !v.IsValid() {
+               return 0, fmt.Errorf("len of untyped nil")
+       }
+       v, isNil := indirect(v)
+       if isNil {
+               return 0, fmt.Errorf("len of nil pointer")
+       }
+       switch v.Kind() {
+       case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
+               return v.Len(), nil
+       }
+       return 0, fmt.Errorf("len of type %s", v.Type())
+}
+
+// Function invocation
+
+// call returns the result of evaluating the first argument as a function.
+// The function must return 1 result, or 2 results, the second of which is an error.
+func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
+       v := indirectInterface(fn)
+       if !v.IsValid() {
+               return reflect.Value{}, fmt.Errorf("call of nil")
+       }
+       typ := v.Type()
+       if typ.Kind() != reflect.Func {
+               return reflect.Value{}, fmt.Errorf("non-function of type %s", typ)
+       }
+       if !goodFunc(typ) {
+               return reflect.Value{}, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
+       }
+       numIn := typ.NumIn()
+       var dddType reflect.Type
+       if typ.IsVariadic() {
+               if len(args) < numIn-1 {
+                       return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
+               }
+               dddType = typ.In(numIn - 1).Elem()
+       } else {
+               if len(args) != numIn {
+                       return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
+               }
+       }
+       argv := make([]reflect.Value, len(args))
+       for i, arg := range args {
+               value := indirectInterface(arg)
+               // Compute the expected type. Clumsy because of variadics.
+               argType := dddType
+               if !typ.IsVariadic() || i < numIn-1 {
+                       argType = typ.In(i)
+               }
+
+               var err error
+               if argv[i], err = prepareArg(value, argType); err != nil {
+                       return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
+               }
+       }
+       return safeCall(v, argv)
+}
+
+// safeCall runs fun.Call(args), and returns the resulting value and error, if
+// any. If the call panics, the panic value is returned as an error.
+func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) {
+       defer func() {
+               if r := recover(); r != nil {
+                       if e, ok := r.(error); ok {
+                               err = e
+                       } else {
+                               err = fmt.Errorf("%v", r)
+                       }
+               }
+       }()
+       ret := fun.Call(args)
+       if len(ret) == 2 && !ret[1].IsNil() {
+               return ret[0], ret[1].Interface().(error)
+       }
+       return ret[0], nil
+}
+
+// Boolean logic.
+
+func truth(arg reflect.Value) bool {
+       t, _ := isTrue(indirectInterface(arg))
+       return t
+}
+
+// and computes the Boolean AND of its arguments, returning
+// the first false argument it encounters, or the last argument.
+func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
+       if !truth(arg0) {
+               return arg0
+       }
+       for i := range args {
+               arg0 = args[i]
+               if !truth(arg0) {
+                       break
+               }
+       }
+       return arg0
+}
+
+// or computes the Boolean OR of its arguments, returning
+// the first true argument it encounters, or the last argument.
+func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
+       if truth(arg0) {
+               return arg0
+       }
+       for i := range args {
+               arg0 = args[i]
+               if truth(arg0) {
+                       break
+               }
+       }
+       return arg0
+}
+
+// not returns the Boolean negation of its argument.
+func not(arg reflect.Value) bool {
+       return !truth(arg)
+}
+
+// Comparison.
+
+// TODO: Perhaps allow comparison between signed and unsigned integers.
+
+var (
+       errBadComparisonType = errors.New("invalid type for comparison")
+       errBadComparison     = errors.New("incompatible types for comparison")
+       errNoComparison      = errors.New("missing argument for comparison")
+)
+
+type kind int
+
+const (
+       invalidKind kind = iota
+       boolKind
+       complexKind
+       intKind
+       floatKind
+       stringKind
+       uintKind
+)
+
+func basicKind(v reflect.Value) (kind, error) {
+       switch v.Kind() {
+       case reflect.Bool:
+               return boolKind, nil
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return intKind, nil
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return uintKind, nil
+       case reflect.Float32, reflect.Float64:
+               return floatKind, nil
+       case reflect.Complex64, reflect.Complex128:
+               return complexKind, nil
+       case reflect.String:
+               return stringKind, nil
+       }
+       return invalidKind, errBadComparisonType
+}
+
+// eq evaluates the comparison a == b || a == c || ...
+func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
+       v1 := indirectInterface(arg1)
+       k1, err := basicKind(v1)
+       if err != nil {
+               return false, err
+       }
+       if len(arg2) == 0 {
+               return false, errNoComparison
+       }
+       for _, arg := range arg2 {
+               v2 := indirectInterface(arg)
+               k2, err := basicKind(v2)
+               if err != nil {
+                       return false, err
+               }
+               truth := false
+               if k1 != k2 {
+                       // Special case: Can compare integer values regardless of type's sign.
+                       switch {
+                       case k1 == intKind && k2 == uintKind:
+                               truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
+                       case k1 == uintKind && k2 == intKind:
+                               truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
+                       default:
+                               return false, errBadComparison
+                       }
+               } else {
+                       switch k1 {
+                       case boolKind:
+                               truth = v1.Bool() == v2.Bool()
+                       case complexKind:
+                               truth = v1.Complex() == v2.Complex()
+                       case floatKind:
+                               truth = v1.Float() == v2.Float()
+                       case intKind:
+                               truth = v1.Int() == v2.Int()
+                       case stringKind:
+                               truth = v1.String() == v2.String()
+                       case uintKind:
+                               truth = v1.Uint() == v2.Uint()
+                       default:
+                               panic("invalid kind")
+                       }
+               }
+               if truth {
+                       return true, nil
+               }
+       }
+       return false, nil
+}
+
+// ne evaluates the comparison a != b.
+func ne(arg1, arg2 reflect.Value) (bool, error) {
+       // != is the inverse of ==.
+       equal, err := eq(arg1, arg2)
+       return !equal, err
+}
+
+// lt evaluates the comparison a < b.
+func lt(arg1, arg2 reflect.Value) (bool, error) {
+       v1 := indirectInterface(arg1)
+       k1, err := basicKind(v1)
+       if err != nil {
+               return false, err
+       }
+       v2 := indirectInterface(arg2)
+       k2, err := basicKind(v2)
+       if err != nil {
+               return false, err
+       }
+       truth := false
+       if k1 != k2 {
+               // Special case: Can compare integer values regardless of type's sign.
+               switch {
+               case k1 == intKind && k2 == uintKind:
+                       truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
+               case k1 == uintKind && k2 == intKind:
+                       truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
+               default:
+                       return false, errBadComparison
+               }
+       } else {
+               switch k1 {
+               case boolKind, complexKind:
+                       return false, errBadComparisonType
+               case floatKind:
+                       truth = v1.Float() < v2.Float()
+               case intKind:
+                       truth = v1.Int() < v2.Int()
+               case stringKind:
+                       truth = v1.String() < v2.String()
+               case uintKind:
+                       truth = v1.Uint() < v2.Uint()
+               default:
+                       panic("invalid kind")
+               }
+       }
+       return truth, nil
+}
+
+// le evaluates the comparison <= b.
+func le(arg1, arg2 reflect.Value) (bool, error) {
+       // <= is < or ==.
+       lessThan, err := lt(arg1, arg2)
+       if lessThan || err != nil {
+               return lessThan, err
+       }
+       return eq(arg1, arg2)
+}
+
+// gt evaluates the comparison a > b.
+func gt(arg1, arg2 reflect.Value) (bool, error) {
+       // > is the inverse of <=.
+       lessOrEqual, err := le(arg1, arg2)
+       if err != nil {
+               return false, err
+       }
+       return !lessOrEqual, nil
+}
+
+// ge evaluates the comparison a >= b.
+func ge(arg1, arg2 reflect.Value) (bool, error) {
+       // >= is the inverse of <.
+       lessThan, err := lt(arg1, arg2)
+       if err != nil {
+               return false, err
+       }
+       return !lessThan, nil
+}
+
+// HTML escaping.
+
+var (
+       htmlQuot = []byte("&#34;") // shorter than "&quot;"
+       htmlApos = []byte("&#39;") // shorter than "&apos;" and apos was not in HTML until HTML5
+       htmlAmp  = []byte("&amp;")
+       htmlLt   = []byte("&lt;")
+       htmlGt   = []byte("&gt;")
+       htmlNull = []byte("\uFFFD")
+)
+
+// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
+func HTMLEscape(w io.Writer, b []byte) {
+       last := 0
+       for i, c := range b {
+               var html []byte
+               switch c {
+               case '\000':
+                       html = htmlNull
+               case '"':
+                       html = htmlQuot
+               case '\'':
+                       html = htmlApos
+               case '&':
+                       html = htmlAmp
+               case '<':
+                       html = htmlLt
+               case '>':
+                       html = htmlGt
+               default:
+                       continue
+               }
+               w.Write(b[last:i])
+               w.Write(html)
+               last = i + 1
+       }
+       w.Write(b[last:])
+}
+
+// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
+func HTMLEscapeString(s string) string {
+       // Avoid allocation if we can.
+       if !strings.ContainsAny(s, "'\"&<>\000") {
+               return s
+       }
+       var b bytes.Buffer
+       HTMLEscape(&b, []byte(s))
+       return b.String()
+}
+
+// HTMLEscaper returns the escaped HTML equivalent of the textual
+// representation of its arguments.
+func HTMLEscaper(args ...interface{}) string {
+       return HTMLEscapeString(evalArgs(args))
+}
+
+// JavaScript escaping.
+
+var (
+       jsLowUni = []byte(`\u00`)
+       hex      = []byte("0123456789ABCDEF")
+
+       jsBackslash = []byte(`\\`)
+       jsApos      = []byte(`\'`)
+       jsQuot      = []byte(`\"`)
+       jsLt        = []byte(`\x3C`)
+       jsGt        = []byte(`\x3E`)
+)
+
+// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
+func JSEscape(w io.Writer, b []byte) {
+       last := 0
+       for i := 0; i < len(b); i++ {
+               c := b[i]
+
+               if !jsIsSpecial(rune(c)) {
+                       // fast path: nothing to do
+                       continue
+               }
+               w.Write(b[last:i])
+
+               if c < utf8.RuneSelf {
+                       // Quotes, slashes and angle brackets get quoted.
+                       // Control characters get written as \u00XX.
+                       switch c {
+                       case '\\':
+                               w.Write(jsBackslash)
+                       case '\'':
+                               w.Write(jsApos)
+                       case '"':
+                               w.Write(jsQuot)
+                       case '<':
+                               w.Write(jsLt)
+                       case '>':
+                               w.Write(jsGt)
+                       default:
+                               w.Write(jsLowUni)
+                               t, b := c>>4, c&0x0f
+                               w.Write(hex[t : t+1])
+                               w.Write(hex[b : b+1])
+                       }
+               } else {
+                       // Unicode rune.
+                       r, size := utf8.DecodeRune(b[i:])
+                       if unicode.IsPrint(r) {
+                               w.Write(b[i : i+size])
+                       } else {
+                               fmt.Fprintf(w, "\\u%04X", r)
+                       }
+                       i += size - 1
+               }
+               last = i + 1
+       }
+       w.Write(b[last:])
+}
+
+// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
+func JSEscapeString(s string) string {
+       // Avoid allocation if we can.
+       if strings.IndexFunc(s, jsIsSpecial) < 0 {
+               return s
+       }
+       var b bytes.Buffer
+       JSEscape(&b, []byte(s))
+       return b.String()
+}
+
+func jsIsSpecial(r rune) bool {
+       switch r {
+       case '\\', '\'', '"', '<', '>':
+               return true
+       }
+       return r < ' ' || utf8.RuneSelf <= r
+}
+
+// JSEscaper returns the escaped JavaScript equivalent of the textual
+// representation of its arguments.
+func JSEscaper(args ...interface{}) string {
+       return JSEscapeString(evalArgs(args))
+}
+
+// URLQueryEscaper returns the escaped value of the textual representation of
+// its arguments in a form suitable for embedding in a URL query.
+func URLQueryEscaper(args ...interface{}) string {
+       return url.QueryEscape(evalArgs(args))
+}
+
+// evalArgs formats the list of arguments into a string. It is therefore equivalent to
+//     fmt.Sprint(args...)
+// except that each argument is indirected (if a pointer), as required,
+// using the same rules as the default string evaluation during template
+// execution.
+func evalArgs(args []interface{}) string {
+       ok := false
+       var s string
+       // Fast path for simple common case.
+       if len(args) == 1 {
+               s, ok = args[0].(string)
+       }
+       if !ok {
+               for i, arg := range args {
+                       a, ok := printableValue(reflect.ValueOf(arg))
+                       if ok {
+                               args[i] = a
+                       } // else let fmt do its thing
+               }
+               s = fmt.Sprint(args...)
+       }
+       return s
+}
diff --git a/tpl/internal/go_templates/texttemplate/helper.go b/tpl/internal/go_templates/texttemplate/helper.go
new file mode 100644 (file)
index 0000000..c9e8900
--- /dev/null
@@ -0,0 +1,130 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Helper functions to make constructing templates easier.
+
+package template
+
+import (
+       "fmt"
+       "io/ioutil"
+       "path/filepath"
+)
+
+// Functions and methods to parse templates.
+
+// Must is a helper that wraps a call to a function returning (*Template, error)
+// and panics if the error is non-nil. It is intended for use in variable
+// initializations such as
+//     var t = template.Must(template.New("name").Parse("text"))
+func Must(t *Template, err error) *Template {
+       if err != nil {
+               panic(err)
+       }
+       return t
+}
+
+// ParseFiles creates a new Template and parses the template definitions from
+// the named files. The returned template's name will have the base name and
+// parsed contents of the first file. There must be at least one file.
+// If an error occurs, parsing stops and the returned *Template is nil.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+// 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...)
+}
+
+// ParseFiles parses the named files and associates the resulting templates with
+// t. If an error occurs, parsing stops and the returned template is nil;
+// otherwise it is t. There must be at least one file.
+// Since the templates created by ParseFiles are named by the base
+// names of the argument files, t should usually have the name of one
+// of the (base) names of the files. If it does not, depending on t's
+// contents before calling ParseFiles, t.Execute may fail. In that
+// case use t.ExecuteTemplate to execute a valid template.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
+       t.init()
+       return parseFiles(t, 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) {
+       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)
+               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
+               // as t, this file becomes the contents of t, so
+               //  t, err := New(name).Funcs(xxx).ParseFiles(name)
+               // works. Otherwise we create a new template associated with t.
+               var tmpl *Template
+               if t == nil {
+                       t = New(name)
+               }
+               if name == t.Name() {
+                       tmpl = t
+               } else {
+                       tmpl = t.New(name)
+               }
+               _, err = tmpl.Parse(s)
+               if err != nil {
+                       return nil, err
+               }
+       }
+       return t, nil
+}
+
+// ParseGlob creates a new Template and parses the template definitions from
+// the files identified by the pattern. The files are matched according to the
+// semantics of filepath.Match, and the pattern must match at least one file.
+// The returned template will have the (base) name and (parsed) contents of the
+// first file matched by the pattern. ParseGlob is equivalent to calling
+// ParseFiles with the list of files matched by the pattern.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+func ParseGlob(pattern string) (*Template, error) {
+       return parseGlob(nil, pattern)
+}
+
+// ParseGlob parses the template definitions in the files identified by the
+// pattern and associates the resulting templates with t. The files are matched
+// according to the semantics of filepath.Match, and the pattern must match at
+// least one file. ParseGlob is equivalent to calling t.ParseFiles with the
+// list of files matched by the pattern.
+//
+// When parsing multiple files with the same name in different directories,
+// the last one mentioned will be the one that results.
+func (t *Template) ParseGlob(pattern string) (*Template, error) {
+       t.init()
+       return parseGlob(t, pattern)
+}
+
+// parseGlob is the implementation of the function and method ParseGlob.
+func parseGlob(t *Template, pattern string) (*Template, error) {
+       filenames, err := filepath.Glob(pattern)
+       if err != nil {
+               return nil, err
+       }
+       if len(filenames) == 0 {
+               return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
+       }
+       return parseFiles(t, filenames...)
+}
diff --git a/tpl/internal/go_templates/texttemplate/hugo_exec.go b/tpl/internal/go_templates/texttemplate/hugo_exec.go
new file mode 100644 (file)
index 0000000..cc3aeb2
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package template
+
+import (
+       "io"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
+
+/*
+
+This files contains the Hugo related addons. All the other files in this
+package is auto generated.
+
+*/
+
+// state represents the state of an execution. It's not part of the
+// template so that multiple executions of the same template
+// can execute in parallel.
+type state struct {
+       tmpl  *Template
+       wr    io.Writer
+       node  parse.Node // current node, for errors
+       vars  []variable // push-down stack of variable values.
+       depth int        // the height of the stack of executing templates.
+}
diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go
new file mode 100644 (file)
index 0000000..e3252e7
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package template
+
+import (
+       "io"
+       "reflect"
+
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+)
+
+/*
+
+This files contains the Hugo related addons. All the other files in this
+package is auto generated.
+
+*/
+
+// TODO1 name
+type Preparer interface {
+       Prepare() (*Template, error)
+}
+
+type TemplateExecutor struct {
+}
+
+func (t *TemplateExecutor) Execute(p Preparer, wr io.Writer, data interface{}) error {
+       tmpl, err := p.Prepare()
+       if err != nil {
+               return err
+       }
+
+       value, ok := data.(reflect.Value)
+       if !ok {
+               value = reflect.ValueOf(data)
+       }
+
+       state := &state{
+               tmpl: tmpl,
+               wr:   wr,
+               vars: []variable{{"$", value}},
+       }
+
+       return tmpl.executeWithState(state, value)
+
+}
+
+// Prepare returns a template ready for execution.
+func (t *Template) Prepare() (*Template, error) {
+
+       return t, nil
+}
+
+// Below are modifed structs etc.
+
+// state represents the state of an execution. It's not part of the
+// template so that multiple executions of the same template
+// can execute in parallel.
+type state struct {
+       tmpl  *Template
+       wr    io.Writer
+       node  parse.Node // current node, for errors
+       vars  []variable // push-down stack of variable values.
+       depth int        // the height of the stack of executing templates.
+}
+
+func (t *Template) executeWithState(state *state, value reflect.Value) (err error) {
+       defer errRecover(&err)
+       if t.Tree == nil || t.Root == nil {
+               state.errorf("%q is an incomplete or empty template", t.Name())
+       }
+       state.walk(value, t.Root)
+       return
+}
diff --git a/tpl/internal/go_templates/texttemplate/multi_test.go b/tpl/internal/go_templates/texttemplate/multi_test.go
new file mode 100644 (file)
index 0000000..e41bb9d
--- /dev/null
@@ -0,0 +1,425 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13,!windows
+
+package template
+
+// Tests for multiple-template parsing and execution.
+
+import (
+       "bytes"
+       "fmt"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+       "testing"
+)
+
+const (
+       noError  = true
+       hasError = false
+)
+
+type multiParseTest struct {
+       name    string
+       input   string
+       ok      bool
+       names   []string
+       results []string
+}
+
+var multiParseTests = []multiParseTest{
+       {"empty", "", noError,
+               nil,
+               nil},
+       {"one", `{{define "foo"}} FOO {{end}}`, noError,
+               []string{"foo"},
+               []string{" FOO "}},
+       {"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
+               []string{"foo", "bar"},
+               []string{" FOO ", " BAR "}},
+       // errors
+       {"missing end", `{{define "foo"}} FOO `, hasError,
+               nil,
+               nil},
+       {"malformed name", `{{define "foo}} FOO `, hasError,
+               nil,
+               nil},
+}
+
+func TestMultiParse(t *testing.T) {
+       for _, test := range multiParseTests {
+               template, err := New("root").Parse(test.input)
+               switch {
+               case err == nil && !test.ok:
+                       t.Errorf("%q: expected error; got none", test.name)
+                       continue
+               case err != nil && test.ok:
+                       t.Errorf("%q: unexpected error: %v", test.name, err)
+                       continue
+               case err != nil && !test.ok:
+                       // expected error, got one
+                       if *debug {
+                               fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
+                       }
+                       continue
+               }
+               if template == nil {
+                       continue
+               }
+               if len(template.tmpl) != len(test.names)+1 { // +1 for root
+                       t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(template.tmpl))
+                       continue
+               }
+               for i, name := range test.names {
+                       tmpl, ok := template.tmpl[name]
+                       if !ok {
+                               t.Errorf("%s: can't find template %q", test.name, name)
+                               continue
+                       }
+                       result := tmpl.Root.String()
+                       if result != test.results[i] {
+                               t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
+                       }
+               }
+       }
+}
+
+var multiExecTests = []execTest{
+       {"empty", "", "", nil, true},
+       {"text", "some text", "some text", nil, true},
+       {"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true},
+       {"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true},
+       {"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true},
+       {"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
+       {"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
+       {"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
+       {"variable declared by template", `{{template "nested" $x:=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true},
+
+       // User-defined function: test argument evaluator.
+       {"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
+       {"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true},
+}
+
+// These strings are also in testdata/*.
+const multiText1 = `
+       {{define "x"}}TEXT{{end}}
+       {{define "dotV"}}{{.V}}{{end}}
+`
+
+const multiText2 = `
+       {{define "dot"}}{{.}}{{end}}
+       {{define "nested"}}{{template "dot" .}}{{end}}
+`
+
+func TestMultiExecute(t *testing.T) {
+       // Declare a couple of templates first.
+       template, err := New("root").Parse(multiText1)
+       if err != nil {
+               t.Fatalf("parse error for 1: %s", err)
+       }
+       _, err = template.Parse(multiText2)
+       if err != nil {
+               t.Fatalf("parse error for 2: %s", err)
+       }
+       testExecute(multiExecTests, template, t)
+}
+
+func TestParseFiles(t *testing.T) {
+       _, err := ParseFiles("DOES NOT EXIST")
+       if err == nil {
+               t.Error("expected error for non-existent file; got none")
+       }
+       template := New("root")
+       _, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl")
+       if err != nil {
+               t.Fatalf("error parsing files: %v", err)
+       }
+       testExecute(multiExecTests, template, t)
+}
+
+func TestParseGlob(t *testing.T) {
+       _, err := ParseGlob("DOES NOT EXIST")
+       if err == nil {
+               t.Error("expected error for non-existent file; got none")
+       }
+       _, err = New("error").ParseGlob("[x")
+       if err == nil {
+               t.Error("expected error for bad pattern; got none")
+       }
+       template := New("root")
+       _, err = template.ParseGlob("testdata/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{
+       {"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\n\ny\ntemplate2\n\nx\n", 0, true},
+}
+
+func TestParseFilesWithData(t *testing.T) {
+       template, err := New("root").ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl")
+       if err != nil {
+               t.Fatalf("error parsing files: %v", err)
+       }
+       testExecute(templateFileExecTests, template, t)
+}
+
+func TestParseGlobWithData(t *testing.T) {
+       template, err := New("root").ParseGlob("testdata/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}}`
+       cloneText3 = `{{define "c"}}root{{end}}`
+       cloneText4 = `{{define "c"}}clone{{end}}`
+)
+
+func TestClone(t *testing.T) {
+       // Create some templates and clone the root.
+       root, err := New("root").Parse(cloneText1)
+       if err != nil {
+               t.Fatal(err)
+       }
+       _, err = root.Parse(cloneText2)
+       if err != nil {
+               t.Fatal(err)
+       }
+       clone := Must(root.Clone())
+       // Add variants to both.
+       _, err = root.Parse(cloneText3)
+       if err != nil {
+               t.Fatal(err)
+       }
+       _, err = clone.Parse(cloneText4)
+       if err != nil {
+               t.Fatal(err)
+       }
+       // Verify that the clone is self-consistent.
+       for k, v := range clone.tmpl {
+               if k == clone.name && v.tmpl[k] != clone {
+                       t.Error("clone does not contain root")
+               }
+               if v != v.tmpl[v.name] {
+                       t.Errorf("clone does not contain self for %q", k)
+               }
+       }
+       // Execute root.
+       var b bytes.Buffer
+       err = root.ExecuteTemplate(&b, "a", 0)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if b.String() != "broot" {
+               t.Errorf("expected %q got %q", "broot", b.String())
+       }
+       // Execute copy.
+       b.Reset()
+       err = clone.ExecuteTemplate(&b, "a", 0)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if b.String() != "bclone" {
+               t.Errorf("expected %q got %q", "bclone", b.String())
+       }
+}
+
+func TestAddParseTree(t *testing.T) {
+       // Create some templates.
+       root, err := New("root").Parse(cloneText1)
+       if err != nil {
+               t.Fatal(err)
+       }
+       _, err = root.Parse(cloneText2)
+       if err != nil {
+               t.Fatal(err)
+       }
+       // Add a new parse tree.
+       tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins)
+       if err != nil {
+               t.Fatal(err)
+       }
+       added, err := root.AddParseTree("c", tree["c"])
+       if err != nil {
+               t.Fatal(err)
+       }
+       // Execute.
+       var b bytes.Buffer
+       err = added.ExecuteTemplate(&b, "a", 0)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if b.String() != "broot" {
+               t.Errorf("expected %q got %q", "broot", b.String())
+       }
+}
+
+// Issue 7032
+func TestAddParseTreeToUnparsedTemplate(t *testing.T) {
+       master := "{{define \"master\"}}{{end}}"
+       tmpl := New("master")
+       tree, err := parse.Parse("master", master, "", "", nil)
+       if err != nil {
+               t.Fatalf("unexpected parse err: %v", err)
+       }
+       masterTree := tree["master"]
+       tmpl.AddParseTree("master", masterTree) // used to panic
+}
+
+func TestRedefinition(t *testing.T) {
+       var tmpl *Template
+       var err error
+       if tmpl, err = New("tmpl1").Parse(`{{define "test"}}foo{{end}}`); err != nil {
+               t.Fatalf("parse 1: %v", err)
+       }
+       if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err != nil {
+               t.Fatalf("got error %v, expected nil", err)
+       }
+       if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err != nil {
+               t.Fatalf("got error %v, expected nil", err)
+       }
+}
+
+// Issue 10879
+func TestEmptyTemplateCloneCrash(t *testing.T) {
+       t1 := New("base")
+       t1.Clone() // used to panic
+}
+
+// Issue 10910, 10926
+func TestTemplateLookUp(t *testing.T) {
+       t1 := New("foo")
+       if t1.Lookup("foo") != nil {
+               t.Error("Lookup returned non-nil value for undefined template foo")
+       }
+       t1.New("bar")
+       if t1.Lookup("bar") != nil {
+               t.Error("Lookup returned non-nil value for undefined template bar")
+       }
+       t1.Parse(`{{define "foo"}}test{{end}}`)
+       if t1.Lookup("foo") == nil {
+               t.Error("Lookup returned nil value for defined template")
+       }
+}
+
+func TestNew(t *testing.T) {
+       // template with same name already exists
+       t1, _ := New("test").Parse(`{{define "test"}}foo{{end}}`)
+       t2 := t1.New("test")
+
+       if t1.common != t2.common {
+               t.Errorf("t1 & t2 didn't share common struct; got %v != %v", t1.common, t2.common)
+       }
+       if t1.Tree == nil {
+               t.Error("defined template got nil Tree")
+       }
+       if t2.Tree != nil {
+               t.Error("undefined template got non-nil Tree")
+       }
+
+       containsT1 := false
+       for _, tmpl := range t1.Templates() {
+               if tmpl == t2 {
+                       t.Error("Templates included undefined template")
+               }
+               if tmpl == t1 {
+                       containsT1 = true
+               }
+       }
+       if !containsT1 {
+               t.Error("Templates didn't include defined template")
+       }
+}
+
+func TestParse(t *testing.T) {
+       // In multiple calls to Parse with the same receiver template, only one call
+       // can contain text other than space, comments, and template definitions
+       t1 := New("test")
+       if _, err := t1.Parse(`{{define "test"}}{{end}}`); err != nil {
+               t.Fatalf("parsing test: %s", err)
+       }
+       if _, err := t1.Parse(`{{define "test"}}{{/* this is a comment */}}{{end}}`); err != nil {
+               t.Fatalf("parsing test: %s", err)
+       }
+       if _, err := t1.Parse(`{{define "test"}}foo{{end}}`); err != nil {
+               t.Fatalf("parsing test: %s", err)
+       }
+}
+
+func TestEmptyTemplate(t *testing.T) {
+       cases := []struct {
+               defn []string
+               in   string
+               want string
+       }{
+               {[]string{""}, "once", ""},
+               {[]string{"", ""}, "twice", ""},
+               {[]string{"{{.}}", "{{.}}"}, "twice", "twice"},
+               {[]string{"{{/* a comment */}}", "{{/* a comment */}}"}, "comment", ""},
+               {[]string{"{{.}}", ""}, "twice", ""},
+       }
+
+       for i, c := range cases {
+               root := New("root")
+
+               var (
+                       m   *Template
+                       err error
+               )
+               for _, d := range c.defn {
+                       m, err = root.New(c.in).Parse(d)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+               }
+               buf := &bytes.Buffer{}
+               if err := m.Execute(buf, c.in); err != nil {
+                       t.Error(i, err)
+                       continue
+               }
+               if buf.String() != c.want {
+                       t.Errorf("expected string %q: got %q", c.want, buf.String())
+               }
+       }
+}
+
+// Issue 19249 was a regression in 1.8 caused by the handling of empty
+// templates added in that release, which got different answers depending
+// on the order templates appeared in the internal map.
+func TestIssue19294(t *testing.T) {
+       // The empty block in "xhtml" should be replaced during execution
+       // by the contents of "stylesheet", but if the internal map associating
+       // names with templates is built in the wrong order, the empty block
+       // looks non-empty and this doesn't happen.
+       var inlined = map[string]string{
+               "stylesheet": `{{define "stylesheet"}}stylesheet{{end}}`,
+               "xhtml":      `{{block "stylesheet" .}}{{end}}`,
+       }
+       all := []string{"stylesheet", "xhtml"}
+       for i := 0; i < 100; i++ {
+               res, err := New("title.xhtml").Parse(`{{template "xhtml" .}}`)
+               if err != nil {
+                       t.Fatal(err)
+               }
+               for _, name := range all {
+                       _, err := res.New(name).Parse(inlined[name])
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+               }
+               var buf bytes.Buffer
+               res.Execute(&buf, 0)
+               if buf.String() != "stylesheet" {
+                       t.Fatalf("iteration %d: got %q; expected %q", i, buf.String(), "stylesheet")
+               }
+       }
+}
diff --git a/tpl/internal/go_templates/texttemplate/option.go b/tpl/internal/go_templates/texttemplate/option.go
new file mode 100644 (file)
index 0000000..addce2d
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains the code to handle template options.
+
+package template
+
+import "strings"
+
+// missingKeyAction defines how to respond to indexing a map with a key that is not present.
+type missingKeyAction int
+
+const (
+       mapInvalid   missingKeyAction = iota // Return an invalid reflect.Value.
+       mapZeroValue                         // Return the zero value for the map element.
+       mapError                             // Error out
+)
+
+type option struct {
+       missingKey missingKeyAction
+}
+
+// Option sets options for the template. Options are described by
+// strings, either a simple string or "key=value". There can be at
+// most one equals sign in an option string. If the option string
+// is unrecognized or otherwise invalid, Option panics.
+//
+// Known options:
+//
+// missingkey: Control the behavior during execution if a map is
+// indexed with a key that is not present in the map.
+//     "missingkey=default" or "missingkey=invalid"
+//             The default behavior: Do nothing and continue execution.
+//             If printed, the result of the index operation is the string
+//             "<no value>".
+//     "missingkey=zero"
+//             The operation returns the zero value for the map type's element.
+//     "missingkey=error"
+//             Execution stops immediately with an error.
+//
+func (t *Template) Option(opt ...string) *Template {
+       t.init()
+       for _, s := range opt {
+               t.setOption(s)
+       }
+       return t
+}
+
+func (t *Template) setOption(opt string) {
+       if opt == "" {
+               panic("empty option string")
+       }
+       elems := strings.Split(opt, "=")
+       switch len(elems) {
+       case 2:
+               // key=value
+               switch elems[0] {
+               case "missingkey":
+                       switch elems[1] {
+                       case "invalid", "default":
+                               t.option.missingKey = mapInvalid
+                               return
+                       case "zero":
+                               t.option.missingKey = mapZeroValue
+                               return
+                       case "error":
+                               t.option.missingKey = mapError
+                               return
+                       }
+               }
+       }
+       panic("unrecognized option: " + opt)
+}
diff --git a/tpl/internal/go_templates/texttemplate/parse/lex.go b/tpl/internal/go_templates/texttemplate/parse/lex.go
new file mode 100644 (file)
index 0000000..3d57708
--- /dev/null
@@ -0,0 +1,666 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package parse
+
+import (
+       "fmt"
+       "strings"
+       "unicode"
+       "unicode/utf8"
+)
+
+// item represents a token or text string returned from the scanner.
+type item struct {
+       typ  itemType // The type of this item.
+       pos  Pos      // The starting position, in bytes, of this item in the input string.
+       val  string   // The value of this item.
+       line int      // The line number at the start of this item.
+}
+
+func (i item) String() string {
+       switch {
+       case i.typ == itemEOF:
+               return "EOF"
+       case i.typ == itemError:
+               return i.val
+       case i.typ > itemKeyword:
+               return fmt.Sprintf("<%s>", i.val)
+       case len(i.val) > 10:
+               return fmt.Sprintf("%.10q...", i.val)
+       }
+       return fmt.Sprintf("%q", i.val)
+}
+
+// itemType identifies the type of lex items.
+type itemType int
+
+const (
+       itemError        itemType = iota // error occurred; value is text of error
+       itemBool                         // boolean constant
+       itemChar                         // printable ASCII character; grab bag for comma etc.
+       itemCharConstant                 // character constant
+       itemComplex                      // complex constant (1+2i); imaginary is just a number
+       itemAssign                       // equals ('=') introducing an assignment
+       itemDeclare                      // colon-equals (':=') introducing a declaration
+       itemEOF
+       itemField      // alphanumeric identifier starting with '.'
+       itemIdentifier // alphanumeric identifier not starting with '.'
+       itemLeftDelim  // left action delimiter
+       itemLeftParen  // '(' inside action
+       itemNumber     // simple number, including imaginary
+       itemPipe       // pipe symbol
+       itemRawString  // raw quoted string (includes quotes)
+       itemRightDelim // right action delimiter
+       itemRightParen // ')' inside action
+       itemSpace      // run of spaces separating arguments
+       itemString     // quoted string (includes quotes)
+       itemText       // plain text
+       itemVariable   // variable starting with '$', such as '$' or  '$1' or '$hello'
+       // Keywords appear after all the rest.
+       itemKeyword  // used only to delimit the keywords
+       itemBlock    // block keyword
+       itemDot      // the cursor, spelled '.'
+       itemDefine   // define keyword
+       itemElse     // else keyword
+       itemEnd      // end keyword
+       itemIf       // if keyword
+       itemNil      // the untyped nil constant, easiest to treat as a keyword
+       itemRange    // range keyword
+       itemTemplate // template keyword
+       itemWith     // with keyword
+)
+
+var key = map[string]itemType{
+       ".":        itemDot,
+       "block":    itemBlock,
+       "define":   itemDefine,
+       "else":     itemElse,
+       "end":      itemEnd,
+       "if":       itemIf,
+       "range":    itemRange,
+       "nil":      itemNil,
+       "template": itemTemplate,
+       "with":     itemWith,
+}
+
+const eof = -1
+
+// Trimming spaces.
+// If the action begins "{{- " rather than "{{", then all space/tab/newlines
+// preceding the action are trimmed; conversely if it ends " -}}" the
+// leading spaces are trimmed. This is done entirely in the lexer; the
+// parser never sees it happen. We require an ASCII space to be
+// present to avoid ambiguity with things like "{{-3}}". It reads
+// better with the space present anyway. For simplicity, only ASCII
+// space does the job.
+const (
+       spaceChars      = " \t\r\n" // These are the space characters defined by Go itself.
+       leftTrimMarker  = "- "      // Attached to left delimiter, trims trailing spaces from preceding text.
+       rightTrimMarker = " -"      // Attached to right delimiter, trims leading spaces from following text.
+       trimMarkerLen   = Pos(len(leftTrimMarker))
+)
+
+// stateFn represents the state of the scanner as a function that returns the next state.
+type stateFn func(*lexer) stateFn
+
+// lexer holds the state of the scanner.
+type lexer struct {
+       name           string    // the name of the input; used only for error reports
+       input          string    // the string being scanned
+       leftDelim      string    // start of action
+       rightDelim     string    // end of action
+       trimRightDelim string    // end of action with trim marker
+       pos            Pos       // current position in the input
+       start          Pos       // start position of this item
+       width          Pos       // width of last rune read from input
+       items          chan item // channel of scanned items
+       parenDepth     int       // nesting depth of ( ) exprs
+       line           int       // 1+number of newlines seen
+       startLine      int       // start line of this item
+}
+
+// next returns the next rune in the input.
+func (l *lexer) next() rune {
+       if int(l.pos) >= len(l.input) {
+               l.width = 0
+               return eof
+       }
+       r, w := utf8.DecodeRuneInString(l.input[l.pos:])
+       l.width = Pos(w)
+       l.pos += l.width
+       if r == '\n' {
+               l.line++
+       }
+       return r
+}
+
+// peek returns but does not consume the next rune in the input.
+func (l *lexer) peek() rune {
+       r := l.next()
+       l.backup()
+       return r
+}
+
+// backup steps back one rune. Can only be called once per call of next.
+func (l *lexer) backup() {
+       l.pos -= l.width
+       // Correct newline count.
+       if l.width == 1 && l.input[l.pos] == '\n' {
+               l.line--
+       }
+}
+
+// emit passes an item back to the client.
+func (l *lexer) emit(t itemType) {
+       l.items <- item{t, l.start, l.input[l.start:l.pos], l.startLine}
+       l.start = l.pos
+       l.startLine = l.line
+}
+
+// ignore skips over the pending input before this point.
+func (l *lexer) ignore() {
+       l.line += strings.Count(l.input[l.start:l.pos], "\n")
+       l.start = l.pos
+       l.startLine = l.line
+}
+
+// accept consumes the next rune if it's from the valid set.
+func (l *lexer) accept(valid string) bool {
+       if strings.ContainsRune(valid, l.next()) {
+               return true
+       }
+       l.backup()
+       return false
+}
+
+// acceptRun consumes a run of runes from the valid set.
+func (l *lexer) acceptRun(valid string) {
+       for strings.ContainsRune(valid, l.next()) {
+       }
+       l.backup()
+}
+
+// errorf returns an error token and terminates the scan by passing
+// back a nil pointer that will be the next state, terminating l.nextItem.
+func (l *lexer) errorf(format string, args ...interface{}) stateFn {
+       l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine}
+       return nil
+}
+
+// nextItem returns the next item from the input.
+// Called by the parser, not in the lexing goroutine.
+func (l *lexer) nextItem() item {
+       return <-l.items
+}
+
+// drain drains the output so the lexing goroutine will exit.
+// Called by the parser, not in the lexing goroutine.
+func (l *lexer) drain() {
+       for range l.items {
+       }
+}
+
+// lex creates a new scanner for the input string.
+func lex(name, input, left, right string) *lexer {
+       if left == "" {
+               left = leftDelim
+       }
+       if right == "" {
+               right = rightDelim
+       }
+       l := &lexer{
+               name:           name,
+               input:          input,
+               leftDelim:      left,
+               rightDelim:     right,
+               trimRightDelim: rightTrimMarker + right,
+               items:          make(chan item),
+               line:           1,
+               startLine:      1,
+       }
+       go l.run()
+       return l
+}
+
+// run runs the state machine for the lexer.
+func (l *lexer) run() {
+       for state := lexText; state != nil; {
+               state = state(l)
+       }
+       close(l.items)
+}
+
+// state functions
+
+const (
+       leftDelim    = "{{"
+       rightDelim   = "}}"
+       leftComment  = "/*"
+       rightComment = "*/"
+)
+
+// lexText scans until an opening action delimiter, "{{".
+func lexText(l *lexer) stateFn {
+       l.width = 0
+       if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 {
+               ldn := Pos(len(l.leftDelim))
+               l.pos += Pos(x)
+               trimLength := Pos(0)
+               if strings.HasPrefix(l.input[l.pos+ldn:], leftTrimMarker) {
+                       trimLength = rightTrimLength(l.input[l.start:l.pos])
+               }
+               l.pos -= trimLength
+               if l.pos > l.start {
+                       l.line += strings.Count(l.input[l.start:l.pos], "\n")
+                       l.emit(itemText)
+               }
+               l.pos += trimLength
+               l.ignore()
+               return lexLeftDelim
+       }
+       l.pos = Pos(len(l.input))
+       // Correctly reached EOF.
+       if l.pos > l.start {
+               l.line += strings.Count(l.input[l.start:l.pos], "\n")
+               l.emit(itemText)
+       }
+       l.emit(itemEOF)
+       return nil
+}
+
+// rightTrimLength returns the length of the spaces at the end of the string.
+func rightTrimLength(s string) Pos {
+       return Pos(len(s) - len(strings.TrimRight(s, spaceChars)))
+}
+
+// atRightDelim reports whether the lexer is at a right delimiter, possibly preceded by a trim marker.
+func (l *lexer) atRightDelim() (delim, trimSpaces bool) {
+       if strings.HasPrefix(l.input[l.pos:], l.trimRightDelim) { // With trim marker.
+               return true, true
+       }
+       if strings.HasPrefix(l.input[l.pos:], l.rightDelim) { // Without trim marker.
+               return true, false
+       }
+       return false, false
+}
+
+// leftTrimLength returns the length of the spaces at the beginning of the string.
+func leftTrimLength(s string) Pos {
+       return Pos(len(s) - len(strings.TrimLeft(s, spaceChars)))
+}
+
+// lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker.
+func lexLeftDelim(l *lexer) stateFn {
+       l.pos += Pos(len(l.leftDelim))
+       trimSpace := strings.HasPrefix(l.input[l.pos:], leftTrimMarker)
+       afterMarker := Pos(0)
+       if trimSpace {
+               afterMarker = trimMarkerLen
+       }
+       if strings.HasPrefix(l.input[l.pos+afterMarker:], leftComment) {
+               l.pos += afterMarker
+               l.ignore()
+               return lexComment
+       }
+       l.emit(itemLeftDelim)
+       l.pos += afterMarker
+       l.ignore()
+       l.parenDepth = 0
+       return lexInsideAction
+}
+
+// lexComment scans a comment. The left comment marker is known to be present.
+func lexComment(l *lexer) stateFn {
+       l.pos += Pos(len(leftComment))
+       i := strings.Index(l.input[l.pos:], rightComment)
+       if i < 0 {
+               return l.errorf("unclosed comment")
+       }
+       l.pos += Pos(i + len(rightComment))
+       delim, trimSpace := l.atRightDelim()
+       if !delim {
+               return l.errorf("comment ends before closing delimiter")
+       }
+       if trimSpace {
+               l.pos += trimMarkerLen
+       }
+       l.pos += Pos(len(l.rightDelim))
+       if trimSpace {
+               l.pos += leftTrimLength(l.input[l.pos:])
+       }
+       l.ignore()
+       return lexText
+}
+
+// lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker.
+func lexRightDelim(l *lexer) stateFn {
+       trimSpace := strings.HasPrefix(l.input[l.pos:], rightTrimMarker)
+       if trimSpace {
+               l.pos += trimMarkerLen
+               l.ignore()
+       }
+       l.pos += Pos(len(l.rightDelim))
+       l.emit(itemRightDelim)
+       if trimSpace {
+               l.pos += leftTrimLength(l.input[l.pos:])
+               l.ignore()
+       }
+       return lexText
+}
+
+// lexInsideAction scans the elements inside action delimiters.
+func lexInsideAction(l *lexer) stateFn {
+       // Either number, quoted string, or identifier.
+       // Spaces separate arguments; runs of spaces turn into itemSpace.
+       // Pipe symbols separate and are emitted.
+       delim, _ := l.atRightDelim()
+       if delim {
+               if l.parenDepth == 0 {
+                       return lexRightDelim
+               }
+               return l.errorf("unclosed left paren")
+       }
+       switch r := l.next(); {
+       case r == eof || isEndOfLine(r):
+               return l.errorf("unclosed action")
+       case isSpace(r):
+               l.backup() // Put space back in case we have " -}}".
+               return lexSpace
+       case r == '=':
+               l.emit(itemAssign)
+       case r == ':':
+               if l.next() != '=' {
+                       return l.errorf("expected :=")
+               }
+               l.emit(itemDeclare)
+       case r == '|':
+               l.emit(itemPipe)
+       case r == '"':
+               return lexQuote
+       case r == '`':
+               return lexRawQuote
+       case r == '$':
+               return lexVariable
+       case r == '\'':
+               return lexChar
+       case r == '.':
+               // special look-ahead for ".field" so we don't break l.backup().
+               if l.pos < Pos(len(l.input)) {
+                       r := l.input[l.pos]
+                       if r < '0' || '9' < r {
+                               return lexField
+                       }
+               }
+               fallthrough // '.' can start a number.
+       case r == '+' || r == '-' || ('0' <= r && r <= '9'):
+               l.backup()
+               return lexNumber
+       case isAlphaNumeric(r):
+               l.backup()
+               return lexIdentifier
+       case r == '(':
+               l.emit(itemLeftParen)
+               l.parenDepth++
+       case r == ')':
+               l.emit(itemRightParen)
+               l.parenDepth--
+               if l.parenDepth < 0 {
+                       return l.errorf("unexpected right paren %#U", r)
+               }
+       case r <= unicode.MaxASCII && unicode.IsPrint(r):
+               l.emit(itemChar)
+               return lexInsideAction
+       default:
+               return l.errorf("unrecognized character in action: %#U", r)
+       }
+       return lexInsideAction
+}
+
+// lexSpace scans a run of space characters.
+// We have not consumed the first space, which is known to be present.
+// Take care if there is a trim-marked right delimiter, which starts with a space.
+func lexSpace(l *lexer) stateFn {
+       var r rune
+       var numSpaces int
+       for {
+               r = l.peek()
+               if !isSpace(r) {
+                       break
+               }
+               l.next()
+               numSpaces++
+       }
+       // Be careful about a trim-marked closing delimiter, which has a minus
+       // after a space. We know there is a space, so check for the '-' that might follow.
+       if strings.HasPrefix(l.input[l.pos-1:], l.trimRightDelim) {
+               l.backup() // Before the space.
+               if numSpaces == 1 {
+                       return lexRightDelim // On the delim, so go right to that.
+               }
+       }
+       l.emit(itemSpace)
+       return lexInsideAction
+}
+
+// lexIdentifier scans an alphanumeric.
+func lexIdentifier(l *lexer) stateFn {
+Loop:
+       for {
+               switch r := l.next(); {
+               case isAlphaNumeric(r):
+                       // absorb.
+               default:
+                       l.backup()
+                       word := l.input[l.start:l.pos]
+                       if !l.atTerminator() {
+                               return l.errorf("bad character %#U", r)
+                       }
+                       switch {
+                       case key[word] > itemKeyword:
+                               l.emit(key[word])
+                       case word[0] == '.':
+                               l.emit(itemField)
+                       case word == "true", word == "false":
+                               l.emit(itemBool)
+                       default:
+                               l.emit(itemIdentifier)
+                       }
+                       break Loop
+               }
+       }
+       return lexInsideAction
+}
+
+// lexField scans a field: .Alphanumeric.
+// The . has been scanned.
+func lexField(l *lexer) stateFn {
+       return lexFieldOrVariable(l, itemField)
+}
+
+// lexVariable scans a Variable: $Alphanumeric.
+// The $ has been scanned.
+func lexVariable(l *lexer) stateFn {
+       if l.atTerminator() { // Nothing interesting follows -> "$".
+               l.emit(itemVariable)
+               return lexInsideAction
+       }
+       return lexFieldOrVariable(l, itemVariable)
+}
+
+// lexVariable scans a field or variable: [.$]Alphanumeric.
+// The . or $ has been scanned.
+func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
+       if l.atTerminator() { // Nothing interesting follows -> "." or "$".
+               if typ == itemVariable {
+                       l.emit(itemVariable)
+               } else {
+                       l.emit(itemDot)
+               }
+               return lexInsideAction
+       }
+       var r rune
+       for {
+               r = l.next()
+               if !isAlphaNumeric(r) {
+                       l.backup()
+                       break
+               }
+       }
+       if !l.atTerminator() {
+               return l.errorf("bad character %#U", r)
+       }
+       l.emit(typ)
+       return lexInsideAction
+}
+
+// atTerminator reports whether the input is at valid termination character to
+// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
+// like "$x+2" not being acceptable without a space, in case we decide one
+// day to implement arithmetic.
+func (l *lexer) atTerminator() bool {
+       r := l.peek()
+       if isSpace(r) || isEndOfLine(r) {
+               return true
+       }
+       switch r {
+       case eof, '.', ',', '|', ':', ')', '(':
+               return true
+       }
+       // Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
+       // succeed but should fail) but only in extremely rare cases caused by willfully
+       // bad choice of delimiter.
+       if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
+               return true
+       }
+       return false
+}
+
+// lexChar scans a character constant. The initial quote is already
+// scanned. Syntax checking is done by the parser.
+func lexChar(l *lexer) stateFn {
+Loop:
+       for {
+               switch l.next() {
+               case '\\':
+                       if r := l.next(); r != eof && r != '\n' {
+                               break
+                       }
+                       fallthrough
+               case eof, '\n':
+                       return l.errorf("unterminated character constant")
+               case '\'':
+                       break Loop
+               }
+       }
+       l.emit(itemCharConstant)
+       return lexInsideAction
+}
+
+// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
+// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
+// and "089" - but when it's wrong the input is invalid and the parser (via
+// strconv) will notice.
+func lexNumber(l *lexer) stateFn {
+       if !l.scanNumber() {
+               return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
+       }
+       if sign := l.peek(); sign == '+' || sign == '-' {
+               // Complex: 1+2i. No spaces, must end in 'i'.
+               if !l.scanNumber() || l.input[l.pos-1] != 'i' {
+                       return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
+               }
+               l.emit(itemComplex)
+       } else {
+               l.emit(itemNumber)
+       }
+       return lexInsideAction
+}
+
+func (l *lexer) scanNumber() bool {
+       // Optional leading sign.
+       l.accept("+-")
+       // Is it hex?
+       digits := "0123456789_"
+       if l.accept("0") {
+               // Note: Leading 0 does not mean octal in floats.
+               if l.accept("xX") {
+                       digits = "0123456789abcdefABCDEF_"
+               } else if l.accept("oO") {
+                       digits = "01234567_"
+               } else if l.accept("bB") {
+                       digits = "01_"
+               }
+       }
+       l.acceptRun(digits)
+       if l.accept(".") {
+               l.acceptRun(digits)
+       }
+       if len(digits) == 10+1 && l.accept("eE") {
+               l.accept("+-")
+               l.acceptRun("0123456789_")
+       }
+       if len(digits) == 16+6+1 && l.accept("pP") {
+               l.accept("+-")
+               l.acceptRun("0123456789_")
+       }
+       // Is it imaginary?
+       l.accept("i")
+       // Next thing mustn't be alphanumeric.
+       if isAlphaNumeric(l.peek()) {
+               l.next()
+               return false
+       }
+       return true
+}
+
+// lexQuote scans a quoted string.
+func lexQuote(l *lexer) stateFn {
+Loop:
+       for {
+               switch l.next() {
+               case '\\':
+                       if r := l.next(); r != eof && r != '\n' {
+                               break
+                       }
+                       fallthrough
+               case eof, '\n':
+                       return l.errorf("unterminated quoted string")
+               case '"':
+                       break Loop
+               }
+       }
+       l.emit(itemString)
+       return lexInsideAction
+}
+
+// lexRawQuote scans a raw quoted string.
+func lexRawQuote(l *lexer) stateFn {
+Loop:
+       for {
+               switch l.next() {
+               case eof:
+                       return l.errorf("unterminated raw quoted string")
+               case '`':
+                       break Loop
+               }
+       }
+       l.emit(itemRawString)
+       return lexInsideAction
+}
+
+// isSpace reports whether r is a space character.
+func isSpace(r rune) bool {
+       return r == ' ' || r == '\t'
+}
+
+// isEndOfLine reports whether r is an end-of-line character.
+func isEndOfLine(r rune) bool {
+       return r == '\r' || r == '\n'
+}
+
+// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
+func isAlphaNumeric(r rune) bool {
+       return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
+}
diff --git a/tpl/internal/go_templates/texttemplate/parse/lex_test.go b/tpl/internal/go_templates/texttemplate/parse/lex_test.go
new file mode 100644 (file)
index 0000000..caadc52
--- /dev/null
@@ -0,0 +1,556 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package parse
+
+import (
+       "fmt"
+       "testing"
+)
+
+// Make the types prettyprint.
+var itemName = map[itemType]string{
+       itemError:        "error",
+       itemBool:         "bool",
+       itemChar:         "char",
+       itemCharConstant: "charconst",
+       itemComplex:      "complex",
+       itemDeclare:      ":=",
+       itemEOF:          "EOF",
+       itemField:        "field",
+       itemIdentifier:   "identifier",
+       itemLeftDelim:    "left delim",
+       itemLeftParen:    "(",
+       itemNumber:       "number",
+       itemPipe:         "pipe",
+       itemRawString:    "raw string",
+       itemRightDelim:   "right delim",
+       itemRightParen:   ")",
+       itemSpace:        "space",
+       itemString:       "string",
+       itemVariable:     "variable",
+
+       // keywords
+       itemDot:      ".",
+       itemBlock:    "block",
+       itemDefine:   "define",
+       itemElse:     "else",
+       itemIf:       "if",
+       itemEnd:      "end",
+       itemNil:      "nil",
+       itemRange:    "range",
+       itemTemplate: "template",
+       itemWith:     "with",
+}
+
+func (i itemType) String() string {
+       s := itemName[i]
+       if s == "" {
+               return fmt.Sprintf("item%d", int(i))
+       }
+       return s
+}
+
+type lexTest struct {
+       name  string
+       input string
+       items []item
+}
+
+func mkItem(typ itemType, text string) item {
+       return item{
+               typ: typ,
+               val: text,
+       }
+}
+
+var (
+       tDot        = mkItem(itemDot, ".")
+       tBlock      = mkItem(itemBlock, "block")
+       tEOF        = mkItem(itemEOF, "")
+       tFor        = mkItem(itemIdentifier, "for")
+       tLeft       = mkItem(itemLeftDelim, "{{")
+       tLpar       = mkItem(itemLeftParen, "(")
+       tPipe       = mkItem(itemPipe, "|")
+       tQuote      = mkItem(itemString, `"abc \n\t\" "`)
+       tRange      = mkItem(itemRange, "range")
+       tRight      = mkItem(itemRightDelim, "}}")
+       tRpar       = mkItem(itemRightParen, ")")
+       tSpace      = mkItem(itemSpace, " ")
+       raw         = "`" + `abc\n\t\" ` + "`"
+       rawNL       = "`now is{{\n}}the time`" // Contains newline inside raw quote.
+       tRawQuote   = mkItem(itemRawString, raw)
+       tRawQuoteNL = mkItem(itemRawString, rawNL)
+)
+
+var lexTests = []lexTest{
+       {"empty", "", []item{tEOF}},
+       {"spaces", " \t\n", []item{mkItem(itemText, " \t\n"), tEOF}},
+       {"text", `now is the time`, []item{mkItem(itemText, "now is the time"), tEOF}},
+       {"text with comment", "hello-{{/* this is a comment */}}-world", []item{
+               mkItem(itemText, "hello-"),
+               mkItem(itemText, "-world"),
+               tEOF,
+       }},
+       {"punctuation", "{{,@% }}", []item{
+               tLeft,
+               mkItem(itemChar, ","),
+               mkItem(itemChar, "@"),
+               mkItem(itemChar, "%"),
+               tSpace,
+               tRight,
+               tEOF,
+       }},
+       {"parens", "{{((3))}}", []item{
+               tLeft,
+               tLpar,
+               tLpar,
+               mkItem(itemNumber, "3"),
+               tRpar,
+               tRpar,
+               tRight,
+               tEOF,
+       }},
+       {"empty action", `{{}}`, []item{tLeft, tRight, tEOF}},
+       {"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}},
+       {"block", `{{block "foo" .}}`, []item{
+               tLeft, tBlock, tSpace, mkItem(itemString, `"foo"`), tSpace, tDot, tRight, tEOF,
+       }},
+       {"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}},
+       {"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}},
+       {"raw quote with newline", "{{" + rawNL + "}}", []item{tLeft, tRawQuoteNL, tRight, tEOF}},
+       {"numbers", "{{1 02 0x14 0X14 -7.2i 1e3 1E3 +1.2e-4 4.2i 1+2i 1_2 0x1.e_fp4 0X1.E_FP4}}", []item{
+               tLeft,
+               mkItem(itemNumber, "1"),
+               tSpace,
+               mkItem(itemNumber, "02"),
+               tSpace,
+               mkItem(itemNumber, "0x14"),
+               tSpace,
+               mkItem(itemNumber, "0X14"),
+               tSpace,
+               mkItem(itemNumber, "-7.2i"),
+               tSpace,
+               mkItem(itemNumber, "1e3"),
+               tSpace,
+               mkItem(itemNumber, "1E3"),
+               tSpace,
+               mkItem(itemNumber, "+1.2e-4"),
+               tSpace,
+               mkItem(itemNumber, "4.2i"),
+               tSpace,
+               mkItem(itemComplex, "1+2i"),
+               tSpace,
+               mkItem(itemNumber, "1_2"),
+               tSpace,
+               mkItem(itemNumber, "0x1.e_fp4"),
+               tSpace,
+               mkItem(itemNumber, "0X1.E_FP4"),
+               tRight,
+               tEOF,
+       }},
+       {"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{
+               tLeft,
+               mkItem(itemCharConstant, `'a'`),
+               tSpace,
+               mkItem(itemCharConstant, `'\n'`),
+               tSpace,
+               mkItem(itemCharConstant, `'\''`),
+               tSpace,
+               mkItem(itemCharConstant, `'\\'`),
+               tSpace,
+               mkItem(itemCharConstant, `'\u00FF'`),
+               tSpace,
+               mkItem(itemCharConstant, `'\xFF'`),
+               tSpace,
+               mkItem(itemCharConstant, `'本'`),
+               tRight,
+               tEOF,
+       }},
+       {"bools", "{{true false}}", []item{
+               tLeft,
+               mkItem(itemBool, "true"),
+               tSpace,
+               mkItem(itemBool, "false"),
+               tRight,
+               tEOF,
+       }},
+       {"dot", "{{.}}", []item{
+               tLeft,
+               tDot,
+               tRight,
+               tEOF,
+       }},
+       {"nil", "{{nil}}", []item{
+               tLeft,
+               mkItem(itemNil, "nil"),
+               tRight,
+               tEOF,
+       }},
+       {"dots", "{{.x . .2 .x.y.z}}", []item{
+               tLeft,
+               mkItem(itemField, ".x"),
+               tSpace,
+               tDot,
+               tSpace,
+               mkItem(itemNumber, ".2"),
+               tSpace,
+               mkItem(itemField, ".x"),
+               mkItem(itemField, ".y"),
+               mkItem(itemField, ".z"),
+               tRight,
+               tEOF,
+       }},
+       {"keywords", "{{range if else end with}}", []item{
+               tLeft,
+               mkItem(itemRange, "range"),
+               tSpace,
+               mkItem(itemIf, "if"),
+               tSpace,
+               mkItem(itemElse, "else"),
+               tSpace,
+               mkItem(itemEnd, "end"),
+               tSpace,
+               mkItem(itemWith, "with"),
+               tRight,
+               tEOF,
+       }},
+       {"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{
+               tLeft,
+               mkItem(itemVariable, "$c"),
+               tSpace,
+               mkItem(itemDeclare, ":="),
+               tSpace,
+               mkItem(itemIdentifier, "printf"),
+               tSpace,
+               mkItem(itemVariable, "$"),
+               tSpace,
+               mkItem(itemVariable, "$hello"),
+               tSpace,
+               mkItem(itemVariable, "$23"),
+               tSpace,
+               mkItem(itemVariable, "$"),
+               tSpace,
+               mkItem(itemVariable, "$var"),
+               mkItem(itemField, ".Field"),
+               tSpace,
+               mkItem(itemField, ".Method"),
+               tRight,
+               tEOF,
+       }},
+       {"variable invocation", "{{$x 23}}", []item{
+               tLeft,
+               mkItem(itemVariable, "$x"),
+               tSpace,
+               mkItem(itemNumber, "23"),
+               tRight,
+               tEOF,
+       }},
+       {"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{
+               mkItem(itemText, "intro "),
+               tLeft,
+               mkItem(itemIdentifier, "echo"),
+               tSpace,
+               mkItem(itemIdentifier, "hi"),
+               tSpace,
+               mkItem(itemNumber, "1.2"),
+               tSpace,
+               tPipe,
+               mkItem(itemIdentifier, "noargs"),
+               tPipe,
+               mkItem(itemIdentifier, "args"),
+               tSpace,
+               mkItem(itemNumber, "1"),
+               tSpace,
+               mkItem(itemString, `"hi"`),
+               tRight,
+               mkItem(itemText, " outro"),
+               tEOF,
+       }},
+       {"declaration", "{{$v := 3}}", []item{
+               tLeft,
+               mkItem(itemVariable, "$v"),
+               tSpace,
+               mkItem(itemDeclare, ":="),
+               tSpace,
+               mkItem(itemNumber, "3"),
+               tRight,
+               tEOF,
+       }},
+       {"2 declarations", "{{$v , $w := 3}}", []item{
+               tLeft,
+               mkItem(itemVariable, "$v"),
+               tSpace,
+               mkItem(itemChar, ","),
+               tSpace,
+               mkItem(itemVariable, "$w"),
+               tSpace,
+               mkItem(itemDeclare, ":="),
+               tSpace,
+               mkItem(itemNumber, "3"),
+               tRight,
+               tEOF,
+       }},
+       {"field of parenthesized expression", "{{(.X).Y}}", []item{
+               tLeft,
+               tLpar,
+               mkItem(itemField, ".X"),
+               tRpar,
+               mkItem(itemField, ".Y"),
+               tRight,
+               tEOF,
+       }},
+       {"trimming spaces before and after", "hello- {{- 3 -}} -world", []item{
+               mkItem(itemText, "hello-"),
+               tLeft,
+               mkItem(itemNumber, "3"),
+               tRight,
+               mkItem(itemText, "-world"),
+               tEOF,
+       }},
+       {"trimming spaces before and after comment", "hello- {{- /* hello */ -}} -world", []item{
+               mkItem(itemText, "hello-"),
+               mkItem(itemText, "-world"),
+               tEOF,
+       }},
+       // errors
+       {"badchar", "#{{\x01}}", []item{
+               mkItem(itemText, "#"),
+               tLeft,
+               mkItem(itemError, "unrecognized character in action: U+0001"),
+       }},
+       {"unclosed action", "{{\n}}", []item{
+               tLeft,
+               mkItem(itemError, "unclosed action"),
+       }},
+       {"EOF in action", "{{range", []item{
+               tLeft,
+               tRange,
+               mkItem(itemError, "unclosed action"),
+       }},
+       {"unclosed quote", "{{\"\n\"}}", []item{
+               tLeft,
+               mkItem(itemError, "unterminated quoted string"),
+       }},
+       {"unclosed raw quote", "{{`xx}}", []item{
+               tLeft,
+               mkItem(itemError, "unterminated raw quoted string"),
+       }},
+       {"unclosed char constant", "{{'\n}}", []item{
+               tLeft,
+               mkItem(itemError, "unterminated character constant"),
+       }},
+       {"bad number", "{{3k}}", []item{
+               tLeft,
+               mkItem(itemError, `bad number syntax: "3k"`),
+       }},
+       {"unclosed paren", "{{(3}}", []item{
+               tLeft,
+               tLpar,
+               mkItem(itemNumber, "3"),
+               mkItem(itemError, `unclosed left paren`),
+       }},
+       {"extra right paren", "{{3)}}", []item{
+               tLeft,
+               mkItem(itemNumber, "3"),
+               tRpar,
+               mkItem(itemError, `unexpected right paren U+0029 ')'`),
+       }},
+
+       // Fixed bugs
+       // Many elements in an action blew the lookahead until
+       // we made lexInsideAction not loop.
+       {"long pipeline deadlock", "{{|||||}}", []item{
+               tLeft,
+               tPipe,
+               tPipe,
+               tPipe,
+               tPipe,
+               tPipe,
+               tRight,
+               tEOF,
+       }},
+       {"text with bad comment", "hello-{{/*/}}-world", []item{
+               mkItem(itemText, "hello-"),
+               mkItem(itemError, `unclosed comment`),
+       }},
+       {"text with comment close separated from delim", "hello-{{/* */ }}-world", []item{
+               mkItem(itemText, "hello-"),
+               mkItem(itemError, `comment ends before closing delimiter`),
+       }},
+       // This one is an error that we can't catch because it breaks templates with
+       // minimized JavaScript. Should have fixed it before Go 1.1.
+       {"unmatched right delimiter", "hello-{.}}-world", []item{
+               mkItem(itemText, "hello-{.}}-world"),
+               tEOF,
+       }},
+}
+
+// collect gathers the emitted items into a slice.
+func collect(t *lexTest, left, right string) (items []item) {
+       l := lex(t.name, t.input, left, right)
+       for {
+               item := l.nextItem()
+               items = append(items, item)
+               if item.typ == itemEOF || item.typ == itemError {
+                       break
+               }
+       }
+       return
+}
+
+func equal(i1, i2 []item, checkPos bool) bool {
+       if len(i1) != len(i2) {
+               return false
+       }
+       for k := range i1 {
+               if i1[k].typ != i2[k].typ {
+                       return false
+               }
+               if i1[k].val != i2[k].val {
+                       return false
+               }
+               if checkPos && i1[k].pos != i2[k].pos {
+                       return false
+               }
+               if checkPos && i1[k].line != i2[k].line {
+                       return false
+               }
+       }
+       return true
+}
+
+func TestLex(t *testing.T) {
+       for _, test := range lexTests {
+               items := collect(&test, "", "")
+               if !equal(items, test.items, false) {
+                       t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
+               }
+       }
+}
+
+// Some easy cases from above, but with delimiters $$ and @@
+var lexDelimTests = []lexTest{
+       {"punctuation", "$$,@%{{}}@@", []item{
+               tLeftDelim,
+               mkItem(itemChar, ","),
+               mkItem(itemChar, "@"),
+               mkItem(itemChar, "%"),
+               mkItem(itemChar, "{"),
+               mkItem(itemChar, "{"),
+               mkItem(itemChar, "}"),
+               mkItem(itemChar, "}"),
+               tRightDelim,
+               tEOF,
+       }},
+       {"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}},
+       {"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}},
+       {"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}},
+       {"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}},
+}
+
+var (
+       tLeftDelim  = mkItem(itemLeftDelim, "$$")
+       tRightDelim = mkItem(itemRightDelim, "@@")
+)
+
+func TestDelims(t *testing.T) {
+       for _, test := range lexDelimTests {
+               items := collect(&test, "$$", "@@")
+               if !equal(items, test.items, false) {
+                       t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
+               }
+       }
+}
+
+var lexPosTests = []lexTest{
+       {"empty", "", []item{{itemEOF, 0, "", 1}}},
+       {"punctuation", "{{,@%#}}", []item{
+               {itemLeftDelim, 0, "{{", 1},
+               {itemChar, 2, ",", 1},
+               {itemChar, 3, "@", 1},
+               {itemChar, 4, "%", 1},
+               {itemChar, 5, "#", 1},
+               {itemRightDelim, 6, "}}", 1},
+               {itemEOF, 8, "", 1},
+       }},
+       {"sample", "0123{{hello}}xyz", []item{
+               {itemText, 0, "0123", 1},
+               {itemLeftDelim, 4, "{{", 1},
+               {itemIdentifier, 6, "hello", 1},
+               {itemRightDelim, 11, "}}", 1},
+               {itemText, 13, "xyz", 1},
+               {itemEOF, 16, "", 1},
+       }},
+       {"trimafter", "{{x -}}\n{{y}}", []item{
+               {itemLeftDelim, 0, "{{", 1},
+               {itemIdentifier, 2, "x", 1},
+               {itemRightDelim, 5, "}}", 1},
+               {itemLeftDelim, 8, "{{", 2},
+               {itemIdentifier, 10, "y", 2},
+               {itemRightDelim, 11, "}}", 2},
+               {itemEOF, 13, "", 2},
+       }},
+       {"trimbefore", "{{x}}\n{{- y}}", []item{
+               {itemLeftDelim, 0, "{{", 1},
+               {itemIdentifier, 2, "x", 1},
+               {itemRightDelim, 3, "}}", 1},
+               {itemLeftDelim, 6, "{{", 2},
+               {itemIdentifier, 10, "y", 2},
+               {itemRightDelim, 11, "}}", 2},
+               {itemEOF, 13, "", 2},
+       }},
+}
+
+// The other tests don't check position, to make the test cases easier to construct.
+// This one does.
+func TestPos(t *testing.T) {
+       for _, test := range lexPosTests {
+               items := collect(&test, "", "")
+               if !equal(items, test.items, true) {
+                       t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
+                       if len(items) == len(test.items) {
+                               // Detailed print; avoid item.String() to expose the position value.
+                               for i := range items {
+                                       if !equal(items[i:i+1], test.items[i:i+1], true) {
+                                               i1 := items[i]
+                                               i2 := test.items[i]
+                                               t.Errorf("\t#%d: got {%v %d %q %d} expected {%v %d %q %d}",
+                                                       i, i1.typ, i1.pos, i1.val, i1.line, i2.typ, i2.pos, i2.val, i2.line)
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+// Test that an error shuts down the lexing goroutine.
+func TestShutdown(t *testing.T) {
+       // We need to duplicate template.Parse here to hold on to the lexer.
+       const text = "erroneous{{define}}{{else}}1234"
+       lexer := lex("foo", text, "{{", "}}")
+       _, err := New("root").parseLexer(lexer)
+       if err == nil {
+               t.Fatalf("expected error")
+       }
+       // The error should have drained the input. Therefore, the lexer should be shut down.
+       token, ok := <-lexer.items
+       if ok {
+               t.Fatalf("input was not drained; got %v", token)
+       }
+}
+
+// parseLexer is a local version of parse that lets us pass in the lexer instead of building it.
+// We expect an error, so the tree set and funcs list are explicitly nil.
+func (t *Tree) parseLexer(lex *lexer) (tree *Tree, err error) {
+       defer t.recover(&err)
+       t.ParseName = t.Name
+       t.startParse(nil, lex, map[string]*Tree{})
+       t.parse()
+       t.add()
+       t.stopParse()
+       return t, nil
+}
diff --git a/tpl/internal/go_templates/texttemplate/parse/node.go b/tpl/internal/go_templates/texttemplate/parse/node.go
new file mode 100644 (file)
index 0000000..1174a4b
--- /dev/null
@@ -0,0 +1,841 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Parse nodes.
+
+package parse
+
+import (
+       "bytes"
+       "fmt"
+       "strconv"
+       "strings"
+)
+
+var textFormat = "%s" // Changed to "%q" in tests for better error messages.
+
+// A Node is an element in the parse tree. The interface is trivial.
+// The interface contains an unexported method so that only
+// types local to this package can satisfy it.
+type Node interface {
+       Type() NodeType
+       String() string
+       // Copy does a deep copy of the Node and all its components.
+       // To avoid type assertions, some XxxNodes also have specialized
+       // CopyXxx methods that return *XxxNode.
+       Copy() Node
+       Position() Pos // byte position of start of node in full original input string
+       // tree returns the containing *Tree.
+       // It is unexported so all implementations of Node are in this package.
+       tree() *Tree
+}
+
+// NodeType identifies the type of a parse tree node.
+type NodeType int
+
+// Pos represents a byte position in the original input text from which
+// this template was parsed.
+type Pos int
+
+func (p Pos) Position() Pos {
+       return p
+}
+
+// Type returns itself and provides an easy default implementation
+// for embedding in a Node. Embedded in all non-trivial Nodes.
+func (t NodeType) Type() NodeType {
+       return t
+}
+
+const (
+       NodeText       NodeType = iota // Plain text.
+       NodeAction                     // A non-control action such as a field evaluation.
+       NodeBool                       // A boolean constant.
+       NodeChain                      // A sequence of field accesses.
+       NodeCommand                    // An element of a pipeline.
+       NodeDot                        // The cursor, dot.
+       nodeElse                       // An else action. Not added to tree.
+       nodeEnd                        // An end action. Not added to tree.
+       NodeField                      // A field or method name.
+       NodeIdentifier                 // An identifier; always a function name.
+       NodeIf                         // An if action.
+       NodeList                       // A list of Nodes.
+       NodeNil                        // An untyped nil constant.
+       NodeNumber                     // A numerical constant.
+       NodePipe                       // A pipeline of commands.
+       NodeRange                      // A range action.
+       NodeString                     // A string constant.
+       NodeTemplate                   // A template invocation action.
+       NodeVariable                   // A $ variable.
+       NodeWith                       // A with action.
+)
+
+// Nodes.
+
+// ListNode holds a sequence of nodes.
+type ListNode struct {
+       NodeType
+       Pos
+       tr    *Tree
+       Nodes []Node // The element nodes in lexical order.
+}
+
+func (t *Tree) newList(pos Pos) *ListNode {
+       return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
+}
+
+func (l *ListNode) append(n Node) {
+       l.Nodes = append(l.Nodes, n)
+}
+
+func (l *ListNode) tree() *Tree {
+       return l.tr
+}
+
+func (l *ListNode) String() string {
+       b := new(bytes.Buffer)
+       for _, n := range l.Nodes {
+               fmt.Fprint(b, n)
+       }
+       return b.String()
+}
+
+func (l *ListNode) CopyList() *ListNode {
+       if l == nil {
+               return l
+       }
+       n := l.tr.newList(l.Pos)
+       for _, elem := range l.Nodes {
+               n.append(elem.Copy())
+       }
+       return n
+}
+
+func (l *ListNode) Copy() Node {
+       return l.CopyList()
+}
+
+// TextNode holds plain text.
+type TextNode struct {
+       NodeType
+       Pos
+       tr   *Tree
+       Text []byte // The text; may span newlines.
+}
+
+func (t *Tree) newText(pos Pos, text string) *TextNode {
+       return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
+}
+
+func (t *TextNode) String() string {
+       return fmt.Sprintf(textFormat, t.Text)
+}
+
+func (t *TextNode) tree() *Tree {
+       return t.tr
+}
+
+func (t *TextNode) Copy() Node {
+       return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
+}
+
+// PipeNode holds a pipeline with optional declaration
+type PipeNode struct {
+       NodeType
+       Pos
+       tr       *Tree
+       Line     int             // The line number in the input. Deprecated: Kept for compatibility.
+       IsAssign bool            // The variables are being assigned, not declared.
+       Decl     []*VariableNode // Variables in lexical order.
+       Cmds     []*CommandNode  // The commands in lexical order.
+}
+
+func (t *Tree) newPipeline(pos Pos, line int, vars []*VariableNode) *PipeNode {
+       return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: vars}
+}
+
+func (p *PipeNode) append(command *CommandNode) {
+       p.Cmds = append(p.Cmds, command)
+}
+
+func (p *PipeNode) String() string {
+       s := ""
+       if len(p.Decl) > 0 {
+               for i, v := range p.Decl {
+                       if i > 0 {
+                               s += ", "
+                       }
+                       s += v.String()
+               }
+               s += " := "
+       }
+       for i, c := range p.Cmds {
+               if i > 0 {
+                       s += " | "
+               }
+               s += c.String()
+       }
+       return s
+}
+
+func (p *PipeNode) tree() *Tree {
+       return p.tr
+}
+
+func (p *PipeNode) CopyPipe() *PipeNode {
+       if p == nil {
+               return p
+       }
+       var vars []*VariableNode
+       for _, d := range p.Decl {
+               vars = append(vars, d.Copy().(*VariableNode))
+       }
+       n := p.tr.newPipeline(p.Pos, p.Line, vars)
+       n.IsAssign = p.IsAssign
+       for _, c := range p.Cmds {
+               n.append(c.Copy().(*CommandNode))
+       }
+       return n
+}
+
+func (p *PipeNode) Copy() Node {
+       return p.CopyPipe()
+}
+
+// ActionNode holds an action (something bounded by delimiters).
+// Control actions have their own nodes; ActionNode represents simple
+// ones such as field evaluations and parenthesized pipelines.
+type ActionNode struct {
+       NodeType
+       Pos
+       tr   *Tree
+       Line int       // The line number in the input. Deprecated: Kept for compatibility.
+       Pipe *PipeNode // The pipeline in the action.
+}
+
+func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
+       return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
+}
+
+func (a *ActionNode) String() string {
+       return fmt.Sprintf("{{%s}}", a.Pipe)
+
+}
+
+func (a *ActionNode) tree() *Tree {
+       return a.tr
+}
+
+func (a *ActionNode) Copy() Node {
+       return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
+
+}
+
+// CommandNode holds a command (a pipeline inside an evaluating action).
+type CommandNode struct {
+       NodeType
+       Pos
+       tr   *Tree
+       Args []Node // Arguments in lexical order: Identifier, field, or constant.
+}
+
+func (t *Tree) newCommand(pos Pos) *CommandNode {
+       return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
+}
+
+func (c *CommandNode) append(arg Node) {
+       c.Args = append(c.Args, arg)
+}
+
+func (c *CommandNode) String() string {
+       s := ""
+       for i, arg := range c.Args {
+               if i > 0 {
+                       s += " "
+               }
+               if arg, ok := arg.(*PipeNode); ok {
+                       s += "(" + arg.String() + ")"
+                       continue
+               }
+               s += arg.String()
+       }
+       return s
+}
+
+func (c *CommandNode) tree() *Tree {
+       return c.tr
+}
+
+func (c *CommandNode) Copy() Node {
+       if c == nil {
+               return c
+       }
+       n := c.tr.newCommand(c.Pos)
+       for _, c := range c.Args {
+               n.append(c.Copy())
+       }
+       return n
+}
+
+// IdentifierNode holds an identifier.
+type IdentifierNode struct {
+       NodeType
+       Pos
+       tr    *Tree
+       Ident string // The identifier's name.
+}
+
+// NewIdentifier returns a new IdentifierNode with the given identifier name.
+func NewIdentifier(ident string) *IdentifierNode {
+       return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
+}
+
+// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
+// Chained for convenience.
+// TODO: fix one day?
+func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
+       i.Pos = pos
+       return i
+}
+
+// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
+// Chained for convenience.
+// TODO: fix one day?
+func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
+       i.tr = t
+       return i
+}
+
+func (i *IdentifierNode) String() string {
+       return i.Ident
+}
+
+func (i *IdentifierNode) tree() *Tree {
+       return i.tr
+}
+
+func (i *IdentifierNode) Copy() Node {
+       return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
+}
+
+// AssignNode holds a list of variable names, possibly with chained field
+// accesses. The dollar sign is part of the (first) name.
+type VariableNode struct {
+       NodeType
+       Pos
+       tr    *Tree
+       Ident []string // Variable name and fields in lexical order.
+}
+
+func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
+       return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
+}
+
+func (v *VariableNode) String() string {
+       s := ""
+       for i, id := range v.Ident {
+               if i > 0 {
+                       s += "."
+               }
+               s += id
+       }
+       return s
+}
+
+func (v *VariableNode) tree() *Tree {
+       return v.tr
+}
+
+func (v *VariableNode) Copy() Node {
+       return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
+}
+
+// DotNode holds the special identifier '.'.
+type DotNode struct {
+       NodeType
+       Pos
+       tr *Tree
+}
+
+func (t *Tree) newDot(pos Pos) *DotNode {
+       return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
+}
+
+func (d *DotNode) Type() NodeType {
+       // Override method on embedded NodeType for API compatibility.
+       // TODO: Not really a problem; could change API without effect but
+       // api tool complains.
+       return NodeDot
+}
+
+func (d *DotNode) String() string {
+       return "."
+}
+
+func (d *DotNode) tree() *Tree {
+       return d.tr
+}
+
+func (d *DotNode) Copy() Node {
+       return d.tr.newDot(d.Pos)
+}
+
+// NilNode holds the special identifier 'nil' representing an untyped nil constant.
+type NilNode struct {
+       NodeType
+       Pos
+       tr *Tree
+}
+
+func (t *Tree) newNil(pos Pos) *NilNode {
+       return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
+}
+
+func (n *NilNode) Type() NodeType {
+       // Override method on embedded NodeType for API compatibility.
+       // TODO: Not really a problem; could change API without effect but
+       // api tool complains.
+       return NodeNil
+}
+
+func (n *NilNode) String() string {
+       return "nil"
+}
+
+func (n *NilNode) tree() *Tree {
+       return n.tr
+}
+
+func (n *NilNode) Copy() Node {
+       return n.tr.newNil(n.Pos)
+}
+
+// FieldNode holds a field (identifier starting with '.').
+// The names may be chained ('.x.y').
+// The period is dropped from each ident.
+type FieldNode struct {
+       NodeType
+       Pos
+       tr    *Tree
+       Ident []string // The identifiers in lexical order.
+}
+
+func (t *Tree) newField(pos Pos, ident string) *FieldNode {
+       return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
+}
+
+func (f *FieldNode) String() string {
+       s := ""
+       for _, id := range f.Ident {
+               s += "." + id
+       }
+       return s
+}
+
+func (f *FieldNode) tree() *Tree {
+       return f.tr
+}
+
+func (f *FieldNode) Copy() Node {
+       return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
+}
+
+// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
+// The names may be chained ('.x.y').
+// The periods are dropped from each ident.
+type ChainNode struct {
+       NodeType
+       Pos
+       tr    *Tree
+       Node  Node
+       Field []string // The identifiers in lexical order.
+}
+
+func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
+       return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
+}
+
+// Add adds the named field (which should start with a period) to the end of the chain.
+func (c *ChainNode) Add(field string) {
+       if len(field) == 0 || field[0] != '.' {
+               panic("no dot in field")
+       }
+       field = field[1:] // Remove leading dot.
+       if field == "" {
+               panic("empty field")
+       }
+       c.Field = append(c.Field, field)
+}
+
+func (c *ChainNode) String() string {
+       s := c.Node.String()
+       if _, ok := c.Node.(*PipeNode); ok {
+               s = "(" + s + ")"
+       }
+       for _, field := range c.Field {
+               s += "." + field
+       }
+       return s
+}
+
+func (c *ChainNode) tree() *Tree {
+       return c.tr
+}
+
+func (c *ChainNode) Copy() Node {
+       return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
+}
+
+// BoolNode holds a boolean constant.
+type BoolNode struct {
+       NodeType
+       Pos
+       tr   *Tree
+       True bool // The value of the boolean constant.
+}
+
+func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
+       return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
+}
+
+func (b *BoolNode) String() string {
+       if b.True {
+               return "true"
+       }
+       return "false"
+}
+
+func (b *BoolNode) tree() *Tree {
+       return b.tr
+}
+
+func (b *BoolNode) Copy() Node {
+       return b.tr.newBool(b.Pos, b.True)
+}
+
+// NumberNode holds a number: signed or unsigned integer, float, or complex.
+// The value is parsed and stored under all the types that can represent the value.
+// This simulates in a small amount of code the behavior of Go's ideal constants.
+type NumberNode struct {
+       NodeType
+       Pos
+       tr         *Tree
+       IsInt      bool       // Number has an integral value.
+       IsUint     bool       // Number has an unsigned integral value.
+       IsFloat    bool       // Number has a floating-point value.
+       IsComplex  bool       // Number is complex.
+       Int64      int64      // The signed integer value.
+       Uint64     uint64     // The unsigned integer value.
+       Float64    float64    // The floating-point value.
+       Complex128 complex128 // The complex value.
+       Text       string     // The original textual representation from the input.
+}
+
+func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
+       n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
+       switch typ {
+       case itemCharConstant:
+               rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
+               if err != nil {
+                       return nil, err
+               }
+               if tail != "'" {
+                       return nil, fmt.Errorf("malformed character constant: %s", text)
+               }
+               n.Int64 = int64(rune)
+               n.IsInt = true
+               n.Uint64 = uint64(rune)
+               n.IsUint = true
+               n.Float64 = float64(rune) // odd but those are the rules.
+               n.IsFloat = true
+               return n, nil
+       case itemComplex:
+               // fmt.Sscan can parse the pair, so let it do the work.
+               if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
+                       return nil, err
+               }
+               n.IsComplex = true
+               n.simplifyComplex()
+               return n, nil
+       }
+       // Imaginary constants can only be complex unless they are zero.
+       if len(text) > 0 && text[len(text)-1] == 'i' {
+               f, err := strconv.ParseFloat(text[:len(text)-1], 64)
+               if err == nil {
+                       n.IsComplex = true
+                       n.Complex128 = complex(0, f)
+                       n.simplifyComplex()
+                       return n, nil
+               }
+       }
+       // Do integer test first so we get 0x123 etc.
+       u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
+       if err == nil {
+               n.IsUint = true
+               n.Uint64 = u
+       }
+       i, err := strconv.ParseInt(text, 0, 64)
+       if err == nil {
+               n.IsInt = true
+               n.Int64 = i
+               if i == 0 {
+                       n.IsUint = true // in case of -0.
+                       n.Uint64 = u
+               }
+       }
+       // If an integer extraction succeeded, promote the float.
+       if n.IsInt {
+               n.IsFloat = true
+               n.Float64 = float64(n.Int64)
+       } else if n.IsUint {
+               n.IsFloat = true
+               n.Float64 = float64(n.Uint64)
+       } else {
+               f, err := strconv.ParseFloat(text, 64)
+               if err == nil {
+                       // If we parsed it as a float but it looks like an integer,
+                       // it's a huge number too large to fit in an int. Reject it.
+                       if !strings.ContainsAny(text, ".eEpP") {
+                               return nil, fmt.Errorf("integer overflow: %q", text)
+                       }
+                       n.IsFloat = true
+                       n.Float64 = f
+                       // If a floating-point extraction succeeded, extract the int if needed.
+                       if !n.IsInt && float64(int64(f)) == f {
+                               n.IsInt = true
+                               n.Int64 = int64(f)
+                       }
+                       if !n.IsUint && float64(uint64(f)) == f {
+                               n.IsUint = true
+                               n.Uint64 = uint64(f)
+                       }
+               }
+       }
+       if !n.IsInt && !n.IsUint && !n.IsFloat {
+               return nil, fmt.Errorf("illegal number syntax: %q", text)
+       }
+       return n, nil
+}
+
+// simplifyComplex pulls out any other types that are represented by the complex number.
+// These all require that the imaginary part be zero.
+func (n *NumberNode) simplifyComplex() {
+       n.IsFloat = imag(n.Complex128) == 0
+       if n.IsFloat {
+               n.Float64 = real(n.Complex128)
+               n.IsInt = float64(int64(n.Float64)) == n.Float64
+               if n.IsInt {
+                       n.Int64 = int64(n.Float64)
+               }
+               n.IsUint = float64(uint64(n.Float64)) == n.Float64
+               if n.IsUint {
+                       n.Uint64 = uint64(n.Float64)
+               }
+       }
+}
+
+func (n *NumberNode) String() string {
+       return n.Text
+}
+
+func (n *NumberNode) tree() *Tree {
+       return n.tr
+}
+
+func (n *NumberNode) Copy() Node {
+       nn := new(NumberNode)
+       *nn = *n // Easy, fast, correct.
+       return nn
+}
+
+// StringNode holds a string constant. The value has been "unquoted".
+type StringNode struct {
+       NodeType
+       Pos
+       tr     *Tree
+       Quoted string // The original text of the string, with quotes.
+       Text   string // The string, after quote processing.
+}
+
+func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
+       return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
+}
+
+func (s *StringNode) String() string {
+       return s.Quoted
+}
+
+func (s *StringNode) tree() *Tree {
+       return s.tr
+}
+
+func (s *StringNode) Copy() Node {
+       return s.tr.newString(s.Pos, s.Quoted, s.Text)
+}
+
+// endNode represents an {{end}} action.
+// It does not appear in the final parse tree.
+type endNode struct {
+       NodeType
+       Pos
+       tr *Tree
+}
+
+func (t *Tree) newEnd(pos Pos) *endNode {
+       return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
+}
+
+func (e *endNode) String() string {
+       return "{{end}}"
+}
+
+func (e *endNode) tree() *Tree {
+       return e.tr
+}
+
+func (e *endNode) Copy() Node {
+       return e.tr.newEnd(e.Pos)
+}
+
+// elseNode represents an {{else}} action. Does not appear in the final tree.
+type elseNode struct {
+       NodeType
+       Pos
+       tr   *Tree
+       Line int // The line number in the input. Deprecated: Kept for compatibility.
+}
+
+func (t *Tree) newElse(pos Pos, line int) *elseNode {
+       return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
+}
+
+func (e *elseNode) Type() NodeType {
+       return nodeElse
+}
+
+func (e *elseNode) String() string {
+       return "{{else}}"
+}
+
+func (e *elseNode) tree() *Tree {
+       return e.tr
+}
+
+func (e *elseNode) Copy() Node {
+       return e.tr.newElse(e.Pos, e.Line)
+}
+
+// BranchNode is the common representation of if, range, and with.
+type BranchNode struct {
+       NodeType
+       Pos
+       tr       *Tree
+       Line     int       // The line number in the input. Deprecated: Kept for compatibility.
+       Pipe     *PipeNode // The pipeline to be evaluated.
+       List     *ListNode // What to execute if the value is non-empty.
+       ElseList *ListNode // What to execute if the value is empty (nil if absent).
+}
+
+func (b *BranchNode) String() string {
+       name := ""
+       switch b.NodeType {
+       case NodeIf:
+               name = "if"
+       case NodeRange:
+               name = "range"
+       case NodeWith:
+               name = "with"
+       default:
+               panic("unknown branch type")
+       }
+       if b.ElseList != nil {
+               return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
+       }
+       return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
+}
+
+func (b *BranchNode) tree() *Tree {
+       return b.tr
+}
+
+func (b *BranchNode) Copy() Node {
+       switch b.NodeType {
+       case NodeIf:
+               return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
+       case NodeRange:
+               return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
+       case NodeWith:
+               return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
+       default:
+               panic("unknown branch type")
+       }
+}
+
+// IfNode represents an {{if}} action and its commands.
+type IfNode struct {
+       BranchNode
+}
+
+func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
+       return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
+}
+
+func (i *IfNode) Copy() Node {
+       return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
+}
+
+// RangeNode represents a {{range}} action and its commands.
+type RangeNode struct {
+       BranchNode
+}
+
+func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
+       return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
+}
+
+func (r *RangeNode) Copy() Node {
+       return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
+}
+
+// WithNode represents a {{with}} action and its commands.
+type WithNode struct {
+       BranchNode
+}
+
+func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
+       return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
+}
+
+func (w *WithNode) Copy() Node {
+       return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
+}
+
+// TemplateNode represents a {{template}} action.
+type TemplateNode struct {
+       NodeType
+       Pos
+       tr   *Tree
+       Line int       // The line number in the input. Deprecated: Kept for compatibility.
+       Name string    // The name of the template (unquoted).
+       Pipe *PipeNode // The command to evaluate as dot for the template.
+}
+
+func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
+       return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
+}
+
+func (t *TemplateNode) String() string {
+       if t.Pipe == nil {
+               return fmt.Sprintf("{{template %q}}", t.Name)
+       }
+       return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
+}
+
+func (t *TemplateNode) tree() *Tree {
+       return t.tr
+}
+
+func (t *TemplateNode) Copy() Node {
+       return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
+}
diff --git a/tpl/internal/go_templates/texttemplate/parse/parse.go b/tpl/internal/go_templates/texttemplate/parse/parse.go
new file mode 100644 (file)
index 0000000..7c35b0f
--- /dev/null
@@ -0,0 +1,736 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package parse builds parse trees for templates as defined by text/template
+// and html/template. Clients should use those packages to construct templates
+// rather than this one, which provides shared internal data structures not
+// intended for general use.
+package parse
+
+import (
+       "bytes"
+       "fmt"
+       "runtime"
+       "strconv"
+       "strings"
+)
+
+// Tree is the representation of a single parsed template.
+type Tree struct {
+       Name      string    // name of the template represented by the tree.
+       ParseName string    // name of the top-level template during parsing, for error messages.
+       Root      *ListNode // top-level root of the tree.
+       text      string    // text parsed to create the template (or its parent)
+       // Parsing only; cleared after parse.
+       funcs     []map[string]interface{}
+       lex       *lexer
+       token     [3]item // three-token lookahead for parser.
+       peekCount int
+       vars      []string // variables defined at the moment.
+       treeSet   map[string]*Tree
+}
+
+// Copy returns a copy of the Tree. Any parsing state is discarded.
+func (t *Tree) Copy() *Tree {
+       if t == nil {
+               return nil
+       }
+       return &Tree{
+               Name:      t.Name,
+               ParseName: t.ParseName,
+               Root:      t.Root.CopyList(),
+               text:      t.text,
+       }
+}
+
+// Parse returns a map from template name to parse.Tree, created by parsing the
+// templates described in the argument string. The top-level template will be
+// given the specified name. If an error is encountered, parsing stops and an
+// empty map is returned with the error.
+func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (map[string]*Tree, error) {
+       treeSet := make(map[string]*Tree)
+       t := New(name)
+       t.text = text
+       _, err := t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
+       return treeSet, err
+}
+
+// next returns the next token.
+func (t *Tree) next() item {
+       if t.peekCount > 0 {
+               t.peekCount--
+       } else {
+               t.token[0] = t.lex.nextItem()
+       }
+       return t.token[t.peekCount]
+}
+
+// backup backs the input stream up one token.
+func (t *Tree) backup() {
+       t.peekCount++
+}
+
+// backup2 backs the input stream up two tokens.
+// The zeroth token is already there.
+func (t *Tree) backup2(t1 item) {
+       t.token[1] = t1
+       t.peekCount = 2
+}
+
+// backup3 backs the input stream up three tokens
+// The zeroth token is already there.
+func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
+       t.token[1] = t1
+       t.token[2] = t2
+       t.peekCount = 3
+}
+
+// peek returns but does not consume the next token.
+func (t *Tree) peek() item {
+       if t.peekCount > 0 {
+               return t.token[t.peekCount-1]
+       }
+       t.peekCount = 1
+       t.token[0] = t.lex.nextItem()
+       return t.token[0]
+}
+
+// nextNonSpace returns the next non-space token.
+func (t *Tree) nextNonSpace() (token item) {
+       for {
+               token = t.next()
+               if token.typ != itemSpace {
+                       break
+               }
+       }
+       return token
+}
+
+// peekNonSpace returns but does not consume the next non-space token.
+func (t *Tree) peekNonSpace() (token item) {
+       for {
+               token = t.next()
+               if token.typ != itemSpace {
+                       break
+               }
+       }
+       t.backup()
+       return token
+}
+
+// Parsing.
+
+// New allocates a new parse tree with the given name.
+func New(name string, funcs ...map[string]interface{}) *Tree {
+       return &Tree{
+               Name:  name,
+               funcs: funcs,
+       }
+}
+
+// ErrorContext returns a textual representation of the location of the node in the input text.
+// The receiver is only used when the node does not have a pointer to the tree inside,
+// which can occur in old code.
+func (t *Tree) ErrorContext(n Node) (location, context string) {
+       pos := int(n.Position())
+       tree := n.tree()
+       if tree == nil {
+               tree = t
+       }
+       text := tree.text[:pos]
+       byteNum := strings.LastIndex(text, "\n")
+       if byteNum == -1 {
+               byteNum = pos // On first line.
+       } else {
+               byteNum++ // After the newline.
+               byteNum = pos - byteNum
+       }
+       lineNum := 1 + strings.Count(text, "\n")
+       context = n.String()
+       return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
+}
+
+// errorf formats the error and terminates processing.
+func (t *Tree) errorf(format string, args ...interface{}) {
+       t.Root = nil
+       format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format)
+       panic(fmt.Errorf(format, args...))
+}
+
+// error terminates processing.
+func (t *Tree) error(err error) {
+       t.errorf("%s", err)
+}
+
+// expect consumes the next token and guarantees it has the required type.
+func (t *Tree) expect(expected itemType, context string) item {
+       token := t.nextNonSpace()
+       if token.typ != expected {
+               t.unexpected(token, context)
+       }
+       return token
+}
+
+// expectOneOf consumes the next token and guarantees it has one of the required types.
+func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
+       token := t.nextNonSpace()
+       if token.typ != expected1 && token.typ != expected2 {
+               t.unexpected(token, context)
+       }
+       return token
+}
+
+// unexpected complains about the token and terminates processing.
+func (t *Tree) unexpected(token item, context string) {
+       t.errorf("unexpected %s in %s", token, context)
+}
+
+// recover is the handler that turns panics into returns from the top level of Parse.
+func (t *Tree) recover(errp *error) {
+       e := recover()
+       if e != nil {
+               if _, ok := e.(runtime.Error); ok {
+                       panic(e)
+               }
+               if t != nil {
+                       t.lex.drain()
+                       t.stopParse()
+               }
+               *errp = e.(error)
+       }
+}
+
+// startParse initializes the parser, using the lexer.
+func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) {
+       t.Root = nil
+       t.lex = lex
+       t.vars = []string{"$"}
+       t.funcs = funcs
+       t.treeSet = treeSet
+}
+
+// stopParse terminates parsing.
+func (t *Tree) stopParse() {
+       t.lex = nil
+       t.vars = nil
+       t.funcs = nil
+       t.treeSet = nil
+}
+
+// Parse parses the template definition string to construct a representation of
+// the template for execution. If either action delimiter string is empty, the
+// default ("{{" or "}}") is used. Embedded template definitions are added to
+// the treeSet map.
+func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
+       defer t.recover(&err)
+       t.ParseName = t.Name
+       t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim), treeSet)
+       t.text = text
+       t.parse()
+       t.add()
+       t.stopParse()
+       return t, nil
+}
+
+// add adds tree to t.treeSet.
+func (t *Tree) add() {
+       tree := t.treeSet[t.Name]
+       if tree == nil || IsEmptyTree(tree.Root) {
+               t.treeSet[t.Name] = t
+               return
+       }
+       if !IsEmptyTree(t.Root) {
+               t.errorf("template: multiple definition of template %q", t.Name)
+       }
+}
+
+// IsEmptyTree reports whether this tree (node) is empty of everything but space.
+func IsEmptyTree(n Node) bool {
+       switch n := n.(type) {
+       case nil:
+               return true
+       case *ActionNode:
+       case *IfNode:
+       case *ListNode:
+               for _, node := range n.Nodes {
+                       if !IsEmptyTree(node) {
+                               return false
+                       }
+               }
+               return true
+       case *RangeNode:
+       case *TemplateNode:
+       case *TextNode:
+               return len(bytes.TrimSpace(n.Text)) == 0
+       case *WithNode:
+       default:
+               panic("unknown node: " + n.String())
+       }
+       return false
+}
+
+// parse is the top-level parser for a template, essentially the same
+// as itemList except it also parses {{define}} actions.
+// It runs to EOF.
+func (t *Tree) parse() {
+       t.Root = t.newList(t.peek().pos)
+       for t.peek().typ != itemEOF {
+               if t.peek().typ == itemLeftDelim {
+                       delim := t.next()
+                       if t.nextNonSpace().typ == itemDefine {
+                               newT := New("definition") // name will be updated once we know it.
+                               newT.text = t.text
+                               newT.ParseName = t.ParseName
+                               newT.startParse(t.funcs, t.lex, t.treeSet)
+                               newT.parseDefinition()
+                               continue
+                       }
+                       t.backup2(delim)
+               }
+               switch n := t.textOrAction(); n.Type() {
+               case nodeEnd, nodeElse:
+                       t.errorf("unexpected %s", n)
+               default:
+                       t.Root.append(n)
+               }
+       }
+}
+
+// parseDefinition parses a {{define}} ...  {{end}} template definition and
+// installs the definition in t.treeSet. The "define" keyword has already
+// been scanned.
+func (t *Tree) parseDefinition() {
+       const context = "define clause"
+       name := t.expectOneOf(itemString, itemRawString, context)
+       var err error
+       t.Name, err = strconv.Unquote(name.val)
+       if err != nil {
+               t.error(err)
+       }
+       t.expect(itemRightDelim, context)
+       var end Node
+       t.Root, end = t.itemList()
+       if end.Type() != nodeEnd {
+               t.errorf("unexpected %s in %s", end, context)
+       }
+       t.add()
+       t.stopParse()
+}
+
+// itemList:
+//     textOrAction*
+// Terminates at {{end}} or {{else}}, returned separately.
+func (t *Tree) itemList() (list *ListNode, next Node) {
+       list = t.newList(t.peekNonSpace().pos)
+       for t.peekNonSpace().typ != itemEOF {
+               n := t.textOrAction()
+               switch n.Type() {
+               case nodeEnd, nodeElse:
+                       return list, n
+               }
+               list.append(n)
+       }
+       t.errorf("unexpected EOF")
+       return
+}
+
+// textOrAction:
+//     text | action
+func (t *Tree) textOrAction() Node {
+       switch token := t.nextNonSpace(); token.typ {
+       case itemText:
+               return t.newText(token.pos, token.val)
+       case itemLeftDelim:
+               return t.action()
+       default:
+               t.unexpected(token, "input")
+       }
+       return nil
+}
+
+// Action:
+//     control
+//     command ("|" command)*
+// Left delim is past. Now get actions.
+// First word could be a keyword such as range.
+func (t *Tree) action() (n Node) {
+       switch token := t.nextNonSpace(); token.typ {
+       case itemBlock:
+               return t.blockControl()
+       case itemElse:
+               return t.elseControl()
+       case itemEnd:
+               return t.endControl()
+       case itemIf:
+               return t.ifControl()
+       case itemRange:
+               return t.rangeControl()
+       case itemTemplate:
+               return t.templateControl()
+       case itemWith:
+               return t.withControl()
+       }
+       t.backup()
+       token := t.peek()
+       // Do not pop variables; they persist until "end".
+       return t.newAction(token.pos, token.line, t.pipeline("command"))
+}
+
+// Pipeline:
+//     declarations? command ('|' command)*
+func (t *Tree) pipeline(context string) (pipe *PipeNode) {
+       token := t.peekNonSpace()
+       pipe = t.newPipeline(token.pos, token.line, nil)
+       // Are there declarations or assignments?
+decls:
+       if v := t.peekNonSpace(); v.typ == itemVariable {
+               t.next()
+               // Since space is a token, we need 3-token look-ahead here in the worst case:
+               // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
+               // argument variable rather than a declaration. So remember the token
+               // adjacent to the variable so we can push it back if necessary.
+               tokenAfterVariable := t.peek()
+               next := t.peekNonSpace()
+               switch {
+               case next.typ == itemAssign, next.typ == itemDeclare:
+                       pipe.IsAssign = next.typ == itemAssign
+                       t.nextNonSpace()
+                       pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val))
+                       t.vars = append(t.vars, v.val)
+               case next.typ == itemChar && next.val == ",":
+                       t.nextNonSpace()
+                       pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val))
+                       t.vars = append(t.vars, v.val)
+                       if context == "range" && len(pipe.Decl) < 2 {
+                               switch t.peekNonSpace().typ {
+                               case itemVariable, itemRightDelim, itemRightParen:
+                                       // second initialized variable in a range pipeline
+                                       goto decls
+                               default:
+                                       t.errorf("range can only initialize variables")
+                               }
+                       }
+                       t.errorf("too many declarations in %s", context)
+               case tokenAfterVariable.typ == itemSpace:
+                       t.backup3(v, tokenAfterVariable)
+               default:
+                       t.backup2(v)
+               }
+       }
+       for {
+               switch token := t.nextNonSpace(); token.typ {
+               case itemRightDelim, itemRightParen:
+                       // At this point, the pipeline is complete
+                       t.checkPipeline(pipe, context)
+                       if token.typ == itemRightParen {
+                               t.backup()
+                       }
+                       return
+               case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
+                       itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
+                       t.backup()
+                       pipe.append(t.command())
+               default:
+                       t.unexpected(token, context)
+               }
+       }
+}
+
+func (t *Tree) checkPipeline(pipe *PipeNode, context string) {
+       // Reject empty pipelines
+       if len(pipe.Cmds) == 0 {
+               t.errorf("missing value for %s", context)
+       }
+       // Only the first command of a pipeline can start with a non executable operand
+       for i, c := range pipe.Cmds[1:] {
+               switch c.Args[0].Type() {
+               case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString:
+                       // With A|B|C, pipeline stage 2 is B
+                       t.errorf("non executable command in pipeline stage %d", i+2)
+               }
+       }
+}
+
+func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
+       defer t.popVars(len(t.vars))
+       pipe = t.pipeline(context)
+       var next Node
+       list, next = t.itemList()
+       switch next.Type() {
+       case nodeEnd: //done
+       case nodeElse:
+               if allowElseIf {
+                       // Special case for "else if". If the "else" is followed immediately by an "if",
+                       // the elseControl will have left the "if" token pending. Treat
+                       //      {{if a}}_{{else if b}}_{{end}}
+                       // as
+                       //      {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
+                       // To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
+                       // is assumed. This technique works even for long if-else-if chains.
+                       // TODO: Should we allow else-if in with and range?
+                       if t.peek().typ == itemIf {
+                               t.next() // Consume the "if" token.
+                               elseList = t.newList(next.Position())
+                               elseList.append(t.ifControl())
+                               // Do not consume the next item - only one {{end}} required.
+                               break
+                       }
+               }
+               elseList, next = t.itemList()
+               if next.Type() != nodeEnd {
+                       t.errorf("expected end; found %s", next)
+               }
+       }
+       return pipe.Position(), pipe.Line, pipe, list, elseList
+}
+
+// If:
+//     {{if pipeline}} itemList {{end}}
+//     {{if pipeline}} itemList {{else}} itemList {{end}}
+// If keyword is past.
+func (t *Tree) ifControl() Node {
+       return t.newIf(t.parseControl(true, "if"))
+}
+
+// Range:
+//     {{range pipeline}} itemList {{end}}
+//     {{range pipeline}} itemList {{else}} itemList {{end}}
+// Range keyword is past.
+func (t *Tree) rangeControl() Node {
+       return t.newRange(t.parseControl(false, "range"))
+}
+
+// With:
+//     {{with pipeline}} itemList {{end}}
+//     {{with pipeline}} itemList {{else}} itemList {{end}}
+// If keyword is past.
+func (t *Tree) withControl() Node {
+       return t.newWith(t.parseControl(false, "with"))
+}
+
+// End:
+//     {{end}}
+// End keyword is past.
+func (t *Tree) endControl() Node {
+       return t.newEnd(t.expect(itemRightDelim, "end").pos)
+}
+
+// Else:
+//     {{else}}
+// Else keyword is past.
+func (t *Tree) elseControl() Node {
+       // Special case for "else if".
+       peek := t.peekNonSpace()
+       if peek.typ == itemIf {
+               // We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
+               return t.newElse(peek.pos, peek.line)
+       }
+       token := t.expect(itemRightDelim, "else")
+       return t.newElse(token.pos, token.line)
+}
+
+// Block:
+//     {{block stringValue pipeline}}
+// Block keyword is past.
+// The name must be something that can evaluate to a string.
+// The pipeline is mandatory.
+func (t *Tree) blockControl() Node {
+       const context = "block clause"
+
+       token := t.nextNonSpace()
+       name := t.parseTemplateName(token, context)
+       pipe := t.pipeline(context)
+
+       block := New(name) // name will be updated once we know it.
+       block.text = t.text
+       block.ParseName = t.ParseName
+       block.startParse(t.funcs, t.lex, t.treeSet)
+       var end Node
+       block.Root, end = block.itemList()
+       if end.Type() != nodeEnd {
+               t.errorf("unexpected %s in %s", end, context)
+       }
+       block.add()
+       block.stopParse()
+
+       return t.newTemplate(token.pos, token.line, name, pipe)
+}
+
+// Template:
+//     {{template stringValue pipeline}}
+// Template keyword is past. The name must be something that can evaluate
+// to a string.
+func (t *Tree) templateControl() Node {
+       const context = "template clause"
+       token := t.nextNonSpace()
+       name := t.parseTemplateName(token, context)
+       var pipe *PipeNode
+       if t.nextNonSpace().typ != itemRightDelim {
+               t.backup()
+               // Do not pop variables; they persist until "end".
+               pipe = t.pipeline(context)
+       }
+       return t.newTemplate(token.pos, token.line, name, pipe)
+}
+
+func (t *Tree) parseTemplateName(token item, context string) (name string) {
+       switch token.typ {
+       case itemString, itemRawString:
+               s, err := strconv.Unquote(token.val)
+               if err != nil {
+                       t.error(err)
+               }
+               name = s
+       default:
+               t.unexpected(token, context)
+       }
+       return
+}
+
+// command:
+//     operand (space operand)*
+// space-separated arguments up to a pipeline character or right delimiter.
+// we consume the pipe character but leave the right delim to terminate the action.
+func (t *Tree) command() *CommandNode {
+       cmd := t.newCommand(t.peekNonSpace().pos)
+       for {
+               t.peekNonSpace() // skip leading spaces.
+               operand := t.operand()
+               if operand != nil {
+                       cmd.append(operand)
+               }
+               switch token := t.next(); token.typ {
+               case itemSpace:
+                       continue
+               case itemError:
+                       t.errorf("%s", token.val)
+               case itemRightDelim, itemRightParen:
+                       t.backup()
+               case itemPipe:
+               default:
+                       t.errorf("unexpected %s in operand", token)
+               }
+               break
+       }
+       if len(cmd.Args) == 0 {
+               t.errorf("empty command")
+       }
+       return cmd
+}
+
+// operand:
+//     term .Field*
+// An operand is a space-separated component of a command,
+// a term possibly followed by field accesses.
+// A nil return means the next item is not an operand.
+func (t *Tree) operand() Node {
+       node := t.term()
+       if node == nil {
+               return nil
+       }
+       if t.peek().typ == itemField {
+               chain := t.newChain(t.peek().pos, node)
+               for t.peek().typ == itemField {
+                       chain.Add(t.next().val)
+               }
+               // Compatibility with original API: If the term is of type NodeField
+               // or NodeVariable, just put more fields on the original.
+               // Otherwise, keep the Chain node.
+               // Obvious parsing errors involving literal values are detected here.
+               // More complex error cases will have to be handled at execution time.
+               switch node.Type() {
+               case NodeField:
+                       node = t.newField(chain.Position(), chain.String())
+               case NodeVariable:
+                       node = t.newVariable(chain.Position(), chain.String())
+               case NodeBool, NodeString, NodeNumber, NodeNil, NodeDot:
+                       t.errorf("unexpected . after term %q", node.String())
+               default:
+                       node = chain
+               }
+       }
+       return node
+}
+
+// term:
+//     literal (number, string, nil, boolean)
+//     function (identifier)
+//     .
+//     .Field
+//     $
+//     '(' pipeline ')'
+// A term is a simple "expression".
+// A nil return means the next item is not a term.
+func (t *Tree) term() Node {
+       switch token := t.nextNonSpace(); token.typ {
+       case itemError:
+               t.errorf("%s", token.val)
+       case itemIdentifier:
+               if !t.hasFunction(token.val) {
+                       t.errorf("function %q not defined", token.val)
+               }
+               return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
+       case itemDot:
+               return t.newDot(token.pos)
+       case itemNil:
+               return t.newNil(token.pos)
+       case itemVariable:
+               return t.useVar(token.pos, token.val)
+       case itemField:
+               return t.newField(token.pos, token.val)
+       case itemBool:
+               return t.newBool(token.pos, token.val == "true")
+       case itemCharConstant, itemComplex, itemNumber:
+               number, err := t.newNumber(token.pos, token.val, token.typ)
+               if err != nil {
+                       t.error(err)
+               }
+               return number
+       case itemLeftParen:
+               pipe := t.pipeline("parenthesized pipeline")
+               if token := t.next(); token.typ != itemRightParen {
+                       t.errorf("unclosed right paren: unexpected %s", token)
+               }
+               return pipe
+       case itemString, itemRawString:
+               s, err := strconv.Unquote(token.val)
+               if err != nil {
+                       t.error(err)
+               }
+               return t.newString(token.pos, token.val, s)
+       }
+       t.backup()
+       return nil
+}
+
+// hasFunction reports if a function name exists in the Tree's maps.
+func (t *Tree) hasFunction(name string) bool {
+       for _, funcMap := range t.funcs {
+               if funcMap == nil {
+                       continue
+               }
+               if funcMap[name] != nil {
+                       return true
+               }
+       }
+       return false
+}
+
+// popVars trims the variable list to the specified length
+func (t *Tree) popVars(n int) {
+       t.vars = t.vars[:n]
+}
+
+// useVar returns a node for a variable reference. It errors if the
+// variable is not defined.
+func (t *Tree) useVar(pos Pos, name string) Node {
+       v := t.newVariable(pos, name)
+       for _, varName := range t.vars {
+               if varName == v.Ident[0] {
+                       return v
+               }
+       }
+       t.errorf("undefined variable %q", v.Ident[0])
+       return nil
+}
diff --git a/tpl/internal/go_templates/texttemplate/parse/parse_test.go b/tpl/internal/go_templates/texttemplate/parse/parse_test.go
new file mode 100644 (file)
index 0000000..5d3b59b
--- /dev/null
@@ -0,0 +1,557 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.13
+
+package parse
+
+import (
+       "flag"
+       "fmt"
+       "strings"
+       "testing"
+)
+
+var debug = flag.Bool("debug", false, "show the errors produced by the main tests")
+
+type numberTest struct {
+       text      string
+       isInt     bool
+       isUint    bool
+       isFloat   bool
+       isComplex bool
+       int64
+       uint64
+       float64
+       complex128
+}
+
+var numberTests = []numberTest{
+       // basics
+       {"0", true, true, true, false, 0, 0, 0, 0},
+       {"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint.
+       {"73", true, true, true, false, 73, 73, 73, 0},
+       {"7_3", true, true, true, false, 73, 73, 73, 0},
+       {"0b10_010_01", true, true, true, false, 73, 73, 73, 0},
+       {"0B10_010_01", true, true, true, false, 73, 73, 73, 0},
+       {"073", true, true, true, false, 073, 073, 073, 0},
+       {"0o73", true, true, true, false, 073, 073, 073, 0},
+       {"0O73", true, true, true, false, 073, 073, 073, 0},
+       {"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0},
+       {"0X73", true, true, true, false, 0x73, 0x73, 0x73, 0},
+       {"0x7_3", true, true, true, false, 0x73, 0x73, 0x73, 0},
+       {"-73", true, false, true, false, -73, 0, -73, 0},
+       {"+73", true, false, true, false, 73, 0, 73, 0},
+       {"100", true, true, true, false, 100, 100, 100, 0},
+       {"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0},
+       {"-1e9", true, false, true, false, -1e9, 0, -1e9, 0},
+       {"-1.2", false, false, true, false, 0, 0, -1.2, 0},
+       {"1e19", false, true, true, false, 0, 1e19, 1e19, 0},
+       {"1e1_9", false, true, true, false, 0, 1e19, 1e19, 0},
+       {"1E19", false, true, true, false, 0, 1e19, 1e19, 0},
+       {"-1e19", false, false, true, false, 0, 0, -1e19, 0},
+       {"0x_1p4", true, true, true, false, 16, 16, 16, 0},
+       {"0X_1P4", true, true, true, false, 16, 16, 16, 0},
+       {"0x_1p-4", false, false, true, false, 0, 0, 1 / 16., 0},
+       {"4i", false, false, false, true, 0, 0, 0, 4i},
+       {"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i},
+       {"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal!
+       // complex with 0 imaginary are float (and maybe integer)
+       {"0i", true, true, true, true, 0, 0, 0, 0},
+       {"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2},
+       {"-12+0i", true, false, true, true, -12, 0, -12, -12},
+       {"13+0i", true, true, true, true, 13, 13, 13, 13},
+       // funny bases
+       {"0123", true, true, true, false, 0123, 0123, 0123, 0},
+       {"-0x0", true, true, true, false, 0, 0, 0, 0},
+       {"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
+       // character constants
+       {`'a'`, true, true, true, false, 'a', 'a', 'a', 0},
+       {`'\n'`, true, true, true, false, '\n', '\n', '\n', 0},
+       {`'\\'`, true, true, true, false, '\\', '\\', '\\', 0},
+       {`'\''`, true, true, true, false, '\'', '\'', '\'', 0},
+       {`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0},
+       {`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
+       {`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
+       {`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
+       // some broken syntax
+       {text: "+-2"},
+       {text: "0x123."},
+       {text: "1e."},
+       {text: "0xi."},
+       {text: "1+2."},
+       {text: "'x"},
+       {text: "'xx'"},
+       {text: "'433937734937734969526500969526500'"}, // Integer too large - issue 10634.
+       // Issue 8622 - 0xe parsed as floating point. Very embarrassing.
+       {"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0},
+}
+
+func TestNumberParse(t *testing.T) {
+       for _, test := range numberTests {
+               // If fmt.Sscan thinks it's complex, it's complex. We can't trust the output
+               // because imaginary comes out as a number.
+               var c complex128
+               typ := itemNumber
+               var tree *Tree
+               if test.text[0] == '\'' {
+                       typ = itemCharConstant
+               } else {
+                       _, err := fmt.Sscan(test.text, &c)
+                       if err == nil {
+                               typ = itemComplex
+                       }
+               }
+               n, err := tree.newNumber(0, test.text, typ)
+               ok := test.isInt || test.isUint || test.isFloat || test.isComplex
+               if ok && err != nil {
+                       t.Errorf("unexpected error for %q: %s", test.text, err)
+                       continue
+               }
+               if !ok && err == nil {
+                       t.Errorf("expected error for %q", test.text)
+                       continue
+               }
+               if !ok {
+                       if *debug {
+                               fmt.Printf("%s\n\t%s\n", test.text, err)
+                       }
+                       continue
+               }
+               if n.IsComplex != test.isComplex {
+                       t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
+               }
+               if test.isInt {
+                       if !n.IsInt {
+                               t.Errorf("expected integer for %q", test.text)
+                       }
+                       if n.Int64 != test.int64 {
+                               t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64)
+                       }
+               } else if n.IsInt {
+                       t.Errorf("did not expect integer for %q", test.text)
+               }
+               if test.isUint {
+                       if !n.IsUint {
+                               t.Errorf("expected unsigned integer for %q", test.text)
+                       }
+                       if n.Uint64 != test.uint64 {
+                               t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64)
+                       }
+               } else if n.IsUint {
+                       t.Errorf("did not expect unsigned integer for %q", test.text)
+               }
+               if test.isFloat {
+                       if !n.IsFloat {
+                               t.Errorf("expected float for %q", test.text)
+                       }
+                       if n.Float64 != test.float64 {
+                               t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64)
+                       }
+               } else if n.IsFloat {
+                       t.Errorf("did not expect float for %q", test.text)
+               }
+               if test.isComplex {
+                       if !n.IsComplex {
+                               t.Errorf("expected complex for %q", test.text)
+                       }
+                       if n.Complex128 != test.complex128 {
+                               t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128)
+                       }
+               } else if n.IsComplex {
+                       t.Errorf("did not expect complex for %q", test.text)
+               }
+       }
+}
+
+type parseTest struct {
+       name   string
+       input  string
+       ok     bool
+       result string // what the user would see in an error message.
+}
+
+const (
+       noError  = true
+       hasError = false
+)
+
+var parseTests = []parseTest{
+       {"empty", "", noError,
+               ``},
+       {"comment", "{{/*\n\n\n*/}}", noError,
+               ``},
+       {"spaces", " \t\n", noError,
+               `" \t\n"`},
+       {"text", "some text", noError,
+               `"some text"`},
+       {"emptyAction", "{{}}", hasError,
+               `{{}}`},
+       {"field", "{{.X}}", noError,
+               `{{.X}}`},
+       {"simple command", "{{printf}}", noError,
+               `{{printf}}`},
+       {"$ invocation", "{{$}}", noError,
+               "{{$}}"},
+       {"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
+               "{{with $x := 3}}{{$x 23}}{{end}}"},
+       {"variable with fields", "{{$.I}}", noError,
+               "{{$.I}}"},
+       {"multi-word command", "{{printf `%d` 23}}", noError,
+               "{{printf `%d` 23}}"},
+       {"pipeline", "{{.X|.Y}}", noError,
+               `{{.X | .Y}}`},
+       {"pipeline with decl", "{{$x := .X|.Y}}", noError,
+               `{{$x := .X | .Y}}`},
+       {"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError,
+               `{{.X (.Y .Z) (.A | .B .C) (.E)}}`},
+       {"field applied to parentheses", "{{(.Y .Z).Field}}", noError,
+               `{{(.Y .Z).Field}}`},
+       {"simple if", "{{if .X}}hello{{end}}", noError,
+               `{{if .X}}"hello"{{end}}`},
+       {"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
+               `{{if .X}}"true"{{else}}"false"{{end}}`},
+       {"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError,
+               `{{if .X}}"true"{{else}}{{if .Y}}"false"{{end}}{{end}}`},
+       {"if else chain", "+{{if .X}}X{{else if .Y}}Y{{else if .Z}}Z{{end}}+", noError,
+               `"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`},
+       {"simple range", "{{range .X}}hello{{end}}", noError,
+               `{{range .X}}"hello"{{end}}`},
+       {"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError,
+               `{{range .X.Y.Z}}"hello"{{end}}`},
+       {"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError,
+               `{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`},
+       {"range with else", "{{range .X}}true{{else}}false{{end}}", noError,
+               `{{range .X}}"true"{{else}}"false"{{end}}`},
+       {"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError,
+               `{{range .X | .M}}"true"{{else}}"false"{{end}}`},
+       {"range []int", "{{range .SI}}{{.}}{{end}}", noError,
+               `{{range .SI}}{{.}}{{end}}`},
+       {"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError,
+               `{{range $x := .SI}}{{.}}{{end}}`},
+       {"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
+               `{{range $x, $y := .SI}}{{.}}{{end}}`},
+       {"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
+               `{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
+       {"template", "{{template `x`}}", noError,
+               `{{template "x"}}`},
+       {"template with arg", "{{template `x` .Y}}", noError,
+               `{{template "x" .Y}}`},
+       {"with", "{{with .X}}hello{{end}}", noError,
+               `{{with .X}}"hello"{{end}}`},
+       {"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
+               `{{with .X}}"hello"{{else}}"goodbye"{{end}}`},
+       // Trimming spaces.
+       {"trim left", "x \r\n\t{{- 3}}", noError, `"x"{{3}}`},
+       {"trim right", "{{3 -}}\n\n\ty", noError, `{{3}}"y"`},
+       {"trim left and right", "x \r\n\t{{- 3 -}}\n\n\ty", noError, `"x"{{3}}"y"`},
+       {"trim with extra spaces", "x\n{{-  3   -}}\ny", noError, `"x"{{3}}"y"`},
+       {"comment trim left", "x \r\n\t{{- /* hi */}}", noError, `"x"`},
+       {"comment trim right", "{{/* hi */ -}}\n\n\ty", noError, `"y"`},
+       {"comment trim left and right", "x \r\n\t{{- /* */ -}}\n\n\ty", noError, `"x""y"`},
+       {"block definition", `{{block "foo" .}}hello{{end}}`, noError,
+               `{{template "foo" .}}`},
+       // Errors.
+       {"unclosed action", "hello{{range", hasError, ""},
+       {"unmatched end", "{{end}}", hasError, ""},
+       {"unmatched else", "{{else}}", hasError, ""},
+       {"unmatched else after if", "{{if .X}}hello{{end}}{{else}}", hasError, ""},
+       {"multiple else", "{{if .X}}1{{else}}2{{else}}3{{end}}", hasError, ""},
+       {"missing end", "hello{{range .x}}", hasError, ""},
+       {"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
+       {"undefined function", "hello{{undefined}}", hasError, ""},
+       {"undefined variable", "{{$x}}", hasError, ""},
+       {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
+       {"variable undefined in template", "{{template $v}}", hasError, ""},
+       {"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""},
+       {"template with field ref", "{{template .X}}", hasError, ""},
+       {"template with var", "{{template $v}}", hasError, ""},
+       {"invalid punctuation", "{{printf 3, 4}}", hasError, ""},
+       {"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""},
+       {"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
+       {"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""},
+       {"adjacent args", "{{printf 3`x`}}", hasError, ""},
+       {"adjacent args with .", "{{printf `x`.}}", hasError, ""},
+       {"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
+       // Other kinds of assignments and operators aren't available yet.
+       {"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
+       {"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
+       {"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""},
+       {"bug0d", "{{$x % 3}}{{$x}}", hasError, ""},
+       // Check the parse fails for := rather than comma.
+       {"bug0e", "{{range $x := $y := 3}}{{end}}", hasError, ""},
+       // Another bug: variable read must ignore following punctuation.
+       {"bug1a", "{{$x:=.}}{{$x!2}}", hasError, ""},                     // ! is just illegal here.
+       {"bug1b", "{{$x:=.}}{{$x+2}}", hasError, ""},                     // $x+2 should not parse as ($x) (+2).
+       {"bug1c", "{{$x:=.}}{{$x +2}}", noError, "{{$x := .}}{{$x +2}}"}, // It's OK with a space.
+       // dot following a literal value
+       {"dot after integer", "{{1.E}}", hasError, ""},
+       {"dot after float", "{{0.1.E}}", hasError, ""},
+       {"dot after boolean", "{{true.E}}", hasError, ""},
+       {"dot after char", "{{'a'.any}}", hasError, ""},
+       {"dot after string", `{{"hello".guys}}`, hasError, ""},
+       {"dot after dot", "{{..E}}", hasError, ""},
+       {"dot after nil", "{{nil.E}}", hasError, ""},
+       // Wrong pipeline
+       {"wrong pipeline dot", "{{12|.}}", hasError, ""},
+       {"wrong pipeline number", "{{.|12|printf}}", hasError, ""},
+       {"wrong pipeline string", "{{.|printf|\"error\"}}", hasError, ""},
+       {"wrong pipeline char", "{{12|printf|'e'}}", hasError, ""},
+       {"wrong pipeline boolean", "{{.|true}}", hasError, ""},
+       {"wrong pipeline nil", "{{'c'|nil}}", hasError, ""},
+       {"empty pipeline", `{{printf "%d" ( ) }}`, hasError, ""},
+       // Missing pipeline in block
+       {"block definition", `{{block "foo"}}hello{{end}}`, hasError, ""},
+}
+
+var builtins = map[string]interface{}{
+       "printf": fmt.Sprintf,
+}
+
+func testParse(doCopy bool, t *testing.T) {
+       textFormat = "%q"
+       defer func() { textFormat = "%s" }()
+       for _, test := range parseTests {
+               tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
+               switch {
+               case err == nil && !test.ok:
+                       t.Errorf("%q: expected error; got none", test.name)
+                       continue
+               case err != nil && test.ok:
+                       t.Errorf("%q: unexpected error: %v", test.name, err)
+                       continue
+               case err != nil && !test.ok:
+                       // expected error, got one
+                       if *debug {
+                               fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
+                       }
+                       continue
+               }
+               var result string
+               if doCopy {
+                       result = tmpl.Root.Copy().String()
+               } else {
+                       result = tmpl.Root.String()
+               }
+               if result != test.result {
+                       t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
+               }
+       }
+}
+
+func TestParse(t *testing.T) {
+       testParse(false, t)
+}
+
+// Same as TestParse, but we copy the node first
+func TestParseCopy(t *testing.T) {
+       testParse(true, t)
+}
+
+type isEmptyTest struct {
+       name  string
+       input string
+       empty bool
+}
+
+var isEmptyTests = []isEmptyTest{
+       {"empty", ``, true},
+       {"nonempty", `hello`, false},
+       {"spaces only", " \t\n \t\n", true},
+       {"definition", `{{define "x"}}something{{end}}`, true},
+       {"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true},
+       {"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false},
+       {"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false},
+}
+
+func TestIsEmpty(t *testing.T) {
+       if !IsEmptyTree(nil) {
+               t.Errorf("nil tree is not empty")
+       }
+       for _, test := range isEmptyTests {
+               tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil)
+               if err != nil {
+                       t.Errorf("%q: unexpected error: %v", test.name, err)
+                       continue
+               }
+               if empty := IsEmptyTree(tree.Root); empty != test.empty {
+                       t.Errorf("%q: expected %t got %t", test.name, test.empty, empty)
+               }
+       }
+}
+
+func TestErrorContextWithTreeCopy(t *testing.T) {
+       tree, err := New("root").Parse("{{if true}}{{end}}", "", "", make(map[string]*Tree), nil)
+       if err != nil {
+               t.Fatalf("unexpected tree parse failure: %v", err)
+       }
+       treeCopy := tree.Copy()
+       wantLocation, wantContext := tree.ErrorContext(tree.Root.Nodes[0])
+       gotLocation, gotContext := treeCopy.ErrorContext(treeCopy.Root.Nodes[0])
+       if wantLocation != gotLocation {
+               t.Errorf("wrong error location want %q got %q", wantLocation, gotLocation)
+       }
+       if wantContext != gotContext {
+               t.Errorf("wrong error location want %q got %q", wantContext, gotContext)
+       }
+}
+
+// All failures, and the result is a string that must appear in the error message.
+var errorTests = []parseTest{
+       // Check line numbers are accurate.
+       {"unclosed1",
+               "line1\n{{",
+               hasError, `unclosed1:2: unexpected unclosed action in command`},
+       {"unclosed2",
+               "line1\n{{define `x`}}line2\n{{",
+               hasError, `unclosed2:3: unexpected unclosed action in command`},
+       // Specific errors.
+       {"function",
+               "{{foo}}",
+               hasError, `function "foo" not defined`},
+       {"comment",
+               "{{/*}}",
+               hasError, `unclosed comment`},
+       {"lparen",
+               "{{.X (1 2 3}}",
+               hasError, `unclosed left paren`},
+       {"rparen",
+               "{{.X 1 2 3)}}",
+               hasError, `unexpected ")"`},
+       {"space",
+               "{{`x`3}}",
+               hasError, `in operand`},
+       {"idchar",
+               "{{a#}}",
+               hasError, `'#'`},
+       {"charconst",
+               "{{'a}}",
+               hasError, `unterminated character constant`},
+       {"stringconst",
+               `{{"a}}`,
+               hasError, `unterminated quoted string`},
+       {"rawstringconst",
+               "{{`a}}",
+               hasError, `unterminated raw quoted string`},
+       {"number",
+               "{{0xi}}",
+               hasError, `number syntax`},
+       {"multidefine",
+               "{{define `a`}}a{{end}}{{define `a`}}b{{end}}",
+               hasError, `multiple definition of template`},
+       {"eof",
+               "{{range .X}}",
+               hasError, `unexpected EOF`},
+       {"variable",
+               // Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration.
+               "{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}",
+               hasError, `unexpected ":="`},
+       {"multidecl",
+               "{{$a,$b,$c := 23}}",
+               hasError, `too many declarations`},
+       {"undefvar",
+               "{{$a}}",
+               hasError, `undefined variable`},
+       {"wrongdot",
+               "{{true.any}}",
+               hasError, `unexpected . after term`},
+       {"wrongpipeline",
+               "{{12|false}}",
+               hasError, `non executable command in pipeline`},
+       {"emptypipeline",
+               `{{ ( ) }}`,
+               hasError, `missing value for parenthesized pipeline`},
+       {"multilinerawstring",
+               "{{ $v := `\n` }} {{",
+               hasError, `multilinerawstring:2: unexpected unclosed action`},
+       {"rangeundefvar",
+               "{{range $k}}{{end}}",
+               hasError, `undefined variable`},
+       {"rangeundefvars",
+               "{{range $k, $v}}{{end}}",
+               hasError, `undefined variable`},
+       {"rangemissingvalue1",
+               "{{range $k,}}{{end}}",
+               hasError, `missing value for range`},
+       {"rangemissingvalue2",
+               "{{range $k, $v := }}{{end}}",
+               hasError, `missing value for range`},
+       {"rangenotvariable1",
+               "{{range $k, .}}{{end}}",
+               hasError, `range can only initialize variables`},
+       {"rangenotvariable2",
+               "{{range $k, 123 := .}}{{end}}",
+               hasError, `range can only initialize variables`},
+}
+
+func TestErrors(t *testing.T) {
+       for _, test := range errorTests {
+               t.Run(test.name, func(t *testing.T) {
+                       _, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree))
+                       if err == nil {
+                               t.Fatalf("expected error %q, got nil", test.result)
+                       }
+                       if !strings.Contains(err.Error(), test.result) {
+                               t.Fatalf("error %q does not contain %q", err, test.result)
+                       }
+               })
+       }
+}
+
+func TestBlock(t *testing.T) {
+       const (
+               input = `a{{block "inner" .}}bar{{.}}baz{{end}}b`
+               outer = `a{{template "inner" .}}b`
+               inner = `bar{{.}}baz`
+       )
+       treeSet := make(map[string]*Tree)
+       tmpl, err := New("outer").Parse(input, "", "", treeSet, nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if g, w := tmpl.Root.String(), outer; g != w {
+               t.Errorf("outer template = %q, want %q", g, w)
+       }
+       inTmpl := treeSet["inner"]
+       if inTmpl == nil {
+               t.Fatal("block did not define template")
+       }
+       if g, w := inTmpl.Root.String(), inner; g != w {
+               t.Errorf("inner template = %q, want %q", g, w)
+       }
+}
+
+func TestLineNum(t *testing.T) {
+       const count = 100
+       text := strings.Repeat("{{printf 1234}}\n", count)
+       tree, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
+       if err != nil {
+               t.Fatal(err)
+       }
+       // Check the line numbers. Each line is an action containing a template, followed by text.
+       // That's two nodes per line.
+       nodes := tree.Root.Nodes
+       for i := 0; i < len(nodes); i += 2 {
+               line := 1 + i/2
+               // Action first.
+               action := nodes[i].(*ActionNode)
+               if action.Line != line {
+                       t.Fatalf("line %d: action is line %d", line, action.Line)
+               }
+               pipe := action.Pipe
+               if pipe.Line != line {
+                       t.Fatalf("line %d: pipe is line %d", line, pipe.Line)
+               }
+       }
+}
+
+func BenchmarkParseLarge(b *testing.B) {
+       text := strings.Repeat("{{1234}}\n", 10000)
+       for i := 0; i < b.N; i++ {
+               _, err := New("bench").Parse(text, "", "", make(map[string]*Tree), builtins)
+               if err != nil {
+                       b.Fatal(err)
+               }
+       }
+}
diff --git a/tpl/internal/go_templates/texttemplate/template.go b/tpl/internal/go_templates/texttemplate/template.go
new file mode 100644 (file)
index 0000000..4df8a9e
--- /dev/null
@@ -0,0 +1,228 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package template
+
+import (
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
+       "reflect"
+       "sync"
+)
+
+// common holds the information shared by related templates.
+type common struct {
+       tmpl   map[string]*Template // Map from name to defined templates.
+       option option
+       // We use two maps, one for parsing and one for execution.
+       // This separation makes the API cleaner since it doesn't
+       // expose reflection to the client.
+       muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
+       parseFuncs FuncMap
+       execFuncs  map[string]reflect.Value
+}
+
+// Template is the representation of a parsed template. The *parse.Tree
+// field is exported only for use by html/template and should be treated
+// as unexported by all other clients.
+type Template struct {
+       name string
+       *parse.Tree
+       *common
+       leftDelim  string
+       rightDelim string
+}
+
+// New allocates a new, undefined template with the given name.
+func New(name string) *Template {
+       t := &Template{
+               name: name,
+       }
+       t.init()
+       return t
+}
+
+// Name returns the name of the template.
+func (t *Template) Name() string {
+       return t.name
+}
+
+// New allocates a new, undefined template associated with the given one and with the same
+// delimiters. The association, which is transitive, allows one template to
+// invoke another with a {{template}} action.
+//
+// Because associated templates share underlying data, template construction
+// cannot be done safely in parallel. Once the templates are constructed, they
+// can be executed in parallel.
+func (t *Template) New(name string) *Template {
+       t.init()
+       nt := &Template{
+               name:       name,
+               common:     t.common,
+               leftDelim:  t.leftDelim,
+               rightDelim: t.rightDelim,
+       }
+       return nt
+}
+
+// init guarantees that t has a valid common structure.
+func (t *Template) init() {
+       if t.common == nil {
+               c := new(common)
+               c.tmpl = make(map[string]*Template)
+               c.parseFuncs = make(FuncMap)
+               c.execFuncs = make(map[string]reflect.Value)
+               t.common = c
+       }
+}
+
+// Clone returns a duplicate of the template, including all associated
+// templates. The actual representation is not copied, but the name space of
+// associated templates is, so further calls to Parse in the copy will add
+// templates to the copy but not to the original. Clone can be used to prepare
+// common templates and use them with variant definitions for other templates
+// by adding the variants after the clone is made.
+func (t *Template) Clone() (*Template, error) {
+       nt := t.copy(nil)
+       nt.init()
+       if t.common == nil {
+               return nt, nil
+       }
+       for k, v := range t.tmpl {
+               if k == t.name {
+                       nt.tmpl[t.name] = nt
+                       continue
+               }
+               // The associated templates share nt's common structure.
+               tmpl := v.copy(nt.common)
+               nt.tmpl[k] = tmpl
+       }
+       t.muFuncs.RLock()
+       defer t.muFuncs.RUnlock()
+       for k, v := range t.parseFuncs {
+               nt.parseFuncs[k] = v
+       }
+       for k, v := range t.execFuncs {
+               nt.execFuncs[k] = v
+       }
+       return nt, nil
+}
+
+// copy returns a shallow copy of t, with common set to the argument.
+func (t *Template) copy(c *common) *Template {
+       nt := New(t.name)
+       nt.Tree = t.Tree
+       nt.common = c
+       nt.leftDelim = t.leftDelim
+       nt.rightDelim = t.rightDelim
+       return nt
+}
+
+// AddParseTree adds parse tree for template with given name and associates it with t.
+// If the template does not already exist, it will create a new one.
+// If the template does exist, it will be replaced.
+func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
+       t.init()
+       // If the name is the name of this template, overwrite this template.
+       nt := t
+       if name != t.name {
+               nt = t.New(name)
+       }
+       // Even if nt == t, we need to install it in the common.tmpl map.
+       if t.associate(nt, tree) || nt.Tree == nil {
+               nt.Tree = tree
+       }
+       return nt, nil
+}
+
+// Templates returns a slice of defined templates associated with t.
+func (t *Template) Templates() []*Template {
+       if t.common == nil {
+               return nil
+       }
+       // Return a slice so we don't expose the map.
+       m := make([]*Template, 0, len(t.tmpl))
+       for _, v := range t.tmpl {
+               m = append(m, v)
+       }
+       return m
+}
+
+// Delims sets the action delimiters to the specified strings, to be used in
+// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
+// definitions will inherit the settings. An empty delimiter stands for the
+// corresponding default: {{ or }}.
+// The return value is the template, so calls can be chained.
+func (t *Template) Delims(left, right string) *Template {
+       t.init()
+       t.leftDelim = left
+       t.rightDelim = right
+       return t
+}
+
+// Funcs adds the elements of the argument map to the template's function map.
+// It must be called before the template is parsed.
+// It panics if a value in the map is not a function with appropriate return
+// type or if the name cannot be used syntactically as a function in a template.
+// It is legal to overwrite elements of the map. The return value is the template,
+// so calls can be chained.
+func (t *Template) Funcs(funcMap FuncMap) *Template {
+       t.init()
+       t.muFuncs.Lock()
+       defer t.muFuncs.Unlock()
+       addValueFuncs(t.execFuncs, funcMap)
+       addFuncs(t.parseFuncs, funcMap)
+       return t
+}
+
+// Lookup returns the template with the given name that is associated with t.
+// It returns nil if there is no such template or the template has no definition.
+func (t *Template) Lookup(name string) *Template {
+       if t.common == nil {
+               return nil
+       }
+       return t.tmpl[name]
+}
+
+// Parse parses text as a template body for t.
+// Named template definitions ({{define ...}} or {{block ...}} statements) in text
+// define additional templates associated with t and are removed from the
+// definition of t itself.
+//
+// Templates can be redefined in successive calls to Parse.
+// A template definition with a body containing only white space and comments
+// is considered empty and will not replace an existing template's body.
+// This allows using Parse to add new named template definitions without
+// overwriting the main template body.
+func (t *Template) Parse(text string) (*Template, error) {
+       t.init()
+       t.muFuncs.RLock()
+       trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
+       t.muFuncs.RUnlock()
+       if err != nil {
+               return nil, err
+       }
+       // Add the newly parsed trees, including the one for t, into our common structure.
+       for name, tree := range trees {
+               if _, err := t.AddParseTree(name, tree); err != nil {
+                       return nil, err
+               }
+       }
+       return t, nil
+}
+
+// associate installs the new template into the group of templates associated
+// with t. The two are already known to share the common structure.
+// The boolean return value reports whether to store this tree as t.Tree.
+func (t *Template) associate(new *Template, tree *parse.Tree) bool {
+       if new.common != t.common {
+               panic("internal error: associate not common")
+       }
+       if old := t.tmpl[new.name]; old != nil && parse.IsEmptyTree(tree.Root) && old.Tree != nil {
+               // If a template by that name exists,
+               // don't replace it with an empty template.
+               return false
+       }
+       t.tmpl[new.name] = new
+       return true
+}
diff --git a/tpl/internal/go_templates/texttemplate/testdata/file1.tmpl b/tpl/internal/go_templates/texttemplate/testdata/file1.tmpl
new file mode 100644 (file)
index 0000000..febf9d9
--- /dev/null
@@ -0,0 +1,2 @@
+{{define "x"}}TEXT{{end}}
+{{define "dotV"}}{{.V}}{{end}}
diff --git a/tpl/internal/go_templates/texttemplate/testdata/file2.tmpl b/tpl/internal/go_templates/texttemplate/testdata/file2.tmpl
new file mode 100644 (file)
index 0000000..39bf6fb
--- /dev/null
@@ -0,0 +1,2 @@
+{{define "dot"}}{{.}}{{end}}
+{{define "nested"}}{{template "dot" .}}{{end}}
diff --git a/tpl/internal/go_templates/texttemplate/testdata/tmpl1.tmpl b/tpl/internal/go_templates/texttemplate/testdata/tmpl1.tmpl
new file mode 100644 (file)
index 0000000..b72b3a3
--- /dev/null
@@ -0,0 +1,3 @@
+template1
+{{define "x"}}x{{end}}
+{{template "y"}}
diff --git a/tpl/internal/go_templates/texttemplate/testdata/tmpl2.tmpl b/tpl/internal/go_templates/texttemplate/testdata/tmpl2.tmpl
new file mode 100644 (file)
index 0000000..16beba6
--- /dev/null
@@ -0,0 +1,3 @@
+template2
+{{define "y"}}y{{end}}
+{{template "x"}}
index da42acb20bcffa9a3d77be3f7a94558fe22d91e0..3380a5a9ed4486ab88a64e94f459a102c78afbe2 100644 (file)
@@ -24,7 +24,8 @@ import (
        "reflect"
        "strings"
        "sync"
-       texttemplate "text/template"
+
+       texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
 
        "github.com/gohugoio/hugo/helpers"
 
index e91605762a43a15366853d5f6b9edaa55b3613bf..d5288fef074a6f9427b4a79afd7824191b78a0c3 100644 (file)
@@ -15,6 +15,7 @@ package safe
 
 import (
        "html/template"
+
        "testing"
 
        qt "github.com/frankban/quicktest"
index 91d533af9d83aa901122c2c733e25dd950d9c39e..e807fe6fa347f786d57309cfdfaf852d1619e8a8 100644 (file)
@@ -18,6 +18,7 @@ import (
        "errors"
        "fmt"
        "html/template"
+
        _strings "strings"
        "unicode/utf8"
 
index e852fd51fd06fa57d507b7ad1cf0c12e2d2a4da3..2fc3dc0283fe96a3129eef9593e9d0f499e93983 100644 (file)
@@ -15,6 +15,7 @@ package strings
 
 import (
        "html/template"
+
        "testing"
 
        qt "github.com/frankban/quicktest"
index 6e3a50ed2db068f1f08a35eab39dc2a017e4e0c7..ff863db7caeb084f98811e24d6019eb4648d0ddb 100644 (file)
@@ -17,6 +17,7 @@ import (
        "errors"
        "html"
        "html/template"
+
        "regexp"
        "unicode"
        "unicode/utf8"
index 31c7028b5f78197385ad2aef98544f4fdcaab369..b4aa1ffad8233b671a424b1169ad6d319dea3675 100644 (file)
@@ -15,6 +15,7 @@ package strings
 
 import (
        "html/template"
+
        "reflect"
        "strings"
        "testing"
index 0d7598fde96a8597968a732fab92a4c65a5f89ae..63bf427303df0f1fc5595dd5c24bb0556befe751 100644 (file)
@@ -15,6 +15,8 @@ package tpl
 
 import (
        "fmt"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+
        "io"
        "path/filepath"
        "regexp"
@@ -29,9 +31,8 @@ import (
 
        "github.com/spf13/afero"
 
-       "html/template"
-       texttemplate "text/template"
-       "text/template/parse"
+       texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 
        bp "github.com/gohugoio/hugo/bufferpool"
        "github.com/gohugoio/hugo/metrics"
index 9f89db5ae518016110a0880b002992700fb36e3a..afd3c4b0016bfd07401bd174e3a6c0346fc7e46f 100644 (file)
@@ -27,5 +27,4 @@ func TestExtractBaseof(t *testing.T) {
        c.Assert(replaced, qt.Equals, "_default/baseof.html")
        c.Assert(extractBaseOf("not baseof for you"), qt.Equals, "")
        c.Assert(extractBaseOf("template: blog/baseof.html:23:11:"), qt.Equals, "blog/baseof.html")
-       c.Assert(extractBaseOf("template: blog/baseof.ace:23:11:"), qt.Equals, "blog/baseof.ace")
 }
diff --git a/tpl/tplimpl/ace.go b/tpl/tplimpl/ace.go
deleted file mode 100644 (file)
index 5aa9fe2..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tplimpl
-
-import (
-       "path/filepath"
-
-       "strings"
-
-       "github.com/gohugoio/hugo/helpers"
-
-       "github.com/yosssi/ace"
-)
-
-func (t *templateHandler) addAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error {
-       helpers.Deprecated("Ace", "See https://github.com/gohugoio/hugo/issues/6609", false)
-       t.checkState()
-       var base, inner *ace.File
-       withoutExt := name[:len(name)-len(filepath.Ext(innerPath))]
-       name = withoutExt + ".html"
-
-       // Fixes issue #1178
-       basePath = strings.Replace(basePath, "\\", "/", -1)
-       innerPath = strings.Replace(innerPath, "\\", "/", -1)
-
-       if basePath != "" {
-               base = ace.NewFile(basePath, baseContent)
-               inner = ace.NewFile(innerPath, innerContent)
-       } else {
-               base = ace.NewFile(innerPath, innerContent)
-               inner = ace.NewFile("", []byte{})
-       }
-
-       parsed, err := ace.ParseSource(ace.NewSource(base, inner, []*ace.File{}), nil)
-       if err != nil {
-               t.errors = append(t.errors, &templateErr{name: name, err: err})
-               return err
-       }
-
-       templ, err := ace.CompileResultWithTemplate(t.html.t.New(name), parsed, nil)
-       if err != nil {
-               t.errors = append(t.errors, &templateErr{name: name, err: err})
-               return err
-       }
-
-       typ := resolveTemplateType(name)
-
-       c, err := applyTemplateTransformersToHMLTTemplate(typ, templ)
-       if err != nil {
-               return err
-       }
-
-       if typ == templateShortcode {
-               t.addShortcodeVariant(name, c.Info, templ)
-       } else {
-               t.templateInfo[name] = c.Info
-       }
-
-       return nil
-}
diff --git a/tpl/tplimpl/amber_compiler.go b/tpl/tplimpl/amber_compiler.go
deleted file mode 100644 (file)
index c9362e7..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2017 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package tplimpl
-
-import (
-       "html/template"
-
-       "github.com/gohugoio/hugo/helpers"
-
-       "github.com/eknkc/amber"
-       "github.com/spf13/afero"
-)
-
-func (t *templateHandler) compileAmberWithTemplate(b []byte, path string, templ *template.Template) (*template.Template, error) {
-       helpers.Deprecated("Amber", "See https://github.com/gohugoio/hugo/issues/6609", false)
-       c := amber.New()
-       c.Options.VirtualFilesystem = afero.NewHttpFs(t.layoutsFs)
-
-       if err := c.ParseData(b, path); err != nil {
-               return nil, err
-       }
-
-       data, err := c.CompileString()
-
-       if err != nil {
-               return nil, err
-       }
-
-       tpl, err := templ.Funcs(t.amberFuncMap).Parse(data)
-
-       if err != nil {
-               return nil, err
-       }
-
-       return tpl, nil
-}
index 6027775243d18dd9212978bb70f6a4d9d27c7f55..0feb3a0de41b2a7e3f96a71d39da8fe945ca7372 100644 (file)
@@ -15,17 +15,18 @@ package tplimpl
 
 import (
        "fmt"
-       "html/template"
+
        "strings"
-       texttemplate "text/template"
-       "text/template/parse"
+
+       template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+
+       texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 
        "github.com/gohugoio/hugo/hugofs"
        "github.com/gohugoio/hugo/tpl/tplimpl/embedded"
        "github.com/pkg/errors"
 
-       "github.com/eknkc/amber"
-
        "os"
 
        "github.com/gohugoio/hugo/output"
@@ -56,9 +57,6 @@ var (
        _ templateFuncsterTemplater = (*textTemplates)(nil)
 )
 
-// Protecting  global map access (Amber)
-var amberMu sync.Mutex
-
 type templateErr struct {
        name string
        err  error
@@ -98,8 +96,6 @@ type templateHandler struct {
        text *textTemplates
        html *htmlTemplates
 
-       amberFuncMap template.FuncMap
-
        errors []*templateErr
 
        // This is the filesystem to load the templates from. All the templates are
@@ -778,23 +774,6 @@ func (t *templateHandler) initFuncs() {
                }
        }
 
-       // Amber is HTML only.
-       t.amberFuncMap = template.FuncMap{}
-
-       amberMu.Lock()
-       for k, v := range amber.FuncMap {
-               t.amberFuncMap[k] = v
-       }
-
-       for k, v := range t.html.funcster.funcMap {
-               t.amberFuncMap[k] = v
-               // Hacky, but we need to make sure that the func names are in the global map.
-               amber.FuncMap[k] = func() string {
-                       panic("should never be invoked")
-               }
-       }
-       amberMu.Unlock()
-
 }
 
 func (t *templateHandler) getTemplateHandler(name string) templateLoader {
@@ -933,54 +912,11 @@ func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) e
        ext := filepath.Ext(path)
        switch ext {
        case ".amber":
-               //      Only HTML support for Amber
-               withoutExt := strings.TrimSuffix(name, filepath.Ext(name))
-               templateName := withoutExt + ".html"
-               b, err := afero.ReadFile(t.Layouts.Fs, path)
-
-               if err != nil {
-                       return err
-               }
-
-               amberMu.Lock()
-               templ, err := t.compileAmberWithTemplate(b, path, t.html.t.New(templateName))
-               amberMu.Unlock()
-               if err != nil {
-                       return err
-               }
-
-               typ := resolveTemplateType(name)
-
-               c, err := applyTemplateTransformersToHMLTTemplate(typ, templ)
-               if err != nil {
-                       return err
-               }
-
-               if typ == templateShortcode {
-                       t.addShortcodeVariant(templateName, c.Info, templ)
-               } else {
-                       t.templateInfo[name] = c.Info
-               }
-
+               helpers.Deprecated("Amber templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
                return nil
-
        case ".ace":
-               //      Only HTML support for Ace
-               var innerContent, baseContent []byte
-               innerContent, err := afero.ReadFile(t.Layouts.Fs, path)
-
-               if err != nil {
-                       return err
-               }
-
-               if baseTemplatePath != "" {
-                       baseContent, err = afero.ReadFile(t.Layouts.Fs, baseTemplatePath)
-                       if err != nil {
-                               return err
-                       }
-               }
-
-               return t.addAceTemplate(name, baseTemplatePath, path, baseContent, innerContent)
+               helpers.Deprecated("ACE templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+               return nil
        default:
 
                if baseTemplatePath != "" {
index e25e70e350e545e8e8e6148c1915ff95e2a74fd3..8a432f272646128c76ea42c4c86eb47a065e5263 100644 (file)
 package tplimpl
 
 import (
-       "html/template"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+
        "strings"
-       texttemplate "text/template"
-       "text/template/parse"
+
+       texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
 
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/tpl"
index 682af277239474024925aedbe9eb640a02dee683..6a0bb8253de96eaf6632e997e2edd9006b098c06 100644 (file)
@@ -15,7 +15,8 @@ package tplimpl
 import (
        "bytes"
        "fmt"
-       "html/template"
+       "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+
        "testing"
        "time"
 
index 3794c6f2ea9105edc8c5f6080ea516d0a9fea1a5..d10ff585c4dad991e5cd9c1724df625537e909f0 100644 (file)
@@ -15,6 +15,7 @@ package transform
 
 import (
        "html/template"
+
        "testing"
 
        "github.com/gohugoio/hugo/common/loggers"
index eaa6538b30a8419315b42d167cb107327fd48e40..5bae411b3ed554c5ebd68a77426e51d484bb91b1 100644 (file)
@@ -19,6 +19,7 @@ import (
        "fmt"
 
        "html/template"
+
        "net/url"
 
        "github.com/gohugoio/hugo/common/urls"