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 = "
`)
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", `
{{ .Inner }}
`)
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", `
{{ .Inner }}
`)
@@ -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", `
Outer, inner is {{ .Inner }}
`)
tem.AddTemplate("_internal/shortcodes/scn2.html", `
SC2
`)
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,19 +79,6 @@ func (t *Template) Prepare() (*Template, error) {
return t, nil
}
-// Below are modifed structs etc.
-
-// state represents the state of an execution. It's not part of the
-// template so that multiple executions of the same template
-// can execute in parallel.
-type state struct {
- tmpl *Template
- wr io.Writer
- node parse.Node // current node, for errors
- vars []variable // push-down stack of variable values.
- depth int // the height of the stack of executing templates.
-}
-
func (t *Template) executeWithState(state *state, value reflect.Value) (err error) {
defer errRecover(&err)
if t.Tree == nil || t.Root == nil {
@@ -83,3 +87,123 @@ func (t *Template) executeWithState(state *state, value reflect.Value) (err erro
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
+ 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 (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)
+ }
+
+ 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 }}
`},
+ {`alias.html`, `
{{ .Permalink }} `},
{`disqus.html`, `{{- $pc := .Site.Config.Privacy.Disqus -}}
{{- if not $pc.Disable -}}
{{ if .Site.DisqusShortname }}
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 @@
+
{{ .Permalink }}
\ 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,278 +51,33 @@ 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
-}
-
-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
-}
-
-type templateFuncsterTemplater interface {
- templateFuncsterSetter
- tpl.TemplateFinder
- setFuncs(funcMap map[string]interface{})
-}
-
-type templateFuncsterSetter interface {
- setTemplateFuncster(f *templateFuncster)
-}
-
-// templateHandler holds the templates in play.
-// It implements the templateLoader and tpl.TemplateHandler interfaces.
-type templateHandler struct {
- mu sync.Mutex
-
- // shortcodes maps shortcode name to template variants
- // (language, output format etc.) of that shortcode.
- shortcodes map[string]*shortcodeTemplates
-
- // 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
-
- // text holds all the pure text templates.
- text *textTemplates
- html *htmlTemplates
-
- errors []*templateErr
-
- // 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
-}
-
const (
shortcodesPathPrefix = "shortcodes/"
internalPathPrefix = "_internal/"
)
-// 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")
- }
+// 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})?\>:`)
+var embeddedTemplatesAliases = map[string][]string{
+ "shortcodes/twitter.html": {"shortcodes/tweet.html"},
}
-func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
- base := templateBaseName(templateShortcode, name)
-
- shortcodename, variants := templateNameAndVariants(base)
-
- templs, found := t.shortcodes[shortcodename]
- if !found {
- templs = &shortcodeTemplates{}
- t.shortcodes[shortcodename] = templs
- }
-
- sv := shortcodeVariant{variants: variants, info: info, templ: templ}
-
- i := templs.indexOf(variants)
-
- 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)
- }
-}
-
-func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder {
- return struct {
- tpl.TemplateParser
- tpl.TemplateLookup
- tpl.TemplateLookupVariant
- }{
- tt,
- tt,
- new(nopLookupVariant),
- }
-
-}
-
-type nopLookupVariant int
-
-func (l nopLookupVariant) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return nil, false, false
-}
-
-func (t *templateHandler) Debug() {
- fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
- fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
-}
-
-// 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.
- // The templates are stored without the prefix identificator.
- name = strings.TrimPrefix(name, textTmplNamePrefix)
-
- return t.applyTemplateInfo(t.text.Lookup(name))
- }
-
- // Look in both
- if te, found := t.html.Lookup(name); found {
- return t.applyTemplateInfo(te, true)
- }
-
- return t.applyTemplateInfo(t.text.Lookup(name))
-
-}
-
-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) {
- name = templateBaseName(templateShortcode, name)
- s, found := t.shortcodes[name]
- if !found {
- return nil, false, false
- }
-
- sv, found := s.fromVariants(variants)
- if !found {
- return nil, false, false
- }
-
- 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
-
-}
-
-func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
-
-func (t *htmlTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
-
-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())
- }
-
- 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
- }
-
- panic(fmt.Sprintf("%T is not a template", in))
-}
-
-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
- }
-
- d.Tmpl = c
- d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
-
- c.initFuncs()
-
- for k, v := range t.html.overlays {
- vc := template.Must(v.Clone())
- // The extra lookup is a workaround, see
- // * https://github.com/golang/go/issues/16101
- // * https://github.com/gohugoio/hugo/issues/2549
- vc = vc.Lookup(vc.Name())
- vc.Funcs(c.html.funcster.funcMap)
- c.html.overlays[k] = vc
- }
-
- for k, v := range t.text.overlays {
- vc := texttemplate.Must(v.Clone())
- vc = vc.Lookup(vc.Name())
- vc.Funcs(texttemplate.FuncMap(c.text.funcster.funcMap))
- c.text.overlays[k] = vc
- }
-
- return c
-
-}
+const baseFileBase = "baseof"
func newTemplateAdapter(deps *deps.Deps) *templateHandler {
+
common := &templatesCommon{
nameBaseTemplateName: make(map[string]string),
transformNotFound: make(map[string]bool),
@@ -327,20 +88,23 @@ func newTemplateAdapter(deps *deps.Deps) *templateHandler {
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,
}
+
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),
+ Deps: deps,
+ layoutsFs: deps.BaseFs.Layouts.Fs,
+ templateHandlerCommon: &templateHandlerCommon{
+ shortcodes: make(map[string]*shortcodeTemplates),
+ templateInfo: make(map[string]tpl.Info),
+ html: htmlT,
+ text: textT,
+ },
}
common.handler = h
@@ -349,21 +113,7 @@ func newTemplateAdapter(deps *deps.Deps) *templateHandler {
}
-// Shared by both HTML and text templates.
-type templatesCommon struct {
- handler *templateHandler
- funcster *templateFuncster
-
- // 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
-}
type htmlTemplates struct {
- mu sync.RWMutex
-
*templatesCommon
t *template.Template
@@ -380,119 +130,29 @@ type htmlTemplates struct {
overlays map[string]*template.Template
}
-func (t *htmlTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
-}
-
func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) {
templ := t.lookup(name)
if templ == nil {
return nil, false
}
- return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics, Fs: t.handler.layoutsFs, NameBaseTemplateName: t.nameBaseTemplateName}, true
+ return templ, true
}
-func (t *htmlTemplates) lookup(name string) *template.Template {
- t.mu.RLock()
- defer t.mu.RUnlock()
-
- // 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) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ return t.handler.LookupVariant(name, variants)
}
-func (t *textTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
+func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
+ _, err := t.addTemplateIn(t.clone, name, tpl)
+ return err
}
-type textTemplates struct {
- *templatesCommon
- *textTemplate
- standalone *textTemplate
- clone *texttemplate.Template
- cloneClone *texttemplate.Template
-
- overlays map[string]*texttemplate.Template
-}
-
-func (t *textTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
- }
- 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
- }
- }
-
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
-
- if t.clone != nil {
- return t.clone.Lookup(name)
- }
-
- return nil
-}
-
-func (t *templateHandler) setFuncs(funcMap map[string]interface{}) {
- t.html.setFuncs(funcMap)
- t.text.setFuncs(funcMap)
- t.setFuncMapInTemplate(t.text.standalone.t, funcMap)
-}
-
-// 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) GetFuncs() map[string]interface{} {
- return t.html.funcster.funcMap
-}
-
-func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) {
- t.t.Funcs(funcMap)
-}
-
-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 *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) {
- t.mu.Lock()
- defer t.mu.Unlock()
-
templ, err := tt.New(name).Parse(tpl)
if err != nil {
return nil, err
@@ -518,276 +178,6 @@ func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) (
return c, 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
-}
-
-type textTemplate struct {
- mu sync.RWMutex
- t *texttemplate.Template
-}
-
-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()
-
- 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()
-
- templ, err := tt.New(name).Parse(tpl)
- if err != nil {
- return nil, err
- }
-
- if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil {
- return nil, err
- }
- return templ, 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
- }
-
- typ := resolveTemplateType(name)
-
- c, err := applyTemplateTransformersToTextTemplate(typ, templ)
- if err != nil {
- return nil, err
- }
-
- 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
-}
-
-func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
-}
-
-func (t *textTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
-}
-
-func (t *templateHandler) addTemplate(name, tpl string) error {
- return t.AddTemplate(name, tpl)
-}
-
-func (t *templateHandler) postTransform() error {
- if len(t.html.transformNotFound) == 0 && len(t.text.transformNotFound) == 0 {
- return nil
- }
-
- defer func() {
- t.text.transformNotFound = make(map[string]bool)
- t.html.transformNotFound = make(map[string]bool)
- }()
-
- for _, s := range []struct {
- lookup func(name string) *parse.Tree
- transformNotFound map[string]bool
- }{
- // html templates
- {func(name string) *parse.Tree {
- templ := t.html.lookup(name)
- if templ == nil {
- return nil
- }
- return templ.Tree
- }, t.html.transformNotFound},
- // text templates
- {func(name string) *parse.Tree {
- templT := t.text.lookup(name)
- if templT == nil {
- return nil
- }
- return templT.Tree
- }, t.text.transformNotFound},
- } {
- for name := range s.transformNotFound {
- templ := s.lookup(name)
- if templ != nil {
- _, err := applyTemplateTransformers(templateUndefined, templ, s.lookup)
- if err != nil {
- return err
- }
- }
- }
- }
-
- 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
- }
- 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
- }
-
- 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
-}
-
-// 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 *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
- }
-
- if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
- return err
- }
-
- return nil
- }
-
- if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil {
- if !os.IsNotExist(err) {
- return err
- }
- return nil
- }
-
- return nil
-
-}
-
-func (t *templateHandler) initFuncs() {
-
- // 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)
-
- // 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)
-
- funcsterHolder.setTemplateFuncster(funcster)
-
- }
-
- for _, v := range t.shortcodes {
- for _, variant := range v.variants {
- t.setFuncMapInTemplate(variant.templ, funcMap)
- }
- }
-
-}
-
-func (t *templateHandler) getTemplateHandler(name string) templateLoader {
- if strings.HasPrefix(name, textTmplNamePrefix) {
- return t.text
- }
- return t.html
-}
-
-func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
- h := t.getTemplateHandler(name)
- return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
-}
-
func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
masterTpl := t.lookup(masterFilename)
@@ -829,59 +219,292 @@ func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename strin
}
-func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
-
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- masterTpl := t.lookup(masterFilename)
-
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- 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
}
-
- masterTpl, err = t.t.New(masterFilename).Parse(templ.template)
- if err != nil {
- return errors.Wrapf(err, "failed to parse %q:", templ.filename)
- }
- t.nameBaseTemplateName[masterFilename] = templ.filename
}
- templ, err := onMissing(overlayFilename)
+ 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
+
+func (l nopLookupVariant) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ 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
+}
- overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ.template)
- if err != nil {
- return errors.Wrapf(err, "failed to parse %q:", templ.filename)
+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())
}
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, overlayTpl); err != nil {
- return err
+ execErr := t.executor.Execute(templ, wr, data)
+ if execErr != nil {
+ execErr = t.addFileContext(templ.Name(), execErr)
}
- t.overlays[name] = overlayTpl
- t.nameBaseTemplateName[name] = templ.filename
- return err
+ return execErr
}
-func removeLeadingBOM(s string) string {
- const bom = '\ufeff'
+func (t *templateHandler) GetFunc(name string) (reflect.Value, bool) {
+ v, found := t.funcs[name]
+ return v, found
- for i, r := range s {
- if i == 0 && r != bom {
- return s
- }
- if i > 0 {
- return s[i:]
- }
+}
+
+// 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.
+ // The templates are stored without the prefix identificator.
+ name = strings.TrimPrefix(name, textTmplNamePrefix)
+
+ return t.applyTemplateInfo(t.text.Lookup(name))
}
- return s
+ // Look in both
+ if te, found := t.html.Lookup(name); found {
+ return t.applyTemplateInfo(te, true)
+ }
+ return t.applyTemplateInfo(t.text.Lookup(name))
+
+}
+
+// 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) {
+ name = templateBaseName(templateShortcode, name)
+ s, found := t.shortcodes[name]
+ if !found {
+ return nil, false, false
+ }
+
+ sv, found := s.fromVariants(variants)
+ if !found {
+ return nil, false, false
+ }
+
+ more := len(s.variants) > 1
+
+ return &tpl.TemplateInfo{
+ Template: sv.templ,
+ Info: sv.info,
+ }, true, more
+
+}
+
+// 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
+ }
+
+ 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
+}
+
+// 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 (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()
+ }
+
+ // 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) getTemplateHandler(name string) templateLoader {
+ if strings.HasPrefix(name, textTmplNamePrefix) {
+ return t.text
+ }
+ return t.html
+}
+
+func (t *templateHandler) addFileContext(name string, inerr error) error {
+ if strings.HasPrefix(name, "_internal") {
+ return inerr
+ }
+
+ f, realFilename, err := t.fileAndFilename(name)
+ if err != nil {
+ return inerr
+
+ }
+ defer f.Close()
+
+ master, hasMaster := t.html.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 *templateHandler) addInternalTemplate(name, tpl string) error {
+ return t.AddTemplate("_internal/"+name, tpl)
+}
+
+func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
+ base := templateBaseName(templateShortcode, name)
+
+ shortcodename, variants := templateNameAndVariants(base)
+
+ templs, found := t.shortcodes[shortcodename]
+ if !found {
+ templs = &shortcodeTemplates{}
+ t.shortcodes[shortcodename] = templs
+ }
+
+ sv := shortcodeVariant{variants: variants, info: info, templ: templ}
+
+ i := templs.indexOf(variants)
+
+ 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)
+ }
}
func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
@@ -937,8 +560,77 @@ func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) e
}
}
-var embeddedTemplatesAliases = map[string][]string{
- "shortcodes/twitter.html": {"shortcodes/tweet.html"},
+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
+ }
+ }
+
+ return templ, found
+}
+
+func (t *templateHandler) checkState() {
+ if t.html.clone != nil || t.text.clone != nil {
+ panic("template is cloned and cannot be modfified")
+ }
+}
+
+func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
+ c := &templateHandler{
+ Deps: d,
+ layoutsFs: d.BaseFs.Layouts.Fs,
+ }
+
+ c.templateHandlerCommon = t.templateHandlerCommon.withNewHandler(c)
+ d.Tmpl = c
+ d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
+ c.executor, c.funcs = newTemplateExecuter(d)
+
+ return c
+
+}
+
+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 *templateHandler) fileAndFilename(name string) (afero.File, string, error) {
+ fs := t.layoutsFs
+ 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
+}
+
+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 *templateHandler) loadEmbedded() error {
@@ -961,26 +653,348 @@ func (t *templateHandler) loadEmbedded() error {
}
-func (t *templateHandler) addInternalTemplate(name, tpl string) error {
- return t.AddTemplate("_internal/"+name, tpl)
-}
+func (t *templateHandler) loadTemplates(prefix string) error {
-func (t *templateHandler) checkState() {
- if t.html.clone != nil || t.text.clone != nil {
- panic("template is cloned and cannot be modfified")
+ 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
+ }
+
+ if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
+ return err
+ }
+
+ return nil
}
+
+ if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil {
+ if !os.IsNotExist(err) {
+ return err
+ }
+ return nil
+ }
+
+ return nil
+
}
-func isDotFile(path string) bool {
- return filepath.Base(path)[0] == '.'
+func (t *templateHandler) postTransform() error {
+ if len(t.html.transformNotFound) == 0 && len(t.text.transformNotFound) == 0 {
+ return nil
+ }
+
+ defer func() {
+ t.text.transformNotFound = make(map[string]bool)
+ t.html.transformNotFound = make(map[string]bool)
+ }()
+
+ for _, s := range []struct {
+ lookup func(name string) *parse.Tree
+ transformNotFound map[string]bool
+ }{
+ // html templates
+ {func(name string) *parse.Tree {
+ templ := t.html.lookup(name)
+ if templ == nil {
+ return nil
+ }
+ return templ.Tree
+ }, t.html.transformNotFound},
+ // text templates
+ {func(name string) *parse.Tree {
+ templT := t.text.lookup(name)
+ if templT == nil {
+ return nil
+ }
+ return templT.Tree
+ }, t.text.transformNotFound},
+ } {
+ for name := range s.transformNotFound {
+ templ := s.lookup(name)
+ if templ != nil {
+ _, err := applyTemplateTransformers(templateUndefined, templ, s.lookup)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder {
+ return struct {
+ tpl.TemplateParser
+ tpl.TemplateLookup
+ tpl.TemplateLookupVariant
+ }{
+ tt,
+ tt,
+ new(nopLookupVariant),
+ }
+
+}
+
+type templateHandlerCommon struct {
+ // shortcodes maps shortcode name to template variants
+ // (language, output format etc.) of that shortcode.
+ shortcodes map[string]*shortcodeTemplates
+
+ // 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
+
+ // text holds all the pure text templates.
+ text *textTemplates
+ html *htmlTemplates
+}
+
+func (t templateHandlerCommon) withNewHandler(h *templateHandler) *templateHandlerCommon {
+ t.text = t.text.withNewHandler(h)
+ t.html = t.html.withNewHandler(h)
+ return &t
+}
+
+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
+}
+
+// Shared by both HTML and text templates.
+type templatesCommon struct {
+ handler *templateHandler
+
+ // 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 templatesCommon) withNewHandler(h *templateHandler) *templatesCommon {
+ t.handler = h
+ return &t
+}
+
+type textTemplate struct {
+ mu sync.RWMutex
+ t *texttemplate.Template
+}
+
+func (t *textTemplate) Lookup(name string) (tpl.Template, bool) {
+ t.mu.RLock()
+ defer t.mu.RUnlock()
+
+ tpl := t.t.Lookup(name)
+ return tpl, tpl != nil
+}
+
+func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) {
+ return t.parseIn(t.t, name, tpl)
+}
+
+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
+ }
+
+ 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 *textTemplates) Lookup(name string) (tpl.Template, bool) {
+ templ := t.lookup(name)
+ if templ == nil {
+ return nil, false
+ }
+ return templ, true
+}
+
+func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ return t.handler.LookupVariant(name, variants)
+}
+
+func (t *textTemplates) addLateTemplate(name, tpl string) error {
+ _, err := t.addTemplateIn(t.clone, name, tpl)
+ return err
+}
+
+func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) {
+ return t.addTemplateIn(t.t, name, tpl)
+}
+
+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
+ }
+
+ typ := resolveTemplateType(name)
+
+ c, err := applyTemplateTransformersToTextTemplate(typ, templ)
+ if err != nil {
+ return nil, err
+ }
+
+ 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
+}
+
+func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+
+ name = strings.TrimPrefix(name, textTmplNamePrefix)
+ masterTpl := t.lookup(masterFilename)
+
+ if masterTpl == nil {
+ templ, err := onMissing(masterFilename)
+ if err != nil {
+ return err
+ }
+
+ masterTpl, err = t.t.New(masterFilename).Parse(templ.template)
+ if err != nil {
+ return errors.Wrapf(err, "failed to parse %q:", templ.filename)
+ }
+ t.nameBaseTemplateName[masterFilename] = templ.filename
+ }
+
+ templ, err := onMissing(overlayFilename)
+ if err != nil {
+ return err
+ }
+
+ overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ.template)
+ if err != nil {
+ return errors.Wrapf(err, "failed to parse %q:", templ.filename)
+ }
+
+ overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
+ if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, overlayTpl); err != nil {
+ return err
+ }
+ t.overlays[name] = overlayTpl
+ t.nameBaseTemplateName[name] = templ.filename
+
+ return err
+
+}
+
+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
+ }
+ }
+
+ if templ := t.t.Lookup(name); templ != nil {
+ return templ
+ }
+
+ 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 isBackupFile(path string) bool {
return path[len(path)-1] == '~'
}
-const baseFileBase = "baseof"
-
func isBaseTemplate(path string) bool {
return strings.Contains(filepath.Base(path), baseFileBase)
}
+
+func isDotFile(path string) bool {
+ return filepath.Base(path)[0] == '.'
+}
+
+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
+
+}
+
+// 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")
+ }
+
+}
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: ¶msHolder{
- 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: ¶msHolder{
- 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", "
Hugo Rocks! "), 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 }}