Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commands/server.go23
-rw-r--r--create/content_template_handler.go4
-rw-r--r--deps/deps.go54
-rw-r--r--hugolib/alias.go43
-rw-r--r--hugolib/case_insensitive_test.go74
-rw-r--r--hugolib/embedded_shortcodes_test.go15
-rw-r--r--hugolib/hugo_modules_test.go4
-rw-r--r--hugolib/hugo_sites.go4
-rw-r--r--hugolib/hugo_smoke_test.go21
-rw-r--r--hugolib/page.go2
-rw-r--r--hugolib/page__per_output.go4
-rw-r--r--hugolib/page_test.go2
-rw-r--r--hugolib/shortcode.go6
-rw-r--r--hugolib/shortcode_test.go36
-rw-r--r--hugolib/site.go10
-rw-r--r--hugolib/sitemap_test.go2
-rw-r--r--hugolib/template_engines_test.go97
-rw-r--r--hugolib/testhelpers_test.go4
-rw-r--r--langs/language.go22
-rw-r--r--resources/resource_transformers/templates/execute_as_template.go27
-rw-r--r--scripts/fork_go_templates/main.go2
-rw-r--r--tpl/collections/apply.go13
-rw-r--r--tpl/collections/apply_test.go15
-rw-r--r--tpl/internal/go_templates/texttemplate/exec.go4
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_exec.go38
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_template.go158
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_template_test.go71
-rw-r--r--tpl/partials/partials.go2
-rw-r--r--tpl/resources/resources.go2
-rw-r--r--tpl/template.go218
-rw-r--r--tpl/tplimpl/embedded/templates.autogen.go1
-rw-r--r--tpl/tplimpl/embedded/templates/alias.html1
-rw-r--r--tpl/tplimpl/shortcodes.go5
-rw-r--r--tpl/tplimpl/template.go1228
-rw-r--r--tpl/tplimpl/templateFuncster.go19
-rw-r--r--tpl/tplimpl/templateProvider.go4
-rw-r--r--tpl/tplimpl/template_ast_transformers.go203
-rw-r--r--tpl/tplimpl/template_ast_transformers_test.go396
-rw-r--r--tpl/tplimpl/template_funcs.go66
-rw-r--r--tpl/tplimpl/template_funcs_test.go8
-rw-r--r--tpl/tplimpl/template_info_test.go2
41 files changed, 1103 insertions, 1807 deletions
diff --git a/commands/server.go b/commands/server.go
index 709181507..7d884096c 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -16,6 +16,7 @@ package commands
import (
"bytes"
"fmt"
+ "io"
"net"
"net/http"
"net/url"
@@ -33,7 +34,6 @@ import (
"github.com/pkg/errors"
"github.com/gohugoio/hugo/livereload"
- "github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
@@ -287,7 +287,7 @@ func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
type fileServer struct {
baseURLs []string
roots []string
- errorTemplate tpl.Template
+ errorTemplate func(err interface{}) (io.Reader, error)
c *commandeer
s *serverCmd
}
@@ -335,8 +335,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
err := f.c.getErrorWithContext()
if err != nil {
w.WriteHeader(500)
- var b bytes.Buffer
- err := f.errorTemplate.Execute(&b, err)
+ r, err := f.errorTemplate(err)
if err != nil {
f.c.logger.ERROR.Println(err)
}
@@ -344,7 +343,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
if !f.c.paused {
port = f.c.Cfg.GetInt("liveReloadPort")
}
- fmt.Fprint(w, injectLiveReloadScript(&b, port))
+ fmt.Fprint(w, injectLiveReloadScript(r, port))
return
}
@@ -422,11 +421,15 @@ func (c *commandeer) serve(s *serverCmd) error {
}
srv := &fileServer{
- baseURLs: baseURLs,
- roots: roots,
- c: c,
- s: s,
- errorTemplate: templ,
+ baseURLs: baseURLs,
+ roots: roots,
+ c: c,
+ s: s,
+ errorTemplate: func(ctx interface{}) (io.Reader, error) {
+ b := &bytes.Buffer{}
+ err := c.hugo().Tmpl.Execute(templ, b, ctx)
+ return b, err
+ },
}
doLiveReload := !c.Cfg.GetBool("disableLiveReload")
diff --git a/create/content_template_handler.go b/create/content_template_handler.go
index 1576fabdb..b70cf02eb 100644
--- a/create/content_template_handler.go
+++ b/create/content_template_handler.go
@@ -129,7 +129,7 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety
archetypeTemplate = []byte(archetypeShortcodeReplacementsPre.Replace(string(archetypeTemplate)))
// Reuse the Hugo template setup to get the template funcs properly set up.
- templateHandler := s.Deps.Tmpl.(tpl.TemplateHandler)
+ templateHandler := s.Deps.Tmpl.(tpl.TemplateManager)
templateName := "_text/" + helpers.Filename(archetypeFilename)
if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil {
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
@@ -138,7 +138,7 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety
templ, _ := templateHandler.Lookup(templateName)
var buff bytes.Buffer
- if err := templ.Execute(&buff, data); err != nil {
+ if err := templateHandler.Execute(templ, &buff, data); err != nil {
return nil, errors.Wrapf(err, "Failed to process archetype file %q:", archetypeFilename)
}
diff --git a/deps/deps.go b/deps/deps.go
index d7b381ce9..ecbba2e56 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -37,8 +37,8 @@ type Deps struct {
// Used to log warnings that may repeat itself many times.
DistinctWarningLog *helpers.DistinctLogger
- // The templates to use. This will usually implement the full tpl.TemplateHandler.
- Tmpl tpl.TemplateFinder `json:"-"`
+ // The templates to use. This will usually implement the full tpl.TemplateManager.
+ Tmpl tpl.TemplateHandler `json:"-"`
// We use this to parse and execute ad-hoc text templates.
TextTmpl tpl.TemplateParseFinder `json:"-"`
@@ -77,7 +77,10 @@ type Deps struct {
OutputFormatsConfig output.Formats
templateProvider ResourceProvider
- WithTemplate func(templ tpl.TemplateHandler) error `json:"-"`
+ WithTemplate func(templ tpl.TemplateManager) error `json:"-"`
+
+ // Used in tests
+ OverloadedTemplateFuncs map[string]interface{}
translationProvider ResourceProvider
@@ -151,8 +154,8 @@ type ResourceProvider interface {
}
// TemplateHandler returns the used tpl.TemplateFinder as tpl.TemplateHandler.
-func (d *Deps) TemplateHandler() tpl.TemplateHandler {
- return d.Tmpl.(tpl.TemplateHandler)
+func (d *Deps) TemplateHandler() tpl.TemplateManager {
+ return d.Tmpl.(tpl.TemplateManager)
}
// LoadResources loads translations and templates.
@@ -239,24 +242,25 @@ func New(cfg DepsCfg) (*Deps, error) {
distinctWarnLogger := helpers.NewDistinctLogger(logger.WARN)
d := &Deps{
- Fs: fs,
- Log: logger,
- DistinctErrorLog: distinctErrorLogger,
- DistinctWarningLog: distinctWarnLogger,
- templateProvider: cfg.TemplateProvider,
- translationProvider: cfg.TranslationProvider,
- WithTemplate: cfg.WithTemplate,
- PathSpec: ps,
- ContentSpec: contentSpec,
- SourceSpec: sp,
- ResourceSpec: resourceSpec,
- Cfg: cfg.Language,
- Language: cfg.Language,
- Site: cfg.Site,
- FileCaches: fileCaches,
- BuildStartListeners: &Listeners{},
- Timeout: time.Duration(timeoutms) * time.Millisecond,
- globalErrHandler: &globalErrHandler{},
+ Fs: fs,
+ Log: logger,
+ DistinctErrorLog: distinctErrorLogger,
+ DistinctWarningLog: distinctWarnLogger,
+ templateProvider: cfg.TemplateProvider,
+ translationProvider: cfg.TranslationProvider,
+ WithTemplate: cfg.WithTemplate,
+ OverloadedTemplateFuncs: cfg.OverloadedTemplateFuncs,
+ PathSpec: ps,
+ ContentSpec: contentSpec,
+ SourceSpec: sp,
+ ResourceSpec: resourceSpec,
+ Cfg: cfg.Language,
+ Language: cfg.Language,
+ Site: cfg.Site,
+ FileCaches: fileCaches,
+ BuildStartListeners: &Listeners{},
+ Timeout: time.Duration(timeoutms) * time.Millisecond,
+ globalErrHandler: &globalErrHandler{},
}
if cfg.Cfg.GetBool("templateMetrics") {
@@ -344,7 +348,9 @@ type DepsCfg struct {
// Template handling.
TemplateProvider ResourceProvider
- WithTemplate func(templ tpl.TemplateHandler) error
+ WithTemplate func(templ tpl.TemplateManager) error
+ // Used in tests
+ OverloadedTemplateFuncs map[string]interface{}
// i18n handling.
TranslationProvider ResourceProvider
diff --git a/hugolib/alias.go b/hugolib/alias.go
index 972f7b01c..c80e7d0d2 100644
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -15,6 +15,7 @@ package hugolib
import (
"bytes"
+ "errors"
"fmt"
"html/template"
"io"
@@ -31,27 +32,15 @@ import (
"github.com/gohugoio/hugo/tpl"
)
-const (
- alias = "<!DOCTYPE html><html><head><title>{{ .Permalink }}</title><link rel=\"canonical\" href=\"{{ .Permalink }}\"/><meta name=\"robots\" content=\"noindex\"><meta charset=\"utf-8\" /><meta http-equiv=\"refresh\" content=\"0; url={{ .Permalink }}\" /></head></html>"
- aliasXHtml = "<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>{{ .Permalink }}</title><link rel=\"canonical\" href=\"{{ .Permalink }}\"/><meta name=\"robots\" content=\"noindex\"><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" /><meta http-equiv=\"refresh\" content=\"0; url={{ .Permalink }}\" /></head></html>"
-)
-
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 {
- t tpl.TemplateFinder
+ t tpl.TemplateHandler
log *loggers.Logger
allowRoot bool
}
-func newAliasHandler(t tpl.TemplateFinder, l *loggers.Logger, allowRoot bool) aliasHandler {
+func newAliasHandler(t tpl.TemplateHandler, l *loggers.Logger, allowRoot bool) aliasHandler {
return aliasHandler{t, l, allowRoot}
}
@@ -60,33 +49,27 @@ type aliasPage struct {
page.Page
}
-func (a aliasHandler) renderAlias(isXHTML bool, permalink string, p page.Page) (io.Reader, error) {
- t := "alias"
- if isXHTML {
- t = "alias-xhtml"
- }
+func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, error) {
var templ tpl.Template
var found bool
- if a.t != nil {
- templ, found = a.t.Lookup("alias.html")
- }
-
+ templ, found = a.t.Lookup("alias.html")
if !found {
- def := defaultAliasTemplates.Lookup(t)
- if def != nil {
- templ = &tpl.TemplateAdapter{Template: def}
+ // TODO(bep) consolidate
+ templ, found = a.t.Lookup("_internal/alias.html")
+ if !found {
+ return nil, errors.New("no alias template found")
}
-
}
+
data := aliasPage{
permalink,
p,
}
buffer := new(bytes.Buffer)
- err := templ.Execute(buffer, data)
+ err := a.t.Execute(templ, buffer, data)
if err != nil {
return nil, err
}
@@ -100,8 +83,6 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format
func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
handler := newAliasHandler(s.Tmpl, s.Log, allowRoot)
- isXHTML := strings.HasSuffix(path, ".xhtml")
-
s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
targetPath, err := handler.targetPathAlias(path)
@@ -109,7 +90,7 @@ func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFo
return err
}
- aliasContent, err := handler.renderAlias(isXHTML, permalink, p)
+ aliasContent, err := handler.renderAlias(permalink, p)
if err != nil {
return err
}
diff --git a/hugolib/case_insensitive_test.go b/hugolib/case_insensitive_test.go
index a8616ab06..42b9d7ef6 100644
--- a/hugolib/case_insensitive_test.go
+++ b/hugolib/case_insensitive_test.go
@@ -14,7 +14,6 @@
package hugolib
import (
- "fmt"
"path/filepath"
"testing"
@@ -232,76 +231,3 @@ Page2: {{ $page2.Params.ColoR }}
"index2|Site: yellow|",
)
}
-
-// TODO1
-func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
- t.Parallel()
-
- noOp := func(s string) string {
- return s
- }
-
- for _, config := range []struct {
- suffix string
- templateFixer func(s string) string
- }{
- //{"amber", amberFixer},
- {"html", noOp},
- //{"ace", noOp},
- } {
- doTestCaseInsensitiveConfigurationForTemplateEngine(t, config.suffix, config.templateFixer)
-
- }
-
-}
-
-func doTestCaseInsensitiveConfigurationForTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
- c := qt.New(t)
- mm := afero.NewMemMapFs()
-
- caseMixingTestsWriteCommonSources(t, mm)
-
- cfg, err := LoadConfigDefault(mm)
- c.Assert(err, qt.IsNil)
-
- fs := hugofs.NewFrom(mm, cfg)
-
- th := newTestHelper(cfg, fs, t)
-
- t.Log("Testing", suffix)
-
- templTemplate := `
-p
- |
- | Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }}
- | Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
- | {{ .Content }}
-
-`
-
- templ := templateFixer(templTemplate)
-
- t.Log(templ)
-
- writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
-
- sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
-
- if err != nil {
- t.Fatalf("Failed to create sites: %s", err)
- }
-
- err = sites.Build(BuildCfg{})
-
- if err != nil {
- t.Fatalf("Failed to build sites: %s", err)
- }
-
- th.assertFileContent(filepath.Join("public", "nn", "sect1", "page1", "index.html"),
- "Page Colors: red|heavenly",
- "Site Colors: green|yellow",
- "Shortcode Page: red|heavenly",
- "Shortcode Site: green|yellow",
- )
-
-}
diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go
index 64f2203e9..a998b85b7 100644
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -27,7 +27,6 @@ import (
"github.com/gohugoio/hugo/deps"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/tpl"
)
const (
@@ -334,18 +333,13 @@ func TestShortcodeTweet(t *testing.T) {
cfg.Set("privacy", this.privacy)
- withTemplate := func(templ tpl.TemplateHandler) error {
- templ.(tpl.TemplateTestMocker).SetFuncs(tweetFuncMap)
- return nil
- }
-
writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
title: Shorty
---
%s`, this.in))
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: tweetFuncMap}, BuildCfg{})
th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected)
@@ -389,18 +383,13 @@ func TestShortcodeInstagram(t *testing.T) {
th = newTestHelper(cfg, fs, t)
)
- withTemplate := func(templ tpl.TemplateHandler) error {
- templ.(tpl.TemplateTestMocker).SetFuncs(instagramFuncMap)
- return nil
- }
-
writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
title: Shorty
---
%s`, this.in))
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content | safeHTML }}`)
- buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: instagramFuncMap}, BuildCfg{})
th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected)
diff --git a/hugolib/hugo_modules_test.go b/hugolib/hugo_modules_test.go
index 9ba039c74..40185e051 100644
--- a/hugolib/hugo_modules_test.go
+++ b/hugolib/hugo_modules_test.go
@@ -42,10 +42,10 @@ import (
func TestHugoModules(t *testing.T) {
t.Parallel()
- if hugo.GoMinorVersion() < 12 {
+ if !isCI() || hugo.GoMinorVersion() < 12 {
// https://github.com/golang/go/issues/26794
// There were some concurrent issues with Go modules in < Go 12.
- t.Skip("skip this for Go <= 1.11 due to a bug in Go's stdlib")
+ t.Skip("skip this on local host and for Go <= 1.11 due to a bug in Go's stdlib")
}
if testing.Short() {
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index c0d75c09f..c71dcaa59 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -426,8 +426,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
return newHugoSites(cfg, sites...)
}
-func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateHandler) error) func(templ tpl.TemplateHandler) error {
- return func(templ tpl.TemplateHandler) error {
+func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateManager) error) func(templ tpl.TemplateManager) error {
+ return func(templ tpl.TemplateManager) error {
if err := templ.LoadTemplates(""); err != nil {
return err
}
diff --git a/hugolib/hugo_smoke_test.go b/hugolib/hugo_smoke_test.go
index 539e79729..406255d51 100644
--- a/hugolib/hugo_smoke_test.go
+++ b/hugolib/hugo_smoke_test.go
@@ -21,6 +21,27 @@ import (
qt "github.com/frankban/quicktest"
)
+// The most basic build test.
+func TestHello(t *testing.T) {
+ t.Parallel()
+ b := newTestSitesBuilder(t)
+ b.WithConfigFile("toml", `
+baseURL="https://example.org"
+disableKinds = ["taxonomy", "taxonomyTerm", "section", "page"]
+`)
+ b.WithContent("p1", `
+---
+title: Page
+---
+
+`)
+ b.WithTemplates("index.html", `Site: {{ .Site.Language.Lang | upper }}`)
+
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/index.html", `Site: EN`)
+}
+
func TestSmoke(t *testing.T) {
t.Parallel()
diff --git a/hugolib/page.go b/hugolib/page.go
index b0e8c4359..56202f5e0 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -480,7 +480,7 @@ func (p *pageState) Render(layout ...string) template.HTML {
templ, _ = p.s.Tmpl.Lookup(layout + ".html")
}
if templ != nil {
- res, err := executeToString(templ, p)
+ res, err := executeToString(p.s.Tmpl, templ, p)
if err != nil {
p.s.SendError(p.wrapError(errors.Wrapf(err, ".Render: failed to execute template %q v", layout)))
return ""
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index 9697468ff..d3a32e15c 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -411,10 +411,10 @@ func (t targetPathsHolder) targetPaths() page.TargetPaths {
return t.paths
}
-func executeToString(templ tpl.Template, data interface{}) (string, error) {
+func executeToString(h tpl.TemplateHandler, templ tpl.Template, data interface{}) (string, error) {
b := bp.GetBuffer()
defer bp.PutBuffer(b)
- if err := templ.Execute(b, data); err != nil {
+ if err := h.Execute(templ, b, data); err != nil {
return "", err
}
return b.String(), nil
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index ff037a3cc..dc8bc821c 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -459,7 +459,7 @@ func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) {
}
cnt := content(p)
- if cnt != "<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup></p>\n<section class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\" role=\"doc-endnote\">\n<p>Many people say so. <a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;</a></p>\n</li>\n</ol>\n</section>" {
+ if cnt != "<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup></p>\n<section class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\" role=\"doc-endnote\">\n<p>Many people say so.<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#8617;</a></p>\n</li>\n</ol>\n</section>" {
t.Fatalf("Got content:\n%q", cnt)
}
}
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index 5e916aeec..69bcb6d4f 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -393,7 +393,7 @@ func renderShortcode(
}
- result, err := renderShortcodeWithPage(tmpl, data)
+ result, err := renderShortcodeWithPage(s.Tmpl, tmpl, data)
if err != nil && sc.isInline {
fe := herrors.ToFileError("html", err)
@@ -634,11 +634,11 @@ func replaceShortcodeTokens(source []byte, replacements map[string]string) ([]by
return source, nil
}
-func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) (string, error) {
+func renderShortcodeWithPage(h tpl.TemplateHandler, tmpl tpl.Template, data *ShortcodeWithPage) (string, error) {
buffer := bp.GetBuffer()
defer bp.PutBuffer(buffer)
- err := tmpl.Execute(buffer, data)
+ err := h.Execute(tmpl, buffer, data)
if err != nil {
return "", _errors.Wrap(err, "failed to process shortcode")
}
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index fdf37b6c2..5e71db501 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -36,12 +36,12 @@ import (
qt "github.com/frankban/quicktest"
)
-func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error) {
+func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateManager) error) {
t.Helper()
CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
}
-func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error, expectError bool) {
+func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateManager) error, expectError bool) {
t.Helper()
cfg, fs := newTestCfg()
@@ -95,7 +95,7 @@ func TestNonSC(t *testing.T) {
// Issue #929
func TestHyphenatedSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
return nil
@@ -107,7 +107,7 @@ func TestHyphenatedSC(t *testing.T) {
// Issue #1753
func TestNoTrailingNewline(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/a.html", `{{ .Get 0 }}`)
return nil
}
@@ -117,7 +117,7 @@ func TestNoTrailingNewline(t *testing.T) {
func TestPositionalParamSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 0 }}`)
return nil
}
@@ -131,7 +131,7 @@ func TestPositionalParamSC(t *testing.T) {
func TestPositionalParamIndexOutOfBounds(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ with .Get 1 }}{{ . }}{{ else }}Missing{{ end }}`)
return nil
}
@@ -141,7 +141,7 @@ func TestPositionalParamIndexOutOfBounds(t *testing.T) {
// #5071
func TestShortcodeRelated(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/a.html", `{{ len (.Site.RegularPages.Related .Page) }}`)
return nil
}
@@ -151,7 +151,7 @@ func TestShortcodeRelated(t *testing.T) {
func TestShortcodeInnerMarkup(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("shortcodes/a.html", `<div>{{ .Inner }}</div>`)
tem.AddTemplate("shortcodes/b.html", `**Bold**: <div>{{ .Inner }}</div>`)
return nil
@@ -175,7 +175,7 @@ func TestShortcodeInnerMarkup(t *testing.T) {
func TestNamedParamSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
return nil
}
@@ -190,7 +190,7 @@ func TestNamedParamSC(t *testing.T) {
// Issue #2294
func TestNestedNamedMissingParam(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) 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>`)
@@ -203,7 +203,7 @@ func TestNestedNamedMissingParam(t *testing.T) {
func TestIsNamedParamsSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/bynameorposition.html", `{{ with .Get "id" }}Named: {{ . }}{{ else }}Pos: {{ .Get 0 }}{{ end }}`)
tem.AddTemplate("_internal/shortcodes/ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
return nil
@@ -216,7 +216,7 @@ func TestIsNamedParamsSC(t *testing.T) {
func TestInnerSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
return nil
}
@@ -227,7 +227,7 @@ func TestInnerSC(t *testing.T) {
func TestInnerSCWithMarkdown(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
// Note: In Hugo 0.55 we made it so any outer {{%'s inner content was rendered as part of the surrounding
// markup. This solved lots of problems, but it also meant that this test had to be adjusted.
tem.AddTemplate("_internal/shortcodes/wrapper.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
@@ -250,7 +250,7 @@ func TestEmbeddedSC(t *testing.T) {
func TestNestedSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
tem.AddTemplate("_internal/shortcodes/scn2.html", `<div>SC2</div>`)
return nil
@@ -262,7 +262,7 @@ func TestNestedSC(t *testing.T) {
func TestNestedComplexSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) 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-`)
@@ -278,7 +278,7 @@ func TestNestedComplexSC(t *testing.T) {
func TestParentShortcode(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) 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 }}`)
@@ -333,7 +333,7 @@ func TestFigureLinkWithTargetAndRel(t *testing.T) {
// #1642
func TestShortcodeWrappedInPIssue(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
+ wt := func(tem tpl.TemplateManager) error {
tem.AddTemplate("_internal/shortcodes/bug.html", `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`)
return nil
}
@@ -564,7 +564,7 @@ title: "Foo"
sources[i] = [2]string{filepath.FromSlash(test.contentPath), test.content}
}
- addTemplates := func(templ tpl.TemplateHandler) error {
+ addTemplates := func(templ tpl.TemplateManager) error {
templ.AddTemplate("_default/single.html", "{{.Content}} Word Count: {{ .WordCount }}")
templ.AddTemplate("_internal/shortcodes/b.html", `b`)
diff --git a/hugolib/site.go b/hugolib/site.go
index 1df7d6076..67ddff4d9 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -474,7 +474,7 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) {
// The site will have a template system loaded and ready to use.
// Note: This is mainly used in single site tests.
// TODO(bep) test refactor -- remove
-func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
+func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
v := viper.New()
if err := loadDefaultSettingsFor(v); err != nil {
return nil, err
@@ -486,7 +486,7 @@ func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateHandler) error) (
// The site will have a template system loaded and ready to use.
// Note: This is mainly used in single site tests.
// TODO(bep) test refactor -- remove
-func NewEnglishSite(withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
+func NewEnglishSite(withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
v := viper.New()
if err := loadDefaultSettingsFor(v); err != nil {
return nil, err
@@ -495,8 +495,8 @@ func NewEnglishSite(withTemplate ...func(templ tpl.TemplateHandler) error) (*Sit
}
// newSiteForLang creates a new site in the given language.
-func newSiteForLang(lang *langs.Language, withTemplate ...func(templ tpl.TemplateHandler) error) (*Site, error) {
- withTemplates := func(templ tpl.TemplateHandler) error {
+func newSiteForLang(lang *langs.Language, withTemplate ...func(templ tpl.TemplateManager) error) (*Site, error) {
+ withTemplates := func(templ tpl.TemplateManager) error {
for _, wt := range withTemplate {
if err := wt(templ); err != nil {
return err
@@ -1589,7 +1589,7 @@ func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.W
return nil
}
- if err = templ.Execute(w, d); err != nil {
+ if err = s.Tmpl.Execute(templ, w, d); err != nil {
return _errors.Wrapf(err, "render of %q failed", name)
}
return
diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go
index 4dfb61ecd..27fbf11d8 100644
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -50,7 +50,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg}
- depsCfg.WithTemplate = func(templ tpl.TemplateHandler) error {
+ depsCfg.WithTemplate = func(templ tpl.TemplateManager) error {
if !internal {
templ.AddTemplate("sitemap.xml", sitemapTemplate)
}
diff --git a/hugolib/template_engines_test.go b/hugolib/template_engines_test.go
deleted file mode 100644
index ea0fca0b6..000000000
--- a/hugolib/template_engines_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2017 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package hugolib
-
-import (
- "fmt"
- "path/filepath"
- "testing"
-
- "github.com/gohugoio/hugo/deps"
-)
-
-// TODO1
-func TestAllTemplateEngines(t *testing.T) {
- noOp := func(s string) string {
- return s
- }
-
- for _, config := range []struct {
- suffix string
- templateFixer func(s string) string
- }{
- //{"amber", amberFixer},
- {"html", noOp},
- //{"ace", noOp},
- } {
- config := config
- t.Run(config.suffix,
- func(t *testing.T) {
- t.Parallel()
- doTestTemplateEngine(t, config.suffix, config.templateFixer)
- })
- }
-
-}
-
-func doTestTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
-
- cfg, fs := newTestCfg()
-
- t.Log("Testing", suffix)
-
- templTemplate := `
-p
- |
- | Page Title: {{ .Title }}
- br
- | Page Content: {{ .Content }}
- br
- | {{ title "hello world" }}
-
-`
-
- templShortcodeTemplate := `
-p
- |
- | Shortcode: {{ .IsNamedParams }}
-`
-
- templ := templateFixer(templTemplate)
- shortcodeTempl := templateFixer(templShortcodeTemplate)
-
- writeSource(t, fs, filepath.Join("content", "p.md"), `
----
-title: My Title
----
-My Content
-
-Shortcode: {{< myShort >}}
-
-`)
-
- writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
- writeSource(t, fs, filepath.Join("layouts", "shortcodes", fmt.Sprintf("myShort.%s", suffix)), shortcodeTempl)
-
- s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
- th := newTestHelper(s.Cfg, s.Fs, t)
-
- th.assertFileContent(filepath.Join("public", "p", "index.html"),
- "Page Title: My Title",
- "My Content",
- "Hello World",
- "Shortcode: false",
- )
-
-}
diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
index d861a5e09..ea1ee9674 100644
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -845,9 +845,9 @@ func newTestSitesFromConfig(t testing.TB, afs afero.Fs, tomlConfig string, layou
return th, h
}
-func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.TemplateHandler) error {
+func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.TemplateManager) error {
- return func(templ tpl.TemplateHandler) error {
+ return func(templ tpl.TemplateManager) error {
for i := 0; i < len(additionalTemplates); i += 2 {
err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
if err != nil {
diff --git a/langs/language.go b/langs/language.go
index 67cb3689a..0e04324e9 100644
--- a/langs/language.go
+++ b/langs/language.go
@@ -16,6 +16,7 @@ package langs
import (
"sort"
"strings"
+ "sync"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
@@ -56,7 +57,9 @@ type Language struct {
// These are params declared in the [params] section of the language merged with the
// site's params, the most specific (language) wins on duplicate keys.
- params map[string]interface{}
+ params map[string]interface{}
+ paramsMu sync.Mutex
+ paramsSet bool
// These are config values, i.e. the settings declared outside of the [params] section of the language.
// This is the map Hugo looks in when looking for configuration values (baseURL etc.).
@@ -123,7 +126,15 @@ func (l Languages) Less(i, j int) bool {
func (l Languages) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
// Params retunrs language-specific params merged with the global params.
-func (l *Language) Params() map[string]interface{} {
+func (l *Language) Params() maps.Params {
+ // TODO(bep) this construct should not be needed. Create the
+ // language params in one go.
+ l.paramsMu.Lock()
+ defer l.paramsMu.Unlock()
+ if !l.paramsSet {
+ maps.ToLower(l.params)
+ l.paramsSet = true
+ }
return l.params
}
@@ -163,7 +174,12 @@ func (l Languages) IsMultihost() bool {
// SetParam sets a param with the given key and value.
// SetParam is case-insensitive.
func (l *Language) SetParam(k string, v interface{}) {
- l.params[strings.ToLower(k)] = v
+ l.paramsMu.Lock()
+ defer l.paramsMu.Unlock()
+ if l.paramsSet {
+ panic("params cannot be changed once set")
+ }
+ l.params[k] = v
}
// GetBool returns the value associated with the key as a boolean.
diff --git a/resources/resource_transformers/templates/execute_as_template.go b/resources/resource_transformers/templates/execute_as_template.go
index 422f1bbe1..953cccc04 100644
--- a/resources/resource_transformers/templates/execute_as_template.go
+++ b/resources/resource_transformers/templates/execute_as_template.go
@@ -27,25 +27,27 @@ import (
type Client struct {
rs *resources.Spec
- textTemplate tpl.TemplateParseFinder
+ templateHandler tpl.TemplateHandler
+ textTemplate tpl.TemplateParseFinder
}
// New creates a new Client with the given specification.
-func New(rs *resources.Spec, textTemplate tpl.TemplateParseFinder) *Client {
+func New(rs *resources.Spec, h tpl.TemplateHandler, textTemplate tpl.TemplateParseFinder) *Client {
if rs == nil {
panic("must provice a resource Spec")
}
if textTemplate == nil {
panic("must provide a textTemplate")
}
- return &Client{rs: rs, textTemplate: textTemplate}
+ return &Client{rs: rs, templateHandler: h, textTemplate: textTemplate}
}
type executeAsTemplateTransform struct {
- rs *resources.Spec
- textTemplate tpl.TemplateParseFinder
- targetPath string
- data interface{}
+ rs *resources.Spec
+ textTemplate tpl.TemplateParseFinder
+ templateHandler tpl.TemplateHandler
+ targetPath string
+ data interface{}
}
func (t *executeAsTemplateTransform) Key() internal.ResourceTransformationKey {
@@ -61,14 +63,15 @@ func (t *executeAsTemplateTransform) Transform(ctx *resources.ResourceTransforma
ctx.OutPath = t.targetPath
- return templ.Execute(ctx.To, t.data)
+ return t.templateHandler.Execute(templ, ctx.To, t.data)
}
func (c *Client) ExecuteAsTemplate(res resources.ResourceTransformer, targetPath string, data interface{}) (resource.Resource, error) {
return res.Transform(&executeAsTemplateTransform{
- rs: c.rs,
- targetPath: helpers.ToSlashTrimLeading(targetPath),
- textTemplate: c.textTemplate,
- data: data,
+ rs: c.rs,
+ targetPath: helpers.ToSlashTrimLeading(targetPath),
+ templateHandler: c.templateHandler,
+ textTemplate: c.textTemplate,
+ data: data,
})
}
diff --git a/scripts/fork_go_templates/main.go b/scripts/fork_go_templates/main.go
index 148785883..1cae78a43 100644
--- a/scripts/fork_go_templates/main.go
+++ b/scripts/fork_go_templates/main.go
@@ -57,6 +57,8 @@ var (
`"internal/fmtsort"`, `"github.com/gohugoio/hugo/tpl/internal/go_templates/fmtsort"`,
// Rename types and function that we want to overload.
"type state struct", "type stateOld struct",
+ "func (s *state) evalFunction", "func (s *state) evalFunctionOld",
+ "func (s *state) evalField(", "func (s *state) evalFieldOld(",
)
htmlTemplateReplacers = strings.NewReplacer(
diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go
index d715aeb00..d41a3b1da 100644
--- a/tpl/collections/apply.go
+++ b/tpl/collections/apply.go
@@ -106,17 +106,8 @@ func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value,
func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
if !strings.ContainsRune(fname, '.') {
- templ, ok := ns.deps.Tmpl.(tpl.TemplateFuncsGetter)
- if !ok {
- panic("Needs a tpl.TemplateFuncsGetter")
- }
- fm := templ.GetFuncs()
- fn, found := fm[fname]
- if !found {
- return reflect.Value{}, false
- }
-
- return reflect.ValueOf(fn), true
+ templ := ns.deps.Tmpl.(tpl.TemplateFuncGetter)
+ return templ.GetFunc(fname)
}
ss := strings.SplitN(fname, ".", 2)
diff --git a/tpl/collections/apply_test.go b/tpl/collections/apply_test.go
index 96dd8896b..5b21d5a97 100644
--- a/tpl/collections/apply_test.go
+++ b/tpl/collections/apply_test.go
@@ -14,6 +14,8 @@
package collections
import (
+ "io"
+ "reflect"
"testing"
"fmt"
@@ -33,10 +35,17 @@ func (templateFinder) LookupVariant(name string, variants tpl.TemplateVariants)
return nil, false, false
}
-func (templateFinder) GetFuncs() map[string]interface{} {
- return map[string]interface{}{
- "print": fmt.Sprint,
+func (templateFinder) Execute(t tpl.Template, wr io.Writer, data interface{}) error {
+ return nil
+}
+
+func (templateFinder) GetFunc(name string) (reflect.Value, bool) {
+ if name == "dobedobedo" {
+ return reflect.Value{}, false
}
+
+ return reflect.ValueOf(fmt.Sprint), true
+
}
func TestApply(t *testing.T) {
diff --git a/tpl/internal/go_templates/texttemplate/exec.go b/tpl/internal/go_templates/texttemplate/exec.go
index d47793320..db64edcb2 100644
--- a/tpl/internal/go_templates/texttemplate/exec.go
+++ b/tpl/internal/go_templates/texttemplate/exec.go
@@ -558,7 +558,7 @@ func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ide
return s.evalField(dot, ident[n-1], node, args, final, receiver)
}
-func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalFunctionOld(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
s.at(node)
name := node.Ident
function, ok := findFunction(name, s.tmpl)
@@ -571,7 +571,7 @@ func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
// The 'final' argument represents the return value from the preceding
// value of the pipeline, if any.
-func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
+func (s *state) evalFieldOld(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
if !receiver.IsValid() {
if s.tmpl.option.missingKey == mapError { // Treat invalid value as missing map key.
s.errorf("nil data; no entry for key %q", fieldName)
diff --git a/tpl/internal/go_templates/texttemplate/hugo_exec.go b/tpl/internal/go_templates/texttemplate/hugo_exec.go
deleted file mode 100644
index cc3aeb2f1..000000000
--- a/tpl/internal/go_templates/texttemplate/hugo_exec.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package template
-
-import (
- "io"
-
- "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
-)
-
-/*
-
-This files contains the Hugo related addons. All the other files in this
-package is auto generated.
-
-*/
-
-// state represents the state of an execution. It's not part of the
-// template so that multiple executions of the same template
-// can execute in parallel.
-type state struct {
- tmpl *Template
- wr io.Writer
- node parse.Node // current node, for errors
- vars []variable // push-down stack of variable values.
- depth int // the height of the stack of executing templates.
-}
diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go
index e3252e7aa..c2738ec53 100644
--- a/tpl/internal/go_templates/texttemplate/hugo_template.go
+++ b/tpl/internal/go_templates/texttemplate/hugo_template.go
@@ -27,15 +27,31 @@ package is auto generated.
*/
-// TODO1 name
+// Preparer prepares the template before execution.
type Preparer interface {
Prepare() (*Template, error)
}
-type TemplateExecutor struct {
+// ExecHelper allows some custom eval hooks.
+type ExecHelper interface {
+ GetFunc(name string) (reflect.Value, bool)
+ GetMapValue(receiver, key reflect.Value) (reflect.Value, bool)
}
-func (t *TemplateExecutor) Execute(p Preparer, wr io.Writer, data interface{}) error {
+// Executer executes a given template.
+type Executer interface {
+ Execute(p Preparer, wr io.Writer, data interface{}) error
+}
+
+type executer struct {
+ helper ExecHelper
+}
+
+func NewExecuter(helper ExecHelper) Executer {
+ return &executer{helper: helper}
+}
+
+func (t *executer) Execute(p Preparer, wr io.Writer, data interface{}) error {
tmpl, err := p.Prepare()
if err != nil {
return err
@@ -47,9 +63,10 @@ func (t *TemplateExecutor) Execute(p Preparer, wr io.Writer, data interface{}) e
}
state := &state{
- tmpl: tmpl,
- wr: wr,
- vars: []variable{{"$", value}},
+ helper: t.helper,
+ tmpl: tmpl,
+ wr: wr,
+ vars: []variable{{"$", value}},
}
return tmpl.executeWithState(state, value)
@@ -62,24 +79,131 @@ func (t *Template) Prepare() (*Template, error) {
return t, nil
}
+func (t *Template) executeWithState(state *state, value reflect.Value) (err error) {
+ defer errRecover(&err)
+ if t.Tree == nil || t.Root == nil {
+ state.errorf("%q is an incomplete or empty template", t.Name())
+ }
+ state.walk(value, t.Root)
+ return
+}
+
// Below are modifed structs etc.
// state represents the state of an execution. It's not part of the
// template so that multiple executions of the same template
// can execute in parallel.
type state struct {
- tmpl *Template
- wr io.Writer
- node parse.Node // current node, for errors
- vars []variable // push-down stack of variable values.
- depth int // the height of the stack of executing templates.
+ tmpl *Template
+ helper ExecHelper
+ wr io.Writer
+ node parse.Node // current node, for errors
+ vars []variable // push-down stack of variable values.
+ depth int // the height of the stack of executing templates.
}
-func (t *Template) executeWithState(state *state, value reflect.Value) (err error) {
- defer errRecover(&err)
- if t.Tree == nil || t.Root == nil {
- state.errorf("%q is an incomplete or empty template", t.Name())
+func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
+ s.at(node)
+ name := node.Ident
+
+ var function reflect.Value
+ var ok bool
+ if s.helper != nil {
+ function, ok = s.helper.GetFunc(name)
}
- state.walk(value, t.Root)
- return
+
+ if !ok {
+ function, ok = findFunction(name, s.tmpl)
+ }
+
+ if !ok {
+ s.errorf("%q is not a defined function", name)
+ }
+ return s.evalCall(dot, function, cmd, name, args, final)
+}
+
+// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
+// The 'final' argument represents the return value from the preceding
+// value of the pipeline, if any.
+func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
+ if !receiver.IsValid() {
+ if s.tmpl.option.missingKey == mapError { // Treat invalid value as missing map key.
+ s.errorf("nil data; no entry for key %q", fieldName)
+ }
+ return zero
+ }
+ typ := receiver.Type()
+ receiver, isNil := indirect(receiver)
+ if receiver.Kind() == reflect.Interface && isNil {
+ // Calling a method on a nil interface can't work. The
+ // MethodByName method call below would panic.
+ s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
+ return zero
+ }
+
+ // Unless it's an interface, need to get to a value of type *T to guarantee
+ // we see all methods of T and *T.
+ ptr := receiver
+ if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() {
+ ptr = ptr.Addr()
+ }
+ if method := ptr.MethodByName(fieldName); method.IsValid() {
+ return s.evalCall(dot, method, node, fieldName, args, final)
+ }
+ hasArgs := len(args) > 1 || final != missingVal
+ // It's not a method; must be a field of a struct or an element of a map.
+ switch receiver.Kind() {
+ case reflect.Struct:
+ tField, ok := receiver.Type().FieldByName(fieldName)
+ if ok {
+ field := receiver.FieldByIndex(tField.Index)
+ if tField.PkgPath != "" { // field is unexported
+ s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
+ }
+ // If it's a function, we must call it.
+ if hasArgs {
+ s.errorf("%s has arguments but cannot be invoked as function", fieldName)
+ }
+ return field
+ }
+ case reflect.Map:
+ // If it's a map, attempt to use the field name as a key.
+ nameVal := reflect.ValueOf(fieldName)
+ if nameVal.Type().AssignableTo(receiver.Type().Key()) {
+ if hasArgs {
+ s.errorf("%s is not a method but has arguments", fieldName)
+ }
+ var result reflect.Value
+ if s.helper != nil {
+ result, _ = s.helper.GetMapValue(receiver, nameVal)
+ } else {
+ result = receiver.MapIndex(nameVal)
+ }
+ if !result.IsValid() {
+ switch s.tmpl.option.missingKey {
+ case mapInvalid:
+ // Just use the invalid value.
+ case mapZeroValue:
+ result = reflect.Zero(receiver.Type().Elem())
+ case mapError:
+ s.errorf("map has no entry for key %q", fieldName)
+ }
+ }
+ return result
+ }
+ case reflect.Ptr:
+ etyp := receiver.Type().Elem()
+ if etyp.Kind() == reflect.Struct {
+ if _, ok := etyp.FieldByName(fieldName); !ok {
+ // If there's no such field, say "can't evaluate"
+ // instead of "nil pointer evaluating".
+ break
+ }
+ }
+ if isNil {
+ s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
+ }
+ }
+ s.errorf("can't evaluate field %s in type %s", fieldName, typ)
+ panic("not reached")
}
diff --git a/tpl/internal/go_templates/texttemplate/hugo_template_test.go b/tpl/internal/go_templates/texttemplate/hugo_template_test.go
new file mode 100644
index 000000000..2424a0a48
--- /dev/null
+++ b/tpl/internal/go_templates/texttemplate/hugo_template_test.go
@@ -0,0 +1,71 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package template
+
+import (
+ "bytes"
+ "reflect"
+ "strings"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+type TestStruct struct {
+ S string
+ M map[string]string
+}
+
+type execHelper struct {
+}
+
+func (e *execHelper) GetFunc(name string) (reflect.Value, bool) {
+ if name == "print" {
+ return zero, false
+ }
+ return reflect.ValueOf(func(s string) string {
+ return "hello " + s
+ }), true
+}
+
+func (e *execHelper) GetMapValue(m, key reflect.Value) (reflect.Value, bool) {
+ key = reflect.ValueOf(strings.ToLower(key.String()))
+ return m.MapIndex(key), true
+}
+
+func TestTemplateExecutor(t *testing.T) {
+ c := qt.New(t)
+
+ templ, err := New("").Parse(`
+{{ print "foo" }}
+{{ printf "hugo" }}
+Map: {{ .M.A }}
+
+`)
+
+ c.Assert(err, qt.IsNil)
+
+ ex := NewExecuter(&execHelper{})
+
+ var b bytes.Buffer
+ data := TestStruct{S: "sv", M: map[string]string{"a": "av"}}
+
+ c.Assert(ex.Execute(templ, &b, data), qt.IsNil)
+ got := b.String()
+
+ c.Assert(got, qt.Contains, "foo")
+ c.Assert(got, qt.Contains, "hello hugo")
+ c.Assert(got, qt.Contains, "Map: av")
+
+}
diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
index 3380a5a9e..bfc3a82d3 100644
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -139,7 +139,7 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
w = b
}
- if err := templ.Execute(w, context); err != nil {
+ if err := ns.deps.Tmpl.Execute(templ, w, context); err != nil {
return "", err
}
diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go
index 20c4d1b3a..9a7b29696 100644
--- a/tpl/resources/resources.go
+++ b/tpl/resources/resources.go
@@ -53,7 +53,7 @@ func New(deps *deps.Deps) (*Namespace, error) {
integrityClient: integrity.New(deps.ResourceSpec),
minifyClient: minifier.New(deps.ResourceSpec),
postcssClient: postcss.New(deps.ResourceSpec),
- templatesClient: templates.New(deps.ResourceSpec, deps.TextTmpl),
+ templatesClient: templates.New(deps.ResourceSpec, deps.Tmpl, deps.TextTmpl),
}, nil
}
diff --git a/tpl/template.go b/tpl/template.go
index 63bf42730..db715c306 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -14,39 +14,22 @@
package tpl
import (
- "fmt"
- "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ "reflect"
"io"
- "path/filepath"
"regexp"
- "strings"
- "time"
"github.com/gohugoio/hugo/output"
- "github.com/gohugoio/hugo/common/herrors"
-
- "github.com/gohugoio/hugo/hugofs"
-
- "github.com/spf13/afero"
-
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
- "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
-
- bp "github.com/gohugoio/hugo/bufferpool"
- "github.com/gohugoio/hugo/metrics"
- "github.com/pkg/errors"
)
-var (
- _ TemplateExecutor = (*TemplateAdapter)(nil)
- _ TemplateInfoProvider = (*TemplateAdapter)(nil)
-)
+var _ TemplateInfoProvider = (*TemplateInfo)(nil)
-// TemplateHandler manages the collection of templates.
-type TemplateHandler interface {
- TemplateFinder
+// TemplateManager manages the collection of templates.
+type TemplateManager interface {
+ TemplateHandler
+ TemplateFuncGetter
AddTemplate(name, tpl string) error
AddLateTemplate(name, tpl string) error
LoadTemplates(prefix string) error
@@ -68,6 +51,12 @@ type TemplateFinder interface {
TemplateLookupVariant
}
+// TemplateHandler finds and executes templates.
+type TemplateHandler interface {
+ TemplateFinder
+ Execute(t Template, wr io.Writer, data interface{}) error
+}
+
type TemplateLookup interface {
Lookup(name string) (Template, bool)
}
@@ -87,8 +76,8 @@ type TemplateLookupVariant interface {
// Template is the common interface between text/template and html/template.
type Template interface {
- Execute(wr io.Writer, data interface{}) error
Name() string
+ Prepare() (*texttemplate.Template, error)
}
// TemplateInfoProvider provides some contextual information about a template.
@@ -107,30 +96,15 @@ type TemplateParseFinder interface {
TemplateFinder
}
-// TemplateExecutor adds some extras to Template.
-type TemplateExecutor interface {
- Template
- ExecuteToString(data interface{}) (string, error)
- Tree() string
-}
-
// TemplateDebugger prints some debug info to stdoud.
type TemplateDebugger interface {
Debug()
}
-// TemplateAdapter implements the TemplateExecutor interface.
-type TemplateAdapter struct {
+// TemplateInfo wraps a Template with some additional information.
+type TemplateInfo struct {
Template
- Metrics metrics.Provider
-
Info Info
-
- // The filesystem where the templates are stored.
- Fs afero.Fs
-
- // Maps to base template if relevant.
- NameBaseTemplateName map[string]string
}
var baseOfRe = regexp.MustCompile("template: (.*?):")
@@ -143,165 +117,11 @@ func extractBaseOf(err string) string {
return ""
}
-// Execute executes the current template. The actual execution is performed
-// by the embedded text or html template, but we add an implementation here so
-// we can add a timer for some metrics.
-func (t *TemplateAdapter) Execute(w io.Writer, data interface{}) (execErr error) {
- defer func() {
- // Panics in templates are a little bit too common (nil pointers etc.)
- // See https://github.com/gohugoio/hugo/issues/5327
- if r := recover(); r != nil {
- execErr = t.addFileContext(t.Name(), fmt.Errorf(`panic in Execute: %s. See "https://github.com/gohugoio/hugo/issues/5327" for the reason why we cannot provide a better error message for this`, r))
- }
- }()
-
- if t.Metrics != nil {
- defer t.Metrics.MeasureSince(t.Name(), time.Now())
- }
-
- execErr = t.Template.Execute(w, data)
- if execErr != nil {
- execErr = t.addFileContext(t.Name(), execErr)
- }
-
- return
-}
-
-func (t *TemplateAdapter) TemplateInfo() Info {
+func (t *TemplateInfo) TemplateInfo() Info {
return t.Info
}
-// The identifiers may be truncated in the log, e.g.
-// "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image"
-var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`)
-
-func (t *TemplateAdapter) extractIdentifiers(line string) []string {
- m := identifiersRe.FindAllStringSubmatch(line, -1)
- identifiers := make([]string, len(m))
- for i := 0; i < len(m); i++ {
- identifiers[i] = m[i][1]
- }
- return identifiers
-}
-
-func (t *TemplateAdapter) addFileContext(name string, inerr error) error {
- if strings.HasPrefix(t.Name(), "_internal") {
- return inerr
- }
-
- f, realFilename, err := t.fileAndFilename(t.Name())
- if err != nil {
- return inerr
-
- }
- defer f.Close()
-
- master, hasMaster := t.NameBaseTemplateName[name]
-
- ferr := errors.Wrap(inerr, "execute of template failed")
-
- // Since this can be a composite of multiple template files (single.html + baseof.html etc.)
- // we potentially need to look in both -- and cannot rely on line number alone.
- lineMatcher := func(m herrors.LineMatcher) bool {
- if m.Position.LineNumber != m.LineNumber {
- return false
- }
- if !hasMaster {
- return true
- }
-
- identifiers := t.extractIdentifiers(m.Error.Error())
-
- for _, id := range identifiers {
- if strings.Contains(m.Line, id) {
- return true
- }
- }
- return false
- }
-
- fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
- if ok || !hasMaster {
- return fe
- }
-
- // Try the base template if relevant
- f, realFilename, err = t.fileAndFilename(master)
- if err != nil {
- return err
- }
- defer f.Close()
-
- fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
-
- if !ok {
- // Return the most specific.
- return ferr
-
- }
- return fe
-
-}
-
-func (t *TemplateAdapter) fileAndFilename(name string) (afero.File, string, error) {
- fs := t.Fs
- filename := filepath.FromSlash(name)
-
- fi, err := fs.Stat(filename)
- if err != nil {
- return nil, "", err
- }
- fim := fi.(hugofs.FileMetaInfo)
- meta := fim.Meta()
-
- f, err := meta.Open()
- if err != nil {
- return nil, "", errors.Wrapf(err, "failed to open template file %q:", filename)
- }
-
- return f, meta.Filename(), nil
-}
-
-// 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 == nil || tree.Root == nil {
- return ""
- }
- s := tree.Root.String()
-
- return s
-}
-
-// TemplateFuncsGetter allows to get a map of functions.
-type TemplateFuncsGetter interface {
- GetFuncs() map[string]interface{}
-}
-
-// 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{})
+// TemplateFuncGetter allows to find a template func by name.
+type TemplateFuncGetter interface {
+ GetFunc(name string) (reflect.Value, bool)
}
diff --git a/tpl/tplimpl/embedded/templates.autogen.go b/tpl/tplimpl/embedded/templates.autogen.go
index cb7fb9512..f64f18ee1 100644
--- a/tpl/tplimpl/embedded/templates.autogen.go
+++ b/tpl/tplimpl/embedded/templates.autogen.go
@@ -87,6 +87,7 @@ var EmbeddedTemplates = [][2]string{
{{ end }}
</sitemapindex>
`},
+ {`alias.html`, `<!DOCTYPE html><html><head><title>{{ .Permalink }}</title><link rel="canonical" href="{{ .Permalink }}"/><meta name="robots" content="noindex"><meta charset="utf-8" /><meta http-equiv="refresh" content="0; url={{ .Permalink }}" /></head></html>`},
{`disqus.html`, `{{- $pc := .Site.Config.Privacy.Disqus -}}
{{- if not $pc.Disable -}}
{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div>
diff --git a/tpl/tplimpl/embedded/templates/alias.html b/tpl/tplimpl/embedded/templates/alias.html
new file mode 100644
index 000000000..ee3f556e5
--- /dev/null
+++ b/tpl/tplimpl/embedded/templates/alias.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><head><title>{{ .Permalink }}</title><link rel="canonical" href="{{ .Permalink }}"/><meta name="robots" content="noindex"><meta charset="utf-8" /><meta http-equiv="refresh" content="0; url={{ .Permalink }}" /></head></html> \ No newline at end of file
diff --git a/tpl/tplimpl/shortcodes.go b/tpl/tplimpl/shortcodes.go
index b41725463..e5dbabdd8 100644
--- a/tpl/tplimpl/shortcodes.go
+++ b/tpl/tplimpl/shortcodes.go
@@ -61,11 +61,6 @@ func (s *shortcodeTemplates) fromVariants(variants tpl.TemplateVariants) (shortc
})
}
-// Get the most specific template given a full name, e.g gtag.no.amp.html.
-func (s *shortcodeTemplates) fromName(name string) (shortcodeVariant, bool) {
- return s.fromVariantsSlice(templateVariants(name))
-}
-
func (s *shortcodeTemplates) fromVariantsSlice(variants []string) (shortcodeVariant, bool) {
var (
bestMatch shortcodeVariant
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index 0feb3a0de..dd8de9067 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -15,6 +15,12 @@ package tplimpl
import (
"fmt"
+ "io"
+ "reflect"
+ "regexp"
+ "time"
+
+ "github.com/gohugoio/hugo/common/herrors"
"strings"
@@ -45,119 +51,196 @@ const (
)
var (
- _ tpl.TemplateHandler = (*templateHandler)(nil)
- _ tpl.TemplateDebugger = (*templateHandler)(nil)
- _ tpl.TemplateFuncsGetter = (*templateHandler)(nil)
- _ tpl.TemplateTestMocker = (*templateHandler)(nil)
- _ tpl.TemplateFinder = (*htmlTemplates)(nil)
- _ tpl.TemplateFinder = (*textTemplates)(nil)
- _ templateLoader = (*htmlTemplates)(nil)
- _ templateLoader = (*textTemplates)(nil)
- _ templateFuncsterTemplater = (*htmlTemplates)(nil)
- _ templateFuncsterTemplater = (*textTemplates)(nil)
+ _ tpl.TemplateManager = (*templateHandler)(nil)
+ _ tpl.TemplateHandler = (*templateHandler)(nil)
+ _ tpl.TemplateDebugger = (*templateHandler)(nil)
+ _ tpl.TemplateFuncGetter = (*templateHandler)(nil)
+ _ tpl.TemplateFinder = (*htmlTemplates)(nil)
+ _ tpl.TemplateFinder = (*textTemplates)(nil)
+ _ templateLoader = (*htmlTemplates)(nil)
+ _ templateLoader = (*textTemplates)(nil)
)
-type templateErr struct {
- name string
- err error
-}
+const (
+ shortcodesPathPrefix = "shortcodes/"
+ internalPathPrefix = "_internal/"
+)
-type templateLoader interface {
- handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error
- addTemplate(name, tpl string) (*templateContext, error)
- addLateTemplate(name, tpl string) error
-}
+// The identifiers may be truncated in the log, e.g.
+// "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image"
+var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`)
-type templateFuncsterTemplater interface {
- templateFuncsterSetter
- tpl.TemplateFinder
- setFuncs(funcMap map[string]interface{})
+var embeddedTemplatesAliases = map[string][]string{
+ "shortcodes/twitter.html": {"shortcodes/tweet.html"},
}
-type templateFuncsterSetter interface {
- setTemplateFuncster(f *templateFuncster)
-}
+const baseFileBase = "baseof"
-// templateHandler holds the templates in play.
-// It implements the templateLoader and tpl.TemplateHandler interfaces.
-type templateHandler struct {
- mu sync.Mutex
+func newTemplateAdapter(deps *deps.Deps) *templateHandler {
- // shortcodes maps shortcode name to template variants
- // (language, output format etc.) of that shortcode.
- shortcodes map[string]*shortcodeTemplates
+ common := &templatesCommon{
+ nameBaseTemplateName: make(map[string]string),
+ transformNotFound: make(map[string]bool),
+ }
- // templateInfo maps template name to some additional information about that template.
- // Note that for shortcodes that same information is embedded in the
- // shortcodeTemplates type.
- templateInfo map[string]tpl.Info
+ htmlT := &htmlTemplates{
+ t: template.New(""),
+ overlays: make(map[string]*template.Template),
+ templatesCommon: common,
+ }
- // text holds all the pure text templates.
- text *textTemplates
- html *htmlTemplates
+ textT := &textTemplates{
+ textTemplate: &textTemplate{t: texttemplate.New("")},
+ standalone: &textTemplate{t: texttemplate.New("")},
+ overlays: make(map[string]*texttemplate.Template),
+ templatesCommon: common,
+ }
+
+ h := &templateHandler{
+ Deps: deps,
+ layoutsFs: deps.BaseFs.Layouts.Fs,
+ templateHandlerCommon: &templateHandlerCommon{
+ shortcodes: make(map[string]*shortcodeTemplates),
+ templateInfo: make(map[string]tpl.Info),
+ html: htmlT,
+ text: textT,
+ },
+ }
- errors []*templateErr
+ common.handler = h
- // This is the filesystem to load the templates from. All the templates are
- // stored in the root of this filesystem.
- layoutsFs afero.Fs
+ return h
- *deps.Deps
}
-const (
- shortcodesPathPrefix = "shortcodes/"
- internalPathPrefix = "_internal/"
-)
+type htmlTemplates struct {
+ *templatesCommon
-// resolves _internal/shortcodes/param.html => param.html etc.
-func templateBaseName(typ templateType, name string) string {
- name = strings.TrimPrefix(name, internalPathPrefix)
- switch typ {
- case templateShortcode:
- return strings.TrimPrefix(name, shortcodesPathPrefix)
- default:
- panic("not implemented")
+ t *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
+}
+
+func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) {
+ templ := t.lookup(name)
+ if templ == nil {
+ return nil, false
}
+ return templ, true
}
-func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
- base := templateBaseName(templateShortcode, name)
+func (t *htmlTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ return t.handler.LookupVariant(name, variants)
+}
- shortcodename, variants := templateNameAndVariants(base)
+func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
+ _, err := t.addTemplateIn(t.clone, name, tpl)
+ return err
+}
- templs, found := t.shortcodes[shortcodename]
- if !found {
- templs = &shortcodeTemplates{}
- t.shortcodes[shortcodename] = templs
+func (t *htmlTemplates) addTemplate(name, tpl string) (*templateContext, error) {
+ return t.addTemplateIn(t.t, name, tpl)
+}
+
+func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) (*templateContext, error) {
+ templ, err := tt.New(name).Parse(tpl)
+ if err != nil {
+ return nil, err
}
- sv := shortcodeVariant{variants: variants, info: info, templ: templ}
+ typ := resolveTemplateType(name)
- i := templs.indexOf(variants)
+ c, err := applyTemplateTransformersToHMLTTemplate(typ, templ)
+ if err != nil {
+ return nil, err
+ }
- if i != -1 {
- // Only replace if it's an override of an internal template.
- if !isInternal(name) {
- templs.variants[i] = sv
- }
+ for k := range c.notFound {
+ t.transformNotFound[k] = true
+ }
+
+ if typ == templateShortcode {
+ t.handler.addShortcodeVariant(name, c.Info, templ)
} else {
- templs.variants = append(templs.variants, sv)
+ t.handler.templateInfo[name] = c.Info
}
+
+ return c, nil
}
-func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder {
- return struct {
- tpl.TemplateParser
- tpl.TemplateLookup
- tpl.TemplateLookupVariant
- }{
- tt,
- tt,
- new(nopLookupVariant),
+func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+
+ masterTpl := t.lookup(masterFilename)
+
+ if masterTpl == nil {
+ templ, err := onMissing(masterFilename)
+ if err != nil {
+ return err
+ }
+
+ masterTpl, err = t.t.New(overlayFilename).Parse(templ.template)
+ if err != nil {
+ return templ.errWithFileContext("parse master failed", err)
+ }
+ }
+
+ templ, err := onMissing(overlayFilename)
+ if err != nil {
+ return err
+ }
+
+ overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template)
+ if err != nil {
+ return templ.errWithFileContext("parse failed", err)
+ }
+
+ // The extra lookup is a workaround, see
+ // * https://github.com/golang/go/issues/16101
+ // * https://github.com/gohugoio/hugo/issues/2549
+ overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
+ if _, err := applyTemplateTransformersToHMLTTemplate(templateUndefined, overlayTpl); err != nil {
+ return err
+ }
+
+ t.overlays[name] = overlayTpl
+ t.nameBaseTemplateName[name] = masterFilename
+
+ return err
+
+}
+
+func (t *htmlTemplates) lookup(name string) *template.Template {
+ // Need to check in the overlay registry first as it will also be found below.
+ if t.overlays != nil {
+ if templ, ok := t.overlays[name]; ok {
+ return templ
+ }
+ }
+
+ if templ := t.t.Lookup(name); templ != nil {
+ return templ
+ }
+
+ if t.clone != nil {
+ return t.clone.Lookup(name)
}
+ return nil
+}
+
+func (t htmlTemplates) withNewHandler(h *templateHandler) *htmlTemplates {
+ t.templatesCommon = t.templatesCommon.withNewHandler(h)
+ return &t
}
type nopLookupVariant int
@@ -166,15 +249,81 @@ func (l nopLookupVariant) LookupVariant(name string, variants tpl.TemplateVarian
return nil, false, false
}
+// templateHandler holds the templates in play.
+// It implements the templateLoader and tpl.TemplateHandler interfaces.
+// There is one templateHandler created per Site.
+type templateHandler struct {
+ executor texttemplate.Executer
+ funcs map[string]reflect.Value
+
+ // This is the filesystem to load the templates from. All the templates are
+ // stored in the root of this filesystem.
+ layoutsFs afero.Fs
+
+ *deps.Deps
+
+ *templateHandlerCommon
+}
+
+// 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 {
+ 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.
+// TODO(bep) clean up these addTemplate variants
+func (t *templateHandler) AddTemplate(name, tpl string) error {
+ h := t.getTemplateHandler(name)
+ _, err := h.addTemplate(name, tpl)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func (t *templateHandler) Debug() {
fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
}
+func (t *templateHandler) Execute(templ tpl.Template, wr io.Writer, data interface{}) error {
+ if t.Metrics != nil {
+ defer t.Metrics.MeasureSince(templ.Name(), time.Now())
+ }
+
+ execErr := t.executor.Execute(templ, wr, data)
+ if execErr != nil {
+ execErr = t.addFileContext(templ.Name(), execErr)
+ }
+
+ return execErr
+
+}
+
+func (t *templateHandler) GetFunc(name string) (reflect.Value, bool) {
+ v, found := t.funcs[name]
+ return v, found
+
+}
+
+// LoadTemplates loads the templates from the layouts filesystem.
+// A prefix can be given to indicate a template namespace to load the templates
+// into, i.e. "_internal" etc.
+func (t *templateHandler) LoadTemplates(prefix string) error {
+ return t.loadTemplates(prefix)
+
+}
+
// 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.Template, bool) {
-
if strings.HasPrefix(name, textTmplNamePrefix) {
// The caller has explicitly asked for a text template, so only look
// in the text template collection.
@@ -193,18 +342,6 @@ func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
}
-func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) {
- if adapter, ok := templ.(*tpl.TemplateAdapter); ok {
- if adapter.Info.IsZero() {
- if info, found := t.templateInfo[templ.Name()]; found {
- adapter.Info = info
- }
- }
- }
-
- return templ, found
-}
-
// This currently only applies to shortcodes and what we get here is the
// shortcode name.
func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
@@ -221,382 +358,349 @@ func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVarian
more := len(s.variants) > 1
- return &tpl.TemplateAdapter{
- Template: sv.templ,
- Info: sv.info,
- Metrics: t.Deps.Metrics,
- Fs: t.layoutsFs,
- NameBaseTemplateName: t.html.nameBaseTemplateName}, true, more
+ return &tpl.TemplateInfo{
+ Template: sv.templ,
+ Info: sv.info,
+ }, true, more
}
-func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
+// 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() error {
+ if err := t.postTransform(); err != nil {
+ return err
+ }
-func (t *htmlTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
+ 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())
+ }
+
+ return nil
}
-func (t *templateHandler) lookupTemplate(in interface{}) tpl.Template {
- switch templ := in.(type) {
- case *texttemplate.Template:
- return t.text.lookup(templ.Name())
- case *template.Template:
- return t.html.lookup(templ.Name())
+// RebuildClone rebuilds the cloned templates. Used for live-reloads.
+func (t *templateHandler) RebuildClone() {
+ if t.html != nil && t.html.cloneClone != nil {
+ t.html.clone = template.Must(t.html.cloneClone.Clone())
+ }
+ if t.text != nil && t.text.cloneClone != nil {
+ t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
}
-
- panic(fmt.Sprintf("%T is not a template", in))
}
-func (t *templateHandler) setFuncMapInTemplate(in interface{}, funcs map[string]interface{}) {
- switch templ := in.(type) {
- case *texttemplate.Template:
- templ.Funcs(funcs)
- return
- case *template.Template:
- templ.Funcs(funcs)
- return
+func (h *templateHandler) initTemplateExecuter() {
+ exec, funcs := newTemplateExecuter(h.Deps)
+ h.executor = exec
+ h.funcs = funcs
+ funcMap := make(map[string]interface{})
+ for k, v := range funcs {
+ funcMap[k] = v.Interface()
}
- panic(fmt.Sprintf("%T is not a template", in))
+ // Note that these funcs are not the ones getting called
+ // on execution, but they are needed at parse time.
+ h.text.textTemplate.t.Funcs(funcMap)
+ h.text.standalone.t.Funcs(funcMap)
+ h.html.t.Funcs(funcMap)
}
-func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
- c := &templateHandler{
- Deps: d,
- layoutsFs: d.BaseFs.Layouts.Fs,
- shortcodes: make(map[string]*shortcodeTemplates),
- templateInfo: t.templateInfo,
- html: &htmlTemplates{t: template.Must(t.html.t.Clone()), overlays: make(map[string]*template.Template), templatesCommon: t.html.templatesCommon},
- text: &textTemplates{
- textTemplate: &textTemplate{t: texttemplate.Must(t.text.t.Clone())},
- standalone: &textTemplate{t: texttemplate.New("")},
- overlays: make(map[string]*texttemplate.Template), templatesCommon: t.text.templatesCommon},
- errors: make([]*templateErr, 0),
- }
-
- for k, v := range t.shortcodes {
- other := *v
- variantsc := make([]shortcodeVariant, len(v.variants))
- for i, variant := range v.variants {
- variantsc[i] = shortcodeVariant{
- info: variant.info,
- variants: variant.variants,
- templ: c.lookupTemplate(variant.templ),
- }
- }
- other.variants = variantsc
- c.shortcodes[k] = &other
+func (t *templateHandler) getTemplateHandler(name string) templateLoader {
+ if strings.HasPrefix(name, textTmplNamePrefix) {
+ return t.text
}
+ return t.html
+}
- d.Tmpl = c
- d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
+func (t *templateHandler) addFileContext(name string, inerr error) error {
+ if strings.HasPrefix(name, "_internal") {
+ return inerr
+ }
- c.initFuncs()
+ f, realFilename, err := t.fileAndFilename(name)
+ if err != nil {
+ return inerr
- 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/gohugoio/hugo/issues/2549
- vc = vc.Lookup(vc.Name())
- vc.Funcs(c.html.funcster.funcMap)
- c.html.overlays[k] = vc
}
+ defer f.Close()
- for k, v := range t.text.overlays {
- vc := texttemplate.Must(v.Clone())
- vc = vc.Lookup(vc.Name())
- vc.Funcs(texttemplate.FuncMap(c.text.funcster.funcMap))
- c.text.overlays[k] = vc
- }
+ master, hasMaster := t.html.nameBaseTemplateName[name]
- return c
+ ferr := errors.Wrap(inerr, "execute of template failed")
-}
+ // Since this can be a composite of multiple template files (single.html + baseof.html etc.)
+ // we potentially need to look in both -- and cannot rely on line number alone.
+ lineMatcher := func(m herrors.LineMatcher) bool {
+ if m.Position.LineNumber != m.LineNumber {
+ return false
+ }
+ if !hasMaster {
+ return true
+ }
-func newTemplateAdapter(deps *deps.Deps) *templateHandler {
- common := &templatesCommon{
- nameBaseTemplateName: make(map[string]string),
- transformNotFound: make(map[string]bool),
- }
+ identifiers := t.extractIdentifiers(m.Error.Error())
- htmlT := &htmlTemplates{
- t: template.New(""),
- overlays: make(map[string]*template.Template),
- templatesCommon: common,
- }
- textT := &textTemplates{
- textTemplate: &textTemplate{t: texttemplate.New("")},
- standalone: &textTemplate{t: texttemplate.New("")},
- overlays: make(map[string]*texttemplate.Template),
- templatesCommon: common,
+ for _, id := range identifiers {
+ if strings.Contains(m.Line, id) {
+ return true
+ }
+ }
+ return false
}
- h := &templateHandler{
- Deps: deps,
- layoutsFs: deps.BaseFs.Layouts.Fs,
- shortcodes: make(map[string]*shortcodeTemplates),
- templateInfo: make(map[string]tpl.Info),
- html: htmlT,
- text: textT,
- errors: make([]*templateErr, 0),
+
+ fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
+ if ok || !hasMaster {
+ return fe
}
- common.handler = h
+ // Try the base template if relevant
+ f, realFilename, err = t.fileAndFilename(master)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
- return h
+ fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
-}
+ if !ok {
+ // Return the most specific.
+ return ferr
-// Shared by both HTML and text templates.
-type templatesCommon struct {
- handler *templateHandler
- funcster *templateFuncster
+ }
+ return fe
- // Used to get proper filenames in errors
- nameBaseTemplateName map[string]string
+}
- // Holds names of the templates not found during the first AST transformation
- // pass.
- transformNotFound map[string]bool
+func (t *templateHandler) addInternalTemplate(name, tpl string) error {
+ return t.AddTemplate("_internal/"+name, tpl)
}
-type htmlTemplates struct {
- mu sync.RWMutex
- *templatesCommon
+func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
+ base := templateBaseName(templateShortcode, name)
- t *template.Template
+ shortcodename, variants := templateNameAndVariants(base)
- // 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
+ templs, found := t.shortcodes[shortcodename]
+ if !found {
+ templs = &shortcodeTemplates{}
+ t.shortcodes[shortcodename] = templs
+ }
- // 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
-}
+ sv := shortcodeVariant{variants: variants, info: info, templ: templ}
-func (t *htmlTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
-}
+ i := templs.indexOf(variants)
-func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+ if i != -1 {
+ // Only replace if it's an override of an internal template.
+ if !isInternal(name) {
+ templs.variants[i] = sv
+ }
+ } else {
+ templs.variants = append(templs.variants, sv)
}
-
- return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics, Fs: t.handler.layoutsFs, NameBaseTemplateName: t.nameBaseTemplateName}, true
}
-func (t *htmlTemplates) lookup(name string) *template.Template {
- t.mu.RLock()
- defer t.mu.RUnlock()
+func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
+ t.checkState()
- // Need to check in the overlay registry first as it will also be found below.
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
+ t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path)
+
+ getTemplate := func(filename string) (templateInfo, error) {
+ fs := t.Layouts.Fs
+ b, err := afero.ReadFile(fs, filename)
+ if err != nil {
+ return templateInfo{filename: filename, fs: fs}, err
}
- }
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
+ s := removeLeadingBOM(string(b))
- if t.clone != nil {
- return t.clone.Lookup(name)
+ realFilename := filename
+ if fi, err := fs.Stat(filename); err == nil {
+ if fim, ok := fi.(hugofs.FileMetaInfo); ok {
+ realFilename = fim.Meta().Filename()
+ }
+ }
+
+ return templateInfo{template: s, filename: filename, realFilename: realFilename, fs: fs}, nil
}
- return nil
-}
+ // get the suffix and switch on that
+ ext := filepath.Ext(path)
+ switch ext {
+ case ".amber":
+ helpers.Deprecated("Amber templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ return nil
+ case ".ace":
+ helpers.Deprecated("ACE templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ return nil
+ default:
-func (t *textTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
-}
+ if baseTemplatePath != "" {
+ return t.handleMaster(name, path, baseTemplatePath, getTemplate)
+ }
-type textTemplates struct {
- *templatesCommon
- *textTemplate
- standalone *textTemplate
- clone *texttemplate.Template
- cloneClone *texttemplate.Template
+ templ, err := getTemplate(path)
- overlays map[string]*texttemplate.Template
-}
+ if err != nil {
+ return err
+ }
-func (t *textTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+ err = t.AddTemplate(name, templ.template)
+ if err != nil {
+ return templ.errWithFileContext("parse failed", err)
+ }
+ return nil
}
- return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics, Fs: t.handler.layoutsFs, NameBaseTemplateName: t.nameBaseTemplateName}, true
}
-func (t *textTemplates) lookup(name string) *texttemplate.Template {
-
- // Need to check in the overlay registry first as it will also be found below.
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
+func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) {
+ if adapter, ok := templ.(*tpl.TemplateInfo); ok {
+ if adapter.Info.IsZero() {
+ if info, found := t.templateInfo[templ.Name()]; found {
+ adapter.Info = info
+ }
+ }
+ } else if templ != nil {
+ if info, found := t.templateInfo[templ.Name()]; found {
+ return &tpl.TemplateInfo{
+ Template: templ,
+ Info: info,
+ }, true
}
}
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
-
- if t.clone != nil {
- return t.clone.Lookup(name)
- }
-
- return nil
+ return templ, found
}
-func (t *templateHandler) setFuncs(funcMap map[string]interface{}) {
- t.html.setFuncs(funcMap)
- t.text.setFuncs(funcMap)
- t.setFuncMapInTemplate(t.text.standalone.t, funcMap)
+func (t *templateHandler) checkState() {
+ if t.html.clone != nil || t.text.clone != nil {
+ panic("template is cloned and cannot be modfified")
+ }
}
-// 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 *templateHandler) clone(d *deps.Deps) *templateHandler {
+ c := &templateHandler{
+ Deps: d,
+ layoutsFs: d.BaseFs.Layouts.Fs,
+ }
-func (t *templateHandler) GetFuncs() map[string]interface{} {
- return t.html.funcster.funcMap
-}
+ c.templateHandlerCommon = t.templateHandlerCommon.withNewHandler(c)
+ d.Tmpl = c
+ d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
+ c.executor, c.funcs = newTemplateExecuter(d)
-func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) {
- t.t.Funcs(funcMap)
-}
+ return c
-func (t *textTemplates) setFuncs(funcMap map[string]interface{}) {
- t.t.Funcs(funcMap)
}
-// LoadTemplates loads the templates from the layouts filesystem.
-// A prefix can be given to indicate a template namespace to load the templates
-// into, i.e. "_internal" etc.
-func (t *templateHandler) LoadTemplates(prefix string) error {
- return t.loadTemplates(prefix)
-
+func (t *templateHandler) extractIdentifiers(line string) []string {
+ m := identifiersRe.FindAllStringSubmatch(line, -1)
+ identifiers := make([]string, len(m))
+ for i := 0; i < len(m); i++ {
+ identifiers[i] = m[i][1]
+ }
+ return identifiers
}
-func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) (*templateContext, error) {
- t.mu.Lock()
- defer t.mu.Unlock()
+func (t *templateHandler) fileAndFilename(name string) (afero.File, string, error) {
+ fs := t.layoutsFs
+ filename := filepath.FromSlash(name)
- templ, err := tt.New(name).Parse(tpl)
+ fi, err := fs.Stat(filename)
if err != nil {
- return nil, err
+ return nil, "", err
}
+ fim := fi.(hugofs.FileMetaInfo)
+ meta := fim.Meta()
- typ := resolveTemplateType(name)
-
- c, err := applyTemplateTransformersToHMLTTemplate(typ, templ)
+ f, err := meta.Open()
if err != nil {
- return nil, err
+ return nil, "", errors.Wrapf(err, "failed to open template file %q:", filename)
}
- for k := range c.notFound {
- t.transformNotFound[k] = true
- }
-
- if typ == templateShortcode {
- t.handler.addShortcodeVariant(name, c.Info, templ)
- } else {
- t.handler.templateInfo[name] = c.Info
- }
-
- return c, nil
+ return f, meta.Filename(), nil
}
-func (t *htmlTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
-}
-
-func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
+func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+ h := t.getTemplateHandler(name)
+ return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
}
-type textTemplate struct {
- mu sync.RWMutex
- t *texttemplate.Template
-}
+func (t *templateHandler) loadEmbedded() error {
+ for _, kv := range embedded.EmbeddedTemplates {
+ name, templ := kv[0], kv[1]
+ if err := t.addInternalTemplate(name, templ); err != nil {
+ return err
+ }
+ if aliases, found := embeddedTemplatesAliases[name]; found {
+ for _, alias := range aliases {
+ if err := t.addInternalTemplate(alias, templ); err != nil {
+ return err
+ }
+ }
-func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) {
- return t.parseIn(t.t, name, tpl)
-}
+ }
+ }
-func (t *textTemplate) Lookup(name string) (tpl.Template, bool) {
- t.mu.RLock()
- defer t.mu.RUnlock()
+ return nil
- tpl := t.t.Lookup(name)
- return tpl, tpl != nil
}
-func (t *textTemplate) parseIn(tt *texttemplate.Template, name, tpl string) (*texttemplate.Template, error) {
- t.mu.Lock()
- defer t.mu.Unlock()
+func (t *templateHandler) loadTemplates(prefix string) error {
- templ, err := tt.New(name).Parse(tpl)
- if err != nil {
- return nil, err
- }
+ walker := func(path string, fi hugofs.FileMetaInfo, err error) error {
+ if err != nil || fi.IsDir() {
+ return err
+ }
- if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil {
- return nil, err
- }
- return templ, nil
-}
+ if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
+ return nil
+ }
-func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) (*templateContext, error) {
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- templ, err := t.parseIn(tt, name, tpl)
- if err != nil {
- return nil, err
- }
+ workingDir := t.PathSpec.WorkingDir
- typ := resolveTemplateType(name)
+ descriptor := output.TemplateLookupDescriptor{
+ WorkingDir: workingDir,
+ RelPath: path,
+ Prefix: prefix,
+ OutputFormats: t.OutputFormatsConfig,
+ FileExists: func(filename string) (bool, error) {
+ return helpers.Exists(filename, t.Layouts.Fs)
+ },
+ ContainsAny: func(filename string, subslices [][]byte) (bool, error) {
+ return helpers.FileContainsAny(filename, subslices, t.Layouts.Fs)
+ },
+ }
- c, err := applyTemplateTransformersToTextTemplate(typ, templ)
- if err != nil {
- return nil, err
- }
+ tplID, err := output.CreateTemplateNames(descriptor)
+ if err != nil {
+ t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
+ return nil
+ }
- for k := range c.notFound {
- t.transformNotFound[k] = true
- }
+ if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
+ return err
+ }
- if typ == templateShortcode {
- t.handler.addShortcodeVariant(name, c.Info, templ)
- } else {
- t.handler.templateInfo[name] = c.Info
+ return nil
}
- return c, nil
-}
-
-func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
-}
+ if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil {
+ if !os.IsNotExist(err) {
+ return err
+ }
+ return nil
+ }
-func (t *textTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
-}
+ return nil
-func (t *templateHandler) addTemplate(name, tpl string) error {
- return t.AddTemplate(name, tpl)
}
func (t *templateHandler) postTransform() error {
@@ -644,189 +748,151 @@ func (t *templateHandler) postTransform() error {
return nil
}
-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 {
- return err
+func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder {
+ return struct {
+ tpl.TemplateParser
+ tpl.TemplateLookup
+ tpl.TemplateLookupVariant
+ }{
+ tt,
+ tt,
+ new(nopLookupVariant),
}
- return nil
-}
-// AddTemplate parses and adds a template to the collection.
-// Templates with name prefixed with "_text" will be handled as plain
-// text templates.
-// TODO(bep) clean up these addTemplate variants
-func (t *templateHandler) AddTemplate(name, tpl string) error {
- h := t.getTemplateHandler(name)
- _, err := h.addTemplate(name, tpl)
- if err != nil {
- return err
- }
- return nil
}
-// 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() error {
- if err := t.postTransform(); err != nil {
- return err
- }
+type templateHandlerCommon struct {
+ // shortcodes maps shortcode name to template variants
+ // (language, output format etc.) of that shortcode.
+ shortcodes map[string]*shortcodeTemplates
- 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())
- }
+ // templateInfo maps template name to some additional information about that template.
+ // Note that for shortcodes that same information is embedded in the
+ // shortcodeTemplates type.
+ templateInfo map[string]tpl.Info
- return nil
+ // text holds all the pure text templates.
+ text *textTemplates
+ html *htmlTemplates
}
-// RebuildClone rebuilds the cloned templates. Used for live-reloads.
-func (t *templateHandler) RebuildClone() {
- if t.html != nil && t.html.cloneClone != nil {
- t.html.clone = template.Must(t.html.cloneClone.Clone())
- }
- if t.text != nil && t.text.cloneClone != nil {
- t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
- }
+func (t templateHandlerCommon) withNewHandler(h *templateHandler) *templateHandlerCommon {
+ t.text = t.text.withNewHandler(h)
+ t.html = t.html.withNewHandler(h)
+ return &t
}
-func (t *templateHandler) loadTemplates(prefix string) error {
-
- walker := func(path string, fi hugofs.FileMetaInfo, err error) error {
- if err != nil || fi.IsDir() {
- return err
- }
-
- if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
- return nil
- }
-
- workingDir := t.PathSpec.WorkingDir
-
- descriptor := output.TemplateLookupDescriptor{
- WorkingDir: workingDir,
- RelPath: path,
- Prefix: prefix,
- OutputFormats: t.OutputFormatsConfig,
- FileExists: func(filename string) (bool, error) {
- return helpers.Exists(filename, t.Layouts.Fs)
- },
- ContainsAny: func(filename string, subslices [][]byte) (bool, error) {
- return helpers.FileContainsAny(filename, subslices, t.Layouts.Fs)
- },
- }
-
- tplID, err := output.CreateTemplateNames(descriptor)
- if err != nil {
- t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
- return nil
- }
+type templateLoader interface {
+ addLateTemplate(name, tpl string) error
+ addTemplate(name, tpl string) (*templateContext, error)
+ handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error
+}
- if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
- return err
- }
+// Shared by both HTML and text templates.
+type templatesCommon struct {
+ handler *templateHandler
- return nil
- }
+ // Used to get proper filenames in errors
+ nameBaseTemplateName map[string]string
- if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil {
- if !os.IsNotExist(err) {
- return err
- }
- return nil
- }
+ // Holds names of the templates not found during the first AST transformation
+ // pass.
+ transformNotFound map[string]bool
+}
- return nil
+func (t templatesCommon) withNewHandler(h *templateHandler) *templatesCommon {
+ t.handler = h
+ return &t
+}
+type textTemplate struct {
+ mu sync.RWMutex
+ t *texttemplate.Template
}
-func (t *templateHandler) initFuncs() {
+func (t *textTemplate) Lookup(name string) (tpl.Template, bool) {
+ t.mu.RLock()
+ defer t.mu.RUnlock()
- // Both template types will get their own funcster instance, which
- // in the current case contains the same set of funcs.
- funcMap := createFuncMap(t.Deps)
- for _, funcsterHolder := range []templateFuncsterSetter{t.html, t.text} {
- funcster := newTemplateFuncster(t.Deps)
+ tpl := t.t.Lookup(name)
+ return tpl, tpl != nil
+}
- // 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(funcMap)
+func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) {
+ return t.parseIn(t.t, name, tpl)
+}
- funcsterHolder.setTemplateFuncster(funcster)
+func (t *textTemplate) parseIn(tt *texttemplate.Template, name, tpl string) (*texttemplate.Template, error) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ templ, err := tt.New(name).Parse(tpl)
+ if err != nil {
+ return nil, err
}
- for _, v := range t.shortcodes {
- for _, variant := range v.variants {
- t.setFuncMapInTemplate(variant.templ, funcMap)
- }
+ if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil {
+ return nil, err
}
+ return templ, nil
+}
+
+type textTemplates struct {
+ *templatesCommon
+ *textTemplate
+ standalone *textTemplate
+ clone *texttemplate.Template
+ cloneClone *texttemplate.Template
+ overlays map[string]*texttemplate.Template
}
-func (t *templateHandler) getTemplateHandler(name string) templateLoader {
- if strings.HasPrefix(name, textTmplNamePrefix) {
- return t.text
+func (t *textTemplates) Lookup(name string) (tpl.Template, bool) {
+ templ := t.lookup(name)
+ if templ == nil {
+ return nil, false
}
- return t.html
+ return templ, true
}
-func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
- h := t.getTemplateHandler(name)
- return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
+func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ return t.handler.LookupVariant(name, variants)
}
-func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
-
- masterTpl := t.lookup(masterFilename)
-
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- return err
- }
+func (t *textTemplates) addLateTemplate(name, tpl string) error {
+ _, err := t.addTemplateIn(t.clone, name, tpl)
+ return err
+}
- masterTpl, err = t.t.New(overlayFilename).Parse(templ.template)
- if err != nil {
- return templ.errWithFileContext("parse master failed", err)
- }
- }
+func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) {
+ return t.addTemplateIn(t.t, name, tpl)
+}
- templ, err := onMissing(overlayFilename)
+func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) (*templateContext, error) {
+ name = strings.TrimPrefix(name, textTmplNamePrefix)
+ templ, err := t.parseIn(tt, name, tpl)
if err != nil {
- return err
+ return nil, err
}
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template)
+ typ := resolveTemplateType(name)
+
+ c, err := applyTemplateTransformersToTextTemplate(typ, templ)
if err != nil {
- return templ.errWithFileContext("parse failed", err)
+ return nil, err
}
- // The extra lookup is a workaround, see
- // * https://github.com/golang/go/issues/16101
- // * https://github.com/gohugoio/hugo/issues/2549
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- if _, err := applyTemplateTransformersToHMLTTemplate(templateUndefined, overlayTpl); err != nil {
- return err
+ for k := range c.notFound {
+ t.transformNotFound[k] = true
}
- t.overlays[name] = overlayTpl
- t.nameBaseTemplateName[name] = masterFilename
-
- return err
+ if typ == templateShortcode {
+ t.handler.addShortcodeVariant(name, c.Info, templ)
+ } else {
+ t.handler.templateInfo[name] = c.Info
+ }
+ return c, nil
}
func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
@@ -868,119 +934,67 @@ func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename strin
}
-func removeLeadingBOM(s string) string {
- const bom = '\ufeff'
-
- for i, r := range s {
- if i == 0 && r != bom {
- return s
- }
- if i > 0 {
- return s[i:]
- }
- }
-
- return s
-
-}
-
-func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
- t.checkState()
-
- t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path)
-
- getTemplate := func(filename string) (templateInfo, error) {
- fs := t.Layouts.Fs
- b, err := afero.ReadFile(fs, filename)
- if err != nil {
- return templateInfo{filename: filename, fs: fs}, err
- }
-
- s := removeLeadingBOM(string(b))
+func (t *textTemplates) lookup(name string) *texttemplate.Template {
- realFilename := filename
- if fi, err := fs.Stat(filename); err == nil {
- if fim, ok := fi.(hugofs.FileMetaInfo); ok {
- realFilename = fim.Meta().Filename()
- }
+ // Need to check in the overlay registry first as it will also be found below.
+ if t.overlays != nil {
+ if templ, ok := t.overlays[name]; ok {
+ return templ
}
-
- return templateInfo{template: s, filename: filename, realFilename: realFilename, fs: fs}, nil
}
- // get the suffix and switch on that
- ext := filepath.Ext(path)
- switch ext {
- case ".amber":
- helpers.Deprecated("Amber templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
- return nil
- case ".ace":
- helpers.Deprecated("ACE templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
- return nil
- default:
-
- if baseTemplatePath != "" {
- return t.handleMaster(name, path, baseTemplatePath, getTemplate)
- }
-
- templ, err := getTemplate(path)
-
- if err != nil {
- return err
- }
-
- err = t.AddTemplate(name, templ.template)
- if err != nil {
- return templ.errWithFileContext("parse failed", err)
- }
- return nil
+ if templ := t.t.Lookup(name); templ != nil {
+ return templ
}
-}
-
-var embeddedTemplatesAliases = map[string][]string{
- "shortcodes/twitter.html": {"shortcodes/tweet.html"},
-}
-
-func (t *templateHandler) loadEmbedded() error {
- for _, kv := range embedded.EmbeddedTemplates {
- name, templ := kv[0], kv[1]
- if err := t.addInternalTemplate(name, templ); err != nil {
- return err
- }
- if aliases, found := embeddedTemplatesAliases[name]; found {
- for _, alias := range aliases {
- if err := t.addInternalTemplate(alias, templ); err != nil {
- return err
- }
- }
- }
+ if t.clone != nil {
+ return t.clone.Lookup(name)
}
return nil
+}
+func (t textTemplates) withNewHandler(h *templateHandler) *textTemplates {
+ t.templatesCommon = t.templatesCommon.withNewHandler(h)
+ return &t
}
-func (t *templateHandler) addInternalTemplate(name, tpl string) error {
- return t.AddTemplate("_internal/"+name, tpl)
+func isBackupFile(path string) bool {
+ return path[len(path)-1] == '~'
}
-func (t *templateHandler) checkState() {
- if t.html.clone != nil || t.text.clone != nil {
- panic("template is cloned and cannot be modfified")
- }
+func isBaseTemplate(path string) bool {
+ return strings.Contains(filepath.Base(path), baseFileBase)
}
func isDotFile(path string) bool {
return filepath.Base(path)[0] == '.'
}
-func isBackupFile(path string) bool {
- return path[len(path)-1] == '~'
+func removeLeadingBOM(s string) string {
+ const bom = '\ufeff'
+
+ for i, r := range s {
+ if i == 0 && r != bom {
+ return s
+ }
+ if i > 0 {
+ return s[i:]
+ }
+ }
+
+ return s
+
}
-const baseFileBase = "baseof"
+// resolves _internal/shortcodes/param.html => param.html etc.
+func templateBaseName(typ templateType, name string) string {
+ name = strings.TrimPrefix(name, internalPathPrefix)
+ switch typ {
+ case templateShortcode:
+ return strings.TrimPrefix(name, shortcodesPathPrefix)
+ default:
+ panic("not implemented")
+ }
-func isBaseTemplate(path string) bool {
- return strings.Contains(filepath.Base(path), baseFileBase)
}
diff --git a/tpl/tplimpl/templateFuncster.go b/tpl/tplimpl/templateFuncster.go
index ad51fbad7..96404f51b 100644
--- a/tpl/tplimpl/templateFuncster.go
+++ b/tpl/tplimpl/templateFuncster.go
@@ -12,22 +12,3 @@
// limitations under the License.
package tplimpl
-
-import (
- "html/template"
-
- "github.com/gohugoio/hugo/deps"
-)
-
-// Some of the template funcs are'nt entirely stateless.
-type templateFuncster struct {
- funcMap template.FuncMap
-
- *deps.Deps
-}
-
-func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
- return &templateFuncster{
- Deps: deps,
- }
-}
diff --git a/tpl/tplimpl/templateProvider.go b/tpl/tplimpl/templateProvider.go
index 605c47d87..910c0be89 100644
--- a/tpl/tplimpl/templateProvider.go
+++ b/tpl/tplimpl/templateProvider.go
@@ -29,8 +29,8 @@ func (*TemplateProvider) Update(deps *deps.Deps) error {
newTmpl := newTemplateAdapter(deps)
deps.Tmpl = newTmpl
deps.TextTmpl = newTmpl.wrapTextTemplate(newTmpl.text.standalone)
-
- newTmpl.initFuncs()
+ // These needs to be there at parse time.
+ newTmpl.initTemplateExecuter()
if err := newTmpl.loadEmbedded(); err != nil {
return err
diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go
index 8a432f272..31d24b71d 100644
--- a/tpl/tplimpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -14,9 +14,7 @@
package tplimpl
import (
- "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
-
- "strings"
+ template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
@@ -27,19 +25,6 @@ import (
"github.com/pkg/errors"
)
-// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
-type decl map[string]string
-
-const (
- paramsIdentifier = "Params"
-)
-
-// Containers that may contain Params that we will not touch.
-var reservedContainers = map[string]bool{
- // Aka .Site.Data.Params which must stay case sensitive.
- "Data": true,
-}
-
type templateType int
const (
@@ -49,7 +34,6 @@ const (
)
type templateContext struct {
- decl decl
visited map[string]bool
notFound map[string]bool
lookupFn func(name string) *parse.Tree
@@ -89,7 +73,6 @@ func newTemplateContext(lookupFn func(name string) *parse.Tree) *templateContext
return &templateContext{
Info: tpl.Info{Config: tpl.DefaultConfig},
lookupFn: lookupFn,
- decl: make(map[string]string),
visited: make(map[string]bool),
notFound: make(map[string]bool)}
}
@@ -219,11 +202,6 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
}
case *parse.PipeNode:
c.collectConfig(x)
- if len(x.Decl) == 1 && len(x.Cmds) == 1 {
- // maps $site => .Site etc.
- c.decl[x.Decl[0].Ident[0]] = x.Cmds[0].String()
- }
-
for i, cmd := range x.Cmds {
keep, _ := c.applyTransformations(cmd)
if !keep {
@@ -237,17 +215,8 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
for _, elem := range x.Args {
switch an := elem.(type) {
- case *parse.FieldNode:
- c.updateIdentsIfNeeded(an.Ident)
- case *parse.VariableNode:
- c.updateIdentsIfNeeded(an.Ident)
case *parse.PipeNode:
c.applyTransformations(an)
- case *parse.ChainNode:
- // site.Params...
- if len(an.Field) > 1 && an.Field[0] == paramsIdentifier {
- c.updateIdentsIfNeeded(an.Field)
- }
}
}
return keep, c.err
@@ -262,19 +231,6 @@ func (c *templateContext) applyTransformationsToNodes(nodes ...parse.Node) {
}
}
-func (c *templateContext) updateIdentsIfNeeded(idents []string) {
- index := c.decl.indexOfReplacementStart(idents)
-
- if index == -1 {
- return
- }
-
- for i := index; i < len(idents); i++ {
- idents[i] = strings.ToLower(idents[i])
- }
-
-}
-
func (c *templateContext) hasIdent(idents []string, ident string) bool {
for _, id := range idents {
if id == ident {
@@ -376,160 +332,3 @@ func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
return false
}
-
-// indexOfReplacementStart will return the index of where to start doing replacement,
-// -1 if none needed.
-func (d decl) indexOfReplacementStart(idents []string) int {
-
- l := len(idents)
-
- if l == 0 {
- return -1
- }
-
- if l == 1 {
- first := idents[0]
- if first == "" || first == paramsIdentifier || first[0] == '$' {
- // This can not be a Params.x
- return -1
- }
- }
-
- var lookFurther bool
- var needsVarExpansion bool
- for _, ident := range idents {
- if ident[0] == '$' {
- lookFurther = true
- needsVarExpansion = true
- break
- } else if ident == paramsIdentifier {
- lookFurther = true
- break
- }
- }
-
- if !lookFurther {
- return -1
- }
-
- var resolvedIdents []string
-
- if !needsVarExpansion {
- resolvedIdents = idents
- } else {
- var ok bool
- resolvedIdents, ok = d.resolveVariables(idents)
- if !ok {
- return -1
- }
- }
-
- var paramFound bool
- for i, ident := range resolvedIdents {
- if ident == paramsIdentifier {
- if i > 0 {
- container := resolvedIdents[i-1]
- if reservedContainers[container] {
- // .Data.Params.someKey
- return -1
- }
- }
-
- paramFound = true
- break
- }
- }
-
- if !paramFound {
- return -1
- }
-
- var paramSeen bool
- idx := -1
- for i, ident := range idents {
- if ident == "" || ident[0] == '$' {
- continue
- }
-
- if ident == paramsIdentifier {
- paramSeen = true
- idx = -1
-
- } else {
- if paramSeen {
- return i
- }
- if idx == -1 {
- idx = i
- }
- }
- }
- return idx
-
-}
-
-func (d decl) resolveVariables(idents []string) ([]string, bool) {
- var (
- replacements []string
- replaced []string
- )
-
- // An Ident can start out as one of
- // [Params] [$blue] [$colors.Blue]
- // We need to resolve the variables, so
- // $blue => [Params Colors Blue]
- // etc.
- replacements = []string{idents[0]}
-
- // Loop until there are no more $vars to resolve.
- for i := 0; i < len(replacements); i++ {
-
- if i > 20 {
- // bail out
- return nil, false
- }
-
- potentialVar := replacements[i]
-
- if potentialVar == "$" {
- continue
- }
-
- if potentialVar == "" || potentialVar[0] != '$' {
- // leave it as is
- replaced = append(replaced, strings.Split(potentialVar, ".")...)
- continue
- }
-
- replacement, ok := d[potentialVar]
-
- if !ok {
- // Temporary range vars. We do not care about those.
- return nil, false
- }
-
- if !d.isKeyword(replacement) {
- continue
- }
-
- replacement = strings.TrimPrefix(replacement, ".")
-
- if replacement == "" {
- continue
- }
-
- if replacement[0] == '$' {
- // Needs further expansion
- replacements = append(replacements, strings.Split(replacement, ".")...)
- } else {
- replaced = append(replaced, strings.Split(replacement, ".")...)
- }
- }
-
- return append(replaced, idents[1:]...), true
-
-}
-
-func (d decl) isKeyword(s string) bool {
- return !strings.ContainsAny(s, " -\"")
-}
diff --git a/tpl/tplimpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go
index 6a0bb8253..0dc91ac32 100644
--- a/tpl/tplimpl/template_ast_transformers_test.go
+++ b/tpl/tplimpl/template_ast_transformers_test.go
@@ -13,400 +13,18 @@
package tplimpl
import (
- "bytes"
- "fmt"
- "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ "strings"
+
+ template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
"testing"
"time"
"github.com/gohugoio/hugo/tpl"
- "github.com/spf13/cast"
-
qt "github.com/frankban/quicktest"
)
-type paramsHolder struct {
- params map[string]interface{}
- page *paramsHolder
-}
-
-func (p paramsHolder) Params() map[string]interface{} {
- return p.params
-}
-
-func (p paramsHolder) GetPage(arg string) *paramsHolder {
- return p.page
-}
-
-var (
- testFuncs = map[string]interface{}{
- "getif": func(v interface{}) interface{} { return v },
- "ToTime": func(v interface{}) interface{} { return cast.ToTime(v) },
- "First": func(v ...interface{}) interface{} { return v[0] },
- "Echo": func(v interface{}) interface{} { return v },
- "where": func(seq, key interface{}, args ...interface{}) (interface{}, error) {
- return map[string]interface{}{
- "ByWeight": fmt.Sprintf("%v:%v:%v", seq, key, args),
- }, nil
- },
- "site": func() paramsHolder {
- return paramsHolder{
- params: map[string]interface{}{
- "lower": "global-site",
- },
- page: &paramsHolder{
- params: map[string]interface{}{
- "lower": "page",
- },
- },
- }
- },
- }
-
- paramsData = map[string]interface{}{
-
- "NotParam": "Hi There",
- "Slice": []int{1, 3},
- "Params": map[string]interface{}{
- "lower": "P1L",
- "slice": []int{1, 3},
- "mydate": "1972-01-28",
- },
- "Pages": map[string]interface{}{
- "ByWeight": []int{1, 3},
- },
- "CurrentSection": map[string]interface{}{
- "Params": map[string]interface{}{
- "lower": "pcurrentsection",
- },
- },
- "Site": map[string]interface{}{
- "Params": map[string]interface{}{
- "lower": "P2L",
- "slice": []int{1, 3},
- },
- "Language": map[string]interface{}{
- "Params": map[string]interface{}{
- "lower": "P22L",
- "nested": map[string]interface{}{
- "lower": "P22L_nested",
- },
- },
- },
- "Data": map[string]interface{}{
- "Params": map[string]interface{}{
- "NOLOW": "P3H",
- },
- },
- },
- "Site2": paramsHolder{
- params: map[string]interface{}{
- "lower": "global-site",
- },
- page: &paramsHolder{
- params: map[string]interface{}{
- "lower": "page",
- },
- },
- },
- }
-
- paramsTempl = `
-{{ $page := . }}
-{{ $pages := .Pages }}
-{{ $pageParams := .Params }}
-{{ $site := .Site }}
-{{ $siteParams := .Site.Params }}
-{{ $data := .Site.Data }}
-{{ $notparam := .NotParam }}
-
-PCurrentSection: {{ .CurrentSection.Params.LOWER }}
-P1: {{ .Params.LOWER }}
-P1_2: {{ $.Params.LOWER }}
-P1_3: {{ $page.Params.LOWER }}
-P1_4: {{ $pageParams.LOWER }}
-P2: {{ .Site.Params.LOWER }}
-P2_2: {{ $.Site.Params.LOWER }}
-P2_3: {{ $site.Params.LOWER }}
-P2_4: {{ $siteParams.LOWER }}
-P22: {{ .Site.Language.Params.LOWER }}
-P22_nested: {{ .Site.Language.Params.NESTED.LOWER }}
-P3: {{ .Site.Data.Params.NOLOW }}
-P3_2: {{ $.Site.Data.Params.NOLOW }}
-P3_3: {{ $site.Data.Params.NOLOW }}
-P3_4: {{ $data.Params.NOLOW }}
-P4: {{ range $i, $e := .Site.Params.SLICE }}{{ $e }}{{ end }}
-P5: {{ Echo .Params.LOWER }}
-P5_2: {{ Echo $site.Params.LOWER }}
-{{ if .Params.LOWER }}
-IF: {{ .Params.LOWER }}
-{{ end }}
-{{ if .Params.NOT_EXIST }}
-{{ else }}
-ELSE: {{ .Params.LOWER }}
-{{ end }}
-
-
-{{ with .Params.LOWER }}
-WITH: {{ . }}
-{{ end }}
-
-
-{{ range .Slice }}
-RANGE: {{ . }}: {{ $.Params.LOWER }}
-{{ end }}
-{{ index .Slice 1 }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ .NotParam }}
-{{ $notparam }}
-
-
-{{ $lower := .Site.Params.LOWER }}
-F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }}
-F2: {{ Echo (printf "themes/%s-theme" $lower) }}
-F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}
-
-PSLICE: {{ range .Params.SLICE }}PSLICE{{.}}|{{ end }}
-
-{{ $pages := "foo" }}
-{{ $pages := where $pages ".Params.toc_hide" "!=" true }}
-PARAMS STRING: {{ $pages.ByWeight }}
-PARAMS STRING2: {{ with $pages }}{{ .ByWeight }}{{ end }}
-{{ $pages3 := where ".Params.TOC_HIDE" "!=" .Params.LOWER }}
-PARAMS STRING3: {{ $pages3.ByWeight }}
-{{ $first := First .Pages .Site.Params.LOWER }}
-PARAMS COMPOSITE: {{ $first.ByWeight }}
-
-
-{{ $time := $.Params.MyDate | ToTime }}
-{{ $time = $time.AddDate 0 1 0 }}
-PARAMS TIME: {{ $time.Format "2006-01-02" }}
-
-{{ $_x := $.Params.MyDate | ToTime }}
-PARAMS TIME2: {{ $_x.AddDate 0 1 0 }}
-
-PARAMS SITE GLOBAL1: {{ site.Params.LOwER }}
-{{ $lower := site.Params.LOwER }}
-{{ $site := site }}
-PARAMS SITE GLOBAL2: {{ $lower }}
-PARAMS SITE GLOBAL3: {{ $site.Params.LOWER }}
-
-{{ $p := $site.GetPage "foo" }}
-PARAMS GETPAGE: {{ $p.Params.LOWER }}
-{{ $p := .Site2.GetPage "foo" }}
-PARAMS GETPAGE2: {{ $p.Params.LOWER }}
-`
-)
-
-func TestParamsKeysToLower(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- _, err := applyTemplateTransformers(templateUndefined, nil, nil)
- c.Assert(err, qt.Not(qt.IsNil))
-
- templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
-
- c.Assert(err, qt.IsNil)
-
- ctx := newTemplateContext(createParseTreeLookup(templ))
-
- c.Assert(ctx.decl.indexOfReplacementStart([]string{}), qt.Equals, -1)
-
- ctx.applyTransformations(templ.Tree.Root)
-
- var b bytes.Buffer
-
- c.Assert(templ.Execute(&b, paramsData), qt.IsNil)
-
- result := b.String()
-
- c.Assert(result, qt.Contains, "P1: P1L")
- c.Assert(result, qt.Contains, "P1_2: P1L")
- c.Assert(result, qt.Contains, "P1_3: P1L")
- c.Assert(result, qt.Contains, "P1_4: P1L")
- c.Assert(result, qt.Contains, "P2: P2L")
- c.Assert(result, qt.Contains, "P2_2: P2L")
- c.Assert(result, qt.Contains, "P2_3: P2L")
- c.Assert(result, qt.Contains, "P2_4: P2L")
- c.Assert(result, qt.Contains, "P22: P22L")
- c.Assert(result, qt.Contains, "P22_nested: P22L_nested")
- c.Assert(result, qt.Contains, "P3: P3H")
- c.Assert(result, qt.Contains, "P3_2: P3H")
- c.Assert(result, qt.Contains, "P3_3: P3H")
- c.Assert(result, qt.Contains, "P3_4: P3H")
- c.Assert(result, qt.Contains, "P4: 13")
- c.Assert(result, qt.Contains, "P5: P1L")
- c.Assert(result, qt.Contains, "P5_2: P2L")
-
- c.Assert(result, qt.Contains, "IF: P1L")
- c.Assert(result, qt.Contains, "ELSE: P1L")
-
- c.Assert(result, qt.Contains, "WITH: P1L")
-
- c.Assert(result, qt.Contains, "RANGE: 3: P1L")
-
- c.Assert(result, qt.Contains, "Hi There")
-
- // Issue #2740
- c.Assert(result, qt.Contains, "F1: themes/P2L-theme")
- c.Assert(result, qt.Contains, "F2: themes/P2L-theme")
- c.Assert(result, qt.Contains, "F3: themes/P2L-theme")
-
- c.Assert(result, qt.Contains, "PSLICE: PSLICE1|PSLICE3|")
- c.Assert(result, qt.Contains, "PARAMS STRING: foo:.Params.toc_hide:[!= true]")
- c.Assert(result, qt.Contains, "PARAMS STRING2: foo:.Params.toc_hide:[!= true]")
- c.Assert(result, qt.Contains, "PARAMS STRING3: .Params.TOC_HIDE:!=:[P1L]")
-
- // Issue #5094
- c.Assert(result, qt.Contains, "PARAMS COMPOSITE: [1 3]")
-
- // Issue #5068
- c.Assert(result, qt.Contains, "PCurrentSection: pcurrentsection")
-
- // Issue #5541
- c.Assert(result, qt.Contains, "PARAMS TIME: 1972-02-28")
- c.Assert(result, qt.Contains, "PARAMS TIME2: 1972-02-28")
-
- // Issue ##5615
- c.Assert(result, qt.Contains, "PARAMS SITE GLOBAL1: global-site")
- c.Assert(result, qt.Contains, "PARAMS SITE GLOBAL2: global-site")
- c.Assert(result, qt.Contains, "PARAMS SITE GLOBAL3: global-site")
-
- //
- c.Assert(result, qt.Contains, "PARAMS GETPAGE: page")
- c.Assert(result, qt.Contains, "PARAMS GETPAGE2: page")
-
-}
-
-func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
- templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
-
- if err != nil {
- b.Fatal(err)
- }
-
- templates := make([]*template.Template, b.N)
-
- for i := 0; i < b.N; i++ {
- templates[i], err = templ.Clone()
- if err != nil {
- b.Fatal(err)
- }
- }
-
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- c := newTemplateContext(createParseTreeLookup(templates[i]))
- c.applyTransformations(templ.Tree.Root)
- }
-}
-
-func TestParamsKeysToLowerVars(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- var (
- data = map[string]interface{}{
- "Params": map[string]interface{}{
- "colors": map[string]interface{}{
- "blue": "Amber",
- "pretty": map[string]interface{}{
- "first": "Indigo",
- },
- },
- },
- }
-
- // This is how Amber behaves:
- paramsTempl = `
-{{$__amber_1 := .Params.Colors}}
-{{$__amber_2 := $__amber_1.Blue}}
-{{$__amber_3 := $__amber_1.Pretty}}
-{{$__amber_4 := .Params}}
-
-Color: {{$__amber_2}}
-Blue: {{ $__amber_1.Blue}}
-Pretty First1: {{ $__amber_3.First}}
-Pretty First2: {{ $__amber_1.Pretty.First}}
-Pretty First3: {{ $__amber_4.COLORS.PRETTY.FIRST}}
-`
- )
-
- templ, err := template.New("foo").Parse(paramsTempl)
-
- c.Assert(err, qt.IsNil)
-
- ctx := newTemplateContext(createParseTreeLookup(templ))
-
- ctx.applyTransformations(templ.Tree.Root)
-
- var b bytes.Buffer
-
- c.Assert(templ.Execute(&b, data), qt.IsNil)
-
- result := b.String()
-
- c.Assert(result, qt.Contains, "Color: Amber")
- c.Assert(result, qt.Contains, "Blue: Amber")
- c.Assert(result, qt.Contains, "Pretty First1: Indigo")
- c.Assert(result, qt.Contains, "Pretty First2: Indigo")
- c.Assert(result, qt.Contains, "Pretty First3: Indigo")
-
-}
-
-func TestParamsKeysToLowerInBlockTemplate(t *testing.T) {
- t.Parallel()
- c := qt.New(t)
-
- var (
- data = map[string]interface{}{
- "Params": map[string]interface{}{
- "lower": "P1L",
- },
- }
-
- master = `
-P1: {{ .Params.LOWER }}
-{{ block "main" . }}DEFAULT{{ end }}`
- overlay = `
-{{ define "main" }}
-P2: {{ .Params.LOWER }}
-{{ end }}`
- )
-
- masterTpl, err := template.New("foo").Parse(master)
- c.Assert(err, qt.IsNil)
-
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(overlay)
- c.Assert(err, qt.IsNil)
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
-
- ctx := newTemplateContext(createParseTreeLookup(overlayTpl))
-
- ctx.applyTransformations(overlayTpl.Tree.Root)
-
- var b bytes.Buffer
-
- c.Assert(overlayTpl.Execute(&b, data), qt.IsNil)
-
- result := b.String()
-
- c.Assert(result, qt.Contains, "P1: P1L")
- c.Assert(result, qt.Contains, "P2: P1L")
-}
-
// Issue #2927
func TestTransformRecursiveTemplate(t *testing.T) {
c := qt.New(t)
@@ -479,7 +97,7 @@ func TestInsertIsZeroFunc(t *testing.T) {
)
d := newD(c)
- h := d.Tmpl.(tpl.TemplateHandler)
+ h := d.Tmpl.(tpl.TemplateManager)
// HTML templates
c.Assert(h.AddTemplate("mytemplate.html", templ1), qt.IsNil)
@@ -493,9 +111,13 @@ func TestInsertIsZeroFunc(t *testing.T) {
for _, name := range []string{"mytemplate.html", "mytexttemplate.txt"} {
tt, _ := d.Tmpl.Lookup(name)
- result, err := tt.(tpl.TemplateExecutor).ExecuteToString(ctx)
+ sb := &strings.Builder{}
+
+ err := d.Tmpl.Execute(tt, sb, ctx)
c.Assert(err, qt.IsNil)
+ result := sb.String()
+
c.Assert(result, qt.Contains, ".True: TRUE")
c.Assert(result, qt.Contains, ".TimeZero1: FALSE")
c.Assert(result, qt.Contains, ".TimeZero2: FALSE")
diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go
index bbaf44ae2..2098732f6 100644
--- a/tpl/tplimpl/template_funcs.go
+++ b/tpl/tplimpl/template_funcs.go
@@ -16,7 +16,13 @@
package tplimpl
import (
- "html/template"
+ "reflect"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/maps"
+
+ template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
"github.com/gohugoio/hugo/deps"
@@ -49,7 +55,55 @@ import (
_ "github.com/gohugoio/hugo/tpl/urls"
)
+var _ texttemplate.ExecHelper = (*templateExecHelper)(nil)
+var zero reflect.Value
+
+type templateExecHelper struct {
+ funcs map[string]reflect.Value
+}
+
+func (t *templateExecHelper) GetFunc(name string) (reflect.Value, bool) {
+ if fn, found := t.funcs[name]; found {
+ return fn, true
+ }
+ return zero, false
+}
+
+func (t *templateExecHelper) GetMapValue(receiver, key reflect.Value) (reflect.Value, bool) {
+ if params, ok := receiver.Interface().(maps.Params); ok {
+ // Case insensitive.
+ keystr := strings.ToLower(key.String())
+ v, found := params[keystr]
+ if !found {
+ return zero, false
+ }
+ return reflect.ValueOf(v), true
+ }
+
+ v := receiver.MapIndex(key)
+
+ return v, v.IsValid()
+}
+
+func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflect.Value) {
+ funcs := createFuncMap(d)
+ funcsv := make(map[string]reflect.Value)
+
+ for k, v := range funcs {
+ funcsv[k] = reflect.ValueOf(v)
+ }
+
+ exeHelper := &templateExecHelper{
+ funcs: funcsv,
+ }
+
+ return texttemplate.NewExecuter(
+ exeHelper,
+ ), funcsv
+}
+
func createFuncMap(d *deps.Deps) map[string]interface{} {
+
funcMap := template.FuncMap{}
// Merge the namespace funcs
@@ -71,10 +125,12 @@ func createFuncMap(d *deps.Deps) map[string]interface{} {
}
+ if d.OverloadedTemplateFuncs != nil {
+ for k, v := range d.OverloadedTemplateFuncs {
+ funcMap[k] = v
+ }
+ }
+
return funcMap
}
-func (t *templateFuncster) initFuncMap(funcMap template.FuncMap) {
- t.funcMap = funcMap
- t.Tmpl.(*templateHandler).setFuncs(funcMap)
-}
diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
index 10fbc2375..6ca9de4da 100644
--- a/tpl/tplimpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -119,7 +119,7 @@ func TestTemplateFuncsExamples(t *testing.T) {
for _, mm := range ns.MethodMappings {
for i, example := range mm.Examples {
in, expected := example[0], example[1]
- d.WithTemplate = func(templ tpl.TemplateHandler) error {
+ d.WithTemplate = func(templ tpl.TemplateManager) error {
c.Assert(templ.AddTemplate("test", in), qt.IsNil)
c.Assert(templ.AddTemplate("partials/header.html", "<title>Hugo Rocks!</title>"), qt.IsNil)
return nil
@@ -128,7 +128,7 @@ func TestTemplateFuncsExamples(t *testing.T) {
var b bytes.Buffer
templ, _ := d.Tmpl.Lookup("test")
- c.Assert(templ.Execute(&b, &data), qt.IsNil)
+ c.Assert(d.Tmpl.Execute(templ, &b, &data), qt.IsNil)
if b.String() != expected {
t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected)
}
@@ -154,7 +154,7 @@ func TestPartialCached(t *testing.T) {
config := newDepsConfig(v)
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
+ config.WithTemplate = func(templ tpl.TemplateManager) error {
err := templ.AddTemplate("partials/"+name, partial)
if err != nil {
return err
@@ -208,7 +208,7 @@ func BenchmarkPartialCached(b *testing.B) {
func doBenchmarkPartial(b *testing.B, f func(ns *partials.Namespace) error) {
c := qt.New(b)
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
+ config.WithTemplate = func(templ tpl.TemplateManager) error {
err := templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
if err != nil {
return err
diff --git a/tpl/tplimpl/template_info_test.go b/tpl/tplimpl/template_info_test.go
index c96e82d06..6841b4c47 100644
--- a/tpl/tplimpl/template_info_test.go
+++ b/tpl/tplimpl/template_info_test.go
@@ -24,7 +24,7 @@ import (
func TestTemplateInfoShortcode(t *testing.T) {
c := qt.New(t)
d := newD(c)
- h := d.Tmpl.(tpl.TemplateHandler)
+ h := d.Tmpl.(tpl.TemplateManager)
c.Assert(h.AddTemplate("shortcodes/mytemplate.html", `
{{ .Inner }}