Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tpl/template.go')
-rw-r--r--tpl/template.go305
1 files changed, 305 insertions, 0 deletions
diff --git a/tpl/template.go b/tpl/template.go
new file mode 100644
index 000000000..935771364
--- /dev/null
+++ b/tpl/template.go
@@ -0,0 +1,305 @@
+// Copyright 2019 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tpl
+
+import (
+ "fmt"
+ "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"
+
+ "html/template"
+ texttemplate "text/template"
+ "text/template/parse"
+
+ bp "github.com/gohugoio/hugo/bufferpool"
+ "github.com/gohugoio/hugo/metrics"
+ "github.com/pkg/errors"
+)
+
+var (
+ _ TemplateExecutor = (*TemplateAdapter)(nil)
+ _ TemplateInfoProvider = (*TemplateAdapter)(nil)
+)
+
+// TemplateHandler manages the collection of templates.
+type TemplateHandler interface {
+ TemplateFinder
+ AddTemplate(name, tpl string) error
+ AddLateTemplate(name, tpl string) error
+ LoadTemplates(prefix string) error
+
+ NewTextTemplate() TemplateParseFinder
+
+ MarkReady() error
+ RebuildClone()
+}
+
+// TemplateVariants describes the possible variants of a template.
+// All of these may be empty.
+type TemplateVariants struct {
+ Language string
+ OutputFormat output.Format
+}
+
+// TemplateFinder finds templates.
+type TemplateFinder interface {
+ TemplateLookup
+ TemplateLookupVariant
+}
+
+type TemplateLookup interface {
+ Lookup(name string) (Template, bool)
+}
+
+type TemplateLookupVariant interface {
+ // TODO(bep) this currently only works for shortcodes.
+ // We may unify and expand this variant pattern to the
+ // other templates, but we need this now for the shortcodes to
+ // quickly determine if a shortcode has a template for a given
+ // output format.
+ // It returns the template, if it was found or not and if there are
+ // alternative representations (output format, language).
+ // We are currently only interested in output formats, so we should improve
+ // this for speed.
+ LookupVariant(name string, variants TemplateVariants) (Template, bool, bool)
+}
+
+// Template is the common interface between text/template and html/template.
+type Template interface {
+ Execute(wr io.Writer, data interface{}) error
+ Name() string
+}
+
+// TemplateInfoProvider provides some contextual information about a template.
+type TemplateInfoProvider interface {
+ TemplateInfo() Info
+}
+
+// TemplateParser is used to parse ad-hoc templates, e.g. in the Resource chain.
+type TemplateParser interface {
+ Parse(name, tpl string) (Template, error)
+}
+
+// TemplateParseFinder provides both parsing and finding.
+type TemplateParseFinder interface {
+ TemplateParser
+ 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 {
+ 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: (.*?):")
+
+func extractBaseOf(err string) string {
+ m := baseOfRe.FindStringSubmatch(err)
+ if len(m) == 2 {
+ return m[1]
+ }
+ 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 {
+ 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
+ }
+ f, err := fs.Open(filename)
+ if err != nil {
+ return nil, "", errors.Wrapf(err, "failed to open template file %q:", filename)
+ }
+
+ return f, fi.(hugofs.RealFilenameInfo).RealFilename(), 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{})
+}