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

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/tpl
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2020-01-15 17:59:56 +0300
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2020-01-22 11:39:49 +0300
commitc6d650c8c8b22fdc7ddedc1e42a3ca698e1390d6 (patch)
tree5a189224439d654a9ef7517b0c702e781874b45a /tpl
parent8585b388d27abde1ab6b6c63ad6addf4066ec8dd (diff)
tpl/tplimpl: Rework template management to get rid of concurrency issues
This more or less completes the simplification of the template handling code in Hugo started in v0.62. The main motivation was to fix a long lasting issue about a crash in HTML content files without front matter. But this commit also comes with a big functional improvement. As we now have moved the base template evaluation to the build stage we now use the same lookup rules for `baseof` as for `list` etc. type of templates. This means that in this simple example you can have a `baseof` template for the `blog` section without having to duplicate the others: ``` layouts ├── _default │   ├── baseof.html │   ├── list.html │   └── single.html └── blog └── baseof.html ``` Also, when simplifying code, you often get rid of some double work, as shown in the "site building" benchmarks below. These benchmarks looks suspiciously good, but I have repeated the below with ca. the same result. Compared to master: ``` name old time/op new time/op delta SiteNew/Bundle_with_image-16 13.1ms ± 1% 10.5ms ± 1% -19.34% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 13.0ms ± 0% 10.7ms ± 1% -18.05% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 46.4ms ± 2% 43.1ms ± 1% -7.15% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 52.2ms ± 2% 47.8ms ± 1% -8.30% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 77.9ms ± 1% 70.9ms ± 1% -9.01% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 43.0ms ± 0% 37.2ms ± 1% -13.54% (p=0.029 n=4+4) SiteNew/Page_collections-16 58.2ms ± 1% 52.4ms ± 1% -9.95% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Bundle_with_image-16 3.81MB ± 0% 2.22MB ± 0% -41.70% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 3.60MB ± 0% 2.01MB ± 0% -44.20% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 19.3MB ± 1% 14.1MB ± 0% -26.91% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 70.7MB ± 0% 69.0MB ± 0% -2.40% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 37.1MB ± 0% 31.2MB ± 0% -15.94% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 17.6MB ± 0% 10.6MB ± 0% -39.92% (p=0.029 n=4+4) SiteNew/Page_collections-16 25.9MB ± 0% 21.2MB ± 0% -17.99% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Bundle_with_image-16 52.3k ± 0% 26.1k ± 0% -50.18% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 52.3k ± 0% 26.1k ± 0% -50.16% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 336k ± 1% 269k ± 0% -19.90% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 422k ± 0% 395k ± 0% -6.43% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 401k ± 0% 313k ± 0% -21.79% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 247k ± 0% 143k ± 0% -42.17% (p=0.029 n=4+4) SiteNew/Page_collections-16 282k ± 0% 207k ± 0% -26.55% (p=0.029 n=4+4) ``` Fixes #6716 Fixes #6760 Fixes #6768 Fixes #6778
Diffstat (limited to 'tpl')
-rw-r--r--tpl/collections/apply.go2
-rw-r--r--tpl/collections/apply_test.go14
-rw-r--r--tpl/partials/partials.go6
-rw-r--r--tpl/resources/resources.go3
-rw-r--r--tpl/template.go12
-rw-r--r--tpl/templates/templates.go2
-rw-r--r--tpl/tplimpl/shortcodes.go3
-rw-r--r--tpl/tplimpl/template.go1163
-rw-r--r--tpl/tplimpl/templateProvider.go27
-rw-r--r--tpl/tplimpl/template_ast_transformers.go130
-rw-r--r--tpl/tplimpl/template_ast_transformers_test.go78
-rw-r--r--tpl/tplimpl/template_errors.go6
-rw-r--r--tpl/tplimpl/template_funcs_test.go4
-rw-r--r--tpl/tplimpl/template_info_test.go7
14 files changed, 659 insertions, 798 deletions
diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go
index d41a3b1da..55d29d3a9 100644
--- a/tpl/collections/apply.go
+++ b/tpl/collections/apply.go
@@ -106,7 +106,7 @@ func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value,
func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
if !strings.ContainsRune(fname, '.') {
- templ := ns.deps.Tmpl.(tpl.TemplateFuncGetter)
+ templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter)
return templ.GetFunc(fname)
}
diff --git a/tpl/collections/apply_test.go b/tpl/collections/apply_test.go
index 5b21d5a97..0d06f52e8 100644
--- a/tpl/collections/apply_test.go
+++ b/tpl/collections/apply_test.go
@@ -22,6 +22,7 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
+ "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/tpl"
)
@@ -31,10 +32,18 @@ func (templateFinder) Lookup(name string) (tpl.Template, bool) {
return nil, false
}
+func (templateFinder) HasTemplate(name string) bool {
+ return false
+}
+
func (templateFinder) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
return nil, false, false
}
+func (templateFinder) LookupLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
+ return nil, false, nil
+}
+
func (templateFinder) Execute(t tpl.Template, wr io.Writer, data interface{}) error {
return nil
}
@@ -51,8 +60,9 @@ func (templateFinder) GetFunc(name string) (reflect.Value, bool) {
func TestApply(t *testing.T) {
t.Parallel()
c := qt.New(t)
-
- ns := New(&deps.Deps{Tmpl: new(templateFinder)})
+ d := &deps.Deps{}
+ d.SetTmpl(new(templateFinder))
+ ns := New(d)
strings := []interface{}{"a\n", "b\n"}
diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
index 6f3ba2d13..e03bf471f 100644
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -105,11 +105,11 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
}
n := "partials/" + name
- templ, found := ns.deps.Tmpl.Lookup(n)
+ templ, found := ns.deps.Tmpl().Lookup(n)
if !found {
// For legacy reasons.
- templ, found = ns.deps.Tmpl.Lookup(n + ".html")
+ templ, found = ns.deps.Tmpl().Lookup(n + ".html")
}
if !found {
@@ -139,7 +139,7 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface
w = b
}
- if err := ns.deps.Tmpl.Execute(templ, 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 9a7b29696..fd0ffc5ec 100644
--- a/tpl/resources/resources.go
+++ b/tpl/resources/resources.go
@@ -45,6 +45,7 @@ func New(deps *deps.Deps) (*Namespace, error) {
if err != nil {
return nil, err
}
+
return &Namespace{
deps: deps,
scssClient: scssClient,
@@ -53,7 +54,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.Tmpl, deps.TextTmpl),
+ templatesClient: templates.New(deps.ResourceSpec, deps),
}, nil
}
diff --git a/tpl/template.go b/tpl/template.go
index 0841236de..b9b0749b6 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -30,9 +30,7 @@ type TemplateManager interface {
TemplateFuncGetter
AddTemplate(name, tpl string) error
AddLateTemplate(name, tpl string) error
- LoadTemplates(prefix string) error
-
- RebuildClone()
+ MarkReady() error
}
// TemplateVariants describes the possible variants of a template.
@@ -52,6 +50,8 @@ type TemplateFinder interface {
type TemplateHandler interface {
TemplateFinder
Execute(t Template, wr io.Writer, data interface{}) error
+ LookupLayout(d output.LayoutDescriptor, f output.Format) (Template, bool, error)
+ HasTemplate(name string) bool
}
type TemplateLookup interface {
@@ -105,6 +105,12 @@ type templateInfoManager struct {
InfoManager
}
+// TemplatesProvider as implemented by deps.Deps.
+type TemplatesProvider interface {
+ Tmpl() TemplateHandler
+ TextTmpl() TemplateParseFinder
+}
+
// WithInfo wraps the info in a template.
func WithInfo(templ Template, info Info) Template {
if manager, ok := info.(InfoManager); ok {
diff --git a/tpl/templates/templates.go b/tpl/templates/templates.go
index 44d397e68..80eb2d378 100644
--- a/tpl/templates/templates.go
+++ b/tpl/templates/templates.go
@@ -34,7 +34,7 @@ type Namespace struct {
// Note that this is the Unix-styled relative path including filename suffix,
// e.g. partials/header.html
func (ns *Namespace) Exists(name string) bool {
- _, found := ns.deps.Tmpl.Lookup(name)
+ _, found := ns.deps.Tmpl().Lookup(name)
return found
}
diff --git a/tpl/tplimpl/shortcodes.go b/tpl/tplimpl/shortcodes.go
index abef11e1e..cc4d99491 100644
--- a/tpl/tplimpl/shortcodes.go
+++ b/tpl/tplimpl/shortcodes.go
@@ -32,8 +32,7 @@ type shortcodeVariant struct {
// A slice of length numTemplateVariants.
variants []string
- info tpl.Info
- templ tpl.Template
+ ts *templateState
}
type shortcodeTemplates struct {
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index 2d2a63cf9..d0c656a2e 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -14,59 +14,44 @@
package tplimpl
import (
- "fmt"
"io"
+ "os"
+ "path/filepath"
"reflect"
"regexp"
+ "strings"
+ "sync"
"time"
- "github.com/gohugoio/hugo/hugofs/files"
-
- "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/common/types"
- "github.com/gohugoio/hugo/common/herrors"
-
- "strings"
+ "github.com/gohugoio/hugo/helpers"
- template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ "github.com/gohugoio/hugo/output"
- texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+ "github.com/gohugoio/hugo/deps"
+ "github.com/spf13/afero"
+ "github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/tpl/tplimpl/embedded"
+ "github.com/gohugoio/hugo/hugofs/files"
"github.com/pkg/errors"
- "os"
-
- "github.com/gohugoio/hugo/output"
+ "github.com/gohugoio/hugo/tpl/tplimpl/embedded"
- "path/filepath"
- "sync"
+ htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
- "github.com/gohugoio/hugo/deps"
- "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl"
- "github.com/spf13/afero"
)
const (
textTmplNamePrefix = "_text/"
-)
-var (
- _ 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)
-)
-
-const (
shortcodesPathPrefix = "shortcodes/"
internalPathPrefix = "_internal/"
+ baseFileBase = "baseof"
)
// The identifiers may be truncated in the log, e.g.
@@ -77,286 +62,273 @@ var embeddedTemplatesAliases = map[string][]string{
"shortcodes/twitter.html": {"shortcodes/tweet.html"},
}
-const baseFileBase = "baseof"
+var (
+ _ tpl.TemplateManager = (*templateExec)(nil)
+ _ tpl.TemplateHandler = (*templateExec)(nil)
+ _ tpl.TemplateFuncGetter = (*templateExec)(nil)
+ _ tpl.TemplateFinder = (*templateExec)(nil)
-func newTemplateAdapter(deps *deps.Deps) *templateHandler {
+ _ tpl.Template = (*templateState)(nil)
+ _ tpl.Info = (*templateState)(nil)
+)
- common := &templatesCommon{
- nameBaseTemplateName: make(map[string]string),
- transformNotFound: make(map[string]bool),
- identityNotFound: make(map[string][]identity.Manager),
- }
+var defineRe = regexp.MustCompile(`{{-?\s?define`)
- htmlT := &htmlTemplates{
- t: template.New(""),
- overlays: make(map[string]*template.Template),
- templatesCommon: common,
- }
+func newIdentity(name string) identity.Manager {
+ return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name))
+}
- textT := &textTemplates{
- textTemplate: &textTemplate{t: texttemplate.New("")},
- standalone: &textTemplate{t: texttemplate.New("")},
- overlays: make(map[string]*texttemplate.Template),
- templatesCommon: common,
+func newStandaloneTextTemplate(funcs map[string]interface{}) tpl.TemplateParseFinder {
+ return &textTemplateWrapperWithLock{
+ RWMutex: &sync.RWMutex{},
+ Template: texttemplate.New("").Funcs(funcs),
}
+}
- h := &templateHandler{
- Deps: deps,
- layoutsFs: deps.BaseFs.Layouts.Fs,
- templateHandlerCommon: &templateHandlerCommon{
- shortcodes: make(map[string]*shortcodeTemplates),
- templateInfo: make(map[string]tpl.Info),
- templateInfoTree: make(map[string]*templateInfoTree),
- html: htmlT,
- text: textT,
- },
+func newTemplateExec(d *deps.Deps) (*templateExec, error) {
+ exec, funcs := newTemplateExecuter(d)
+ funcMap := make(map[string]interface{})
+ for k, v := range funcs {
+ funcMap[k] = v.Interface()
}
- textT.textTemplate.templates = textT
- textT.standalone.templates = textT
- common.handler = h
-
- return h
-
-}
-
-type htmlTemplates struct {
- *templatesCommon
+ h := &templateHandler{
+ nameBaseTemplateName: make(map[string]string),
+ transformNotFound: make(map[string]*templateState),
+ identityNotFound: make(map[string][]identity.Manager),
- t *template.Template
+ shortcodes: make(map[string]*shortcodeTemplates),
+ templateInfo: make(map[string]tpl.Info),
+ baseof: make(map[string]templateInfo),
+ needsBaseof: make(map[string]templateInfo),
- // This looks, and is, strange.
- // The clone is used by non-renderable content pages, and these need to be
- // re-parsed on content change, and to avoid the
- // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
- clone *template.Template
- cloneClone *template.Template
+ main: newTemplateNamespace(funcMap, false),
- // a separate storage for the overlays created from cloned master templates.
- // note: No mutex protection, so we add these in one Go routine, then just read.
- overlays map[string]*template.Template
-}
+ Deps: d,
+ layoutHandler: output.NewLayoutHandler(),
+ layoutsFs: d.BaseFs.Layouts.Fs,
+ layoutTemplateCache: make(map[layoutCacheKey]tpl.Template),
+ }
-func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+ if err := h.loadEmbedded(); err != nil {
+ return nil, err
}
- return templ, true
-}
+ if err := h.loadTemplates(); err != nil {
+ return nil, err
+ }
-func (t *htmlTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
+ e := &templateExec{
+ d: d,
+ executor: exec,
+ funcs: funcs,
+ templateHandler: h,
+ }
-func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
-}
+ d.SetTmpl(e)
+ d.SetTextTmpl(newStandaloneTextTemplate(funcMap))
-func (t *htmlTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
-}
+ if d.WithTemplate != nil {
+ if err := d.WithTemplate(e); err != nil {
+ return nil, err
-func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, templstr string) (*templateContext, error) {
- templ, err := tt.New(name).Parse(templstr)
- if err != nil {
- return nil, err
+ }
}
- typ := resolveTemplateType(name)
+ return e, nil
+}
- c, err := t.handler.applyTemplateTransformersToHMLTTemplate(typ, templ)
- if err != nil {
- return nil, err
+func newTemplateNamespace(funcs map[string]interface{}, lock bool) *templateNamespace {
+ var mu *sync.RWMutex
+ if lock {
+ mu = &sync.RWMutex{}
}
- for k := range c.templateNotFound {
- t.transformNotFound[k] = true
- t.identityNotFound[k] = append(t.identityNotFound[k], c.id)
+ return &templateNamespace{
+ prototypeHTML: htmltemplate.New("").Funcs(funcs),
+ prototypeText: texttemplate.New("").Funcs(funcs),
+ templateStateMap: &templateStateMap{
+ mu: mu,
+ templates: make(map[string]*templateState),
+ },
}
+}
- for k := range c.identityNotFound {
- t.identityNotFound[k] = append(t.identityNotFound[k], c.id)
+func newTemplateState(templ tpl.Template, info templateInfo) *templateState {
+ return &templateState{
+ info: info,
+ typ: info.resolveType(),
+ Template: templ,
+ Manager: newIdentity(info.name),
+ parseInfo: tpl.DefaultParseInfo,
}
-
- return c, nil
}
-func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+type layoutCacheKey struct {
+ d output.LayoutDescriptor
+ f string
+}
- masterTpl := t.lookup(masterFilename)
+type templateExec struct {
+ d *deps.Deps
+ executor texttemplate.Executer
+ funcs map[string]reflect.Value
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- return err
- }
+ *templateHandler
+}
- masterTpl, err = t.t.New(overlayFilename).Parse(templ.template)
- if err != nil {
- return templ.errWithFileContext("parse master failed", err)
- }
- }
+func (t templateExec) Clone(d *deps.Deps) *templateExec {
+ exec, funcs := newTemplateExecuter(d)
+ t.executor = exec
+ t.funcs = funcs
+ t.d = d
+ return &t
+}
- templ, err := onMissing(overlayFilename)
- if err != nil {
- return err
+func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data interface{}) error {
+ if rlocker, ok := templ.(types.RLocker); ok {
+ rlocker.RLock()
+ defer rlocker.RUnlock()
}
-
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template)
- if err != nil {
- return templ.errWithFileContext("parse failed", err)
+ if t.Metrics != nil {
+ defer t.Metrics.MeasureSince(templ.Name(), time.Now())
}
- // The extra lookup is a workaround, see
- // * https://github.com/golang/go/issues/16101
- // * https://github.com/gohugoio/hugo/issues/2549
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- if _, err := t.handler.applyTemplateTransformersToHMLTTemplate(templateUndefined, overlayTpl); err != nil {
- return err
+ execErr := t.executor.Execute(templ, wr, data)
+ if execErr != nil {
+ execErr = t.addFileContext(templ, execErr)
}
+ return execErr
+}
- t.overlays[name] = overlayTpl
- t.nameBaseTemplateName[name] = masterFilename
-
- return err
-
+func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
+ v, found := t.funcs[name]
+ return v, found
}
-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
+func (t *templateExec) MarkReady() error {
+ var err error
+ t.readyInit.Do(func() {
+ // We only need the clones if base templates are in use.
+ if len(t.needsBaseof) > 0 {
+ err = t.main.createPrototypes()
}
- }
+ })
- if templ := t.t.Lookup(name); templ != nil {
- return templ
+ if err != nil {
+ return err
}
- if t.clone != nil {
- return t.clone.Lookup(name)
+ if t.Deps.BuildFlags.HasLateTemplate.Load() {
+ // This costs memory, so try to avoid it if we don't have to.
+ // The late templates are used to handle HTML in files in /content
+ // without front matter.
+ t.readyLateInit.Do(func() {
+ t.late = t.main.Clone(true)
+ t.late.createPrototypes()
+ })
}
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 {
- ready bool
+ main *templateNamespace
+ needsBaseof map[string]templateInfo
+ baseof map[string]templateInfo
- executor texttemplate.Executer
- funcs map[string]reflect.Value
+ late *templateNamespace // Templates added after main has started executing.
+
+ readyInit sync.Once
+ readyLateInit sync.Once
// This is the filesystem to load the templates from. All the templates are
// stored in the root of this filesystem.
layoutsFs afero.Fs
+ layoutHandler *output.LayoutHandler
+
+ layoutTemplateCache map[layoutCacheKey]tpl.Template
+ layoutTemplateCacheMu sync.RWMutex
+
*deps.Deps
- *templateHandlerCommon
+ // Used to get proper filenames in errors
+ nameBaseTemplateName map[string]string
+
+ // Holds name and source of template definitions not found during the first
+ // AST transformation pass.
+ transformNotFound map[string]*templateState
+
+ // Holds identities of templates not found during first pass.
+ identityNotFound map[string][]identity.Manager
+
+ // 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
}
-// AddLateTemplate is used to add a template late, i.e. after the
+// AddLateTemplate is used to add a template after the
// regular templates have started its execution.
+// These are currently "pure HTML content files".
func (t *templateHandler) AddLateTemplate(name, tpl string) error {
- h := t.getTemplateHandler(name)
- if err := h.addLateTemplate(name, tpl); err != nil {
- return err
- }
- return nil
+ _, err := t.late.parse(t.newTemplateInfo(name, tpl))
+ return err
}
// 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
+ templ, err := t.addTemplateTo(t.newTemplateInfo(name, tpl), t.main)
+ if err == nil {
+ t.applyTemplateTransformers(t.main, templ)
}
- return nil
-}
-
-func (t *templateHandler) Debug() {
- fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
- fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
+ return err
}
-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())
+func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
+ templ, found := t.main.Lookup(name)
+ if found {
+ return templ, true
}
- execErr := t.executor.Execute(templ, wr, data)
- if execErr != nil {
- execErr = t.addFileContext(templ.Name(), execErr)
+ if t.late != nil {
+ return t.late.Lookup(name)
}
- return execErr
-
+ return nil, false
}
-func (t *templateHandler) GetFunc(name string) (reflect.Value, bool) {
- v, found := t.funcs[name]
- return v, found
-
-}
-
-// LoadTemplates loads the templates from the layouts filesystem.
-// A prefix can be given to indicate a template namespace to load the templates
-// into, i.e. "_internal" etc.
-func (t *templateHandler) LoadTemplates(prefix string) error {
- return t.loadTemplates(prefix)
-
-}
-
-// Lookup tries to find a template with the given name in both template
-// collections: First HTML, then the plain text template collection.
-func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
-
- if strings.HasPrefix(name, textTmplNamePrefix) {
- // The caller has explicitly asked for a text template, so only look
- // in the text template collection.
- // The templates are stored without the prefix identificator.
- name = strings.TrimPrefix(name, textTmplNamePrefix)
-
- return t.applyTemplateInfo(t.text.Lookup(name))
+func (t *templateHandler) LookupLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
+ key := layoutCacheKey{d, f.Name}
+ t.layoutTemplateCacheMu.RLock()
+ if cacheVal, found := t.layoutTemplateCache[key]; found {
+ t.layoutTemplateCacheMu.RUnlock()
+ return cacheVal, true, nil
}
+ t.layoutTemplateCacheMu.RUnlock()
- // Look in both
- if te, found := t.html.Lookup(name); found {
- return t.applyTemplateInfo(te, true)
- }
+ t.layoutTemplateCacheMu.Lock()
+ defer t.layoutTemplateCacheMu.Unlock()
- return t.applyTemplateInfo(t.text.Lookup(name))
+ templ, found, err := t.findLayout(d, f)
+ if err == nil && found {
+ t.layoutTemplateCache[key] = templ
+ return templ, true, nil
+ }
+ return nil, false, err
}
// 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) {
- if !t.ready {
- panic("handler not ready")
- }
name = templateBaseName(templateShortcode, name)
s, found := t.shortcodes[name]
if !found {
@@ -370,131 +342,142 @@ func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVarian
more := len(s.variants) > 1
- return tpl.WithInfo(sv.templ, sv.info), true, more
+ return sv.ts, true, more
}
-// markReady marks the templates as "ready for execution". No changes allowed
-// after this is set.
-func (t *templateHandler) markReady() error {
- defer func() {
- t.ready = true
- }()
-
- if err := t.postTransform(); err != nil {
- return err
+func (t *templateHandler) HasTemplate(name string) bool {
+ if _, found := t.baseof[name]; found {
+ return true
}
+ _, found := t.Lookup(name)
+ return found
+}
- 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())
- }
+func (t *templateHandler) findLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
+ layouts, _ := t.layoutHandler.For(d, f)
+ for _, name := range layouts {
+ templ, found := t.main.Lookup(name)
+ if found {
+ return templ, true, nil
+ }
- return nil
-}
+ overlay, found := t.needsBaseof[name]
+
+ if !found {
+ continue
+ }
+
+ d.Baseof = true
+ baseLayouts, _ := t.layoutHandler.For(d, f)
+ var base templateInfo
+ found = false
+ for _, l := range baseLayouts {
+ base, found = t.baseof[l]
+ if found {
+ break
+ }
+ }
+
+ if !found {
+ return nil, false, errors.Errorf("no baseof layout found for %q:", name)
+ }
+
+ templ, err := t.applyBaseTemplate(overlay, base)
+ if err != nil {
+ return nil, false, err
+ }
+
+ ts := newTemplateState(templ, overlay)
+ ts.baseInfo = base
+
+ // Add the base identity to detect changes
+ ts.Add(identity.NewPathIdentity(files.ComponentFolderLayouts, base.name))
+
+ t.applyTemplateTransformers(t.main, ts)
+
+ return ts, true, 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())
}
+
+ return nil, false, nil
}
-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()
+func (t *templateHandler) findTemplate(name string) *templateState {
+ if templ, found := t.Lookup(name); found {
+ return templ.(*templateState)
}
-
- // 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)
+ return nil
}
-func (t *templateHandler) getTemplateHandler(name string) templateLoader {
- if strings.HasPrefix(name, textTmplNamePrefix) {
- return t.text
+func (t *templateHandler) newTemplateInfo(name, tpl string) templateInfo {
+ var isText bool
+ name, isText = t.nameIsText(name)
+ return templateInfo{
+ name: name,
+ isText: isText,
+ template: tpl,
}
- return t.html
}
-func (t *templateHandler) addFileContext(name string, inerr error) error {
- if strings.HasPrefix(name, "_internal") {
+func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error {
+ if strings.HasPrefix(templ.Name(), "_internal") {
return inerr
}
- f, realFilename, err := t.fileAndFilename(name)
- if err != nil {
+ ts, ok := templ.(*templateState)
+ if !ok {
return inerr
-
}
- defer f.Close()
- master, hasMaster := t.html.nameBaseTemplateName[name]
+ //lint:ignore ST1008 the error is the main result
+ checkFilename := func(info templateInfo, inErr error) (error, bool) {
+ if info.filename == "" {
+ return inErr, false
+ }
+
+ lineMatcher := func(m herrors.LineMatcher) bool {
+ if m.Position.LineNumber != m.LineNumber {
+ return false
+ }
- ferr := errors.Wrap(inerr, "execute of template failed")
+ identifiers := t.extractIdentifiers(m.Error.Error())
- // 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 {
+ for _, id := range identifiers {
+ if strings.Contains(m.Line, id) {
+ return true
+ }
+ }
return false
}
- if !hasMaster {
- return true
- }
- identifiers := t.extractIdentifiers(m.Error.Error())
+ f, err := t.layoutsFs.Open(info.filename)
+ if err != nil {
+ return inErr, false
+ }
+ defer f.Close()
- for _, id := range identifiers {
- if strings.Contains(m.Line, id) {
- return true
- }
+ fe, ok := herrors.WithFileContext(inErr, info.realFilename, f, lineMatcher)
+ if ok {
+ return fe, true
}
- return false
+ return inErr, false
}
- fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
- if ok || !hasMaster {
- return fe
- }
+ inerr = errors.Wrap(inerr, "execute of template failed")
- // Try the base template if relevant
- f, realFilename, err = t.fileAndFilename(master)
- if err != nil {
+ if err, ok := checkFilename(ts.info, inerr); ok {
return err
}
- defer f.Close()
-
- fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
- if !ok {
- // Return the most specific.
- return ferr
-
- }
- return fe
+ err, _ := checkFilename(ts.baseInfo, inerr)
-}
+ return err
-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) {
-
+func (t *templateHandler) addShortcodeVariant(ts *templateState) {
+ name := ts.Name()
base := templateBaseName(templateShortcode, name)
shortcodename, variants := templateNameAndVariants(base)
@@ -505,7 +488,7 @@ func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ
t.shortcodes[shortcodename] = templs
}
- sv := shortcodeVariant{variants: variants, info: info, templ: templ}
+ sv := shortcodeVariant{variants: variants, ts: ts}
i := templs.indexOf(variants)
@@ -519,11 +502,7 @@ func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ
}
}
-func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
- t.checkState()
-
- t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path)
-
+func (t *templateHandler) addTemplateFile(name, path string) error {
getTemplate := func(filename string) (templateInfo, error) {
fs := t.Layouts.Fs
b, err := afero.ReadFile(fs, filename)
@@ -540,71 +519,97 @@ func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) e
}
}
- return templateInfo{template: s, filename: filename, realFilename: realFilename, fs: fs}, nil
+ var isText bool
+ name, isText = t.nameIsText(name)
+
+ return templateInfo{
+ name: name,
+ isText: isText,
+ template: s,
+ filename: filename,
+ realFilename: realFilename,
+ fs: fs,
+ }, nil
+ }
+
+ tinfo, err := getTemplate(path)
+ if err != nil {
+ return err
}
- // get the suffix and switch on that
- ext := filepath.Ext(path)
- switch ext {
- case ".amber":
- helpers.Deprecated("Amber templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ if isBaseTemplate(name) {
+ // Store it for later.
+ t.baseof[name] = tinfo
return nil
- case ".ace":
- helpers.Deprecated("ACE templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ }
+
+ needsBaseof := !t.noBaseNeeded(name) && defineRe.MatchString(tinfo.template)
+ if needsBaseof {
+ t.needsBaseof[name] = tinfo
return nil
- default:
+ }
- if baseTemplatePath != "" {
- return t.handleMaster(name, path, baseTemplatePath, getTemplate)
- }
+ templ, err := t.addTemplateTo(tinfo, t.main)
+ if err != nil {
+ return tinfo.errWithFileContext("parse failed", err)
+ }
+ t.applyTemplateTransformers(t.main, templ)
+
+ return nil
+
+}
- templ, err := getTemplate(path)
+func (t *templateHandler) addTemplateTo(info templateInfo, to *templateNamespace) (*templateState, error) {
+ return to.parse(info)
+}
+func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) {
+ if overlay.isText {
+ templ, err := t.main.prototypeTextClone.New(overlay.name).Parse(base.template)
if err != nil {
- return err
+ return nil, base.errWithFileContext("parse failed", err)
}
-
- err = t.AddTemplate(name, templ.template)
+ templ, err = templ.Parse(overlay.template)
if err != nil {
- return templ.errWithFileContext("parse failed", err)
+ return nil, overlay.errWithFileContext("parse failed", err)
}
- return nil
+ return templ, nil
}
-}
-func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) {
- if templ != nil {
- if info, found := t.templateInfo[templ.Name()]; found {
- return tpl.WithInfo(templ, info), true
- }
+ templ, err := t.main.prototypeHTMLClone.New(overlay.name).Parse(base.template)
+ if err != nil {
+ return nil, base.errWithFileContext("parse failed", err)
}
- return templ, found
-}
-
-func (t *templateHandler) checkState() {
- if t.html.clone != nil || t.text.clone != nil {
- panic("template is cloned and cannot be modfified")
+ templ, err = htmltemplate.Must(templ.Clone()).Parse(overlay.template)
+ if err != nil {
+ return nil, overlay.errWithFileContext("parse failed", err)
}
+
+ // The extra lookup is a workaround, see
+ // * https://github.com/golang/go/issues/16101
+ // * https://github.com/gohugoio/hugo/issues/2549
+ templ = templ.Lookup(templ.Name())
+
+ return templ, err
}
-func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
- if !t.ready {
- panic("invalid state")
- }
- c := &templateHandler{
- ready: true,
- Deps: d,
- layoutsFs: d.BaseFs.Layouts.Fs,
+func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *templateState) (*templateContext, error) {
+ c, err := applyTemplateTransformers(ts, ns.newTemplateLookup(ts))
+ if err != nil {
+ return nil, err
}
- c.templateHandlerCommon = t.templateHandlerCommon.withNewHandler(c)
- d.Tmpl = c
- d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
- c.executor, c.funcs = newTemplateExecuter(d)
+ for k := range c.templateNotFound {
+ t.transformNotFound[k] = ts
+ t.identityNotFound[k] = append(t.identityNotFound[k], c.t)
+ }
- return c
+ for k := range c.identityNotFound {
+ t.identityNotFound[k] = append(t.identityNotFound[k], c.t)
+ }
+ return c, err
}
func (t *templateHandler) extractIdentifiers(line string) []string {
@@ -616,83 +621,44 @@ func (t *templateHandler) extractIdentifiers(line string) []string {
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 {
for _, kv := range embedded.EmbeddedTemplates {
name, templ := kv[0], kv[1]
- if err := t.addInternalTemplate(name, templ); err != nil {
+ if err := t.AddTemplate(internalPathPrefix+name, templ); err != nil {
return err
}
if aliases, found := embeddedTemplatesAliases[name]; found {
+ // TODO(bep) avoid reparsing these aliases
for _, alias := range aliases {
- if err := t.addInternalTemplate(alias, templ); err != nil {
+ alias = internalPathPrefix + alias
+ if err := t.AddTemplate(alias, templ); err != nil {
return err
}
}
-
}
}
-
return nil
-
}
-func (t *templateHandler) loadTemplates(prefix string) error {
-
+func (t *templateHandler) loadTemplates() 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) {
+ if isDotFile(path) || isBackupFile(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)
- },
- }
+ name := strings.TrimPrefix(filepath.ToSlash(path), "/")
+ filename := filepath.Base(path)
+ outputFormat, found := t.OutputFormatsConfig.FromFilename(filename)
- 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 found && outputFormat.IsPlainText {
+ name = textTmplNamePrefix + name
}
- if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
+ if err := t.addTemplateFile(name, path); err != nil {
return err
}
@@ -710,86 +676,44 @@ func (t *templateHandler) loadTemplates(prefix string) error {
}
-func (t *templateHandler) getOrCreateTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) {
- info, found := t.templateInfo[name]
- if found {
- return info.(identity.Manager), info.ParseInfo()
+func (t *templateHandler) nameIsText(name string) (string, bool) {
+ isText := strings.HasPrefix(name, textTmplNamePrefix)
+ if isText {
+ name = strings.TrimPrefix(name, textTmplNamePrefix)
}
- return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo
+ return name, isText
}
-func (t *templateHandler) createTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) {
- _, found := t.templateInfo[name]
- if found {
- panic("already created: " + name)
+func (t *templateHandler) noBaseNeeded(name string) bool {
+ if strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/") {
+ return true
}
-
- return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo
+ return strings.Contains(name, "_markup/")
}
func (t *templateHandler) postTransform() error {
- for k, v := range t.templateInfoTree {
- if v.id != nil {
- info := tpl.NewInfo(
- v.id,
- v.info,
- )
- t.templateInfo[k] = info
-
- if v.typ == templateShortcode {
- t.addShortcodeVariant(k, info, v.templ)
- }
+ for _, v := range t.main.templates {
+ if v.typ == templateShortcode {
+ t.addShortcodeVariant(v)
}
}
- for _, s := range []struct {
- lookup func(name string) *templateInfoTree
- transformNotFound map[string]bool
- identityNotFound map[string][]identity.Manager
- }{
- // html templates
- {func(name string) *templateInfoTree {
- templ := t.html.lookup(name)
- if templ == nil {
- return nil
- }
- id, info := t.getOrCreateTemplateInfo(name)
- return &templateInfoTree{
- id: id,
- info: info,
- tree: templ.Tree,
- }
- }, t.html.transformNotFound, t.html.identityNotFound},
- // text templates
- {func(name string) *templateInfoTree {
- templT := t.text.lookup(name)
- if templT == nil {
- return nil
- }
- id, info := t.getOrCreateTemplateInfo(name)
- return &templateInfoTree{
- id: id,
- info: info,
- tree: templT.Tree,
- }
- }, t.text.transformNotFound, t.text.identityNotFound},
- } {
- for name := range s.transformNotFound {
- templ := s.lookup(name)
- if templ != nil {
- _, err := applyTemplateTransformers(templateUndefined, templ, s.lookup)
- if err != nil {
- return err
- }
+ for name, source := range t.transformNotFound {
+ lookup := t.main.newTemplateLookup(source)
+ templ := lookup(name)
+ if templ != nil {
+ _, err := applyTemplateTransformers(templ, lookup)
+ if err != nil {
+ return err
}
}
+ }
- for k, v := range s.identityNotFound {
- tmpl := s.lookup(k)
- if tmpl != nil {
- for _, im := range v {
- im.Add(tmpl.id)
- }
+ for k, v := range t.identityNotFound {
+ ts := t.findTemplate(k)
+ if ts != nil {
+ for _, im := range v {
+ im.Add(ts)
}
}
}
@@ -797,215 +721,169 @@ func (t *templateHandler) postTransform() error {
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
-
- // Used to track templates during the AST transformations.
- templateInfoTree map[string]*templateInfoTree
+type templateNamespace struct {
+ prototypeText *texttemplate.Template
+ prototypeHTML *htmltemplate.Template
+ prototypeTextClone *texttemplate.Template
+ prototypeHTMLClone *htmltemplate.Template
- // text holds all the pure text templates.
- text *textTemplates
- html *htmlTemplates
+ *templateStateMap
}
-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
-}
+func (t templateNamespace) Clone(lock bool) *templateNamespace {
+ if t.mu != nil {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ }
-// Shared by both HTML and text templates.
-type templatesCommon struct {
- handler *templateHandler
+ var mu *sync.RWMutex
+ if lock {
+ mu = &sync.RWMutex{}
+ }
- // Used to get proper filenames in errors
- nameBaseTemplateName map[string]string
+ t.templateStateMap = &templateStateMap{
+ templates: make(map[string]*templateState),
+ mu: mu,
+ }
- // Holds names of the template definitions not found during the first AST transformation
- // pass.
- transformNotFound map[string]bool
+ t.prototypeText = texttemplate.Must(t.prototypeText.Clone())
+ t.prototypeHTML = htmltemplate.Must(t.prototypeHTML.Clone())
- // Holds identities of templates not found during first pass.
- identityNotFound map[string][]identity.Manager
-}
-
-func (t templatesCommon) withNewHandler(h *templateHandler) *templatesCommon {
- t.handler = h
return &t
}
-type textTemplate struct {
- mu sync.RWMutex
- t *texttemplate.Template
- templates *textTemplates
-}
+func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
+ if t.mu != nil {
+ t.mu.RLock()
+ defer t.mu.RLock()
+ }
-func (t *textTemplate) Lookup(name string) (tpl.Template, bool) {
- t.mu.RLock()
- defer t.mu.RUnlock()
+ templ, found := t.templates[name]
+ if !found {
+ return nil, false
+ }
- tpl := t.t.Lookup(name)
- return tpl, tpl != nil
-}
+ if t.mu != nil {
+ return &templateWrapperWithLock{RWMutex: t.mu, Template: templ}, true
+ }
-func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) {
- return t.parseIn(t.t, name, tpl)
+ return templ, found
}
-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
- }
+func (t *templateNamespace) createPrototypes() error {
+ t.prototypeTextClone = texttemplate.Must(t.prototypeText.Clone())
+ t.prototypeHTMLClone = htmltemplate.Must(t.prototypeHTML.Clone())
- if _, err := t.templates.handler.applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil {
- return nil, err
- }
- return templ, nil
+ return nil
}
-type textTemplates struct {
- *templatesCommon
- *textTemplate
- standalone *textTemplate
- clone *texttemplate.Template
- cloneClone *texttemplate.Template
+func (t *templateNamespace) newTemplateLookup(in *templateState) func(name string) *templateState {
+ return func(name string) *templateState {
+ if templ, found := t.templates[name]; found {
+ if templ.isText() != in.isText() {
+ return nil
+ }
+ return templ
+ }
+ if templ, found := findTemplateIn(name, in); found {
+ return newTemplateState(templ, templateInfo{name: templ.Name()})
+ }
+ return nil
- overlays map[string]*texttemplate.Template
+ }
}
-func (t *textTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+func (t *templateNamespace) parse(info templateInfo) (*templateState, error) {
+ if t.mu != nil {
+ t.mu.Lock()
+ defer t.mu.Unlock()
}
- return templ, true
-}
-func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
+ if info.isText {
+ prototype := t.prototypeText
-func (t *textTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
-}
+ templ, err := prototype.New(info.name).Parse(info.template)
+ if err != nil {
+ return nil, err
+ }
-func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
-}
+ ts := newTemplateState(templ, info)
-func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tplstr string) (*templateContext, error) {
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- templ, err := t.parseIn(tt, name, tplstr)
- if err != nil {
- return nil, err
+ t.templates[info.name] = ts
+
+ return ts, nil
}
- typ := resolveTemplateType(name)
+ prototype := t.prototypeHTML
- c, err := t.handler.applyTemplateTransformersToTextTemplate(typ, templ)
+ templ, err := prototype.New(info.name).Parse(info.template)
if err != nil {
return nil, err
}
- for k := range c.templateNotFound {
- t.transformNotFound[k] = true
- }
+ ts := newTemplateState(templ, info)
- return c, nil
-}
-
-func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+ t.templates[info.name] = ts
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- masterTpl := t.lookup(masterFilename)
+ return ts, nil
+}
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- return err
- }
+type templateState struct {
+ tpl.Template
- 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
- }
+ typ templateType
+ parseInfo tpl.ParseInfo
+ identity.Manager
- 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 := t.handler.applyTemplateTransformersToTextTemplate(templateUndefined, overlayTpl); err != nil {
- return err
- }
- t.overlays[name] = overlayTpl
- t.nameBaseTemplateName[name] = templ.filename
+ info templateInfo
+ baseInfo templateInfo // Set when a base template is used.
+}
- return err
+func (t *templateState) ParseInfo() tpl.ParseInfo {
+ return t.parseInfo
+}
+func (t *templateState) isText() bool {
+ _, isText := t.Template.(*texttemplate.Template)
+ return isText
}
-func (t *textTemplates) lookup(name string) *texttemplate.Template {
+type templateStateMap struct {
+ mu *sync.RWMutex // May be nil
+ templates map[string]*templateState
+}
- // 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
- }
- }
+type templateWrapperWithLock struct {
+ *sync.RWMutex
+ tpl.Template
+}
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
+type textTemplateWrapperWithLock struct {
+ *sync.RWMutex
+ *texttemplate.Template
+}
- if t.clone != nil {
- return t.clone.Lookup(name)
+func (t *textTemplateWrapperWithLock) Lookup(name string) (tpl.Template, bool) {
+ t.RLock()
+ templ := t.Template.Lookup(name)
+ t.RUnlock()
+ if templ == nil {
+ return nil, false
}
+ return &textTemplateWrapperWithLock{
+ RWMutex: t.RWMutex,
+ Template: templ,
+ }, true
+}
- return nil
+func (t *textTemplateWrapperWithLock) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ panic("not supported")
}
-func (t textTemplates) withNewHandler(h *templateHandler) *textTemplates {
- t.templatesCommon = t.templatesCommon.withNewHandler(h)
- return &t
+func (t *textTemplateWrapperWithLock) Parse(name, tpl string) (tpl.Template, error) {
+ t.Lock()
+ defer t.Unlock()
+ return t.Template.New(name).Parse(tpl)
}
func isBackupFile(path string) bool {
@@ -1047,3 +925,10 @@ func templateBaseName(typ templateType, name string) string {
}
}
+
+func unwrap(templ tpl.Template) tpl.Template {
+ if ts, ok := templ.(*templateState); ok {
+ return ts.Template
+ }
+ return templ
+}
diff --git a/tpl/tplimpl/templateProvider.go b/tpl/tplimpl/templateProvider.go
index 68de00561..933ee7dc3 100644
--- a/tpl/tplimpl/templateProvider.go
+++ b/tpl/tplimpl/templateProvider.go
@@ -25,32 +25,17 @@ var DefaultTemplateProvider *TemplateProvider
// Update updates the Hugo Template System in the provided Deps
// with all the additional features, templates & functions.
-func (*TemplateProvider) Update(deps *deps.Deps) error {
- newTmpl := newTemplateAdapter(deps)
- deps.Tmpl = newTmpl
- deps.TextTmpl = newTmpl.wrapTextTemplate(newTmpl.text.standalone)
- // These needs to be there at parse time.
- newTmpl.initTemplateExecuter()
-
- if err := newTmpl.loadEmbedded(); err != nil {
+func (*TemplateProvider) Update(d *deps.Deps) error {
+ tmpl, err := newTemplateExec(d)
+ if err != nil {
return err
}
-
- if deps.WithTemplate != nil {
- err := deps.WithTemplate(newTmpl)
- if err != nil {
- return err
- }
-
- }
-
- return newTmpl.markReady()
-
+ return tmpl.postTransform()
}
// Clone clones.
func (*TemplateProvider) Clone(d *deps.Deps) error {
- t := d.Tmpl.(*templateHandler)
- t.clone(d)
+ t := d.Tmpl().(*templateExec)
+ d.SetTmpl(t.Clone(d))
return nil
}
diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go
index d36149819..015cf72af 100644
--- a/tpl/tplimpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -17,10 +17,9 @@ import (
"regexp"
"strings"
- "github.com/gohugoio/hugo/identity"
-
- template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
"github.com/gohugoio/hugo/common/maps"
@@ -41,25 +40,21 @@ type templateContext struct {
visited map[string]bool
templateNotFound map[string]bool
identityNotFound map[string]bool
- lookupFn func(name string) *templateInfoTree
+ lookupFn func(name string) *templateState
// The last error encountered.
err error
- typ templateType
-
// Set when we're done checking for config header.
configChecked bool
- // Contains some info about the template
- parseInfo *tpl.ParseInfo
- id identity.Manager
+ t *templateState
// Store away the return node in partials.
returnNode *parse.CommandNode
}
-func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
+func (c templateContext) getIfNotVisited(name string) *templateState {
if c.visited[name] {
return nil
}
@@ -76,13 +71,11 @@ func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
}
func newTemplateContext(
- id identity.Manager,
- info *tpl.ParseInfo,
- lookupFn func(name string) *templateInfoTree) *templateContext {
+ t *templateState,
+ lookupFn func(name string) *templateState) *templateContext {
return &templateContext{
- id: id,
- parseInfo: info,
+ t: t,
lookupFn: lookupFn,
visited: make(map[string]bool),
templateNotFound: make(map[string]bool),
@@ -90,79 +83,36 @@ func newTemplateContext(
}
}
-func createGetTemplateInfoTreeFor(getID func(name string) *templateInfoTree) func(nn string) *templateInfoTree {
- return func(nn string) *templateInfoTree {
- return getID(nn)
- }
-}
-
-func (t *templateHandler) applyTemplateTransformersToHMLTTemplate(typ templateType, templ *template.Template) (*templateContext, error) {
- id, info := t.createTemplateInfo(templ.Name())
- ti := &templateInfoTree{
- tree: templ.Tree,
- templ: templ,
- typ: typ,
- id: id,
- info: info,
- }
- t.templateInfoTree[templ.Name()] = ti
- getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
- return t.templateInfoTree[name]
- })
-
- return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
-}
-
-func (t *templateHandler) applyTemplateTransformersToTextTemplate(typ templateType, templ *texttemplate.Template) (*templateContext, error) {
- id, info := t.createTemplateInfo(templ.Name())
- ti := &templateInfoTree{
- tree: templ.Tree,
- templ: templ,
- typ: typ,
- id: id,
- info: info,
- }
-
- t.templateInfoTree[templ.Name()] = ti
- getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
- return t.templateInfoTree[name]
- })
-
- return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
-
-}
-
-type templateInfoTree struct {
- info tpl.ParseInfo
- typ templateType
- id identity.Manager
- templ tpl.Template
- tree *parse.Tree
-}
-
func applyTemplateTransformers(
- typ templateType,
- templ *templateInfoTree,
- lookupFn func(name string) *templateInfoTree) (*templateContext, error) {
+ t *templateState,
+ lookupFn func(name string) *templateState) (*templateContext, error) {
- if templ == nil {
+ if t == nil {
return nil, errors.New("expected template, but none provided")
}
- c := newTemplateContext(templ.id, &templ.info, lookupFn)
- c.typ = typ
+ c := newTemplateContext(t, lookupFn)
+ tree := getParseTree(t.Template)
- _, err := c.applyTransformations(templ.tree.Root)
+ _, err := c.applyTransformations(tree.Root)
if err == nil && c.returnNode != nil {
// This is a partial with a return statement.
- c.parseInfo.HasReturn = true
- templ.tree.Root = c.wrapInPartialReturnWrapper(templ.tree.Root)
+ c.t.parseInfo.HasReturn = true
+ tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
}
return c, err
}
+func getParseTree(templ tpl.Template) *parse.Tree {
+ templ = unwrap(templ)
+ if text, ok := templ.(*texttemplate.Template); ok {
+ return text.Tree
+ }
+ return templ.(*htmltemplate.Template).Tree
+}
+
const (
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
)
@@ -215,7 +165,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
case *parse.TemplateNode:
subTempl := c.getIfNotVisited(x.Name)
if subTempl != nil {
- c.applyTransformationsToNodes(subTempl.tree.Root)
+ c.applyTransformationsToNodes(getParseTree(subTempl.Template).Root)
}
case *parse.PipeNode:
c.collectConfig(x)
@@ -263,7 +213,7 @@ func (c *templateContext) hasIdent(idents []string, ident string) bool {
// on the form:
// {{ $_hugo_config:= `{ "version": 1 }` }}
func (c *templateContext) collectConfig(n *parse.PipeNode) {
- if c.typ != templateShortcode {
+ if c.t.typ != templateShortcode {
return
}
if c.configChecked {
@@ -295,7 +245,7 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
c.err = errors.Wrap(err, errMsg)
return
}
- if err := mapstructure.WeakDecode(m, &c.parseInfo.Config); err != nil {
+ if err := mapstructure.WeakDecode(m, &c.t.parseInfo.Config); err != nil {
c.err = errors.Wrap(err, errMsg)
}
}
@@ -304,10 +254,10 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
// collectInner determines if the given CommandNode represents a
// shortcode call to its .Inner.
func (c *templateContext) collectInner(n *parse.CommandNode) {
- if c.typ != templateShortcode {
+ if c.t.typ != templateShortcode {
return
}
- if c.parseInfo.IsInner || len(n.Args) == 0 {
+ if c.t.parseInfo.IsInner || len(n.Args) == 0 {
return
}
@@ -321,7 +271,7 @@ func (c *templateContext) collectInner(n *parse.CommandNode) {
}
if c.hasIdent(idents, "Inner") {
- c.parseInfo.IsInner = true
+ c.t.parseInfo.IsInner = true
break
}
}
@@ -351,8 +301,9 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
}
partialName = "partials/" + partialName
info := c.lookupFn(partialName)
+
if info != nil {
- c.id.Add(info.id)
+ c.t.Add(info)
} else {
// Delay for later
c.identityNotFound[partialName] = true
@@ -361,7 +312,7 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
}
func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
- if c.typ != templatePartial || c.returnNode != nil {
+ if c.t.typ != templatePartial || c.returnNode != nil {
return true
}
@@ -381,3 +332,18 @@ func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
return false
}
+
+func findTemplateIn(name string, in tpl.Template) (tpl.Template, bool) {
+ in = unwrap(in)
+ if text, ok := in.(*texttemplate.Template); ok {
+ if templ := text.Lookup(name); templ != nil {
+ return templ, true
+ }
+ return nil, false
+ }
+ if templ := in.(*htmltemplate.Template).Lookup(name); templ != nil {
+ return templ, true
+ }
+ return nil, false
+
+}
diff --git a/tpl/tplimpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go
index 5efa6a14d..b38446235 100644
--- a/tpl/tplimpl/template_ast_transformers_test.go
+++ b/tpl/tplimpl/template_ast_transformers_test.go
@@ -13,15 +13,11 @@
package tplimpl
import (
- "github.com/gohugoio/hugo/hugofs/files"
-
"testing"
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
- "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl"
)
@@ -33,7 +29,7 @@ func TestTransformRecursiveTemplate(t *testing.T) {
{{ define "menu-nodes" }}
{{ template "menu-node" }}
{{ end }}
-{{ define "menu-nßode" }}
+{{ define "menu-node" }}
{{ template "menu-node" }}
{{ end }}
{{ template "menu-nodes" }}
@@ -41,34 +37,44 @@ func TestTransformRecursiveTemplate(t *testing.T) {
templ, err := template.New("foo").Parse(recursive)
c.Assert(err, qt.IsNil)
- parseInfo := tpl.DefaultParseInfo
+ ts := newTestTemplate(templ)
ctx := newTemplateContext(
- newTemplateInfo("test").(identity.Manager),
- &parseInfo,
- createGetTemplateInfoTree(templ.Tree),
+ ts,
+ newTestTemplateLookup(ts),
)
ctx.applyTransformations(templ.Tree.Root)
}
-func createGetTemplateInfoTree(tree *parse.Tree) func(name string) *templateInfoTree {
- return func(name string) *templateInfoTree {
- return &templateInfoTree{
- tree: tree,
- }
- }
+func newTestTemplate(templ tpl.Template) *templateState {
+ return newTemplateState(
+ templ,
+ templateInfo{
+ name: templ.Name(),
+ },
+ )
}
-type I interface {
- Method0()
-}
+func newTestTemplateLookup(in *templateState) func(name string) *templateState {
+ m := make(map[string]*templateState)
+ return func(name string) *templateState {
+ if in.Name() == name {
+ return in
+ }
-type T struct {
- NonEmptyInterfaceTypedNil I
-}
+ if ts, found := m[name]; found {
+ return ts
+ }
-func (T) Method0() {
+ if templ, found := findTemplateIn(name, in); found {
+ ts := newTestTemplate(templ)
+ m[name] = ts
+ return ts
+ }
+
+ return nil
+ }
}
func TestCollectInfo(t *testing.T) {
@@ -98,13 +104,14 @@ func TestCollectInfo(t *testing.T) {
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
c.Assert(err, qt.IsNil)
- parseInfo := tpl.DefaultParseInfo
-
+ ts := newTestTemplate(templ)
+ ts.typ = templateShortcode
ctx := newTemplateContext(
- newTemplateInfo("test").(identity.Manager), &parseInfo, createGetTemplateInfoTree(templ.Tree))
- ctx.typ = templateShortcode
+ ts,
+ newTestTemplateLookup(ts),
+ )
ctx.applyTransformations(templ.Tree.Root)
- c.Assert(ctx.parseInfo, qt.DeepEquals, &test.expected)
+ c.Assert(ctx.t.parseInfo, qt.DeepEquals, test.expected)
})
}
@@ -141,11 +148,13 @@ func TestPartialReturn(t *testing.T) {
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
c.Assert(err, qt.IsNil)
+ ts := newTestTemplate(templ)
+ ctx := newTemplateContext(
+ ts,
+ newTestTemplateLookup(ts),
+ )
- _, err = applyTemplateTransformers(
- templatePartial,
- &templateInfoTree{tree: templ.Tree, info: tpl.DefaultParseInfo},
- createGetTemplateInfoTree(templ.Tree))
+ _, err = ctx.applyTransformations(templ.Tree.Root)
// Just check that it doesn't fail in this test. We have functional tests
// in hugoblib.
@@ -155,10 +164,3 @@ func TestPartialReturn(t *testing.T) {
}
}
-
-func newTemplateInfo(name string) tpl.Info {
- return tpl.NewInfo(
- identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)),
- tpl.DefaultParseInfo,
- )
-}
diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go
index 63695c5f6..48818cb60 100644
--- a/tpl/tplimpl/template_errors.go
+++ b/tpl/tplimpl/template_errors.go
@@ -20,7 +20,9 @@ import (
)
type templateInfo struct {
+ name string
template string
+ isText bool // HTML or plain text template.
// Used to create some error context in error situations
fs afero.Fs
@@ -32,6 +34,10 @@ type templateInfo struct {
realFilename string
}
+func (t templateInfo) resolveType() templateType {
+ return resolveTemplateType(t.name)
+}
+
func (info templateInfo) errWithFileContext(what string, err error) error {
err = errors.Wrapf(err, what)
diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
index 6ca9de4da..852b63930 100644
--- a/tpl/tplimpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -127,8 +127,8 @@ func TestTemplateFuncsExamples(t *testing.T) {
c.Assert(d.LoadResources(), qt.IsNil)
var b bytes.Buffer
- templ, _ := d.Tmpl.Lookup("test")
- c.Assert(d.Tmpl.Execute(templ, &b, &data), qt.IsNil)
+ templ, _ := d.Tmpl().Lookup("test")
+ 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)
}
diff --git a/tpl/tplimpl/template_info_test.go b/tpl/tplimpl/template_info_test.go
index e72e859ed..1324b458e 100644
--- a/tpl/tplimpl/template_info_test.go
+++ b/tpl/tplimpl/template_info_test.go
@@ -24,14 +24,15 @@ import (
func TestTemplateInfoShortcode(t *testing.T) {
c := qt.New(t)
d := newD(c)
- h := d.Tmpl.(*templateHandler)
+ h := d.Tmpl().(*templateExec)
c.Assert(h.AddTemplate("shortcodes/mytemplate.html", `
{{ .Inner }}
`), qt.IsNil)
- c.Assert(h.markReady(), qt.IsNil)
- tt, found, _ := d.Tmpl.LookupVariant("mytemplate", tpl.TemplateVariants{})
+ c.Assert(h.postTransform(), qt.IsNil)
+
+ tt, found, _ := d.Tmpl().LookupVariant("mytemplate", tpl.TemplateVariants{})
c.Assert(found, qt.Equals, true)
tti, ok := tt.(tpl.Info)