URI: 
       tpl: Rework to handle both text and HTML templates - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit 5c5efa03d2512749950b0d05a7d4bde35ecbdc37
   DIR parent 73c1c7b69d8302000fa5c5b804ad3eeac36da12f
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Mon, 27 Mar 2017 20:43:49 +0200
       
       tpl: Rework to handle both text and HTML templates
       
       Before this commit, Hugo used `html/template` for all Go templates.
       
       While this is a fine choice for HTML and maybe also RSS feeds, it is painful for plain text formats such as CSV, JSON etc.
       
       This commit fixes that by using the `IsPlainText` attribute on the output format to decide what to use.
       
       A couple of notes:
       
       * The above requires a nonambiguous template name to type mapping. I.e. `/layouts/_default/list.json` will only work if there is only one JSON output format, `/layouts/_default/list.mytype.json` will always work.
       * Ambiguous types will fall back to HTML.
       * Partials inherits the text vs HTML identificator of the container template. This also means that plain text templates can only include plain text partials.
       * Shortcode templates are, by definition, currently HTML templates only.
       
       Fixes #3221
       
       Diffstat:
         M deps/deps.go                        |       6 +++---
         M hugolib/alias.go                    |      27 ++++++++++++++++++---------
         M hugolib/embedded_shortcodes_test.go |       8 ++++----
         M hugolib/hugo_sites.go               |       8 ++++----
         M hugolib/page.go                     |       3 ++-
         M hugolib/page_output.go              |      24 ++++++++++++++++++++++--
         M hugolib/shortcode.go                |      16 +++++++---------
         M hugolib/shortcode_test.go           |     103 ++++++++++++++++---------------
         M hugolib/site.go                     |      32 +++++++++++--------------------
         M hugolib/site_output_test.go         |      16 ++++++++++++++--
         M hugolib/site_test.go                |      18 ------------------
         M hugolib/sitemap_test.go             |       2 +-
         M hugolib/testhelpers_test.go         |       4 ++--
         M output/layout.go                    |      28 ++++++++++++++++++++++++----
         M output/layout_base.go               |      19 ++++++++++++++++++-
         M output/layout_base_test.go          |       6 ++++++
         M output/layout_test.go               |       4 ++++
         M output/outputFormat.go              |      40 +++++++++++++++++++++++++++++++
         M output/outputFormat_test.go         |      27 ++++++++++++++++++++++++++-
         M tpl/template.go                     |     111 ++++++++++++++++++++++++++-----
         A tpl/tplimpl/ace.go                  |      51 +++++++++++++++++++++++++++++++
         M tpl/tplimpl/amber_compiler.go       |       4 ++--
         M tpl/tplimpl/template.go             |     770 ++++++++++++++++++-------------
         A tpl/tplimpl/templateFuncster.go     |      86 ++++++++++++++++++++++++++++++
         A tpl/tplimpl/templateProvider.go     |      59 +++++++++++++++++++++++++++++++
         M tpl/tplimpl/template_ast_transform… |      50 +++++++++++++++++++++++--------
         M tpl/tplimpl/template_ast_transform… |      12 ++++++------
         M tpl/tplimpl/template_embedded.go    |      57 ++++++++++++++-----------------
         M tpl/tplimpl/template_funcs.go       |      33 ++++++++-----------------------
         M tpl/tplimpl/template_funcs_test.go  |      66 +++++++++++++++++++++++++++----
         M tpl/tplimpl/template_test.go        |     232 +------------------------------
       
       31 files changed, 1145 insertions(+), 777 deletions(-)
       ---
   DIR diff --git a/deps/deps.go b/deps/deps.go
       @@ -20,7 +20,7 @@ type Deps struct {
                Log *jww.Notepad `json:"-"`
        
                // The templates to use.
       -        Tmpl tpl.Template `json:"-"`
       +        Tmpl tpl.TemplateHandler `json:"-"`
        
                // The file systems to use.
                Fs *hugofs.Fs `json:"-"`
       @@ -40,7 +40,7 @@ type Deps struct {
                Language *helpers.Language
        
                templateProvider ResourceProvider
       -        WithTemplate     func(templ tpl.Template) error `json:"-"`
       +        WithTemplate     func(templ tpl.TemplateHandler) error `json:"-"`
        
                translationProvider ResourceProvider
        }
       @@ -158,7 +158,7 @@ type DepsCfg struct {
        
                // Template handling.
                TemplateProvider ResourceProvider
       -        WithTemplate     func(templ tpl.Template) error
       +        WithTemplate     func(templ tpl.TemplateHandler) error
        
                // i18n handling.
                TranslationProvider ResourceProvider
   DIR diff --git a/hugolib/alias.go b/hugolib/alias.go
       @@ -22,6 +22,8 @@ import (
                "runtime"
                "strings"
        
       +        "github.com/spf13/hugo/tpl"
       +
                jww "github.com/spf13/jwalterweatherman"
        
                "github.com/spf13/hugo/helpers"
       @@ -35,18 +37,19 @@ const (
        var defaultAliasTemplates *template.Template
        
        func init() {
       +        //TODO(bep) consolidate
                defaultAliasTemplates = template.New("")
                template.Must(defaultAliasTemplates.New("alias").Parse(alias))
                template.Must(defaultAliasTemplates.New("alias-xhtml").Parse(aliasXHtml))
        }
        
        type aliasHandler struct {
       -        Templates *template.Template
       +        t         tpl.TemplateHandler
                log       *jww.Notepad
                allowRoot bool
        }
        
       -func newAliasHandler(t *template.Template, l *jww.Notepad, allowRoot bool) aliasHandler {
       +func newAliasHandler(t tpl.TemplateHandler, l *jww.Notepad, allowRoot bool) aliasHandler {
                return aliasHandler{t, l, allowRoot}
        }
        
       @@ -56,12 +59,19 @@ func (a aliasHandler) renderAlias(isXHTML bool, permalink string, page *Page) (i
                        t = "alias-xhtml"
                }
        
       -        template := defaultAliasTemplates
       -        if a.Templates != nil {
       -                template = a.Templates
       -                t = "alias.html"
       +        var templ *tpl.TemplateAdapter
       +
       +        if a.t != nil {
       +                templ = a.t.Lookup("alias.html")
                }
        
       +        if templ == nil {
       +                def := defaultAliasTemplates.Lookup(t)
       +                if def != nil {
       +                        templ = &tpl.TemplateAdapter{def}
       +                }
       +
       +        }
                data := struct {
                        Permalink string
                        Page      *Page
       @@ -71,7 +81,7 @@ func (a aliasHandler) renderAlias(isXHTML bool, permalink string, page *Page) (i
                }
        
                buffer := new(bytes.Buffer)
       -        err := template.ExecuteTemplate(buffer, t, data)
       +        err := templ.Execute(buffer, data)
                if err != nil {
                        return nil, err
                }
       @@ -83,8 +93,7 @@ func (s *Site) writeDestAlias(path, permalink string, p *Page) (err error) {
        }
        
        func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, p *Page) (err error) {
       -
       -        handler := newAliasHandler(s.Tmpl.Lookup("alias.html"), s.Log, allowRoot)
       +        handler := newAliasHandler(s.Tmpl, s.Log, allowRoot)
        
                isXHTML := strings.HasSuffix(path, ".xhtml")
        
   DIR diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go
       @@ -335,8 +335,8 @@ func TestShortcodeTweet(t *testing.T) {
                                th      = testHelper{cfg, fs, t}
                        )
        
       -                withTemplate := func(templ tpl.Template) error {
       -                        templ.Funcs(tweetFuncMap)
       +                withTemplate := func(templ tpl.TemplateHandler) error {
       +                        templ.(tpl.TemplateTestMocker).SetFuncs(tweetFuncMap)
                                return nil
                        }
        
       @@ -390,8 +390,8 @@ func TestShortcodeInstagram(t *testing.T) {
                                th      = testHelper{cfg, fs, t}
                        )
        
       -                withTemplate := func(templ tpl.Template) error {
       -                        templ.Funcs(instagramFuncMap)
       +                withTemplate := func(templ tpl.TemplateHandler) error {
       +                        templ.(tpl.TemplateTestMocker).SetFuncs(instagramFuncMap)
                                return nil
                        }
        
   DIR diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
       @@ -127,11 +127,11 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
                return newHugoSites(cfg, sites...)
        }
        
       -func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.Template) error) func(templ tpl.Template) error {
       -        return func(templ tpl.Template) error {
       -                templ.LoadTemplates(s.PathSpec.GetLayoutDirPath())
       +func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateHandler) error) func(templ tpl.TemplateHandler) error {
       +        return func(templ tpl.TemplateHandler) error {
       +                templ.LoadTemplates(s.PathSpec.GetLayoutDirPath(), "")
                        if s.PathSpec.ThemeSet() {
       -                        templ.LoadTemplatesWithPrefix(s.PathSpec.GetThemeDir()+"/layouts", "theme")
       +                        templ.LoadTemplates(s.PathSpec.GetThemeDir()+"/layouts", "theme")
                        }
        
                        for _, wt := range withTemplates {
   DIR diff --git a/hugolib/page.go b/hugolib/page.go
       @@ -1384,13 +1384,14 @@ func (p *Page) prepareLayouts() error {
                if p.Kind == KindPage {
                        if !p.IsRenderable() {
                                self := "__" + p.UniqueID()
       -                        _, err := p.s.Tmpl.GetClone().New(self).Parse(string(p.Content))
       +                        err := p.s.Tmpl.AddLateTemplate(self, string(p.Content))
                                if err != nil {
                                        return err
                                }
                                p.selfLayout = self
                        }
                }
       +
                return nil
        }
        
   DIR diff --git a/hugolib/page_output.go b/hugolib/page_output.go
       @@ -110,9 +110,29 @@ func (p *PageOutput) Render(layout ...string) template.HTML {
                l, err := p.layouts(layout...)
                if err != nil {
                        helpers.DistinctErrorLog.Printf("in .Render: Failed to resolve layout %q for page %q", layout, p.pathOrTitle())
       -                return template.HTML("")
       +                return ""
                }
       -        return p.s.Tmpl.ExecuteTemplateToHTML(p, l...)
       +
       +        for _, layout := range l {
       +                templ := p.s.Tmpl.Lookup(layout)
       +                if templ == nil {
       +                        // This is legacy from when we had only one output format and
       +                        // HTML templates only. Some have references to layouts without suffix.
       +                        // We default to good old HTML.
       +                        templ = p.s.Tmpl.Lookup(layout + ".html")
       +                }
       +                if templ != nil {
       +                        res, err := templ.ExecuteToString(p)
       +                        if err != nil {
       +                                helpers.DistinctErrorLog.Printf("in .Render: Failed to execute template %q for page %q", layout, p.pathOrTitle())
       +                                return template.HTML("")
       +                        }
       +                        return template.HTML(res)
       +                }
       +        }
       +
       +        return ""
       +
        }
        
        func (p *Page) Render(layout ...string) template.HTML {
   DIR diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
       @@ -177,7 +177,7 @@ var isInnerShortcodeCache = struct {
        // to avoid potential costly look-aheads for closing tags we look inside the template itself
        // we could change the syntax to self-closing tags, but that would make users cry
        // the value found is cached
       -func isInnerShortcode(t *template.Template) (bool, error) {
       +func isInnerShortcode(t tpl.TemplateExecutor) (bool, error) {
                isInnerShortcodeCache.RLock()
                m, ok := isInnerShortcodeCache.m[t.Name()]
                isInnerShortcodeCache.RUnlock()
       @@ -188,10 +188,7 @@ func isInnerShortcode(t *template.Template) (bool, error) {
        
                isInnerShortcodeCache.Lock()
                defer isInnerShortcodeCache.Unlock()
       -        if t.Tree == nil {
       -                return false, errors.New("Template failed to compile")
       -        }
       -        match, _ := regexp.MatchString("{{.*?\\.Inner.*?}}", t.Tree.Root.String())
       +        match, _ := regexp.MatchString("{{.*?\\.Inner.*?}}", t.Tree())
                isInnerShortcodeCache.m[t.Name()] = match
        
                return match, nil
       @@ -398,8 +395,6 @@ Loop:
                        case tScName:
                                sc.name = currItem.val
                                tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
       -                        {
       -                        }
                                if tmpl == nil {
                                        return sc, fmt.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
                                }
       @@ -570,7 +565,10 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin
                return source, nil
        }
        
       -func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
       +func getShortcodeTemplate(name string, t tpl.TemplateHandler) *tpl.TemplateAdapter {
       +        isInnerShortcodeCache.RLock()
       +        defer isInnerShortcodeCache.RUnlock()
       +
                if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
                        return x
                }
       @@ -580,7 +578,7 @@ func getShortcodeTemplate(name string, t tpl.Template) *template.Template {
                return t.Lookup("_internal/shortcodes/" + name + ".html")
        }
        
       -func renderShortcodeWithPage(tmpl *template.Template, data *ShortcodeWithPage) string {
       +func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) string {
                buffer := bp.GetBuffer()
                defer bp.PutBuffer(buffer)
        
   DIR diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
       @@ -30,7 +30,7 @@ import (
        )
        
        // TODO(bep) remove
       -func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template) error) (*Page, error) {
       +func pageFromString(in, filename string, withTemplate ...func(templ tpl.TemplateHandler) error) (*Page, error) {
                s := newTestSite(nil)
                if len(withTemplate) > 0 {
                        // Have to create a new site
       @@ -47,11 +47,11 @@ func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template
                return s.NewPageFrom(strings.NewReader(in), filename)
        }
        
       -func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) {
       +func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error) {
                CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
        }
        
       -func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) {
       +func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error, expectError bool) {
        
                cfg, fs := newTestCfg()
        
       @@ -100,8 +100,9 @@ func TestNonSC(t *testing.T) {
        // Issue #929
        func TestHyphenatedSC(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +
       +                tem.AddTemplate("_internal/shortcodes/hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
                        return nil
                }
        
       @@ -111,8 +112,8 @@ func TestHyphenatedSC(t *testing.T) {
        // Issue #1753
        func TestNoTrailingNewline(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/a.html", `{{ .Get 0 }}`)
                        return nil
                }
        
       @@ -121,8 +122,8 @@ func TestNoTrailingNewline(t *testing.T) {
        
        func TestPositionalParamSC(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 0 }}`)
                        return nil
                }
        
       @@ -135,8 +136,8 @@ func TestPositionalParamSC(t *testing.T) {
        
        func TestPositionalParamIndexOutOfBounds(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 1 }}`)
                        return nil
                }
                CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video error: index out of range for positional param at position 1", wt)
       @@ -146,8 +147,8 @@ func TestPositionalParamIndexOutOfBounds(t *testing.T) {
        
        func TestNamedParamSC(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, wt)
       @@ -161,10 +162,10 @@ func TestNamedParamSC(t *testing.T) {
        // Issue #2294
        func TestNestedNamedMissingParam(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
       -                tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
       -                tem.AddInternalShortcode("div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/acc.html", `<div class="acc">{{ .Inner }}</div>`)
       +                tem.AddTemplate("_internal/shortcodes/div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
       +                tem.AddTemplate("_internal/shortcodes/div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
                        return nil
                }
                CheckShortCodeMatch(t,
       @@ -174,10 +175,10 @@ func TestNestedNamedMissingParam(t *testing.T) {
        
        func TestIsNamedParamsSC(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
       -                tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
       -                tem.AddInternalShortcode("ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/byposition.html", `<div id="{{ .Get 0 }}">`)
       +                tem.AddTemplate("_internal/shortcodes/byname.html", `<div id="{{ .Get "id" }}">`)
       +                tem.AddTemplate("_internal/shortcodes/ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `<div id="name">`, wt)
       @@ -190,8 +191,8 @@ func TestIsNamedParamsSC(t *testing.T) {
        
        func TestInnerSC(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, wt)
       @@ -201,8 +202,8 @@ func TestInnerSC(t *testing.T) {
        
        func TestInnerSCWithMarkdown(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{% inside %}}
       @@ -215,8 +216,8 @@ func TestInnerSCWithMarkdown(t *testing.T) {
        
        func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{% inside %}}
       @@ -246,9 +247,9 @@ func TestEmbeddedSC(t *testing.T) {
        
        func TestNestedSC(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
       -                tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
       +                tem.AddTemplate("_internal/shortcodes/scn2.html", `<div>SC2</div>`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "<div>Outer, inner is <div>SC2</div>\n</div>", wt)
       @@ -258,10 +259,10 @@ func TestNestedSC(t *testing.T) {
        
        func TestNestedComplexSC(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
       -                tem.AddInternalShortcode("column.html", `-col-{{.Inner    }}-colStop-`)
       -                tem.AddInternalShortcode("aside.html", `-aside-{{    .Inner  }}-asideStop-`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/row.html", `-row-{{ .Inner}}-rowStop-`)
       +                tem.AddTemplate("_internal/shortcodes/column.html", `-col-{{.Inner    }}-colStop-`)
       +                tem.AddTemplate("_internal/shortcodes/aside.html", `-aside-{{    .Inner  }}-asideStop-`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{< row >}}1-s{{% column %}}2-**s**{{< aside >}}3-**s**{{< /aside >}}4-s{{% /column %}}5-s{{< /row >}}6-s`,
       @@ -274,10 +275,10 @@ func TestNestedComplexSC(t *testing.T) {
        
        func TestParentShortcode(t *testing.T) {
                t.Parallel()
       -        wt := func(tem tpl.Template) error {
       -                tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
       -                tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
       -                tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
       +        wt := func(tem tpl.TemplateHandler) error {
       +                tem.AddTemplate("_internal/shortcodes/r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
       +                tem.AddTemplate("_internal/shortcodes/r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
       +                tem.AddTemplate("_internal/shortcodes/r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
                        return nil
                }
                CheckShortCodeMatch(t, `{{< r1 pr1="p1" >}}1: {{< r2 pr2="p2" >}}2: {{< r3 pr3="p3" >}}{{< /r3 >}}{{< /r2 >}}{{< /r1 >}}`,
       @@ -342,13 +343,13 @@ func TestExtractShortcodes(t *testing.T) {
                                fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
                } {
        
       -                p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error {
       -                        templ.AddInternalShortcode("tag.html", `tag`)
       -                        templ.AddInternalShortcode("sc1.html", `sc1`)
       -                        templ.AddInternalShortcode("sc2.html", `sc2`)
       -                        templ.AddInternalShortcode("inner.html", `{{with .Inner }}{{ . }}{{ end }}`)
       -                        templ.AddInternalShortcode("inner2.html", `{{.Inner}}`)
       -                        templ.AddInternalShortcode("inner3.html", `{{.Inner}}`)
       +                p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.TemplateHandler) error {
       +                        templ.AddTemplate("_internal/shortcodes/tag.html", `tag`)
       +                        templ.AddTemplate("_internal/shortcodes/sc1.html", `sc1`)
       +                        templ.AddTemplate("_internal/shortcodes/sc2.html", `sc2`)
       +                        templ.AddTemplate("_internal/shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`)
       +                        templ.AddTemplate("_internal/shortcodes/inner2.html", `{{.Inner}}`)
       +                        templ.AddTemplate("_internal/shortcodes/inner3.html", `{{.Inner}}`)
                                return nil
                        })
        
       @@ -517,14 +518,14 @@ tags:
                        sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)}
                }
        
       -        addTemplates := func(templ tpl.Template) error {
       +        addTemplates := func(templ tpl.TemplateHandler) error {
                        templ.AddTemplate("_default/single.html", "{{.Content}}")
        
       -                templ.AddInternalShortcode("b.html", `b`)
       -                templ.AddInternalShortcode("c.html", `c`)
       -                templ.AddInternalShortcode("d.html", `d`)
       -                templ.AddInternalShortcode("menu.html", `{{ len (index .Page.Menus "main").Children }}`)
       -                templ.AddInternalShortcode("tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
       +                templ.AddTemplate("_internal/shortcodes/b.html", `b`)
       +                templ.AddTemplate("_internal/shortcodes/c.html", `c`)
       +                templ.AddTemplate("_internal/shortcodes/d.html", `d`)
       +                templ.AddTemplate("_internal/shortcodes/menu.html", `{{ len (index .Page.Menus "main").Children }}`)
       +                templ.AddTemplate("_internal/shortcodes/tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
        
                        return nil
        
   DIR diff --git a/hugolib/site.go b/hugolib/site.go
       @@ -193,7 +193,7 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) {
        // NewSiteDefaultLang creates a new site in the default language.
        // The site will have a template system loaded and ready to use.
        // Note: This is mainly used in single site tests.
       -func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) (*Site, error) {
       +func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
                v := viper.New()
                loadDefaultSettingsFor(v)
                return newSiteForLang(helpers.NewDefaultLanguage(v), withTemplate...)
       @@ -202,15 +202,15 @@ func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) (*Site, 
        // NewEnglishSite creates a new site in English language.
        // The site will have a template system loaded and ready to use.
        // Note: This is mainly used in single site tests.
       -func NewEnglishSite(withTemplate ...func(templ tpl.Template) error) (*Site, error) {
       +func NewEnglishSite(withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
                v := viper.New()
                loadDefaultSettingsFor(v)
                return newSiteForLang(helpers.NewLanguage("en", v), withTemplate...)
        }
        
        // newSiteForLang creates a new site in the given language.
       -func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tpl.Template) error) (*Site, error) {
       -        withTemplates := func(templ tpl.Template) error {
       +func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
       +        withTemplates := func(templ tpl.TemplateHandler) error {
                        for _, wt := range withTemplate {
                                if err := wt(templ); err != nil {
                                        return err
       @@ -1911,14 +1911,14 @@ Your rendered home page is blank: /index.html is zero-length
        }
        
        func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts ...string) error {
       -        layout, found := s.findFirstLayout(layouts...)
       -        if !found {
       +        templ := s.findFirstTemplate(layouts...)
       +        if templ == nil {
                        s.Log.WARN.Printf("[%s] Unable to locate layout for %s: %s\n", s.Language.Lang, name, layouts)
        
                        return nil
                }
        
       -        if err := s.renderThing(d, layout, w); err != nil {
       +        if err := templ.Execute(w, d); err != nil {
        
                        // Behavior here should be dependent on if running in server or watch mode.
                        distinctErrorLogger.Printf("Error while rendering %q: %s", name, err)
       @@ -1933,23 +1933,13 @@ func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts
                return nil
        }
        
       -func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
       +func (s *Site) findFirstTemplate(layouts ...string) tpl.Template {
                for _, layout := range layouts {
       -                if s.Tmpl.Lookup(layout) != nil {
       -                        return layout, true
       +                if templ := s.Tmpl.Lookup(layout); templ != nil {
       +                        return templ
                        }
                }
       -        return "", false
       -}
       -
       -func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {
       -
       -        // If the template doesn't exist, then return, but leave the Writer open
       -        if templ := s.Tmpl.Lookup(layout); templ != nil {
       -                return templ.Execute(w, d)
       -        }
       -        return fmt.Errorf("Layout not found: %s", layout)
       -
       +        return nil
        }
        
        func (s *Site) publish(path string, r io.Reader) (err error) {
   DIR diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
       @@ -90,9 +90,15 @@ outputs: %s
        Alt Output: {{ .Name -}}|
        {{- end -}}|
        {{- range .OutputFormats -}}
       -Output/Rel: {{ .Name -}}/{{ .Rel }}|
       +Output/Rel: {{ .Name -}}/{{ .Rel }}|{{ .MediaType }}
        {{- end -}}
       + {{ with .OutputFormats.Get "JSON" }}
       +<atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" />
       +{{ end }}
        `,
       +                "layouts/_default/list.html", `List HTML|{{ with .OutputFormats.Get "HTML" -}}
       +<atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" />
       +{{- end -}}`,
                )
                require.Len(t, h.Sites, 1)
        
       @@ -113,7 +119,6 @@ Output/Rel: {{ .Name -}}/{{ .Rel }}|
        
                require.Len(t, home.outputFormats, lenOut)
        
       -        // TODO(bep) output assert template/text
                // There is currently always a JSON output to make it simpler ...
                altFormats := lenOut - 1
                hasHTML := helpers.InStringArray(outputs, "html")
       @@ -128,9 +133,16 @@ Output/Rel: {{ .Name -}}/{{ .Rel }}|
                                "Output/Rel: JSON/alternate|",
                                "Output/Rel: HTML/canonical|",
                        )
       +                th.assertFileContent("public/index.html",
       +                        // The HTML entity is a deliberate part of this test: The HTML templates are
       +                        // parsed with html/template.
       +                        `List HTML|<atom:link href=http://example.com/blog/ rel="self" type="text/html&#43;html" />`,
       +                )
                } else {
                        th.assertFileContent("public/index.json",
                                "Output/Rel: JSON/canonical|",
       +                        // JSON is plain text, so no need to safeHTML this and that
       +                        `<atom:link href=http://example.com/blog/index.json rel="self" type="application/json+json" />`,
                        )
                }
        
   DIR diff --git a/hugolib/site_test.go b/hugolib/site_test.go
       @@ -52,24 +52,6 @@ func pageMust(p *Page, err error) *Page {
                return p
        }
        
       -func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
       -        t.Parallel()
       -        cfg, fs := newTestCfg()
       -
       -        writeSource(t, fs, filepath.Join("content", "a", "file.md"), pageSimpleTitle)
       -
       -        s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
       -
       -        require.Len(t, s.RegularPages, 1)
       -
       -        p := s.RegularPages[0]
       -
       -        err := s.renderThing(p, "foobar", nil)
       -        if err == nil {
       -                t.Errorf("Expected err to be returned when missing the template.")
       -        }
       -}
       -
        func TestRenderWithInvalidTemplate(t *testing.T) {
                t.Parallel()
                cfg, fs := newTestCfg()
   DIR diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go
       @@ -48,7 +48,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
                depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg}
        
                if !internal {
       -                depsCfg.WithTemplate = func(templ tpl.Template) error {
       +                depsCfg.WithTemplate = func(templ tpl.TemplateHandler) error {
                                templ.AddTemplate("sitemap.xml", sitemapTemplate)
                                return nil
                        }
   DIR diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
       @@ -164,9 +164,9 @@ func newDebugLogger() *jww.Notepad {
        func newErrorLogger() *jww.Notepad {
                return jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
        }
       -func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.Template) error {
       +func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.TemplateHandler) error {
        
       -        return func(templ tpl.Template) error {
       +        return func(templ tpl.TemplateHandler) error {
                        for i := 0; i < len(additionalTemplates); i += 2 {
                                err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
                                if err != nil {
   DIR diff --git a/output/layout.go b/output/layout.go
       @@ -152,9 +152,11 @@ func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format)
                                }
                        }
        
       -                return layoutsWithThemeLayouts, nil
       +                layouts = layoutsWithThemeLayouts
                }
        
       +        layouts = prependTextPrefixIfNeeded(f, layouts...)
       +
                l.mu.Lock()
                l.cache[key] = layouts
                l.mu.Unlock()
       @@ -184,10 +186,26 @@ func resolveListTemplate(d LayoutDescriptor, f Format,
        }
        
        func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string {
       -        return strings.Fields(replaceKeyValues(templ,
       +        layouts := strings.Fields(replaceKeyValues(templ,
                        "SUFFIX", f.MediaType.Suffix,
                        "NAME", strings.ToLower(f.Name),
                        "SECTION", d.Section))
       +
       +        return layouts
       +}
       +
       +func prependTextPrefixIfNeeded(f Format, layouts ...string) []string {
       +        if !f.IsPlainText {
       +                return layouts
       +        }
       +
       +        newLayouts := make([]string, len(layouts))
       +
       +        for i, l := range layouts {
       +                newLayouts[i] = "_text/" + l
       +        }
       +
       +        return newLayouts
        }
        
        func replaceKeyValues(s string, oldNew ...string) string {
       @@ -195,7 +213,9 @@ func replaceKeyValues(s string, oldNew ...string) string {
                return replacer.Replace(s)
        }
        
       -func regularPageLayouts(types string, layout string, f Format) (layouts []string) {
       +func regularPageLayouts(types string, layout string, f Format) []string {
       +        var layouts []string
       +
                if layout == "" {
                        layout = "single"
                }
       @@ -219,5 +239,5 @@ func regularPageLayouts(types string, layout string, f Format) (layouts []string
                layouts = append(layouts, fmt.Sprintf("_default/%s.%s.%s", layout, name, suffix))
                layouts = append(layouts, fmt.Sprintf("_default/%s.%s", layout, suffix))
        
       -        return
       +        return layouts
        }
   DIR diff --git a/output/layout_base.go b/output/layout_base.go
       @@ -29,7 +29,10 @@ var (
        )
        
        type TemplateNames struct {
       -        Name            string
       +        // The name used as key in the template map. Note that this will be
       +        // prefixed with "_text/" if it should be parsed with text/template.
       +        Name string
       +
                OverlayFilename string
                MasterFilename  string
        }
       @@ -51,6 +54,10 @@ type TemplateLookupDescriptor struct {
                // The theme name if active.
                Theme string
        
       +        // All the output formats in play. This is used to decide if text/template or
       +        // html/template.
       +        OutputFormats Formats
       +
                FileExists  func(filename string) (bool, error)
                ContainsAny func(filename string, subslices [][]byte) (bool, error)
        }
       @@ -74,6 +81,12 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
                // index.amp.html
                // index.json
                filename := filepath.Base(d.RelPath)
       +        isPlainText := false
       +        outputFormat, found := d.OutputFormats.FromFilename(filename)
       +
       +        if found && outputFormat.IsPlainText {
       +                isPlainText = true
       +        }
        
                var ext, outFormat string
        
       @@ -90,6 +103,10 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
                id.OverlayFilename = fullPath
                id.Name = name
        
       +        if isPlainText {
       +                id.Name = "_text/" + id.Name
       +        }
       +
                // Ace and Go templates may have both a base and inner template.
                pathDir := filepath.Dir(fullPath)
        
   DIR diff --git a/output/layout_base_test.go b/output/layout_base_test.go
       @@ -141,6 +141,7 @@ func TestLayoutBase(t *testing.T) {
                                        return this.needsBase, nil
                                }
        
       +                        this.d.OutputFormats = Formats{AMPFormat, HTMLFormat, RSSFormat, JSONFormat}
                                this.d.WorkingDir = filepath.FromSlash(this.d.WorkingDir)
                                this.d.LayoutDir = filepath.FromSlash(this.d.LayoutDir)
                                this.d.RelPath = filepath.FromSlash(this.d.RelPath)
       @@ -150,6 +151,11 @@ func TestLayoutBase(t *testing.T) {
                                this.expect.MasterFilename = filepath.FromSlash(this.expect.MasterFilename)
                                this.expect.OverlayFilename = filepath.FromSlash(this.expect.OverlayFilename)
        
       +                        if strings.Contains(this.d.RelPath, "json") {
       +                                // currently the only plain text templates in this test.
       +                                this.expect.Name = "_text/" + this.expect.Name
       +                        }
       +
                                id, err := CreateTemplateNames(this.d)
        
                                require.NoError(t, err)
   DIR diff --git a/output/layout_test.go b/output/layout_test.go
       @@ -64,6 +64,10 @@ func TestLayout(t *testing.T) {
                                []string{"taxonomy/tag.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
                        {"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, "", RSSFormat,
                                []string{"taxonomy/tag.terms.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
       +                {"Home plain text", LayoutDescriptor{Kind: "home"}, true, "", JSONFormat,
       +                        []string{"_text/index.json.json", "_text/index.json", "_text/_default/list.json.json", "_text/_default/list.json", "_text/theme/index.json.json", "_text/theme/index.json"}},
       +                {"Page plain text", LayoutDescriptor{Kind: "page"}, true, "", JSONFormat,
       +                        []string{"_text/_default/single.json.json", "_text/_default/single.json", "_text/theme/_default/single.json.json"}},
                } {
                        t.Run(this.name, func(t *testing.T) {
                                l := NewLayoutHandler(this.hasTheme)
   DIR diff --git a/output/outputFormat.go b/output/outputFormat.go
       @@ -33,6 +33,7 @@ var (
                        IsHTML:    true,
                }
        
       +        // CalendarFormat is AAA
                CalendarFormat = Format{
                        Name:        "Calendar",
                        MediaType:   media.CalendarType,
       @@ -104,6 +105,45 @@ func (formats Formats) GetByName(name string) (f Format, found bool) {
                return
        }
        
       +func (formats Formats) GetBySuffix(name string) (f Format, found bool) {
       +        for _, ff := range formats {
       +                if name == ff.MediaType.Suffix {
       +                        if found {
       +                                // ambiguous
       +                                found = false
       +                                return
       +                        }
       +                        f = ff
       +                        found = true
       +                }
       +        }
       +        return
       +}
       +
       +func (formats Formats) FromFilename(filename string) (f Format, found bool) {
       +        // mytemplate.amp.html
       +        // mytemplate.html
       +        // mytemplate
       +        var ext, outFormat string
       +
       +        parts := strings.Split(filename, ".")
       +        if len(parts) > 2 {
       +                outFormat = parts[1]
       +                ext = parts[2]
       +        } else if len(parts) > 1 {
       +                ext = parts[1]
       +        }
       +
       +        if outFormat != "" {
       +                return formats.GetByName(outFormat)
       +        }
       +
       +        if ext != "" {
       +                return formats.GetBySuffix(ext)
       +        }
       +        return
       +}
       +
        // Format represents an output representation, usually to a file on disk.
        type Format struct {
                // The Name is used as an identifier. Internal output formats (i.e. HTML and RSS)
   DIR diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go
       @@ -65,7 +65,7 @@ func TestDefaultTypes(t *testing.T) {
        
        }
        
       -func TestGetType(t *testing.T) {
       +func TestGetFormat(t *testing.T) {
                tp, _ := GetFormat("html")
                require.Equal(t, HTMLFormat, tp)
                tp, _ = GetFormat("HTML")
       @@ -73,3 +73,28 @@ func TestGetType(t *testing.T) {
                _, found := GetFormat("FOO")
                require.False(t, found)
        }
       +
       +func TestGeGetFormatByName(t *testing.T) {
       +        formats := Formats{AMPFormat, CalendarFormat}
       +        tp, _ := formats.GetByName("AMP")
       +        require.Equal(t, AMPFormat, tp)
       +        _, found := formats.GetByName("HTML")
       +        require.False(t, found)
       +        _, found = formats.GetByName("FOO")
       +        require.False(t, found)
       +}
       +
       +func TestGeGetFormatByExt(t *testing.T) {
       +        formats1 := Formats{AMPFormat, CalendarFormat}
       +        formats2 := Formats{AMPFormat, HTMLFormat, CalendarFormat}
       +        tp, _ := formats1.GetBySuffix("html")
       +        require.Equal(t, AMPFormat, tp)
       +        tp, _ = formats1.GetBySuffix("ics")
       +        require.Equal(t, CalendarFormat, tp)
       +        _, found := formats1.GetBySuffix("not")
       +        require.False(t, found)
       +
       +        // ambiguous
       +        _, found = formats2.GetByName("html")
       +        require.False(t, found)
       +}
   DIR diff --git a/tpl/template.go b/tpl/template.go
       @@ -1,28 +1,103 @@
       +// Copyright 2017-present 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 tpl
        
        import (
       -        "html/template"
                "io"
       +
       +        "text/template/parse"
       +
       +        "html/template"
       +        texttemplate "text/template"
       +
       +        bp "github.com/spf13/hugo/bufferpool"
        )
        
       -// TODO(bep) make smaller
       -type Template interface {
       -        ExecuteTemplate(wr io.Writer, name string, data interface{}) error
       -        ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML
       -        Lookup(name string) *template.Template
       -        Templates() []*template.Template
       -        New(name string) *template.Template
       -        GetClone() *template.Template
       -        RebuildClone() *template.Template
       -        LoadTemplates(absPath string)
       -        LoadTemplatesWithPrefix(absPath, prefix string)
       +var (
       +        _ TemplateExecutor = (*TemplateAdapter)(nil)
       +)
       +
       +// TemplateHandler manages the collection of templates.
       +type TemplateHandler interface {
       +        TemplateFinder
                AddTemplate(name, tpl string) error
       -        AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error
       -        AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
       -        AddInternalTemplate(prefix, name, tpl string) error
       -        AddInternalShortcode(name, tpl string) error
       -        Partial(name string, contextList ...interface{}) template.HTML
       +        AddLateTemplate(name, tpl string) error
       +        LoadTemplates(absPath, prefix string)
                PrintErrors()
       -        Funcs(funcMap template.FuncMap)
       +
                MarkReady()
       +        RebuildClone()
       +}
       +
       +// TemplateFinder finds templates.
       +type TemplateFinder interface {
       +        Lookup(name string) *TemplateAdapter
       +}
       +
       +// Template is the common interface between text/template and html/template.
       +type Template interface {
       +        Execute(wr io.Writer, data interface{}) error
       +        Name() string
       +}
       +
       +// TemplateExecutor adds some extras to Template.
       +type TemplateExecutor interface {
       +        Template
       +        ExecuteToString(data interface{}) (string, error)
       +        Tree() string
       +}
       +
       +// TemplateAdapter implements the TemplateExecutor interface.
       +type TemplateAdapter struct {
       +        Template
       +}
       +
       +// ExecuteToString executes the current template and returns the result as a
       +// string.
       +func (t *TemplateAdapter) ExecuteToString(data interface{}) (string, error) {
       +        b := bp.GetBuffer()
       +        defer bp.PutBuffer(b)
       +        if err := t.Execute(b, data); err != nil {
       +                return "", err
       +        }
       +        return b.String(), nil
       +}
       +
       +// Tree returns the template Parse tree as a string.
       +// Note: this isn't safe for parallel execution on the same template
       +// vs Lookup and Execute.
       +func (t *TemplateAdapter) Tree() string {
       +        var tree *parse.Tree
       +        switch tt := t.Template.(type) {
       +        case *template.Template:
       +                tree = tt.Tree
       +        case *texttemplate.Template:
       +                tree = tt.Tree
       +        default:
       +                panic("Unknown template")
       +        }
       +
       +        if tree.Root == nil {
       +                return ""
       +        }
       +        s := tree.Root.String()
       +
       +        return s
       +}
       +
       +// TemplateTestMocker adds a way to override some template funcs during tests.
       +// The interface is named so it's not used in regular application code.
       +type TemplateTestMocker interface {
       +        SetFuncs(funcMap map[string]interface{})
        }
   DIR diff --git a/tpl/tplimpl/ace.go b/tpl/tplimpl/ace.go
       @@ -0,0 +1,51 @@
       +// Copyright 2017-present 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/yosssi/ace"
       +)
       +
       +func (t *templateHandler) addAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error {
       +        t.checkState()
       +        var base, inner *ace.File
       +        name = name[:len(name)-len(filepath.Ext(innerPath))] + ".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
       +        }
       +        return applyTemplateTransformersToHMLTTemplate(templ)
       +}
   DIR diff --git a/tpl/tplimpl/amber_compiler.go b/tpl/tplimpl/amber_compiler.go
       @@ -19,7 +19,7 @@ import (
                "github.com/eknkc/amber"
        )
        
       -func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) {
       +func (t *templateHandler) compileAmberWithTemplate(b []byte, path string, templ *template.Template) (*template.Template, error) {
                c := amber.New()
        
                if err := c.ParseData(b, path); err != nil {
       @@ -32,7 +32,7 @@ func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *tem
                        return nil, err
                }
        
       -        tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
       +        tpl, err := templ.Funcs(t.amberFuncMap).Parse(data)
        
                if err != nil {
                        return nil, err
   DIR diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
       @@ -1,4 +1,4 @@
       -// Copyright 2016 The Hugo Authors. All rights reserved.
       +// Copyright 2017-present 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.
       @@ -15,23 +15,39 @@ package tplimpl
        
        import (
                "html/template"
       -        "io"
       -        "os"
       -        "path/filepath"
                "strings"
       +        texttemplate "text/template"
       +
       +        "github.com/eknkc/amber"
       +
       +        "os"
        
       +        "github.com/spf13/hugo/output"
       +
       +        "path/filepath"
                "sync"
        
       -        "github.com/eknkc/amber"
                "github.com/spf13/afero"
       -        bp "github.com/spf13/hugo/bufferpool"
                "github.com/spf13/hugo/deps"
                "github.com/spf13/hugo/helpers"
       -        "github.com/spf13/hugo/output"
       -        "github.com/yosssi/ace"
       +        "github.com/spf13/hugo/tpl"
       +)
       +
       +const (
       +        textTmplNamePrefix = "_text/"
        )
        
       -// TODO(bep) globals get rid of the rest of the jww.ERR etc.
       +var (
       +        _ tpl.TemplateHandler       = (*templateHandler)(nil)
       +        _ tpl.TemplateTestMocker    = (*templateHandler)(nil)
       +        _ tpl.TemplateFinder        = (*htmlTemplates)(nil)
       +        _ tpl.TemplateFinder        = (*textTemplates)(nil)
       +        _ templateLoader            = (*htmlTemplates)(nil)
       +        _ templateLoader            = (*textTemplates)(nil)
       +        _ templateLoader            = (*templateHandler)(nil)
       +        _ templateFuncsterTemplater = (*htmlTemplates)(nil)
       +        _ templateFuncsterTemplater = (*textTemplates)(nil)
       +)
        
        // Protecting global map access (Amber)
        var amberMu sync.Mutex
       @@ -41,340 +57,539 @@ type templateErr struct {
                err  error
        }
        
       -type GoHTMLTemplate struct {
       -        *template.Template
       -
       -        // This looks, and is, strange.
       -        // The clone is used by non-renderable content pages, and these need to be
       -        // re-parsed on content change, and to avoid the
       -        // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
       -        clone      *template.Template
       -        cloneClone *template.Template
       -
       -        // a separate storage for the overlays created from cloned master templates.
       -        // note: No mutex protection, so we add these in one Go routine, then just read.
       -        overlays map[string]*template.Template
       +type templateLoader interface {
       +        handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error
       +        addTemplate(name, tpl string) error
       +        addLateTemplate(name, tpl string) error
       +}
        
       -        errors []*templateErr
       +type templateFuncsterTemplater interface {
       +        tpl.TemplateFinder
       +        setFuncs(funcMap map[string]interface{})
       +        setTemplateFuncster(f *templateFuncster)
       +}
        
       -        funcster *templateFuncster
       +// templateHandler holds the templates in play.
       +// It implements the templateLoader and tpl.TemplateHandler interfaces.
       +type templateHandler struct {
       +        // text holds all the pure text templates.
       +        text *textTemplates
       +        html *htmlTemplates
        
                amberFuncMap template.FuncMap
        
       +        errors []*templateErr
       +
                *deps.Deps
        }
        
       -type TemplateProvider struct{}
       -
       -var DefaultTemplateProvider *TemplateProvider
       +func (t *templateHandler) addError(name string, err error) {
       +        t.errors = append(t.errors, &templateErr{name, err})
       +}
        
       -// Update updates the Hugo Template System in the provided Deps.
       -// with all the additional features, templates & functions
       -func (*TemplateProvider) Update(deps *deps.Deps) error {
       -        tmpl := &GoHTMLTemplate{
       -                Template: template.New(""),
       -                overlays: make(map[string]*template.Template),
       -                errors:   make([]*templateErr, 0),
       -                Deps:     deps,
       +// PrintErrors prints the accumulated errors as ERROR to the log.
       +func (t *templateHandler) PrintErrors() {
       +        for _, e := range t.errors {
       +                t.Log.ERROR.Println(e.name, ":", e.err)
                }
       +}
        
       -        deps.Tmpl = tmpl
       -
       -        tmpl.initFuncs(deps)
       -
       -        tmpl.LoadEmbedded()
       +// Lookup tries to find a template with the given name in both template
       +// collections: First HTML, then the plain text template collection.
       +func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
       +        var te *tpl.TemplateAdapter
        
       -        if deps.WithTemplate != nil {
       -                err := deps.WithTemplate(tmpl)
       -                if err != nil {
       -                        tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
       -                }
       +        isTextTemplate := strings.HasPrefix(name, textTmplNamePrefix)
        
       +        if isTextTemplate {
       +                // The templates are stored without the prefix identificator.
       +                name = strings.TrimPrefix(name, textTmplNamePrefix)
       +                te = t.text.Lookup(name)
       +        } else {
       +                te = t.html.Lookup(name)
                }
        
       -        tmpl.MarkReady()
       -
       -        return nil
       +        if te == nil {
       +                return nil
       +        }
        
       +        return te
        }
        
       -// Clone clones
       -func (*TemplateProvider) Clone(d *deps.Deps) error {
       -
       -        t := d.Tmpl.(*GoHTMLTemplate)
       -
       -        // 1. Clone the clone with new template funcs
       -        // 2. Clone any overlays with new template funcs
       -
       -        tmpl := &GoHTMLTemplate{
       -                Template: template.Must(t.Template.Clone()),
       -                overlays: make(map[string]*template.Template),
       -                errors:   make([]*templateErr, 0),
       -                Deps:     d,
       +func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
       +        c := &templateHandler{
       +                Deps:   d,
       +                html:   &htmlTemplates{t: template.Must(t.html.t.Clone()), overlays: make(map[string]*template.Template)},
       +                text:   &textTemplates{t: texttemplate.Must(t.text.t.Clone()), overlays: make(map[string]*texttemplate.Template)},
       +                errors: make([]*templateErr, 0),
                }
        
       -        d.Tmpl = tmpl
       -        tmpl.initFuncs(d)
       +        d.Tmpl = c
        
       -        for k, v := range t.overlays {
       +        c.initFuncs()
       +
       +        for k, v := range t.html.overlays {
                        vc := template.Must(v.Clone())
                        // The extra lookup is a workaround, see
                        // * https://github.com/golang/go/issues/16101
                        // * https://github.com/spf13/hugo/issues/2549
                        vc = vc.Lookup(vc.Name())
       -                vc.Funcs(tmpl.funcster.funcMap)
       -                tmpl.overlays[k] = vc
       +                vc.Funcs(t.html.funcster.funcMap)
       +                c.html.overlays[k] = vc
       +        }
       +
       +        for k, v := range t.text.overlays {
       +                vc := texttemplate.Must(v.Clone())
       +                vc = vc.Lookup(vc.Name())
       +                vc.Funcs(texttemplate.FuncMap(t.text.funcster.funcMap))
       +                c.text.overlays[k] = vc
                }
        
       -        tmpl.MarkReady()
       +        return c
        
       -        return nil
        }
        
       -func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) {
       +func newTemplateAdapter(deps *deps.Deps) *templateHandler {
       +        htmlT := &htmlTemplates{
       +                t:        template.New(""),
       +                overlays: make(map[string]*template.Template),
       +        }
       +        textT := &textTemplates{
       +                t:        texttemplate.New(""),
       +                overlays: make(map[string]*texttemplate.Template),
       +        }
       +        return &templateHandler{
       +                Deps:   deps,
       +                html:   htmlT,
       +                text:   textT,
       +                errors: make([]*templateErr, 0),
       +        }
        
       -        t.funcster = newTemplateFuncster(d)
       +}
        
       -        // The URL funcs in the funcMap is somewhat language dependent,
       -        // so we need to wait until the language and site config is loaded.
       -        t.funcster.initFuncMap()
       +type htmlTemplates struct {
       +        funcster *templateFuncster
        
       -        t.amberFuncMap = template.FuncMap{}
       +        t *template.Template
        
       -        amberMu.Lock()
       -        for k, v := range amber.FuncMap {
       -                t.amberFuncMap[k] = v
       -        }
       +        // This looks, and is, strange.
       +        // The clone is used by non-renderable content pages, and these need to be
       +        // re-parsed on content change, and to avoid the
       +        // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
       +        clone      *template.Template
       +        cloneClone *template.Template
        
       -        for k, v := range t.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()
       +        // a separate storage for the overlays created from cloned master templates.
       +        // note: No mutex protection, so we add these in one Go routine, then just read.
       +        overlays map[string]*template.Template
       +}
        
       +func (t *htmlTemplates) setTemplateFuncster(f *templateFuncster) {
       +        t.funcster = f
        }
        
       -func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) {
       -        t.Template.Funcs(funcMap)
       +func (t *htmlTemplates) Lookup(name string) *tpl.TemplateAdapter {
       +        templ := t.lookup(name)
       +        if templ == nil {
       +                return nil
       +        }
       +        return &tpl.TemplateAdapter{templ}
        }
        
       -func (t *GoHTMLTemplate) Partial(name string, contextList ...interface{}) template.HTML {
       -        if strings.HasPrefix("partials/", name) {
       -                name = name[8:]
       +func (t *htmlTemplates) lookup(name string) *template.Template {
       +        if templ := t.t.Lookup(name); templ != nil {
       +                return templ
       +        }
       +        if t.overlays != nil {
       +                if templ, ok := t.overlays[name]; ok {
       +                        return templ
       +                }
                }
       -        var context interface{}
        
       -        if len(contextList) == 0 {
       -                context = nil
       -        } else {
       -                context = contextList[0]
       +        if t.clone != nil {
       +                return t.clone.Lookup(name)
                }
       -        return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
       +
       +        return nil
        }
        
       -func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) {
       -        var worked bool
       -        for _, layout := range layouts {
       -                templ := t.Lookup(layout)
       -                if templ == nil {
       -                        // TODO(bep) output
       -                        layout += ".html"
       -                        templ = t.Lookup(layout)
       -                }
       +type textTemplates struct {
       +        funcster *templateFuncster
        
       -                if templ != nil {
       -                        if err := templ.Execute(w, context); err != nil {
       -                                helpers.DistinctErrorLog.Println(layout, err)
       -                        }
       -                        worked = true
       -                        break
       -                }
       -        }
       -        if !worked {
       -                t.Log.ERROR.Println("Unable to render", layouts)
       -                t.Log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts)
       -        }
       +        t *texttemplate.Template
       +
       +        clone      *texttemplate.Template
       +        cloneClone *texttemplate.Template
       +
       +        overlays map[string]*texttemplate.Template
        }
        
       -func (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML {
       -        b := bp.GetBuffer()
       -        defer bp.PutBuffer(b)
       -        t.executeTemplate(context, b, layouts...)
       -        return template.HTML(b.String())
       +func (t *textTemplates) setTemplateFuncster(f *templateFuncster) {
       +        t.funcster = f
        }
        
       -func (t *GoHTMLTemplate) Lookup(name string) *template.Template {
       +func (t *textTemplates) Lookup(name string) *tpl.TemplateAdapter {
       +        templ := t.lookup(name)
       +        if templ == nil {
       +                return nil
       +        }
       +        return &tpl.TemplateAdapter{templ}
       +}
        
       -        if templ := t.Template.Lookup(name); templ != nil {
       +func (t *textTemplates) lookup(name string) *texttemplate.Template {
       +        if templ := t.t.Lookup(name); templ != nil {
                        return templ
                }
       -
                if t.overlays != nil {
                        if templ, ok := t.overlays[name]; ok {
                                return templ
                        }
                }
        
       -        // The clone is used for the non-renderable HTML pages (p.IsRenderable == false) that is parsed
       -        // as Go templates late in the build process.
                if t.clone != nil {
       -                if templ := t.clone.Lookup(name); templ != nil {
       -                        return templ
       -                }
       +                return t.clone.Lookup(name)
                }
        
                return nil
       +}
        
       +func (t *templateHandler) setFuncs(funcMap map[string]interface{}) {
       +        t.html.setFuncs(funcMap)
       +        t.text.setFuncs(funcMap)
        }
        
       -func (t *GoHTMLTemplate) GetClone() *template.Template {
       -        return t.clone
       +// SetFuncs replaces the funcs in the func maps with new definitions.
       +// This is only used in tests.
       +func (t *templateHandler) SetFuncs(funcMap map[string]interface{}) {
       +        t.setFuncs(funcMap)
        }
        
       -func (t *GoHTMLTemplate) RebuildClone() *template.Template {
       -        t.clone = template.Must(t.cloneClone.Clone())
       -        return t.clone
       +func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) {
       +        t.t.Funcs(funcMap)
        }
        
       -func (t *GoHTMLTemplate) LoadEmbedded() {
       -        t.EmbedShortcodes()
       -        t.EmbedTemplates()
       +func (t *textTemplates) setFuncs(funcMap map[string]interface{}) {
       +        t.t.Funcs(funcMap)
        }
        
       -// MarkReady marks the template as "ready for execution". No changes allowed
       -// after this is set.
       -// TODO(bep) if this proves to be resource heavy, we could detect
       -// earlier if we really need this, or make it lazy.
       -func (t *GoHTMLTemplate) MarkReady() {
       -        if t.clone == nil {
       -                t.clone = template.Must(t.Template.Clone())
       -                t.cloneClone = template.Must(t.clone.Clone())
       -        }
       +// LoadTemplates loads the templates, starting from the given absolute path.
       +// A prefix can be given to indicate a template namespace to load the templates
       +// into, i.e. "_internal" etc.
       +func (t *templateHandler) LoadTemplates(absPath, prefix string) {
       +        // TODO(bep) output formats. Will have to get to complete list when that is ready.
       +        t.loadTemplates(absPath, prefix, output.Formats{output.HTMLFormat, output.RSSFormat, output.CalendarFormat, output.AMPFormat, output.JSONFormat})
       +
        }
        
       -func (t *GoHTMLTemplate) checkState() {
       -        if t.clone != nil {
       -                panic("template is cloned and cannot be modfified")
       +func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) error {
       +        templ, err := tt.New(name).Parse(tpl)
       +        if err != nil {
       +                return err
                }
       -}
        
       -func (t *GoHTMLTemplate) AddInternalTemplate(prefix, name, tpl string) error {
       -        if prefix != "" {
       -                return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
       +        if err := applyTemplateTransformersToHMLTTemplate(templ); err != nil {
       +                return err
                }
       -        return t.AddTemplate("_internal/"+name, tpl)
       +
       +        return nil
        }
        
       -func (t *GoHTMLTemplate) AddInternalShortcode(name, content string) error {
       -        return t.AddInternalTemplate("shortcodes", name, content)
       +func (t *htmlTemplates) addTemplate(name, tpl string) error {
       +        return t.addTemplateIn(t.t, name, tpl)
        }
        
       -func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error {
       -        t.checkState()
       -        templ, err := t.New(name).Parse(tpl)
       +func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
       +        return t.addTemplateIn(t.clone, name, tpl)
       +}
       +
       +func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) error {
       +        name = strings.TrimPrefix(name, textTmplNamePrefix)
       +        templ, err := tt.New(name).Parse(tpl)
                if err != nil {
       -                t.errors = append(t.errors, &templateErr{name: name, err: err})
                        return err
                }
       -        if err := applyTemplateTransformers(templ); err != nil {
       +
       +        if err := applyTemplateTransformersToTextTemplate(templ); err != nil {
       +                return err
       +        }
       +
       +        return nil
       +}
       +
       +func (t *textTemplates) addTemplate(name, tpl string) error {
       +        return t.addTemplateIn(t.t, name, tpl)
       +}
       +
       +func (t *textTemplates) addLateTemplate(name, tpl string) error {
       +        return t.addTemplateIn(t.clone, name, tpl)
       +}
       +
       +func (t *templateHandler) addTemplate(name, tpl string) error {
       +        return t.AddTemplate(name, tpl)
       +}
       +
       +func (t *templateHandler) addLateTemplate(name, tpl string) error {
       +        return t.AddLateTemplate(name, tpl)
       +}
       +
       +// AddLateTemplate is used to add a template late, i.e. after the
       +// regular templates have started its execution.
       +func (t *templateHandler) AddLateTemplate(name, tpl string) error {
       +        h := t.getTemplateHandler(name)
       +        if err := h.addLateTemplate(name, tpl); err != nil {
       +                t.addError(name, err)
                        return err
                }
       +        return nil
       +}
        
       +// AddTemplate parses and adds a template to the collection.
       +// Templates with name prefixed with "_text" will be handled as plain
       +// text templates.
       +func (t *templateHandler) AddTemplate(name, tpl string) error {
       +        h := t.getTemplateHandler(name)
       +        if err := h.addTemplate(name, tpl); err != nil {
       +                t.addError(name, err)
       +                return err
       +        }
                return nil
        }
        
       -func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error {
       +// MarkReady marks the templates as "ready for execution". No changes allowed
       +// after this is set.
       +// TODO(bep) if this proves to be resource heavy, we could detect
       +// earlier if we really need this, or make it lazy.
       +func (t *templateHandler) MarkReady() {
       +        if t.html.clone == nil {
       +                t.html.clone = template.Must(t.html.t.Clone())
       +                t.html.cloneClone = template.Must(t.html.clone.Clone())
       +        }
       +        if t.text.clone == nil {
       +                t.text.clone = texttemplate.Must(t.text.t.Clone())
       +                t.text.cloneClone = texttemplate.Must(t.text.clone.Clone())
       +        }
       +}
       +
       +// RebuildClone rebuilds the cloned templates. Used for live-reloads.
       +func (t *templateHandler) RebuildClone() {
       +        t.html.clone = template.Must(t.html.cloneClone.Clone())
       +        t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
       +}
       +
       +func (t *templateHandler) loadTemplates(absPath string, prefix string, formats output.Formats) {
       +        t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
       +        walker := func(path string, fi os.FileInfo, err error) error {
       +                if err != nil {
       +                        return nil
       +                }
       +
       +                t.Log.DEBUG.Println("Template path", path)
       +                if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
       +                        link, err := filepath.EvalSymlinks(absPath)
       +                        if err != nil {
       +                                t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
       +                                return nil
       +                        }
       +
       +                        linkfi, err := t.Fs.Source.Stat(link)
       +                        if err != nil {
       +                                t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
       +                                return nil
       +                        }
       +
       +                        if !linkfi.Mode().IsRegular() {
       +                                t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
       +                        }
       +                        return nil
       +                }
       +
       +                if !fi.IsDir() {
       +                        if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
       +                                return nil
       +                        }
       +
       +                        var (
       +                                workingDir = t.PathSpec.WorkingDir()
       +                                themeDir   = t.PathSpec.GetThemeDir()
       +                                layoutDir  = t.PathSpec.LayoutDir()
       +                        )
       +
       +                        if themeDir != "" && strings.HasPrefix(absPath, themeDir) {
       +                                workingDir = themeDir
       +                                layoutDir = "layouts"
       +                        }
       +
       +                        li := strings.LastIndex(path, layoutDir) + len(layoutDir) + 1
       +                        relPath := path[li:]
       +
       +                        descriptor := output.TemplateLookupDescriptor{
       +                                WorkingDir:    workingDir,
       +                                LayoutDir:     layoutDir,
       +                                RelPath:       relPath,
       +                                Prefix:        prefix,
       +                                Theme:         t.PathSpec.Theme(),
       +                                OutputFormats: formats,
       +                                FileExists: func(filename string) (bool, error) {
       +                                        return helpers.Exists(filename, t.Fs.Source)
       +                                },
       +                                ContainsAny: func(filename string, subslices [][]byte) (bool, error) {
       +                                        return helpers.FileContainsAny(filename, subslices, t.Fs.Source)
       +                                },
       +                        }
       +
       +                        tplID, err := output.CreateTemplateNames(descriptor)
       +                        if err != nil {
       +                                t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
       +
       +                                return nil
       +                        }
       +
       +                        if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
       +                                t.Log.ERROR.Printf("Failed to add template %q in path %q: %s", tplID.Name, path, err)
       +                        }
       +
       +                }
       +                return nil
       +        }
       +        if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
       +                t.Log.ERROR.Printf("Failed to load templates: %s", err)
       +        }
       +}
       +
       +func (t *templateHandler) initFuncs() {
       +
       +        // The template funcs need separation between text and html templates.
       +        for _, funcsterHolder := range []templateFuncsterTemplater{t.html, t.text} {
       +                funcster := newTemplateFuncster(t.Deps, funcsterHolder)
       +
       +                // The URL funcs in the funcMap is somewhat language dependent,
       +                // so we need to wait until the language and site config is loaded.
       +                funcster.initFuncMap()
       +
       +                funcsterHolder.setTemplateFuncster(funcster)
       +
       +        }
        
       -        // There is currently no known way to associate a cloned template with an existing one.
       -        // This funky master/overlay design will hopefully improve in a future version of Go.
       -        //
       -        // Simplicity is hard.
       -        //
       -        // Until then we'll have to live with this hackery.
       -        //
       -        // See https://github.com/golang/go/issues/14285
       -        //
       -        // So, to do minimum amount of changes to get this to work:
       -        //
       -        // 1. Lookup or Parse the master
       -        // 2. Parse and store the overlay in a separate map
       +        // Amber is HTML only.
       +        t.amberFuncMap = template.FuncMap{}
        
       -        masterTpl := t.Lookup(masterFilename)
       +        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 {
       +        if strings.HasPrefix(name, textTmplNamePrefix) {
       +                return t.text
       +        }
       +        return t.html
       +}
       +
       +func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
       +        h := t.getTemplateHandler(name)
       +        return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
       +}
       +
       +func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
       +        masterTpl := t.lookup(masterFilename)
        
                if masterTpl == nil {
       -                b, err := afero.ReadFile(t.Fs.Source, masterFilename)
       +                templ, err := onMissing(masterFilename)
                        if err != nil {
                                return err
                        }
       -                masterTpl, err = t.New(masterFilename).Parse(string(b))
        
       +                masterTpl, err = t.t.New(overlayFilename).Parse(templ)
                        if err != nil {
       -                        // TODO(bep) Add a method that does this
       -                        t.errors = append(t.errors, &templateErr{name: name, err: err})
                                return err
                        }
                }
        
       -        b, err := afero.ReadFile(t.Fs.Source, overlayFilename)
       +        templ, err := onMissing(overlayFilename)
                if err != nil {
                        return err
                }
        
       -        overlayTpl, err := template.Must(masterTpl.Clone()).Parse(string(b))
       +        overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ)
                if err != nil {
       -                t.errors = append(t.errors, &templateErr{name: name, err: err})
       -        } else {
       -                // The extra lookup is a workaround, see
       -                // * https://github.com/golang/go/issues/16101
       -                // * https://github.com/spf13/hugo/issues/2549
       -                overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
       -                if err := applyTemplateTransformers(overlayTpl); err != nil {
       -                        return err
       -                }
       -                t.overlays[name] = overlayTpl
       +                return err
                }
        
       +        // The extra lookup is a workaround, see
       +        // * https://github.com/golang/go/issues/16101
       +        // * https://github.com/spf13/hugo/issues/2549
       +        overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
       +        if err := applyTemplateTransformersToHMLTTemplate(overlayTpl); err != nil {
       +                return err
       +        }
       +        t.overlays[name] = overlayTpl
       +
                return err
       +
        }
        
       -func (t *GoHTMLTemplate) AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error {
       -        t.checkState()
       -        var base, inner *ace.File
       -        name = name[:len(name)-len(filepath.Ext(innerPath))] + ".html"
       +func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
       +        masterTpl := t.lookup(masterFilename)
        
       -        // Fixes issue #1178
       -        basePath = strings.Replace(basePath, "\\", "/", -1)
       -        innerPath = strings.Replace(innerPath, "\\", "/", -1)
       +        if masterTpl == nil {
       +                templ, err := onMissing(masterFilename)
       +                if err != nil {
       +                        return err
       +                }
        
       -        if basePath != "" {
       -                base = ace.NewFile(basePath, baseContent)
       -                inner = ace.NewFile(innerPath, innerContent)
       -        } else {
       -                base = ace.NewFile(innerPath, innerContent)
       -                inner = ace.NewFile("", []byte{})
       +                masterTpl, err = t.t.New(overlayFilename).Parse(templ)
       +                if err != nil {
       +                        return err
       +                }
                }
       -        parsed, err := ace.ParseSource(ace.NewSource(base, inner, []*ace.File{}), nil)
       +
       +        templ, err := onMissing(overlayFilename)
                if err != nil {
       -                t.errors = append(t.errors, &templateErr{name: name, err: err})
                        return err
                }
       -        templ, err := ace.CompileResultWithTemplate(t.New(name), parsed, nil)
       +
       +        overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ)
                if err != nil {
       -                t.errors = append(t.errors, &templateErr{name: name, err: err})
                        return err
                }
       -        return applyTemplateTransformers(templ)
       +
       +        overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
       +        if err := applyTemplateTransformersToTextTemplate(overlayTpl); err != nil {
       +                return err
       +        }
       +        t.overlays[name] = overlayTpl
       +
       +        return err
       +
        }
        
       -func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) error {
       +func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
                t.checkState()
       +
       +        getTemplate := func(filename string) (string, error) {
       +                b, err := afero.ReadFile(t.Fs.Source, filename)
       +                if err != nil {
       +                        return "", err
       +                }
       +                return string(b), nil
       +        }
       +
                // get the suffix and switch on that
                ext := filepath.Ext(path)
                switch ext {
                case ".amber":
       +                //        Only HTML support for Amber
                        templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html"
                        b, err := afero.ReadFile(t.Fs.Source, path)
        
       @@ -383,14 +598,15 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er
                        }
        
                        amberMu.Lock()
       -                templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName))
       +                templ, err := t.compileAmberWithTemplate(b, path, t.html.t.New(templateName))
                        amberMu.Unlock()
                        if err != nil {
                                return err
                        }
        
       -                return applyTemplateTransformers(templ)
       +                return applyTemplateTransformersToHMLTTemplate(templ)
                case ".ace":
       +                //        Only HTML support for Ace
                        var innerContent, baseContent []byte
                        innerContent, err := afero.ReadFile(t.Fs.Source, path)
        
       @@ -405,14 +621,14 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er
                                }
                        }
        
       -                return t.AddAceTemplate(name, baseTemplatePath, path, baseContent, innerContent)
       +                return t.addAceTemplate(name, baseTemplatePath, path, baseContent, innerContent)
                default:
        
                        if baseTemplatePath != "" {
       -                        return t.AddTemplateFileWithMaster(name, path, baseTemplatePath)
       +                        return t.handleMaster(name, path, baseTemplatePath, getTemplate)
                        }
        
       -                b, err := afero.ReadFile(t.Fs.Source, path)
       +                templ, err := getTemplate(path)
        
                        if err != nil {
                                return err
       @@ -420,14 +636,31 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er
        
                        t.Log.DEBUG.Printf("Add template file from path %s", path)
        
       -                return t.AddTemplate(name, string(b))
       +                return t.AddTemplate(name, templ)
       +        }
       +
       +}
       +
       +func (t *templateHandler) loadEmbedded() {
       +        t.embedShortcodes()
       +        t.embedTemplates()
       +}
       +
       +func (t *templateHandler) addInternalTemplate(prefix, name, tpl string) error {
       +        if prefix != "" {
       +                return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
                }
       +        return t.AddTemplate("_internal/"+name, tpl)
       +}
        
       +func (t *templateHandler) addInternalShortcode(name, content string) error {
       +        return t.addInternalTemplate("shortcodes", name, content)
        }
        
       -func (t *GoHTMLTemplate) GenerateTemplateNameFrom(base, path string) string {
       -        name, _ := filepath.Rel(base, path)
       -        return filepath.ToSlash(name)
       +func (t *templateHandler) checkState() {
       +        if t.html.clone != nil || t.text.clone != nil {
       +                panic("template is cloned and cannot be modfified")
       +        }
        }
        
        func isDotFile(path string) bool {
       @@ -443,96 +676,3 @@ const baseFileBase = "baseof"
        func isBaseTemplate(path string) bool {
                return strings.Contains(path, baseFileBase)
        }
       -
       -func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) {
       -        t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix)
       -        walker := func(path string, fi os.FileInfo, err error) error {
       -                if err != nil {
       -                        return nil
       -                }
       -
       -                t.Log.DEBUG.Println("Template path", path)
       -                if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
       -                        link, err := filepath.EvalSymlinks(absPath)
       -                        if err != nil {
       -                                t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err)
       -                                return nil
       -                        }
       -
       -                        linkfi, err := t.Fs.Source.Stat(link)
       -                        if err != nil {
       -                                t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err)
       -                                return nil
       -                        }
       -
       -                        if !linkfi.Mode().IsRegular() {
       -                                t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath)
       -                        }
       -                        return nil
       -                }
       -
       -                if !fi.IsDir() {
       -                        if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
       -                                return nil
       -                        }
       -
       -                        var (
       -                                workingDir = t.PathSpec.WorkingDir()
       -                                themeDir   = t.PathSpec.GetThemeDir()
       -                                layoutDir  = t.PathSpec.LayoutDir()
       -                        )
       -
       -                        if themeDir != "" && strings.HasPrefix(absPath, themeDir) {
       -                                workingDir = themeDir
       -                                layoutDir = "layouts"
       -                        }
       -
       -                        li := strings.LastIndex(path, layoutDir) + len(layoutDir) + 1
       -                        relPath := path[li:]
       -
       -                        descriptor := output.TemplateLookupDescriptor{
       -                                WorkingDir: workingDir,
       -                                LayoutDir:  layoutDir,
       -                                RelPath:    relPath,
       -                                Prefix:     prefix,
       -                                Theme:      t.PathSpec.Theme(),
       -                                FileExists: func(filename string) (bool, error) {
       -                                        return helpers.Exists(filename, t.Fs.Source)
       -                                },
       -                                ContainsAny: func(filename string, subslices [][]byte) (bool, error) {
       -                                        return helpers.FileContainsAny(filename, subslices, t.Fs.Source)
       -                                },
       -                        }
       -
       -                        tplID, err := output.CreateTemplateNames(descriptor)
       -                        if err != nil {
       -                                t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
       -
       -                                return nil
       -                        }
       -
       -                        if err := t.AddTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
       -                                t.Log.ERROR.Printf("Failed to add template %q in path %q: %s", tplID.Name, path, err)
       -                        }
       -
       -                }
       -                return nil
       -        }
       -        if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
       -                t.Log.ERROR.Printf("Failed to load templates: %s", err)
       -        }
       -}
       -
       -func (t *GoHTMLTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) {
       -        t.loadTemplates(absPath, prefix)
       -}
       -
       -func (t *GoHTMLTemplate) LoadTemplates(absPath string) {
       -        t.loadTemplates(absPath, "")
       -}
       -
       -func (t *GoHTMLTemplate) PrintErrors() {
       -        for i, e := range t.errors {
       -                t.Log.ERROR.Println(i, ":", e.err)
       -        }
       -}
   DIR diff --git a/tpl/tplimpl/templateFuncster.go b/tpl/tplimpl/templateFuncster.go
       @@ -0,0 +1,86 @@
       +// Copyright 2017-present 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 (
       +        "fmt"
       +        "html/template"
       +        "strings"
       +
       +        bp "github.com/spf13/hugo/bufferpool"
       +
       +        "image"
       +
       +        "github.com/spf13/hugo/deps"
       +)
       +
       +// Some of the template funcs are'nt entirely stateless.
       +type templateFuncster struct {
       +        funcMap        template.FuncMap
       +        cachedPartials partialCache
       +        image          *imageHandler
       +
       +        // Make sure each funcster gets its own TemplateFinder to get
       +        // proper text and HTML template separation.
       +        Tmpl templateFuncsterTemplater
       +
       +        *deps.Deps
       +}
       +
       +func newTemplateFuncster(deps *deps.Deps, t templateFuncsterTemplater) *templateFuncster {
       +        return &templateFuncster{
       +                Deps:           deps,
       +                Tmpl:           t,
       +                cachedPartials: partialCache{p: make(map[string]interface{})},
       +                image:          &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
       +        }
       +}
       +
       +// Partial executes the named partial and returns either a string,
       +// when called from text/template, for or a template.HTML.
       +func (t *templateFuncster) partial(name string, contextList ...interface{}) (interface{}, error) {
       +        if strings.HasPrefix("partials/", name) {
       +                name = name[8:]
       +        }
       +        var context interface{}
       +
       +        if len(contextList) == 0 {
       +                context = nil
       +        } else {
       +                context = contextList[0]
       +        }
       +
       +        for _, n := range []string{"partials/" + name, "theme/partials/" + name} {
       +                templ := t.Tmpl.Lookup(n)
       +                if templ != nil {
       +                        b := bp.GetBuffer()
       +                        defer bp.PutBuffer(b)
       +
       +                        if err := templ.Execute(b, context); err != nil {
       +                                return "", err
       +                        }
       +
       +                        switch t.Tmpl.(type) {
       +                        case *htmlTemplates:
       +                                return template.HTML(b.String()), nil
       +                        case *textTemplates:
       +                                return b.String(), nil
       +                        default:
       +                                panic("Unknown type")
       +                        }
       +                }
       +        }
       +
       +        return "", fmt.Errorf("Partial %q not found", name)
       +}
   DIR diff --git a/tpl/tplimpl/templateProvider.go b/tpl/tplimpl/templateProvider.go
       @@ -0,0 +1,59 @@
       +// Copyright 2017-present 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 (
       +        "github.com/spf13/hugo/deps"
       +)
       +
       +type TemplateProvider struct{}
       +
       +var DefaultTemplateProvider *TemplateProvider
       +
       +// Update updates the Hugo Template System in the provided Deps.
       +// with all the additional features, templates & functions
       +func (*TemplateProvider) Update(deps *deps.Deps) error {
       +
       +        newTmpl := newTemplateAdapter(deps)
       +        deps.Tmpl = newTmpl
       +
       +        newTmpl.initFuncs()
       +        newTmpl.loadEmbedded()
       +
       +        if deps.WithTemplate != nil {
       +                err := deps.WithTemplate(newTmpl)
       +                if err != nil {
       +                        newTmpl.addError("init", err)
       +                }
       +
       +        }
       +
       +        newTmpl.MarkReady()
       +
       +        return nil
       +
       +}
       +
       +// Clone clones.
       +func (*TemplateProvider) Clone(d *deps.Deps) error {
       +
       +        t := d.Tmpl.(*templateHandler)
       +        clone := t.clone(d)
       +
       +        d.Tmpl = clone
       +
       +        clone.MarkReady()
       +
       +        return nil
       +}
   DIR diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go
       @@ -17,6 +17,7 @@ import (
                "errors"
                "html/template"
                "strings"
       +        texttemplate "text/template"
                "text/template/parse"
        )
        
       @@ -35,32 +36,57 @@ var paramsPaths = [][]string{
        }
        
        type templateContext struct {
       -        decl    decl
       -        templ   *template.Template
       -        visited map[string]bool
       +        decl     decl
       +        visited  map[string]bool
       +        lookupFn func(name string) *parse.Tree
        }
        
       -func (c templateContext) getIfNotVisited(name string) *template.Template {
       +func (c templateContext) getIfNotVisited(name string) *parse.Tree {
                if c.visited[name] {
                        return nil
                }
                c.visited[name] = true
       -        return c.templ.Lookup(name)
       +        return c.lookupFn(name)
        }
        
       -func newTemplateContext(templ *template.Template) *templateContext {
       -        return &templateContext{templ: templ, decl: make(map[string]string), visited: make(map[string]bool)}
       +func newTemplateContext(lookupFn func(name string) *parse.Tree) *templateContext {
       +        return &templateContext{lookupFn: lookupFn, decl: make(map[string]string), visited: make(map[string]bool)}
        
        }
        
       -func applyTemplateTransformers(templ *template.Template) error {
       -        if templ == nil || templ.Tree == nil {
       +func createParseTreeLookup(templ *template.Template) func(nn string) *parse.Tree {
       +        return func(nn string) *parse.Tree {
       +                tt := templ.Lookup(nn)
       +                if tt != nil {
       +                        return tt.Tree
       +                }
       +                return nil
       +        }
       +}
       +
       +func applyTemplateTransformersToHMLTTemplate(templ *template.Template) error {
       +        return applyTemplateTransformers(templ.Tree, createParseTreeLookup(templ))
       +}
       +
       +func applyTemplateTransformersToTextTemplate(templ *texttemplate.Template) error {
       +        return applyTemplateTransformers(templ.Tree,
       +                func(nn string) *parse.Tree {
       +                        tt := templ.Lookup(nn)
       +                        if tt != nil {
       +                                return tt.Tree
       +                        }
       +                        return nil
       +                })
       +}
       +
       +func applyTemplateTransformers(templ *parse.Tree, lookupFn func(name string) *parse.Tree) error {
       +        if templ == nil {
                        return errors.New("expected template, but none provided")
                }
        
       -        c := newTemplateContext(templ)
       +        c := newTemplateContext(lookupFn)
        
       -        c.paramsKeysToLower(templ.Tree.Root)
       +        c.paramsKeysToLower(templ.Root)
        
                return nil
        }
       @@ -84,7 +110,7 @@ func (c *templateContext) paramsKeysToLower(n parse.Node) {
                case *parse.TemplateNode:
                        subTempl := c.getIfNotVisited(x.Name)
                        if subTempl != nil {
       -                        c.paramsKeysToLowerForNodes(subTempl.Tree.Root)
       +                        c.paramsKeysToLowerForNodes(subTempl.Root)
                        }
                case *parse.PipeNode:
                        for i, elem := range x.Decl {
   DIR diff --git a/tpl/tplimpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go
       @@ -115,13 +115,13 @@ F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}
        func TestParamsKeysToLower(t *testing.T) {
                t.Parallel()
        
       -        require.Error(t, applyTemplateTransformers(nil))
       +        require.Error(t, applyTemplateTransformers(nil, nil))
        
                templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
        
                require.NoError(t, err)
        
       -        c := newTemplateContext(templ)
       +        c := newTemplateContext(createParseTreeLookup(templ))
        
                require.Equal(t, -1, c.decl.indexOfReplacementStart([]string{}))
        
       @@ -185,7 +185,7 @@ func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
                b.ResetTimer()
        
                for i := 0; i < b.N; i++ {
       -                c := newTemplateContext(templates[i])
       +                c := newTemplateContext(createParseTreeLookup(templates[i]))
                        c.paramsKeysToLower(templ.Tree.Root)
                }
        }
       @@ -214,7 +214,7 @@ Blue: {{ $__amber_1.Blue}}
        
                require.NoError(t, err)
        
       -        c := newTemplateContext(templ)
       +        c := newTemplateContext(createParseTreeLookup(templ))
        
                c.paramsKeysToLower(templ.Tree.Root)
        
       @@ -254,7 +254,7 @@ P2: {{ .Params.LOWER }}
                require.NoError(t, err)
                overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
        
       -        c := newTemplateContext(overlayTpl)
       +        c := newTemplateContext(createParseTreeLookup(overlayTpl))
        
                c.paramsKeysToLower(overlayTpl.Tree.Root)
        
       @@ -284,7 +284,7 @@ func TestTransformRecursiveTemplate(t *testing.T) {
                templ, err := template.New("foo").Parse(recursive)
                require.NoError(t, err)
        
       -        c := newTemplateContext(templ)
       +        c := newTemplateContext(createParseTreeLookup(templ))
                c.paramsKeysToLower(templ.Tree.Root)
        
        }
   DIR diff --git a/tpl/tplimpl/template_embedded.go b/tpl/tplimpl/template_embedded.go
       @@ -1,4 +1,4 @@
       -// Copyright 2015 The Hugo Authors. All rights reserved.
       +// Copyright 2017-present 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.
       @@ -13,17 +13,12 @@
        
        package tplimpl
        
       -type Tmpl struct {
       -        Name string
       -        Data string
       -}
       -
       -func (t *GoHTMLTemplate) EmbedShortcodes() {
       -        t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
       -        t.AddInternalShortcode("relref.html", `{{ .Get 0 | relref .Page }}`)
       -        t.AddInternalShortcode("highlight.html", `{{ if len .Params | eq 2 }}{{ highlight .Inner (.Get 0) (.Get 1) }}{{ else }}{{ highlight .Inner (.Get 0) "" }}{{ end }}`)
       -        t.AddInternalShortcode("test.html", `This is a simple Test`)
       -        t.AddInternalShortcode("figure.html", `<!-- image -->
       +func (t *templateHandler) embedShortcodes() {
       +        t.addInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
       +        t.addInternalShortcode("relref.html", `{{ .Get 0 | relref .Page }}`)
       +        t.addInternalShortcode("highlight.html", `{{ if len .Params | eq 2 }}{{ highlight .Inner (.Get 0) (.Get 1) }}{{ else }}{{ highlight .Inner (.Get 0) "" }}{{ end }}`)
       +        t.addInternalShortcode("test.html", `This is a simple Test`)
       +        t.addInternalShortcode("figure.html", `<!-- image -->
        <figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
            {{ with .Get "link"}}<a href="{{.}}">{{ end }}
                <img src="{{ .Get "src" }}" {{ if or (.Get "alt") (.Get "caption") }}alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}" {{ end }}{{ with .Get "width" }}width="{{.}}" {{ end }}/>
       @@ -41,8 +36,8 @@ func (t *GoHTMLTemplate) EmbedShortcodes() {
            {{ end }}
        </figure>
        <!-- image -->`)
       -        t.AddInternalShortcode("speakerdeck.html", "<script async class='speakerdeck-embed' data-id='{{ index .Params 0 }}' data-ratio='1.33333333333333' src='//speakerdeck.com/assets/embed.js'></script>")
       -        t.AddInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
       +        t.addInternalShortcode("speakerdeck.html", "<script async class='speakerdeck-embed' data-id='{{ index .Params 0 }}' data-ratio='1.33333333333333' src='//speakerdeck.com/assets/embed.js'></script>")
       +        t.addInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
        <div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
          <iframe src="//www.youtube.com/embed/{{ .Get "id" }}?{{ with .Get "autoplay" }}{{ if eq . "true" }}autoplay=1{{ end }}{{ end }}" 
          {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
       @@ -51,21 +46,21 @@ func (t *GoHTMLTemplate) EmbedShortcodes() {
          <iframe src="//www.youtube.com/embed/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}allowfullscreen frameborder="0"></iframe>
         </div>
        {{ end }}`)
       -        t.AddInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
       +        t.addInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}<div {{ if .Get "class" }}class="{{ .Get "class" }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
          <iframe src="//player.vimeo.com/video/{{ .Get "id" }}" {{ if not (.Get "class") }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
         </div>{{ else }}
        <div {{ if len .Params | eq 2 }}class="{{ .Get 1 }}"{{ else }}style="position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden;"{{ end }}>
          <iframe src="//player.vimeo.com/video/{{ .Get 0 }}" {{ if len .Params | eq 1 }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" {{ end }}webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
         </div>
        {{ end }}`)
       -        t.AddInternalShortcode("gist.html", `<script src="//gist.github.com/{{ index .Params 0 }}/{{ index .Params 1 }}.js{{if len .Params | eq 3 }}?file={{ index .Params 2 }}{{end}}"></script>`)
       -        t.AddInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`)
       -        t.AddInternalShortcode("instagram.html", `{{ if len .Params | eq 2 }}{{ if eq (.Get 1) "hidecaption" }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1" }}{{ .html | safeHTML }}{{ end }}{{ end }}{{ else }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0" }}{{ .html | safeHTML }}{{ end }}{{ end }}`)
       +        t.addInternalShortcode("gist.html", `<script src="//gist.github.com/{{ index .Params 0 }}/{{ index .Params 1 }}.js{{if len .Params | eq 3 }}?file={{ index .Params 2 }}{{end}}"></script>`)
       +        t.addInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`)
       +        t.addInternalShortcode("instagram.html", `{{ if len .Params | eq 2 }}{{ if eq (.Get 1) "hidecaption" }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1" }}{{ .html | safeHTML }}{{ end }}{{ end }}{{ else }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0" }}{{ .html | safeHTML }}{{ end }}{{ end }}`)
        }
        
       -func (t *GoHTMLTemplate) EmbedTemplates() {
       +func (t *templateHandler) embedTemplates() {
        
       -        t.AddInternalTemplate("_default", "rss.xml", `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
       +        t.addInternalTemplate("_default", "rss.xml", `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
          <channel>
            <title>{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
            <link>{{ .Permalink }}</link>
       @@ -92,7 +87,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
          </channel>
        </rss>`)
        
       -        t.AddInternalTemplate("_default", "sitemap.xml", `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
       +        t.addInternalTemplate("_default", "sitemap.xml", `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
          {{ range .Data.Pages }}
          <url>
            <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
       @@ -104,7 +99,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
        </urlset>`)
        
                // For multilanguage sites
       -        t.AddInternalTemplate("_default", "sitemapindex.xml", `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
       +        t.addInternalTemplate("_default", "sitemapindex.xml", `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
                {{ range . }}
                <sitemap>
                           <loc>{{ .SitemapAbsURL }}</loc>
       @@ -116,7 +111,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
        </sitemapindex>
        `)
        
       -        t.AddInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }}
       +        t.addInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }}
            {{ if gt $pag.TotalPages 1 }}
            <ul class="pagination">
                {{ with $pag.First }}
       @@ -144,7 +139,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
            </ul>
            {{ end }}`)
        
       -        t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div>
       +        t.addInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div>
        <script type="text/javascript">
            var disqus_shortname = '{{ .Site.DisqusShortname }}';
            var disqus_identifier = '{{with .GetParam "disqus_identifier" }}{{ . }}{{ else }}{{ .Permalink }}{{end}}';
       @@ -161,7 +156,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
        <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>{{end}}`)
        
                // Add SEO & Social metadata
       -        t.AddInternalTemplate("", "opengraph.html", `<meta property="og:title" content="{{ .Title }}" />
       +        t.addInternalTemplate("", "opengraph.html", `<meta property="og:title" content="{{ .Title }}" />
        <meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
        <meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
        <meta property="og:url" content="{{ .Permalink }}" />
       @@ -205,7 +200,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
        <!-- Facebook Page Admin ID for Domain Insights -->
        {{ with .Site.Social.facebook_admin }}<meta property="fb:admins" content="{{ . }}" />{{ end }}`)
        
       -        t.AddInternalTemplate("", "twitter_cards.html", `{{ if .IsPage }}
       +        t.addInternalTemplate("", "twitter_cards.html", `{{ if .IsPage }}
        {{ with .Params.images }}
        <!-- Twitter summary card with large image must be at least 280x150px -->
          <meta name="twitter:card" content="summary_large_image"/>
       @@ -223,11 +218,11 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
          {{ with .twitter }}<meta name="twitter:creator" content="@{{ . }}"/>{{ end }}
        {{ end }}{{ end }}`)
        
       -        t.AddInternalTemplate("", "google_news.html", `{{ if .IsPage }}{{ with .Params.news_keywords }}
       +        t.addInternalTemplate("", "google_news.html", `{{ if .IsPage }}{{ with .Params.news_keywords }}
          <meta name="news_keywords" content="{{ range $i, $kw := first 10 . }}{{ if $i }},{{ end }}{{ $kw }}{{ end }}" />
        {{ end }}{{ end }}`)
        
       -        t.AddInternalTemplate("", "schema.html", `{{ with .Site.Social.GooglePlus }}<link rel="publisher" href="{{ . }}"/>{{ end }}
       +        t.addInternalTemplate("", "schema.html", `{{ with .Site.Social.GooglePlus }}<link rel="publisher" href="{{ . }}"/>{{ end }}
        <meta itemprop="name" content="{{ .Title }}">
        <meta itemprop="description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
        
       @@ -243,7 +238,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
        <meta itemprop="keywords" content="{{ range $plural, $terms := .Site.Taxonomies }}{{ range $term, $val := $terms }}{{ printf "%s," $term }}{{ end }}{{ end }}" />
        {{ end }}`)
        
       -        t.AddInternalTemplate("", "google_analytics.html", `{{ with .Site.GoogleAnalytics }}
       +        t.addInternalTemplate("", "google_analytics.html", `{{ with .Site.GoogleAnalytics }}
        <script>
        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
       @@ -255,7 +250,7 @@ ga('send', 'pageview');
        </script>
        {{ end }}`)
        
       -        t.AddInternalTemplate("", "google_analytics_async.html", `{{ with .Site.GoogleAnalytics }}
       +        t.addInternalTemplate("", "google_analytics_async.html", `{{ with .Site.GoogleAnalytics }}
        <script>
        window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
        ga('create', '{{ . }}', 'auto');
       @@ -264,5 +259,5 @@ ga('send', 'pageview');
        <script async src='//www.google-analytics.com/analytics.js'></script>
        {{ end }}`)
        
       -        t.AddInternalTemplate("_default", "robots.txt", "User-agent: *")
       +        t.addInternalTemplate("_default", "robots.txt", "User-agent: *")
        }
   DIR diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go
       @@ -45,7 +45,6 @@ import (
                "github.com/bep/inflect"
                "github.com/spf13/afero"
                "github.com/spf13/cast"
       -        "github.com/spf13/hugo/deps"
                "github.com/spf13/hugo/helpers"
                jww "github.com/spf13/jwalterweatherman"
        
       @@ -55,22 +54,6 @@ import (
                _ "image/png"
        )
        
       -// Some of the template funcs are'nt entirely stateless.
       -type templateFuncster struct {
       -        funcMap        template.FuncMap
       -        cachedPartials partialCache
       -        image          *imageHandler
       -        *deps.Deps
       -}
       -
       -func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
       -        return &templateFuncster{
       -                Deps:           deps,
       -                cachedPartials: partialCache{p: make(map[string]template.HTML)},
       -                image:          &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
       -        }
       -}
       -
        // eq returns the boolean truth of arg1 == arg2.
        func eq(x, y interface{}) bool {
                normalize := func(v interface{}) interface{} {
       @@ -1558,13 +1541,13 @@ func replace(a, b, c interface{}) (string, error) {
        // partialCache represents a cache of partials protected by a mutex.
        type partialCache struct {
                sync.RWMutex
       -        p map[string]template.HTML
       +        p map[string]interface{}
        }
        
        // Get retrieves partial output from the cache based upon the partial name.
        // If the partial is not found in the cache, the partial is rendered and added
        // to the cache.
       -func (t *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) {
       +func (t *templateFuncster) Get(key, name string, context interface{}) (p interface{}, err error) {
                var ok bool
        
                t.cachedPartials.RLock()
       @@ -1572,13 +1555,13 @@ func (t *templateFuncster) Get(key, name string, context interface{}) (p templat
                t.cachedPartials.RUnlock()
        
                if ok {
       -                return p
       +                return
                }
        
                t.cachedPartials.Lock()
                if p, ok = t.cachedPartials.p[key]; !ok {
                        t.cachedPartials.Unlock()
       -                p = t.Tmpl.Partial(name, context)
       +                p, err = t.partial(name, context)
        
                        t.cachedPartials.Lock()
                        t.cachedPartials.p[key] = p
       @@ -1586,14 +1569,14 @@ func (t *templateFuncster) Get(key, name string, context interface{}) (p templat
                }
                t.cachedPartials.Unlock()
        
       -        return p
       +        return
        }
        
        // partialCached executes and caches partial templates.  An optional variant
        // string parameter (a string slice actually, but be only use a variadic
        // argument to make it optional) can be passed so that a given partial can have
        // multiple uses.  The cache is created with name+variant as the key.
       -func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML {
       +func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) (interface{}, error) {
                key := name
                if len(variant) > 0 {
                        for i := 0; i < len(variant); i++ {
       @@ -2195,7 +2178,7 @@ func (t *templateFuncster) initFuncMap() {
                        "mul":           func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') },
                        "ne":            ne,
                        "now":           func() time.Time { return time.Now() },
       -                "partial":       t.Tmpl.Partial,
       +                "partial":       t.partial,
                        "partialCached": t.partialCached,
                        "plainify":      plainify,
                        "pluralize":     pluralize,
       @@ -2249,5 +2232,5 @@ func (t *templateFuncster) initFuncMap() {
                }
        
                t.funcMap = funcMap
       -        t.Tmpl.Funcs(funcMap)
       +        t.Tmpl.setFuncs(funcMap)
        }
   DIR diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
       @@ -281,8 +281,8 @@ urlize: bat-man
                v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v))
        
                config := newDepsConfig(v)
       -        config.WithTemplate = func(templ tpl.Template) error {
       -                if _, err := templ.New("test").Parse(in); err != nil {
       +        config.WithTemplate = func(templ tpl.TemplateHandler) error {
       +                if err := templ.AddTemplate("test", in); err != nil {
                                t.Fatal("Got error on parse", err)
                        }
                        return nil
       @@ -2858,6 +2858,56 @@ func TestReadFile(t *testing.T) {
                }
        }
        
       +func TestPartialHTMLAndText(t *testing.T) {
       +        t.Parallel()
       +        config := newDepsConfig(viper.New())
       +
       +        data := struct {
       +                Name string
       +        }{
       +                Name: "a+b+c", // This should get encoded in HTML.
       +        }
       +
       +        config.WithTemplate = func(templ tpl.TemplateHandler) error {
       +                if err := templ.AddTemplate("htmlTemplate.html", `HTML Test Partial: {{ partial "test.foo" . -}}`); err != nil {
       +                        return err
       +                }
       +
       +                if err := templ.AddTemplate("_text/textTemplate.txt", `Text Test Partial: {{ partial "test.foo" . -}}`); err != nil {
       +                        return err
       +                }
       +
       +                // Use "foo" here to say that the extension doesn't really matter in this scenario.
       +                // It will look for templates in "partials/test.foo" and "partials/test.foo.html".
       +                if err := templ.AddTemplate("partials/test.foo", "HTML Name: {{ .Name }}"); err != nil {
       +                        return err
       +                }
       +                if err := templ.AddTemplate("_text/partials/test.foo", "Text Name: {{ .Name }}"); err != nil {
       +                        return err
       +                }
       +
       +                return nil
       +        }
       +
       +        de, err := deps.New(config)
       +        require.NoError(t, err)
       +        require.NoError(t, de.LoadResources())
       +
       +        templ := de.Tmpl.Lookup("htmlTemplate.html")
       +        require.NotNil(t, templ)
       +        resultHTML, err := templ.ExecuteToString(data)
       +        require.NoError(t, err)
       +
       +        templ = de.Tmpl.Lookup("_text/textTemplate.txt")
       +        require.NotNil(t, templ)
       +        resultText, err := templ.ExecuteToString(data)
       +        require.NoError(t, err)
       +
       +        require.Contains(t, resultHTML, "HTML Test Partial: HTML Name: a&#43;b&#43;c")
       +        require.Contains(t, resultText, "Text Test Partial: Text Name: a+b+c")
       +
       +}
       +
        func TestPartialCached(t *testing.T) {
                t.Parallel()
                testCases := []struct {
       @@ -2893,7 +2943,7 @@ func TestPartialCached(t *testing.T) {
        
                        config := newDepsConfig(viper.New())
        
       -                config.WithTemplate = func(templ tpl.Template) error {
       +                config.WithTemplate = func(templ tpl.TemplateHandler) error {
                                err := templ.AddTemplate("testroot", tmp)
                                if err != nil {
                                        return err
       @@ -2933,7 +2983,7 @@ func TestPartialCached(t *testing.T) {
        
        func BenchmarkPartial(b *testing.B) {
                config := newDepsConfig(viper.New())
       -        config.WithTemplate = func(templ tpl.Template) error {
       +        config.WithTemplate = func(templ tpl.TemplateHandler) error {
                        err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
                        if err != nil {
                                return err
       @@ -2965,7 +3015,7 @@ func BenchmarkPartial(b *testing.B) {
        
        func BenchmarkPartialCached(b *testing.B) {
                config := newDepsConfig(viper.New())
       -        config.WithTemplate = func(templ tpl.Template) error {
       +        config.WithTemplate = func(templ tpl.TemplateHandler) error {
                        err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
                        if err != nil {
                                return err
       @@ -3010,12 +3060,12 @@ func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster {
                        panic(err)
                }
        
       -        return d.Tmpl.(*GoHTMLTemplate).funcster
       +        return d.Tmpl.(*templateHandler).html.funcster
        }
        
       -func newTestTemplate(t *testing.T, name, template string) *template.Template {
       +func newTestTemplate(t *testing.T, name, template string) tpl.Template {
                config := newDepsConfig(viper.New())
       -        config.WithTemplate = func(templ tpl.Template) error {
       +        config.WithTemplate = func(templ tpl.TemplateHandler) error {
                        err := templ.AddTemplate(name, template)
                        if err != nil {
                                return err
   DIR diff --git a/tpl/tplimpl/template_test.go b/tpl/tplimpl/template_test.go
       @@ -14,17 +14,10 @@
        package tplimpl
        
        import (
       -        "bytes"
                "errors"
       -        "html/template"
                "io/ioutil"
       -        "os"
       -        "path/filepath"
       -        "runtime"
       -        "strings"
                "testing"
        
       -        "github.com/spf13/afero"
                "github.com/spf13/hugo/deps"
        
                "github.com/spf13/hugo/tpl"
       @@ -32,223 +25,6 @@ import (
                "github.com/stretchr/testify/require"
        )
        
       -// Some tests for Issue #1178 -- Ace
       -func TestAceTemplates(t *testing.T) {
       -        t.Parallel()
       -
       -        for i, this := range []struct {
       -                basePath     string
       -                innerPath    string
       -                baseContent  string
       -                innerContent string
       -                expect       string
       -                expectErr    int
       -        }{
       -                {"", filepath.FromSlash("_default/single.ace"), "", "{{ . }}", "DATA", 0},
       -                {filepath.FromSlash("_default/baseof.ace"), filepath.FromSlash("_default/single.ace"),
       -                        `= content main
       -  h2 This is a content named "main" of an inner template. {{ . }}`,
       -                        `= doctype html
       -html lang=en
       -  head
       -    meta charset=utf-8
       -    title Base and Inner Template
       -  body
       -    h1 This is a base template {{ . }}
       -    = yield main`, `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Base and Inner Template</title></head><body><h1>This is a base template DATA</h1></body></html>`, 0},
       -        } {
       -
       -                for _, root := range []string{"", os.TempDir()} {
       -
       -                        basePath := this.basePath
       -                        innerPath := this.innerPath
       -
       -                        if basePath != "" && root != "" {
       -                                basePath = filepath.Join(root, basePath)
       -                        }
       -
       -                        if innerPath != "" && root != "" {
       -                                innerPath = filepath.Join(root, innerPath)
       -                        }
       -
       -                        d := "DATA"
       -
       -                        config := newDepsConfig(viper.New())
       -                        config.WithTemplate = func(templ tpl.Template) error {
       -                                return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
       -                                        []byte(this.baseContent), []byte(this.innerContent))
       -                        }
       -
       -                        a, err := deps.New(config)
       -                        require.NoError(t, err)
       -
       -                        if err := a.LoadResources(); err != nil {
       -                                t.Fatal(err)
       -                        }
       -
       -                        templ := a.Tmpl.(*GoHTMLTemplate)
       -
       -                        if len(templ.errors) > 0 && this.expectErr == 0 {
       -                                t.Errorf("Test %d with root '%s' errored: %v", i, root, templ.errors)
       -                        } else if len(templ.errors) == 0 && this.expectErr == 1 {
       -                                t.Errorf("#1 Test %d with root '%s' should have errored", i, root)
       -                        }
       -
       -                        var buff bytes.Buffer
       -                        err = a.Tmpl.ExecuteTemplate(&buff, "mytemplate.html", d)
       -
       -                        if err != nil && this.expectErr == 0 {
       -                                t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
       -                        } else if err == nil && this.expectErr == 2 {
       -                                t.Errorf("#2 Test with root '%s' %d should have errored", root, i)
       -                        } else {
       -                                result := buff.String()
       -                                if result != this.expect {
       -                                        t.Errorf("Test %d  with root '%s' got\n%s\nexpected\n%s", i, root, result, this.expect)
       -                                }
       -                        }
       -
       -                }
       -        }
       -
       -}
       -
       -func isAtLeastGo16() bool {
       -        version := runtime.Version()
       -        return strings.Contains(version, "1.6") || strings.Contains(version, "1.7")
       -}
       -
       -func TestAddTemplateFileWithMaster(t *testing.T) {
       -        t.Parallel()
       -
       -        if !isAtLeastGo16() {
       -                t.Skip("This test only runs on Go >= 1.6")
       -        }
       -
       -        for i, this := range []struct {
       -                masterTplContent  string
       -                overlayTplContent string
       -                writeSkipper      int
       -                expect            interface{}
       -        }{
       -                {`A{{block "main" .}}C{{end}}C`, `{{define "main"}}B{{end}}`, 0, "ABC"},
       -                {`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}`, 0, "ABCDE"},
       -                {`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}{{define "sub"}}Z{{end}}`, 0, "ABCZE"},
       -                {`tpl`, `tpl`, 1, false},
       -                {`tpl`, `tpl`, 2, false},
       -                {`{{.0.E}}`, `tpl`, 0, false},
       -                {`tpl`, `{{.0.E}}`, 0, false},
       -        } {
       -
       -                overlayTplName := "ot"
       -                masterTplName := "mt"
       -                finalTplName := "tp"
       -
       -                config := newDepsConfig(viper.New())
       -                config.WithTemplate = func(templ tpl.Template) error {
       -
       -                        err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
       -
       -                        if b, ok := this.expect.(bool); ok && !b {
       -                                if err == nil {
       -                                        t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
       -                                }
       -                        } else {
       -
       -                                if err != nil {
       -                                        t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
       -                                        return nil
       -                                }
       -
       -                                resultTpl := templ.Lookup(finalTplName)
       -
       -                                if resultTpl == nil {
       -                                        t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
       -                                        return nil
       -                                }
       -
       -                                var b bytes.Buffer
       -                                err := resultTpl.Execute(&b, nil)
       -
       -                                if err != nil {
       -                                        t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
       -                                        return nil
       -                                }
       -                                resultContent := b.String()
       -
       -                                if resultContent != this.expect {
       -                                        t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
       -                                }
       -                        }
       -
       -                        return nil
       -                }
       -
       -                if this.writeSkipper != 1 {
       -                        afero.WriteFile(config.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
       -                }
       -                if this.writeSkipper != 2 {
       -                        afero.WriteFile(config.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
       -                }
       -
       -                deps.New(config)
       -
       -        }
       -
       -}
       -
       -// A Go stdlib test for linux/arm. Will remove later.
       -// See #1771
       -func TestBigIntegerFunc(t *testing.T) {
       -        t.Parallel()
       -        var func1 = func(v int64) error {
       -                return nil
       -        }
       -        var funcs = map[string]interface{}{
       -                "A": func1,
       -        }
       -
       -        tpl, err := template.New("foo").Funcs(funcs).Parse("{{ A 3e80 }}")
       -        if err != nil {
       -                t.Fatal("Parse failed:", err)
       -        }
       -        err = tpl.Execute(ioutil.Discard, "foo")
       -
       -        if err == nil {
       -                t.Fatal("Execute should have failed")
       -        }
       -
       -        t.Log("Got expected error:", err)
       -
       -}
       -
       -// A Go stdlib test for linux/arm. Will remove later.
       -// See #1771
       -type BI struct {
       -}
       -
       -func (b BI) A(v int64) error {
       -        return nil
       -}
       -func TestBigIntegerMethod(t *testing.T) {
       -        t.Parallel()
       -
       -        data := &BI{}
       -
       -        tpl, err := template.New("foo2").Parse("{{ .A 3e80 }}")
       -        if err != nil {
       -                t.Fatal("Parse failed:", err)
       -        }
       -        err = tpl.ExecuteTemplate(ioutil.Discard, "foo2", data)
       -
       -        if err == nil {
       -                t.Fatal("Execute should have failed")
       -        }
       -
       -        t.Log("Got expected error:", err)
       -
       -}
       -
        // Test for bugs discovered by https://github.com/dvyukov/go-fuzz
        func TestTplGoFuzzReports(t *testing.T) {
                t.Parallel()
       @@ -285,7 +61,7 @@ func TestTplGoFuzzReports(t *testing.T) {
        
                        config := newDepsConfig(viper.New())
        
       -                config.WithTemplate = func(templ tpl.Template) error {
       +                config.WithTemplate = func(templ tpl.TemplateHandler) error {
                                return templ.AddTemplate("fuzz", this.data)
                        }
        
       @@ -293,7 +69,7 @@ func TestTplGoFuzzReports(t *testing.T) {
                        require.NoError(t, err)
                        require.NoError(t, de.LoadResources())
        
       -                templ := de.Tmpl.(*GoHTMLTemplate)
       +                templ := de.Tmpl.(*templateHandler)
        
                        if len(templ.errors) > 0 && this.expectErr == 0 {
                                t.Errorf("Test %d errored: %v", i, templ.errors)
       @@ -301,7 +77,9 @@ func TestTplGoFuzzReports(t *testing.T) {
                                t.Errorf("#1 Test %d should have errored", i)
                        }
        
       -                err = de.Tmpl.ExecuteTemplate(ioutil.Discard, "fuzz", d)
       +                tt := de.Tmpl.Lookup("fuzz")
       +                require.NotNil(t, tt)
       +                err = tt.Execute(ioutil.Discard, d)
        
                        if err != nil && this.expectErr == 0 {
                                t.Fatalf("Test %d errored: %s", i, err)