diff options
Diffstat (limited to 'tpl/tplimpl/template.go')
-rw-r--r-- | tpl/tplimpl/template.go | 1228 |
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 + } + } -func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) { - return t.parseIn(t.t, name, tpl) -} + } + } -func (t *textTemplate) Lookup(name string) (tpl.Template, bool) { - t.mu.RLock() - defer t.mu.RUnlock() + return nil - tpl := t.t.Lookup(name) - return tpl, tpl != nil } -func (t *textTemplate) parseIn(tt *texttemplate.Template, name, tpl string) (*texttemplate.Template, error) { - t.mu.Lock() - defer t.mu.Unlock() +func (t *templateHandler) loadTemplates(prefix string) error { - templ, err := tt.New(name).Parse(tpl) - if err != nil { - return nil, err - } + walker := func(path string, fi hugofs.FileMetaInfo, err error) error { + if err != nil || fi.IsDir() { + return err + } - if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil { - return nil, err - } - return templ, nil -} + if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) { + return nil + } -func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) (*templateContext, error) { - name = strings.TrimPrefix(name, textTmplNamePrefix) - templ, err := t.parseIn(tt, name, tpl) - if err != nil { - return nil, err - } + workingDir := t.PathSpec.WorkingDir - typ := resolveTemplateType(name) + 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) + }, + } - c, err := applyTemplateTransformersToTextTemplate(typ, templ) - if err != nil { - return nil, err - } + tplID, err := output.CreateTemplateNames(descriptor) + if err != nil { + t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err) + return nil + } - for k := range c.notFound { - t.transformNotFound[k] = true - } + if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil { + return err + } - if typ == templateShortcode { - t.handler.addShortcodeVariant(name, c.Info, templ) - } else { - t.handler.templateInfo[name] = c.Info + return nil } - return c, nil -} - -func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) { - return t.addTemplateIn(t.t, name, tpl) -} + if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil { + if !os.IsNotExist(err) { + return err + } + return nil + } -func (t *textTemplates) addLateTemplate(name, tpl string) error { - _, err := t.addTemplateIn(t.clone, name, tpl) - return err -} + return nil -func (t *templateHandler) addTemplate(name, tpl string) error { - return t.AddTemplate(name, tpl) } func (t *templateHandler) postTransform() error { @@ -644,189 +748,151 @@ func (t *templateHandler) postTransform() error { return nil } -func (t *templateHandler) addLateTemplate(name, tpl string) error { - return t.AddLateTemplate(name, tpl) -} - -// AddLateTemplate is used to add a template late, i.e. after the -// regular templates have started its execution. -func (t *templateHandler) AddLateTemplate(name, tpl string) error { - h := t.getTemplateHandler(name) - if err := h.addLateTemplate(name, tpl); err != nil { - return err +func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder { + return struct { + tpl.TemplateParser + tpl.TemplateLookup + tpl.TemplateLookupVariant + }{ + tt, + tt, + new(nopLookupVariant), } - return nil -} -// AddTemplate parses and adds a template to the collection. -// Templates with name prefixed with "_text" will be handled as plain -// text templates. -// TODO(bep) clean up these addTemplate variants -func (t *templateHandler) AddTemplate(name, tpl string) error { - h := t.getTemplateHandler(name) - _, err := h.addTemplate(name, tpl) - if err != nil { - return err - } - return nil } -// MarkReady marks the templates as "ready for execution". No changes allowed -// after this is set. -// TODO(bep) if this proves to be resource heavy, we could detect -// earlier if we really need this, or make it lazy. -func (t *templateHandler) MarkReady() error { - if err := t.postTransform(); err != nil { - return err - } +type templateHandlerCommon struct { + // shortcodes maps shortcode name to template variants + // (language, output format etc.) of that shortcode. + shortcodes map[string]*shortcodeTemplates - 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()) - } + // 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 - return nil + // text holds all the pure text templates. + text *textTemplates + html *htmlTemplates } -// RebuildClone rebuilds the cloned templates. Used for live-reloads. -func (t *templateHandler) RebuildClone() { - if t.html != nil && t.html.cloneClone != nil { - t.html.clone = template.Must(t.html.cloneClone.Clone()) - } - if t.text != nil && t.text.cloneClone != nil { - t.text.clone = texttemplate.Must(t.text.cloneClone.Clone()) - } +func (t templateHandlerCommon) withNewHandler(h *templateHandler) *templateHandlerCommon { + t.text = t.text.withNewHandler(h) + t.html = t.html.withNewHandler(h) + return &t } -func (t *templateHandler) loadTemplates(prefix string) error { - - walker := func(path string, fi hugofs.FileMetaInfo, err error) error { - if err != nil || fi.IsDir() { - return err - } - - if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) { - return nil - } - - workingDir := t.PathSpec.WorkingDir - - descriptor := output.TemplateLookupDescriptor{ - WorkingDir: workingDir, - RelPath: path, - Prefix: prefix, - OutputFormats: t.OutputFormatsConfig, - FileExists: func(filename string) (bool, error) { - return helpers.Exists(filename, t.Layouts.Fs) - }, - ContainsAny: func(filename string, subslices [][]byte) (bool, error) { - return helpers.FileContainsAny(filename, subslices, t.Layouts.Fs) - }, - } - - tplID, err := output.CreateTemplateNames(descriptor) - if err != nil { - t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err) - return nil - } +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 +} - if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil { - return err - } +// Shared by both HTML and text templates. +type templatesCommon struct { + handler *templateHandler - return nil - } + // Used to get proper filenames in errors + nameBaseTemplateName map[string]string - if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil { - if !os.IsNotExist(err) { - return err - } - return nil - } + // Holds names of the templates not found during the first AST transformation + // pass. + transformNotFound map[string]bool +} - return nil +func (t templatesCommon) withNewHandler(h *templateHandler) *templatesCommon { + t.handler = h + return &t +} +type textTemplate struct { + mu sync.RWMutex + t *texttemplate.Template } -func (t *templateHandler) initFuncs() { +func (t *textTemplate) Lookup(name string) (tpl.Template, bool) { + t.mu.RLock() + defer t.mu.RUnlock() - // Both template types will get their own funcster instance, which - // in the current case contains the same set of funcs. - funcMap := createFuncMap(t.Deps) - for _, funcsterHolder := range []templateFuncsterSetter{t.html, t.text} { - funcster := newTemplateFuncster(t.Deps) + tpl := t.t.Lookup(name) + return tpl, tpl != nil +} - // The URL funcs in the funcMap is somewhat language dependent, - // so we need to wait until the language and site config is loaded. - funcster.initFuncMap(funcMap) +func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) { + return t.parseIn(t.t, name, tpl) +} - funcsterHolder.setTemplateFuncster(funcster) +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 } - for _, v := range t.shortcodes { - for _, variant := range v.variants { - t.setFuncMapInTemplate(variant.templ, funcMap) - } + if _, err := applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil { + return nil, err } + return templ, nil +} + +type textTemplates struct { + *templatesCommon + *textTemplate + standalone *textTemplate + clone *texttemplate.Template + cloneClone *texttemplate.Template + overlays map[string]*texttemplate.Template } -func (t *templateHandler) getTemplateHandler(name string) templateLoader { - if strings.HasPrefix(name, textTmplNamePrefix) { - return t.text +func (t *textTemplates) Lookup(name string) (tpl.Template, bool) { + templ := t.lookup(name) + if templ == nil { + return nil, false } - return t.html + return templ, true } -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 *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) { + return t.handler.LookupVariant(name, variants) } -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 - } +func (t *textTemplates) addLateTemplate(name, tpl string) error { + _, err := t.addTemplateIn(t.clone, name, tpl) + return err +} - masterTpl, err = t.t.New(overlayFilename).Parse(templ.template) - if err != nil { - return templ.errWithFileContext("parse master failed", err) - } - } +func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) { + return t.addTemplateIn(t.t, name, tpl) +} - templ, err := onMissing(overlayFilename) +func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) (*templateContext, error) { + name = strings.TrimPrefix(name, textTmplNamePrefix) + templ, err := t.parseIn(tt, name, tpl) if err != nil { - return err + return nil, err } - overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template) + typ := resolveTemplateType(name) + + c, err := applyTemplateTransformersToTextTemplate(typ, templ) if err != nil { - return templ.errWithFileContext("parse failed", err) + return nil, 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 + for k := range c.notFound { + t.transformNotFound[k] = true } - t.overlays[name] = overlayTpl - t.nameBaseTemplateName[name] = masterFilename - - return err + if typ == templateShortcode { + t.handler.addShortcodeVariant(name, c.Info, templ) + } else { + t.handler.templateInfo[name] = c.Info + } + return c, nil } func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error { @@ -868,119 +934,67 @@ func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename strin } -func removeLeadingBOM(s string) string { - const bom = '\ufeff' - - for i, r := range s { - if i == 0 && r != bom { - return s - } - if i > 0 { - return s[i:] - } - } - - return s - -} - -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) - - 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 - } - - s := removeLeadingBOM(string(b)) +func (t *textTemplates) lookup(name string) *texttemplate.Template { - realFilename := filename - if fi, err := fs.Stat(filename); err == nil { - if fim, ok := fi.(hugofs.FileMetaInfo); ok { - realFilename = fim.Meta().Filename() - } + // 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 } - - return templateInfo{template: s, filename: filename, realFilename: realFilename, fs: fs}, 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: - - if baseTemplatePath != "" { - return t.handleMaster(name, path, baseTemplatePath, getTemplate) - } - - templ, err := getTemplate(path) - - if err != nil { - return err - } - - err = t.AddTemplate(name, templ.template) - if err != nil { - return templ.errWithFileContext("parse failed", err) - } - return nil + if templ := t.t.Lookup(name); templ != nil { + return templ } -} - -var embeddedTemplatesAliases = map[string][]string{ - "shortcodes/twitter.html": {"shortcodes/tweet.html"}, -} - -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 - } - } - } + if t.clone != nil { + return t.clone.Lookup(name) } return nil +} +func (t textTemplates) withNewHandler(h *templateHandler) *textTemplates { + t.templatesCommon = t.templatesCommon.withNewHandler(h) + return &t } -func (t *templateHandler) addInternalTemplate(name, tpl string) error { - return t.AddTemplate("_internal/"+name, tpl) +func isBackupFile(path string) bool { + return path[len(path)-1] == '~' } -func (t *templateHandler) checkState() { - if t.html.clone != nil || t.text.clone != nil { - panic("template is cloned and cannot be modfified") - } +func isBaseTemplate(path string) bool { + return strings.Contains(filepath.Base(path), baseFileBase) } func isDotFile(path string) bool { return filepath.Base(path)[0] == '.' } -func isBackupFile(path string) bool { - return path[len(path)-1] == '~' +func removeLeadingBOM(s string) string { + const bom = '\ufeff' + + for i, r := range s { + if i == 0 && r != bom { + return s + } + if i > 0 { + return s[i:] + } + } + + return s + } -const baseFileBase = "baseof" +// 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") + } -func isBaseTemplate(path string) bool { - return strings.Contains(filepath.Base(path), baseFileBase) } |