mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
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
This commit is contained in:
127
tpl/template.go
127
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
|
||||
|
Reference in New Issue
Block a user