diff options
Diffstat (limited to 'tpl/template.go')
-rw-r--r-- | tpl/template.go | 218 |
1 files changed, 19 insertions, 199 deletions
diff --git a/tpl/template.go b/tpl/template.go index 63bf42730..db715c306 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -14,39 +14,22 @@ package tpl import ( - "fmt" - "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" + "reflect" "io" - "path/filepath" "regexp" - "strings" - "time" "github.com/gohugoio/hugo/output" - "github.com/gohugoio/hugo/common/herrors" - - "github.com/gohugoio/hugo/hugofs" - - "github.com/spf13/afero" - texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" - "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse" - - bp "github.com/gohugoio/hugo/bufferpool" - "github.com/gohugoio/hugo/metrics" - "github.com/pkg/errors" ) -var ( - _ TemplateExecutor = (*TemplateAdapter)(nil) - _ TemplateInfoProvider = (*TemplateAdapter)(nil) -) +var _ TemplateInfoProvider = (*TemplateInfo)(nil) -// TemplateHandler manages the collection of templates. -type TemplateHandler interface { - TemplateFinder +// TemplateManager manages the collection of templates. +type TemplateManager interface { + TemplateHandler + TemplateFuncGetter AddTemplate(name, tpl string) error AddLateTemplate(name, tpl string) error LoadTemplates(prefix string) error @@ -68,6 +51,12 @@ type TemplateFinder interface { TemplateLookupVariant } +// TemplateHandler finds and executes templates. +type TemplateHandler interface { + TemplateFinder + Execute(t Template, wr io.Writer, data interface{}) error +} + type TemplateLookup interface { Lookup(name string) (Template, bool) } @@ -87,8 +76,8 @@ type TemplateLookupVariant interface { // Template is the common interface between text/template and html/template. type Template interface { - Execute(wr io.Writer, data interface{}) error Name() string + Prepare() (*texttemplate.Template, error) } // TemplateInfoProvider provides some contextual information about a template. @@ -107,30 +96,15 @@ type TemplateParseFinder interface { TemplateFinder } -// TemplateExecutor adds some extras to Template. -type TemplateExecutor interface { - Template - ExecuteToString(data interface{}) (string, error) - Tree() string -} - // TemplateDebugger prints some debug info to stdoud. type TemplateDebugger interface { Debug() } -// TemplateAdapter implements the TemplateExecutor interface. -type TemplateAdapter struct { +// TemplateInfo wraps a Template with some additional information. +type TemplateInfo struct { Template - Metrics metrics.Provider - Info Info - - // The filesystem where the templates are stored. - Fs afero.Fs - - // Maps to base template if relevant. - NameBaseTemplateName map[string]string } var baseOfRe = regexp.MustCompile("template: (.*?):") @@ -143,165 +117,11 @@ func extractBaseOf(err string) string { return "" } -// Execute executes the current template. The actual execution is performed -// by the embedded text or html template, but we add an implementation here so -// we can add a timer for some metrics. -func (t *TemplateAdapter) Execute(w io.Writer, data interface{}) (execErr error) { - defer func() { - // Panics in templates are a little bit too common (nil pointers etc.) - // See https://github.com/gohugoio/hugo/issues/5327 - if r := recover(); r != nil { - execErr = t.addFileContext(t.Name(), fmt.Errorf(`panic in Execute: %s. See "https://github.com/gohugoio/hugo/issues/5327" for the reason why we cannot provide a better error message for this`, r)) - } - }() - - if t.Metrics != nil { - defer t.Metrics.MeasureSince(t.Name(), time.Now()) - } - - execErr = t.Template.Execute(w, data) - if execErr != nil { - execErr = t.addFileContext(t.Name(), execErr) - } - - return -} - -func (t *TemplateAdapter) TemplateInfo() Info { +func (t *TemplateInfo) TemplateInfo() Info { return t.Info } -// 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})?\>:`) - -func (t *TemplateAdapter) 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 *TemplateAdapter) addFileContext(name string, inerr error) error { - if strings.HasPrefix(t.Name(), "_internal") { - return inerr - } - - f, realFilename, err := t.fileAndFilename(t.Name()) - if err != nil { - return inerr - - } - defer f.Close() - - master, hasMaster := t.NameBaseTemplateName[name] - - 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 - } - - identifiers := t.extractIdentifiers(m.Error.Error()) - - for _, id := range identifiers { - if strings.Contains(m.Line, id) { - return true - } - } - return false - } - - fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher) - if ok || !hasMaster { - return fe - } - - // Try the base template if relevant - f, realFilename, err = t.fileAndFilename(master) - if err != nil { - return err - } - defer f.Close() - - fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher) - - if !ok { - // Return the most specific. - return ferr - - } - return fe - -} - -func (t *TemplateAdapter) fileAndFilename(name string) (afero.File, string, error) { - fs := t.Fs - filename := filepath.FromSlash(name) - - fi, err := fs.Stat(filename) - if err != nil { - return nil, "", err - } - fim := fi.(hugofs.FileMetaInfo) - meta := fim.Meta() - - f, err := meta.Open() - if err != nil { - return nil, "", errors.Wrapf(err, "failed to open template file %q:", filename) - } - - return f, meta.Filename(), nil -} - -// ExecuteToString executes the current template and returns the result as a -// string. -func (t *TemplateAdapter) ExecuteToString(data interface{}) (string, error) { - b := bp.GetBuffer() - defer bp.PutBuffer(b) - if err := t.Execute(b, data); err != nil { - return "", err - } - return b.String(), nil -} - -// Tree returns the template Parse tree as a string. -// Note: this isn't safe for parallel execution on the same template -// vs Lookup and Execute. -func (t *TemplateAdapter) Tree() string { - var tree *parse.Tree - switch tt := t.Template.(type) { - case *template.Template: - tree = tt.Tree - case *texttemplate.Template: - tree = tt.Tree - default: - panic("Unknown template") - } - - if tree == nil || tree.Root == nil { - return "" - } - s := tree.Root.String() - - return s -} - -// TemplateFuncsGetter allows to get a map of functions. -type TemplateFuncsGetter interface { - GetFuncs() map[string]interface{} -} - -// TemplateTestMocker adds a way to override some template funcs during tests. -// The interface is named so it's not used in regular application code. -type TemplateTestMocker interface { - SetFuncs(funcMap map[string]interface{}) +// TemplateFuncGetter allows to find a template func by name. +type TemplateFuncGetter interface { + GetFunc(name string) (reflect.Value, bool) } |