diff options
Diffstat (limited to 'tpl/tplimpl/template.go')
-rw-r--r-- | tpl/tplimpl/template.go | 1163 |
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, |