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:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2018-10-03 15:58:09 +0300
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2018-10-16 23:10:56 +0300
commit35fbfb19a173b01bc881f2bbc5d104136633a7ec (patch)
tree636d0d51fa262dc808eb3c5cc9cf92ad977a0c6a /tpl/template.go
parent3a3089121b852332b5744d1f566959c8cf93cef4 (diff)
commands: Show server error info in browser
The main item in this commit is showing of errors with a file context when running `hugo server`. This can be turned off: `hugo server --disableBrowserError` (can also be set in `config.toml`). But to get there, the error handling in Hugo needed a revision. There are some items left TODO for commits soon to follow, most notable errors in content and config files. Fixes #5284 Fixes #5290 See #5325 See #5324
Diffstat (limited to 'tpl/template.go')
-rw-r--r--tpl/template.go127
1 files changed, 121 insertions, 6 deletions
diff --git a/tpl/template.go b/tpl/template.go
index 2cef92bb2..02b2d4a9b 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -1,4 +1,4 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
+// Copyright 2018 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.
@@ -14,16 +14,26 @@
package tpl
import (
+ "fmt"
"io"
+ "path/filepath"
+ "regexp"
+ "strings"
"time"
- "text/template/parse"
+ "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 (
@@ -35,8 +45,7 @@ type TemplateHandler interface {
TemplateFinder
AddTemplate(name, tpl string) error
AddLateTemplate(name, tpl string) error
- LoadTemplates(prefix string)
- PrintErrors()
+ LoadTemplates(prefix string) error
NewTextTemplate() TemplateParseFinder
@@ -82,16 +91,122 @@ type TemplateDebugger interface {
type TemplateAdapter struct {
Template
Metrics metrics.Provider
+
+ // 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{}) error {
+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.)
+ if r := recover(); r != nil {
+ execErr = t.addFileContext(t.Name(), fmt.Errorf("panic in Execute: %s", r))
+ }
+ }()
+
if t.Metrics != nil {
defer t.Metrics.MeasureSince(t.Name(), time.Now())
}
- return t.Template.Execute(w, data)
+
+ execErr = t.Template.Execute(w, data)
+ if execErr != nil {
+ execErr = t.addFileContext(t.Name(), execErr)
+ }
+
+ return
+}
+
+var identifiersRe = regexp.MustCompile("at \\<(.*?)\\>:")
+
+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 {
+ f, realFilename, err := t.fileAndFilename(t.Name())
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ master, hasMaster := t.NameBaseTemplateName[name]
+
+ ferr := errors.Wrapf(inerr, "execute of template %q failed", realFilename)
+
+ // 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(le herrors.FileError, lineNumber int, line string) bool {
+ if le.LineNumber() != lineNumber {
+ return false
+ }
+ if !hasMaster {
+ return true
+ }
+
+ identifiers := t.extractIdentifiers(le.Error())
+
+ for _, id := range identifiers {
+ if strings.Contains(line, id) {
+ return true
+ }
+ }
+ return false
+ }
+
+ // TODO(bep) 2errors text vs HTML
+ fe, ok := herrors.WithFileContext(ferr, f, "go-html-template", 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()
+
+ ferr = errors.Wrapf(inerr, "execute of template %q failed", realFilename)
+ fe, _ = herrors.WithFileContext(ferr, f, "go-html-template", lineMatcher)
+ 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, "", errors.Wrapf(err, "failed to Stat %q", filename)
+ }
+ 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