summaryrefslogtreecommitdiffstats
path: root/tpl/tplimpl/template.go
diff options
context:
space:
mode:
Diffstat (limited to 'tpl/tplimpl/template.go')
-rw-r--r--tpl/tplimpl/template.go1163
1 files changed, 524 insertions, 639 deletions
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,