summaryrefslogtreecommitdiffstats
path: root/tpl/tplimpl/template.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-12-10 19:56:44 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-12-12 10:04:35 +0100
commita03c631c420a03f9d90699abdf9be7e4fca0ff61 (patch)
treefcc245c75aa6cc6dc5be40a614700b6aca26c84f /tpl/tplimpl/template.go
parent167c01530bb295c8b8d35921eb27ffa5bee76dfe (diff)
Rework template handling for function and map lookups
This is a big commit, but it deletes lots of code and simplifies a lot. * Resolving the template funcs at execution time means we don't have to create template clones per site * Having a custom map resolver means that we can remove the AST lower case transformation for the special lower case Params map Not only is the above easier to reason about, it's also faster, especially if you have more than one language, as in the benchmark below: ``` name old time/op new time/op delta SiteNew/Deep_content_tree-16 53.7ms ± 0% 48.1ms ± 2% -10.38% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Deep_content_tree-16 41.0MB ± 0% 36.8MB ± 0% -10.26% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Deep_content_tree-16 481k ± 0% 410k ± 0% -14.66% (p=0.029 n=4+4) ``` This should be even better if you also have lots of templates. Closes #6594
Diffstat (limited to 'tpl/tplimpl/template.go')
-rw-r--r--tpl/tplimpl/template.go1228
1 files changed, 621 insertions, 607 deletions
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index 0feb3a0de..dd8de9067 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -15,6 +15,12 @@ package tplimpl
import (
"fmt"
+ "io"
+ "reflect"
+ "regexp"
+ "time"
+
+ "github.com/gohugoio/hugo/common/herrors"
"strings"
@@ -45,119 +51,196 @@ const (
)
var (
- _ tpl.TemplateHandler = (*templateHandler)(nil)
- _ tpl.TemplateDebugger = (*templateHandler)(nil)
- _ tpl.TemplateFuncsGetter = (*templateHandler)(nil)
- _ tpl.TemplateTestMocker = (*templateHandler)(nil)
- _ tpl.TemplateFinder = (*htmlTemplates)(nil)
- _ tpl.TemplateFinder = (*textTemplates)(nil)
- _ templateLoader = (*htmlTemplates)(nil)
- _ templateLoader = (*textTemplates)(nil)
- _ templateFuncsterTemplater = (*htmlTemplates)(nil)
- _ templateFuncsterTemplater = (*textTemplates)(nil)
+ _ tpl.TemplateManager = (*templateHandler)(nil)
+ _ tpl.TemplateHandler = (*templateHandler)(nil)
+ _ tpl.TemplateDebugger = (*templateHandler)(nil)
+ _ tpl.TemplateFuncGetter = (*templateHandler)(nil)
+ _ tpl.TemplateFinder = (*htmlTemplates)(nil)
+ _ tpl.TemplateFinder = (*textTemplates)(nil)
+ _ templateLoader = (*htmlTemplates)(nil)
+ _ templateLoader = (*textTemplates)(nil)
)
-type templateErr struct {
- name string
- err error
-}
+const (
+ shortcodesPathPrefix = "shortcodes/"
+ internalPathPrefix = "_internal/"
+)
-type templateLoader interface {
- handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error
- addTemplate(name, tpl string) (*templateContext, error)
- addLateTemplate(name, tpl string) error
-}
+// The identifiers may be truncated in the log, e.g.
+// "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image"
+var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`)
-type templateFuncsterTemplater interface {
- templateFuncsterSetter
- tpl.TemplateFinder
- setFuncs(funcMap map[string]interface{})
+var embeddedTemplatesAliases = map[string][]string{
+ "shortcodes/twitter.html": {"shortcodes/tweet.html"},
}
-type templateFuncsterSetter interface {
- setTemplateFuncster(f *templateFuncster)
-}
+const baseFileBase = "baseof"
-// templateHandler holds the templates in play.
-// It implements the templateLoader and tpl.TemplateHandler interfaces.
-type templateHandler struct {
- mu sync.Mutex
+func newTemplateAdapter(deps *deps.Deps) *templateHandler {
- // shortcodes maps shortcode name to template variants
- // (language, output format etc.) of that shortcode.
- shortcodes map[string]*shortcodeTemplates
+ common := &templatesCommon{
+ nameBaseTemplateName: make(map[string]string),
+ transformNotFound: make(map[string]bool),
+ }
- // templateInfo maps template name to some additional information about that template.
- // Note that for shortcodes that same information is embedded in the
- // shortcodeTemplates type.
- templateInfo map[string]tpl.Info
+ htmlT := &htmlTemplates{
+ t: template.New(""),
+ overlays: make(map[string]*template.Template),
+ templatesCommon: common,
+ }
- // text holds all the pure text templates.
- text *textTemplates
- html *htmlTemplates
+ textT := &textTemplates{
+ textTemplate: &textTemplate{t: texttemplate.New("")},
+ standalone: &textTemplate{t: texttemplate.New("")},
+ overlays: make(map[string]*texttemplate.Template),
+ templatesCommon: common,
+ }
+
+ h := &templateHandler{
+ Deps: deps,
+ layoutsFs: deps.BaseFs.Layouts.Fs,
+ templateHandlerCommon: &templateHandlerCommon{
+ shortcodes: make(map[string]*shortcodeTemplates),
+ templateInfo: make(map[string]tpl.Info),
+ html: htmlT,
+ text: textT,
+ },
+ }
- errors []*templateErr
+ common.handler = h
- // This is the filesystem to load the templates from. All the templates are
- // stored in the root of this filesystem.
- layoutsFs afero.Fs
+ return h
- *deps.Deps
}
-const (
- shortcodesPathPrefix = "shortcodes/"
- internalPathPrefix = "_internal/"
-)
+type htmlTemplates struct {
+ *templatesCommon
-// resolves _internal/shortcodes/param.html => param.html etc.
-func templateBaseName(typ templateType, name string) string {
- name = strings.TrimPrefix(name, internalPathPrefix)
- switch typ {
- case templateShortcode:
- return strings.TrimPrefix(name, shortcodesPathPrefix)
- default:
- panic("not implemented")
+ t *template.Template
+
+ // This looks, and is, strange.
+ // The clone is used by non-renderable content pages, and these need to be
+ // re-parsed on content change, and to avoid the
+ // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
+ clone *template.Template
+ cloneClone *template.Template
+
+ // a separate storage for the overlays created from cloned master templates.
+ // note: No mutex protection, so we add these in one Go routine, then just read.
+ overlays map[string]*template.Template
+}
+
+func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) {
+ templ := t.lookup(name)
+ if templ == nil {
+ return nil, false
}
+ return templ, true
}
-func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
- base := templateBaseName(templateShortcode, name)
+func (t *htmlTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ return t.handler.LookupVariant(name, variants)
+}
- shortcodename, variants := templateNameAndVariants(base)
+func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
+ _, err := t.addTemplateIn(t.clone, name, tpl)
+ return err
+}
- templs, found := t.shortcodes[shortcodename]
- if !found {
- templs = &shortcodeTemplates{}
- t.shortcodes[shortcodename] = templs
+func (t *htmlTemplates) addTemplate(name, tpl string) (*templateContext, error) {
+ return t.addTemplateIn(t.t, name, tpl)
+}
+
+func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) (*templateContext, error) {
+ templ, err := tt.New(name).Parse(tpl)
+ if err != nil {
+ return nil, err
}
- sv := shortcodeVariant{variants: variants, info: info, templ: templ}
+ typ := resolveTemplateType(name)
- i := templs.indexOf(variants)
+ c, err := applyTemplateTransformersToHMLTTemplate(typ, templ)
+ if err != nil {
+ return nil, err
+ }
- if i != -1 {
- // Only replace if it's an override of an internal template.
- if !isInternal(name) {
- templs.variants[i] = sv
- }
+ for k := range c.notFound {
+ t.transformNotFound[k] = true
+ }
+
+ if typ == templateShortcode {
+ t.handler.addShortcodeVariant(name, c.Info, templ)
} else {
- templs.variants = append(templs.variants, sv)
+ t.handler.templateInfo[name] = c.Info
}
+
+ return c, nil
}
-func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder {
- return struct {
- tpl.TemplateParser
- tpl.TemplateLookup
- tpl.TemplateLookupVariant
- }{
- tt,
- tt,
- new(nopLookupVariant),
+func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+
+ masterTpl := t.lookup(masterFilename)
+
+ if masterTpl == nil {
+ templ, err := onMissing(masterFilename)
+ if err != nil {
+ return err
+ }
+
+ masterTpl, err = t.t.New(overlayFilename).Parse(templ.template)
+ if err != nil {
+ return templ.errWithFileContext("parse master failed", err)
+ }
+ }
+
+ templ, err := onMissing(overlayFilename)
+ if err != nil {
+ return err
+ }
+
+ overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template)
+ if err != nil {
+ return templ.errWithFileContext("parse failed", err)
+ }
+
+ // The extra lookup is a workaround, see
+ // * https://github.com/golang/go/issues/16101
+ // * https://github.com/gohugoio/hugo/issues/2549
+ overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
+ if _, err := applyTemplateTransformersToHMLTTemplate(templateUndefined, overlayTpl); err != nil {
+ return err
+ }
+
+ t.overlays[name] = overlayTpl
+ t.nameBaseTemplateName[name] = masterFilename
+
+ return err
+
+}
+
+func (t *htmlTemplates) lookup(name string) *template.Template {
+ // Need to check in the overlay registry first as it will also be found below.
+ if t.overlays != nil {
+ if templ, ok := t.overlays[name]; ok {
+ return templ
+ }
+ }
+
+ if templ := t.t.Lookup(name); templ != nil {
+ return templ
+ }
+
+ if t.clone != nil {
+ return t.clone.Lookup(name)
}
+ return nil
+}
+
+func (t htmlTemplates) withNewHandler(h *templateHandler) *htmlTemplates {
+ t.templatesCommon = t.templatesCommon.withNewHandler(h)
+ return &t
}
type nopLookupVariant int
@@ -166,15 +249,81 @@ func (l nopLookupVariant) LookupVariant(name string, variants tpl.TemplateVarian
return nil, false, false
}
+// templateHandler holds the templates in play.
+// It implements the templateLoader and tpl.TemplateHandler interfaces.
+// There is one templateHandler created per Site.
+type templateHandler struct {
+ executor texttemplate.Executer
+ funcs map[string]reflect.Value
+
+ // This is the filesystem to load the templates from. All the templates are
+ // stored in the root of this filesystem.
+ layoutsFs afero.Fs
+
+ *deps.Deps
+
+ *templateHandlerCommon
+}
+
+// AddLateTemplate is used to add a template late, i.e. after the
+// regular templates have started its execution.
+func (t *templateHandler) AddLateTemplate(name, tpl string) error {
+ h := t.getTemplateHandler(name)
+ if err := h.addLateTemplate(name, tpl); err != nil {
+ return err
+ }
+ return nil
+}
+
+// AddTemplate parses and adds a template to the collection.
+// Templates with name prefixed with "_text" will be handled as plain
+// text templates.
+// TODO(bep) clean up these addTemplate variants
+func (t *templateHandler) AddTemplate(name, tpl string) error {
+ h := t.getTemplateHandler(name)
+ _, err := h.addTemplate(name, tpl)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func (t *templateHandler) Debug() {
fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
}
+func (t *templateHandler) Execute(templ tpl.Template, wr io.Writer, data interface{}) error {
+ if t.Metrics != nil {
+ defer t.Metrics.MeasureSince(templ.Name(), time.Now())
+ }
+
+ execErr := t.executor.Execute(templ, wr, data)
+ if execErr != nil {
+ execErr = t.addFileContext(templ.Name(), execErr)
+ }
+
+ return execErr
+
+}
+
+func (t *templateHandler) GetFunc(name string) (reflect.Value, bool) {
+ v, found := t.funcs[name]
+ return v, found
+
+}
+
+// LoadTemplates loads the templates from the layouts filesystem.
+// A prefix can be given to indicate a template namespace to load the templates
+// into, i.e. "_internal" etc.
+func (t *templateHandler) LoadTemplates(prefix string) error {
+ return t.loadTemplates(prefix)
+
+}
+
// Lookup tries to find a template with the given name in both template
// collections: First HTML, then the plain text template collection.
func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
-
if strings.HasPrefix(name, textTmplNamePrefix) {
// The caller has explicitly asked for a text template, so only look
// in the text template collection.
@@ -193,18 +342,6 @@ func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
}
-func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) {
- if adapter, ok := templ.(*tpl.TemplateAdapter); ok {
- if adapter.Info.IsZero() {
- if info, found := t.templateInfo[templ.Name()]; found {
- adapter.Info = info
- }
- }
- }
-
- return templ, found
-}
-
// This currently only applies to shortcodes and what we get here is the
// shortcode name.
func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
@@ -221,382 +358,349 @@ func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVarian
more := len(s.variants) > 1
- return &tpl.TemplateAdapter{
- Template: sv.templ,
- Info: sv.info,
- Metrics: t.Deps.Metrics,
- Fs: t.layoutsFs,
- NameBaseTemplateName: t.html.nameBaseTemplateName}, true, more
+ return &tpl.TemplateInfo{
+ Template: sv.templ,
+ Info: sv.info,
+ }, true, more
}
-func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
+// MarkReady marks the templates as "ready for execution". No changes allowed
+// after this is set.
+// TODO(bep) if this proves to be resource heavy, we could detect
+// earlier if we really need this, or make it lazy.
+func (t *templateHandler) MarkReady() error {
+ if err := t.postTransform(); err != nil {
+ return err
+ }
-func (t *htmlTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
+ if t.html.clone == nil {
+ t.html.clone = template.Must(t.html.t.Clone())
+ t.html.cloneClone = template.Must(t.html.clone.Clone())
+ }
+ if t.text.clone == nil {
+ t.text.clone = texttemplate.Must(t.text.t.Clone())
+ t.text.cloneClone = texttemplate.Must(t.text.clone.Clone())
+ }
+
+ return nil
}
-func (t *templateHandler) lookupTemplate(in interface{}) tpl.Template {
- switch templ := in.(type) {
- case *texttemplate.Template:
- return t.text.lookup(templ.Name())
- case *template.Template:
- return t.html.lookup(templ.Name())
+// RebuildClone rebuilds the cloned templates. Used for live-reloads.
+func (t *templateHandler) RebuildClone() {
+ if t.html != nil && t.html.cloneClone != nil {
+ t.html.clone = template.Must(t.html.cloneClone.Clone())
+ }
+ if t.text != nil && t.text.cloneClone != nil {
+ t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
}
-
- panic(fmt.Sprintf("%T is not a template", in))
}
-func (t *templateHandler) setFuncMapInTemplate(in interface{}, funcs map[string]interface{}) {
- switch templ := in.(type) {
- case *texttemplate.Template:
- templ.Funcs(funcs)
- return
- case *template.Template:
- templ.Funcs(funcs)
- return
+func (h *templateHandler) initTemplateExecuter() {
+ exec, funcs := newTemplateExecuter(h.Deps)
+ h.executor = exec
+ h.funcs = funcs
+ funcMap := make(map[string]interface{})
+ for k, v := range funcs {
+ funcMap[k] = v.Interface()
}
- panic(fmt.Sprintf("%T is not a template", in))
+ // Note that these funcs are not the ones getting called
+ // on execution, but they are needed at parse time.
+ h.text.textTemplate.t.Funcs(funcMap)
+ h.text.standalone.t.Funcs(funcMap)
+ h.html.t.Funcs(funcMap)
}
-func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
- c := &templateHandler{
- Deps: d,
- layoutsFs: d.BaseFs.Layouts.Fs,
- shortcodes: make(map[string]*shortcodeTemplates),
- templateInfo: t.templateInfo,
- html: &htmlTemplates{t: template.Must(t.html.t.Clone()), overlays: make(map[string]*template.Template), templatesCommon: t.html.templatesCommon},
- text: &textTemplates{
- textTemplate: &textTemplate{t: texttemplate.Must(t.text.t.Clone())},
- standalone: &textTemplate{t: texttemplate.New("")},
- overlays: make(map[string]*texttemplate.Template), templatesCommon: t.text.templatesCommon},
- errors: make([]*templateErr, 0),
- }
-
- for k, v := range t.shortcodes {
- other := *v
- variantsc := make([]shortcodeVariant, len(v.variants))
- for i, variant := range v.variants {
- variantsc[i] = shortcodeVariant{
- info: variant.info,
- variants: variant.variants,
- templ: c.lookupTemplate(variant.templ),
- }
- }
- other.variants = variantsc
- c.shortcodes[k] = &other
+func (t *templateHandler) getTemplateHandler(name string) templateLoader {
+ if strings.HasPrefix(name, textTmplNamePrefix) {
+ return t.text
}
+ return t.html
+}
- d.Tmpl = c
- d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
+func (t *templateHandler) addFileContext(name string, inerr error) error {
+ if strings.HasPrefix(name, "_internal") {
+ return inerr
+ }
- c.initFuncs()
+ f, realFilename, err := t.fileAndFilename(name)
+ if err != nil {
+ return inerr
- for k, v := range t.html.overlays {
- vc := template.Must(v.Clone())
- // The extra lookup is a workaround, see
- // * https://github.com/golang/go/issues/16101
- // * https://github.com/gohugoio/hugo/issues/2549
- vc = vc.Lookup(vc.Name())
- vc.Funcs(c.html.funcster.funcMap)
- c.html.overlays[k] = vc
}
+ defer f.Close()
- for k, v := range t.text.overlays {
- vc := texttemplate.Must(v.Clone())
- vc = vc.Lookup(vc.Name())
- vc.Funcs(texttemplate.FuncMap(c.text.funcster.funcMap))
- c.text.overlays[k] = vc
- }
+ master, hasMaster := t.html.nameBaseTemplateName[name]
- return c
+ ferr := errors.Wrap(inerr, "execute of template failed")
-}
+ // Since this can be a composite of multiple template files (single.html + baseof.html etc.)
+ // we potentially need to look in both -- and cannot rely on line number alone.
+ lineMatcher := func(m herrors.LineMatcher) bool {
+ if m.Position.LineNumber != m.LineNumber {
+ return false
+ }
+ if !hasMaster {
+ return true
+ }
-func newTemplateAdapter(deps *deps.Deps) *templateHandler {
- common := &templatesCommon{
- nameBaseTemplateName: make(map[string]string),
- transformNotFound: make(map[string]bool),
- }
+ identifiers := t.extractIdentifiers(m.Error.Error())
- htmlT := &htmlTemplates{
- t: template.New(""),
- overlays: make(map[string]*template.Template),
- templatesCommon: common,
- }
- textT := &textTemplates{
- textTemplate: &textTemplate{t: texttemplate.New("")},
- standalone: &textTemplate{t: texttemplate.New("")},
- overlays: make(map[string]*texttemplate.Template),
- templatesCommon: common,
+ for _, id := range identifiers {
+ if strings.Contains(m.Line, id) {
+ return true
+ }
+ }
+ return false
}
- h := &templateHandler{
- Deps: deps,
- layoutsFs: deps.BaseFs.Layouts.Fs,
- shortcodes: make(map[string]*shortcodeTemplates),
- templateInfo: make(map[string]tpl.Info),
- html: htmlT,
- text: textT,
- errors: make([]*templateErr, 0),
+
+ fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
+ if ok || !hasMaster {
+ return fe
}
- common.handler = h
+ // Try the base template if relevant
+ f, realFilename, err = t.fileAndFilename(master)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
- return h
+ fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
-}
+ if !ok {
+ // Return the most specific.
+ return ferr
-// Shared by both HTML and text templates.
-type templatesCommon struct {
- handler *templateHandler
- funcster *templateFuncster
+ }
+ return fe
- // Used to get proper filenames in errors
- nameBaseTemplateName map[string]string
+}
- // Holds names of the templates not found during the first AST transformation
- // pass.
- transformNotFound map[string]bool
+func (t *templateHandler) addInternalTemplate(name, tpl string) error {
+ return t.AddTemplate("_internal/"+name, tpl)
}
-type htmlTemplates struct {
- mu sync.RWMutex
- *templatesCommon
+func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
+ base := templateBaseName(templateShortcode, name)
- t *template.Template
+ shortcodename, variants := templateNameAndVariants(base)
- // This looks, and is, strange.
- // The clone is used by non-renderable content pages, and these need to be
- // re-parsed on content change, and to avoid the
- // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
- clone *template.Template
- cloneClone *template.Template
+ templs, found := t.shortcodes[shortcodename]
+ if !found {
+ templs = &shortcodeTemplates{}
+ t.shortcodes[shortcodename] = templs
+ }
- // a separate storage for the overlays created from cloned master templates.
- // note: No mutex protection, so we add these in one Go routine, then just read.
- overlays map[string]*template.Template
-}
+ sv := shortcodeVariant{variants: variants, info: info, templ: templ}
-func (t *htmlTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
-}
+ i := templs.indexOf(variants)
-func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+ if i != -1 {
+ // Only replace if it's an override of an internal template.
+ if !isInternal(name) {
+ templs.variants[i] = sv
+ }
+ } else {
+ templs.variants = append(templs.variants, sv)
}
-
- return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics, Fs: t.handler.layoutsFs, NameBaseTemplateName: t.nameBaseTemplateName}, true
}
-func (t *htmlTemplates) lookup(name string) *template.Template {
- t.mu.RLock()
- defer t.mu.RUnlock()
+func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
+ t.checkState()
- // Need to check in the overlay registry first as it will also be found below.
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
+ t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path)
+
+ getTemplate := func(filename string) (templateInfo, error) {
+ fs := t.Layouts.Fs
+ b, err := afero.ReadFile(fs, filename)
+ if err != nil {
+ return templateInfo{filename: filename, fs: fs}, err
}
- }
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
+ s := removeLeadingBOM(string(b))
- if t.clone != nil {
- return t.clone.Lookup(name)
+ realFilename := filename
+ if fi, err := fs.Stat(filename); err == nil {
+ if fim, ok := fi.(hugofs.FileMetaInfo); ok {
+ realFilename = fim.Meta().Filename()
+ }
+ }
+
+ return templateInfo{template: s, filename: filename, realFilename: realFilename, fs: fs}, nil
}
- return nil
-}
+ // get the suffix and switch on that
+ ext := filepath.Ext(path)
+ switch ext {
+ case ".amber":
+ helpers.Deprecated("Amber templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ return nil
+ case ".ace":
+ helpers.Deprecated("ACE templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ return nil
+ default:
-func (t *textTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
-}
+ if baseTemplatePath != "" {
+ return t.handleMaster(name, path, baseTemplatePath, getTemplate)
+ }
-type textTemplates struct {
- *templatesCommon
- *textTemplate
- standalone *textTemplate
- clone *texttemplate.Template
- cloneClone *texttemplate.Template
+ templ, err := getTemplate(path)
- overlays map[string]*texttemplate.Template
-}
+ if err != nil {
+ return err
+ }
-func (t *textTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+ err = t.AddTemplate(name, templ.template)
+ if err != nil {
+ return templ.errWithFileContext("parse failed", err)
+ }
+ return nil
}
- return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics, Fs: t.handler.layoutsFs, NameBaseTemplateName: t.nameBaseTemplateName}, true
}
-func (t *textTemplates) lookup(name string) *texttemplate.Template {
-
- // Need to check in the overlay registry first as it will also be found below.
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
+func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) {
+ if adapter, ok := templ.(*tpl.TemplateInfo); ok {
+ if adapter.Info.IsZero() {
+ if info, found := t.templateInfo[templ.Name()]; found {
+ adapter.Info = info
+ }
+ }
+ } else if templ != nil {
+ if info, found := t.templateInfo[templ.Name()]; found {
+ return &tpl.TemplateInfo{
+ Template: templ,
+ Info: info,
+ }, true
}
}
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
-
- if t.clone != nil {
- return t.clone.Lookup(name)
- }
-
- return nil
+ return templ, found
}
-func (t *templateHandler) setFuncs(funcMap map[string]interface{}) {
- t.html.setFuncs(funcMap)
- t.text.setFuncs(funcMap)
- t.setFuncMapInTemplate(t.text.standalone.t, funcMap)
+func (t *templateHandler) checkState() {
+ if t.html.clone != nil || t.text.clone != nil {
+ panic("template is cloned and cannot be modfified")
+ }
}
-// SetFuncs replaces the funcs in the func maps with new definitions.
-// This is only used in tests.
-func (t *templateHandler) SetFuncs(funcMap map[string]interface{}) {
- t.setFuncs(funcMap)
-}
+func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
+ c := &templateHandler{
+ Deps: d,
+ layoutsFs: d.BaseFs.Layouts.Fs,
+ }
-func (t *templateHandler) GetFuncs() map[string]interface{} {
- return t.html.funcster.funcMap
-}
+ c.templateHandlerCommon = t.templateHandlerCommon.withNewHandler(c)
+ d.Tmpl = c
+ d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
+ c.executor, c.funcs = newTemplateExecuter(d)
-func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) {
- t.t.Funcs(funcMap)
-}
+ return c
-func (t *textTemplates) setFuncs(funcMap map[string]interface{}) {
- t.t.Funcs(funcMap)
}
-// LoadTemplates loads the templates from the layouts filesystem.
-// A prefix can be given to indicate a template namespace to load the templates
-// into, i.e. "_internal" etc.
-func (t *templateHandler) LoadTemplates(prefix string) error {
- return t.loadTemplates(prefix)
-
+func (t *templateHandler) extractIdentifiers(line string) []string {
+ m := identifiersRe.FindAllStringSubmatch(line, -1)
+ identifiers := make([]string, len(m))
+ for i := 0; i < len(m); i++ {
+ identifiers[i] = m[i][1]
+ }
+ return identifiers
}
-func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) (*templateContext, error) {
- t.mu.Lock()
- defer t.mu.Unlock()
+func (t *templateHandler) fileAndFilename(name string) (afero.File, string, error) {
+ fs := t.layoutsFs
+ filename := filepath.FromSlash(name)
- templ, err := tt.New(name).Parse(tpl)
+ fi, err := fs.Stat(filename)
if err != nil {
- return nil, err
+ return nil, "", err
}
+ fim := fi.(hugofs.FileMetaInfo)
+ meta := fim.Meta()
- typ := resolveTemplateType(name)
-
- c, err := applyTemplateTransformersToHMLTTemplate(typ, templ)
+ f, err := meta.Open()
if err != nil {
- return nil, err
+ return nil, "", errors.Wrapf(err, "failed to open template file %q:", filename)
}
- for k := range c.notFound {
- t.transformNotFound[k] = true
- }
-
- if typ == templateShortcode {
- t.handler.addShortcodeVariant(name, c.Info, templ)
- } else {
- t.handler.templateInfo[name] = c.Info
- }
-
- return c, nil
+ return f, meta.Filename(), nil
}
-func (t *htmlTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
-}
-
-func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
+func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+ h := t.getTemplateHandler(name)
+ return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
}
-type textTemplate struct {
- mu sync.RWMutex
- t *texttemplate.Template
-}
+func (t *templateHandler) loadEmbedded() error {
+ for _, kv := range embedded.EmbeddedTemplates {
+ name, templ := kv[0], kv[1]
+ if err := t.addInternalTemplate(name, templ); err != nil {
+ return err
+ }
+ if aliases, found := embeddedTemplatesAliases[name]; found {
+ for _, alias := range aliases {
+ if err := t.addInternalTemplate(alias, templ); err != nil {
+ return err
+ }