hugolib: Continue the file context/line number errors work
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 21 Oct 2018 10:20:21 +0000 (12:20 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 22 Oct 2018 18:46:14 +0000 (20:46 +0200)
See #5324

23 files changed:
commands/server_errors.go
common/herrors/error_locator.go
common/herrors/error_locator_test.go
common/herrors/file_error.go
common/herrors/file_error_test.go
common/herrors/line_number_extractors.go
deps/deps.go
hugolib/hugo_sites.go
hugolib/hugo_sites_build.go
hugolib/hugo_sites_build_errors_test.go
hugolib/page.go
hugolib/page_content.go
hugolib/page_errors.go [new file with mode: 0644]
hugolib/shortcode.go
hugolib/site.go
hugolib/testhelpers_test.go
parser/pageparser/pagelexer.go
parser/pageparser/pageparser.go
parser/pageparser/pageparser_intro_test.go
tpl/data/data.go
tpl/data/data_test.go
tpl/template.go
tpl/tplimpl/template_errors.go

index 1a469dac84fd2f3fb3b2abecb55f05399dac018b..8ee02e5f25a8be4f25685b82c3196fdd65e90941 100644 (file)
@@ -72,7 +72,7 @@ var buildErrorTemplate = `<!doctype html>
                <main>
                        {{ highlight .Error "apl" "noclasses=true,style=monokai" }}
                        {{ with .File }}
-                       {{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .Pos 1) .LineNumber }}
+                       {{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .Pos 1) (sub .LineNumber .Pos) }}
                        {{ $lexer := .ChromaLexer | default "go-html-template" }}
                        {{  highlight (delimit .Lines "\n") $lexer $params }}
                        {{ end }}
index cc41e8868e24d899aaaf1928174ee6b27bcfb8b6..306f8f46b17850156f182a6237b3865b33e6049d 100644 (file)
@@ -16,12 +16,17 @@ package herrors
 
 import (
        "bufio"
+       "fmt"
        "io"
        "strings"
 
+       "github.com/gohugoio/hugo/helpers"
+
        "github.com/spf13/afero"
 )
 
+var fileErrorFormat = "\"%s:%d:%d\": %s"
+
 // LineMatcher is used to match a line with an error.
 type LineMatcher func(le FileError, lineNumber int, line string) bool
 
@@ -34,6 +39,8 @@ var SimpleLineMatcher = func(le FileError, lineNumber int, line string) bool {
 // ErrorContext contains contextual information about an error. This will
 // typically be the lines surrounding some problem in a file.
 type ErrorContext struct {
+       // The source filename.
+       Filename string
 
        // If a match will contain the matched line and up to 2 lines before and after.
        // Will be empty if no match.
@@ -45,6 +52,9 @@ type ErrorContext struct {
        // The linenumber in the source file from where the Lines start. Starting at 1.
        LineNumber int
 
+       // The column number in the source file. Starting at 1.
+       ColumnNumber int
+
        // The lexer to use for syntax highlighting.
        // https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
        ChromaLexer string
@@ -60,7 +70,7 @@ type ErrorWithFileContext struct {
 }
 
 func (e *ErrorWithFileContext) Error() string {
-       return e.cause.Error()
+       return fmt.Sprintf(fileErrorFormat, e.Filename, e.LineNumber, e.ColumnNumber, e.cause.Error())
 }
 
 func (e *ErrorWithFileContext) Cause() error {
@@ -69,39 +79,40 @@ func (e *ErrorWithFileContext) Cause() error {
 
 // WithFileContextForFile will try to add a file context with lines matching the given matcher.
 // If no match could be found, the original error is returned with false as the second return value.
-func WithFileContextForFile(e error, filename string, fs afero.Fs, chromaLexer string, matcher LineMatcher) (error, bool) {
+func WithFileContextForFile(e error, realFilename, filename string, fs afero.Fs, matcher LineMatcher) (error, bool) {
        f, err := fs.Open(filename)
        if err != nil {
                return e, false
        }
        defer f.Close()
-       return WithFileContext(e, f, chromaLexer, matcher)
+       return WithFileContext(e, realFilename, f, matcher)
 }
 
 // WithFileContextForFile will try to add a file context with lines matching the given matcher.
 // If no match could be found, the original error is returned with false as the second return value.
-func WithFileContext(e error, r io.Reader, chromaLexer string, matcher LineMatcher) (error, bool) {
+func WithFileContext(e error, realFilename string, r io.Reader, matcher LineMatcher) (error, bool) {
        if e == nil {
                panic("error missing")
        }
        le := UnwrapFileError(e)
        if le == nil {
                var ok bool
-               if le, ok = ToFileError("bash", e).(FileError); !ok {
+               if le, ok = ToFileError("", e).(FileError); !ok {
                        return e, false
                }
        }
 
        errCtx := locateError(r, le, matcher)
+       errCtx.Filename = realFilename
 
        if errCtx.LineNumber == -1 {
                return e, false
        }
 
-       if chromaLexer != "" {
-               errCtx.ChromaLexer = chromaLexer
-       } else {
+       if le.Type() != "" {
                errCtx.ChromaLexer = chromaLexerFromType(le.Type())
+       } else {
+               errCtx.ChromaLexer = chromaLexerFromFilename(realFilename)
        }
 
        return &ErrorWithFileContext{cause: e, ErrorContext: errCtx}, true
@@ -124,9 +135,22 @@ func UnwrapErrorWithFileContext(err error) *ErrorWithFileContext {
 }
 
 func chromaLexerFromType(fileType string) string {
+       switch fileType {
+       case "html", "htm":
+               return "go-html-template"
+       }
        return fileType
 }
 
+func chromaLexerFromFilename(filename string) string {
+       if strings.Contains(filename, "layouts") {
+               return "go-html-template"
+       }
+
+       ext := helpers.ExtNoDelimiter(filename)
+       return chromaLexerFromType(ext)
+}
+
 func locateErrorInString(le FileError, src string, matcher LineMatcher) ErrorContext {
        return locateError(strings.NewReader(src), nil, matcher)
 }
@@ -135,6 +159,11 @@ func locateError(r io.Reader, le FileError, matches LineMatcher) ErrorContext {
        var errCtx ErrorContext
        s := bufio.NewScanner(r)
 
+       errCtx.ColumnNumber = 1
+       if le != nil {
+               errCtx.ColumnNumber = le.ColumnNumber()
+       }
+
        lineNo := 0
 
        var buff [6]string
@@ -152,7 +181,7 @@ func locateError(r io.Reader, le FileError, matches LineMatcher) ErrorContext {
 
                if errCtx.Pos == -1 && matches(le, lineNo, txt) {
                        errCtx.Pos = i
-                       errCtx.LineNumber = lineNo - i
+                       errCtx.LineNumber = lineNo
                }
 
                if errCtx.Pos == -1 && i == 2 {
@@ -171,7 +200,7 @@ func locateError(r io.Reader, le FileError, matches LineMatcher) ErrorContext {
                if matches(le, lineNo, "") {
                        buff[i] = ""
                        errCtx.Pos = i
-                       errCtx.LineNumber = lineNo - 1
+                       errCtx.LineNumber = lineNo
 
                        i++
                }
index 6c879727e3559da8e0c411c50caca672adcc16d7..caa6e638541423fb919ade585a4c939651c116b0 100644 (file)
@@ -41,7 +41,7 @@ LINE 8
        location := locateErrorInString(nil, lines, lineMatcher)
        assert.Equal([]string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"}, location.Lines)
 
-       assert.Equal(3, location.LineNumber)
+       assert.Equal(5, location.LineNumber)
        assert.Equal(2, location.Pos)
 
        assert.Equal([]string{"This is THEONE"}, locateErrorInString(nil, `This is THEONE`, lineMatcher).Lines)
@@ -92,7 +92,7 @@ I
 J`, lineMatcher)
 
        assert.Equal([]string{"D", "E", "F", "G", "H"}, location.Lines)
-       assert.Equal(4, location.LineNumber)
+       assert.Equal(6, location.LineNumber)
        assert.Equal(2, location.Pos)
 
        // Test match EOF
@@ -106,7 +106,7 @@ C
 `, lineMatcher)
 
        assert.Equal([]string{"B", "C", ""}, location.Lines)
-       assert.Equal(3, location.LineNumber)
+       assert.Equal(4, location.LineNumber)
        assert.Equal(2, location.Pos)
 
 }
index f29f91fcc93aefa3cc65681f62c3c6b2339276f2..86ccfcefb4979ccc9d41e3ed04301ea53b945785 100644 (file)
 
 package herrors
 
-import (
-       "fmt"
-)
-
 var _ causer = (*fileError)(nil)
 
 // FileError represents an error when handling a file: Parsing a config file,
@@ -27,6 +23,8 @@ type FileError interface {
        // LineNumber gets the error location, starting at line 1.
        LineNumber() int
 
+       ColumnNumber() int
+
        // A string identifying the type of file, e.g. JSON, TOML, markdown etc.
        Type() string
 }
@@ -34,9 +32,9 @@ type FileError interface {
 var _ FileError = (*fileError)(nil)
 
 type fileError struct {
-       lineNumber int
-       fileType   string
-       msg        string
+       lineNumber   int
+       columnNumber int
+       fileType     string
 
        cause error
 }
@@ -45,32 +43,28 @@ func (e *fileError) LineNumber() int {
        return e.lineNumber
 }
 
+func (e *fileError) ColumnNumber() int {
+       return e.columnNumber
+}
+
 func (e *fileError) Type() string {
        return e.fileType
 }
 
 func (e *fileError) Error() string {
-       return e.msg
+       if e.cause == nil {
+               return ""
+       }
+       return e.cause.Error()
 }
 
 func (f *fileError) Cause() error {
        return f.cause
 }
 
-func (e *fileError) Format(s fmt.State, verb rune) {
-       switch verb {
-       case 'v':
-               fallthrough
-       case 's':
-               fmt.Fprintf(s, "%s:%d: %s:%s", e.fileType, e.lineNumber, e.msg, e.cause)
-       case 'q':
-               fmt.Fprintf(s, "%q:%d: %q:%q", e.fileType, e.lineNumber, e.msg, e.cause)
-       }
-}
-
 // NewFileError creates a new FileError.
-func NewFileError(fileType string, lineNumber int, msg string, err error) FileError {
-       return &fileError{cause: err, fileType: fileType, lineNumber: lineNumber, msg: msg}
+func NewFileError(fileType string, lineNumber, columnNumber int, err error) FileError {
+       return &fileError{cause: err, fileType: fileType, lineNumber: lineNumber, columnNumber: columnNumber}
 }
 
 // UnwrapFileError tries to unwrap a FileError from err.
@@ -101,9 +95,10 @@ func ToFileError(fileType string, err error) error {
 // If will fall back to returning the original error if a line number cannot be extracted.
 func ToFileErrorWithOffset(fileType string, err error, offset int) error {
        for _, handle := range lineNumberExtractors {
-               lno, msg := handle(err, offset)
+
+               lno, col := handle(err)
                if lno > 0 {
-                       return NewFileError(fileType, lno, msg, err)
+                       return NewFileError(fileType, lno+offset, col, err)
                }
        }
        // Fall back to the original.
index e266ff1dcb959f2b4d1deb9561444f4769aa6754..0d4e82f6658f2ab808b8e430f4caeda7ec26d563 100644 (file)
@@ -28,16 +28,16 @@ func TestToLineNumberError(t *testing.T) {
        assert := require.New(t)
 
        for i, test := range []struct {
-               in         error
-               offset     int
-               lineNumber int
+               in           error
+               offset       int
+               lineNumber   int
+               columnNumber int
        }{
-               {errors.New("no line number for you"), 0, -1},
-               {errors.New(`template: _default/single.html:2:15: executing "_default/single.html" at <.Titles>: can't evaluate field`), 0, 2},
-               {errors.New("parse failed: template: _default/bundle-resource-meta.html:11: unexpected in operand"), 0, 11},
-               {errors.New(`failed:: template: _default/bundle-resource-meta.html:2:7: executing "main" at <.Titles>`), 0, 2},
-               {errors.New("error in front matter: Near line 32 (last key parsed 'title')"), 0, 32},
-               {errors.New("error in front matter: Near line 32 (last key parsed 'title')"), 2, 34},
+               {errors.New("no line number for you"), 0, -1, 1},
+               {errors.New(`template: _default/single.html:4:15: executing "_default/single.html" at <.Titles>: can't evaluate field Titles in type *hugolib.PageOutput`), 0, 4, 15},
+               {errors.New("parse failed: template: _default/bundle-resource-meta.html:11: unexpected in operand"), 0, 11, 1},
+               {errors.New(`failed:: template: _default/bundle-resource-meta.html:2:7: executing "main" at <.Titles>`), 0, 2, 7},
+               {errors.New("error in front matter: Near line 32 (last key parsed 'title')"), 0, 32, 1},
        } {
 
                got := ToFileErrorWithOffset("template", test.in, test.offset)
@@ -48,6 +48,7 @@ func TestToLineNumberError(t *testing.T) {
                if test.lineNumber > 0 {
                        assert.True(ok)
                        assert.Equal(test.lineNumber, le.LineNumber(), errMsg)
+                       assert.Equal(test.columnNumber, le.ColumnNumber(), errMsg)
                        assert.Contains(got.Error(), strconv.Itoa(le.LineNumber()))
                } else {
                        assert.False(ok)
index 01a7450f9a2cc523dd3142ea22248f51d5a62e46..8740afdf75ec0a94b12c26e4eccf9130e93efd0e 100644 (file)
 package herrors
 
 import (
-       "fmt"
        "regexp"
        "strconv"
 )
 
 var lineNumberExtractors = []lineNumberExtractor{
        // Template/shortcode parse errors
-       newLineNumberErrHandlerFromRegexp("(.*?:)(\\d+)(:.*)"),
+       newLineNumberErrHandlerFromRegexp("(.*?:)(\\d+)(:)(\\d+)?(.*)"),
 
        // TOML parse errors
        newLineNumberErrHandlerFromRegexp("(.*Near line )(\\d+)(\\s.*)"),
@@ -30,7 +29,7 @@ var lineNumberExtractors = []lineNumberExtractor{
        newLineNumberErrHandlerFromRegexp("(line )(\\d+)(:)"),
 }
 
-type lineNumberExtractor func(e error, offset int) (int, string)
+type lineNumberExtractor func(e error) (int, int)
 
 func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
        re := regexp.MustCompile(expression)
@@ -38,22 +37,26 @@ func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
 }
 
 func extractLineNo(re *regexp.Regexp) lineNumberExtractor {
-       return func(e error, offset int) (int, string) {
+       return func(e error) (int, int) {
                if e == nil {
                        panic("no error")
                }
+               col := 1
                s := e.Error()
                m := re.FindStringSubmatch(s)
-               if len(m) == 4 {
-                       i, _ := strconv.Atoi(m[2])
-                       msg := e.Error()
-                       if offset != 0 {
-                               i = i + offset
-                               msg = re.ReplaceAllString(s, fmt.Sprintf("${1}%d${3}", i))
+               if len(m) >= 4 {
+                       lno, _ := strconv.Atoi(m[2])
+                       if len(m) > 4 {
+                               col, _ = strconv.Atoi(m[4])
                        }
-                       return i, msg
+
+                       if col <= 0 {
+                               col = 1
+                       }
+
+                       return lno, col
                }
 
-               return -1, ""
+               return -1, col
        }
 }
index 1e2686421dd768cba88a127bf35c10cd3a5e1eec..db59ad212fa079c8e0e34e6ea2b66f253fa42d1c 100644 (file)
@@ -5,7 +5,6 @@ import (
        "time"
 
        "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/hugofs"
@@ -16,6 +15,7 @@ import (
        "github.com/gohugoio/hugo/resource"
        "github.com/gohugoio/hugo/source"
        "github.com/gohugoio/hugo/tpl"
+       jww "github.com/spf13/jwalterweatherman"
 )
 
 // Deps holds dependencies used by many.
@@ -73,6 +73,33 @@ type Deps struct {
 
        // BuildStartListeners will be notified before a build starts.
        BuildStartListeners *Listeners
+
+       *globalErrHandler
+}
+
+type globalErrHandler struct {
+       // Channel for some "hard to get to" build errors
+       buildErrors chan error
+}
+
+// SendErr sends the error on a channel to be handled later.
+// This can be used in situations where returning and aborting the current
+// operation isn't practical.
+func (e *globalErrHandler) SendError(err error) {
+       if e.buildErrors != nil {
+               select {
+               case e.buildErrors <- err:
+               default:
+               }
+               return
+       }
+
+       jww.ERROR.Println(err)
+}
+
+func (e *globalErrHandler) StartErrorCollector() chan error {
+       e.buildErrors = make(chan error, 10)
+       return e.buildErrors
 }
 
 // Listeners represents an event listener.
@@ -194,6 +221,7 @@ func New(cfg DepsCfg) (*Deps, error) {
                Language:            cfg.Language,
                BuildStartListeners: &Listeners{},
                Timeout:             time.Duration(timeoutms) * time.Millisecond,
+               globalErrHandler:    &globalErrHandler{},
        }
 
        if cfg.Cfg.GetBool("templateMetrics") {
index 7f70967d65ca68afa1460a1b8d09a227000bd0cf..a184e8877098f7d6d8ec1e20ca689085fae776c3 100644 (file)
@@ -21,6 +21,7 @@ import (
        "strings"
        "sync"
 
+       "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/helpers"
@@ -53,6 +54,40 @@ type HugoSites struct {
        gitInfo *gitInfo
 }
 
+func (h *HugoSites) pickOneAndLogTheRest(errors []error) error {
+       if len(errors) == 0 {
+               return nil
+       }
+
+       var i int
+
+       for j, err := range errors {
+               // If this is in server mode, we want to return an error to the client
+               // with a file context, if possible.
+               if herrors.UnwrapErrorWithFileContext(err) != nil {
+                       i = j
+                       break
+               }
+       }
+
+       // Log the rest, but add a threshold to avoid flooding the log.
+       const errLogThreshold = 5
+
+       for j, err := range errors {
+               if j == i || err == nil {
+                       continue
+               }
+
+               if j >= errLogThreshold {
+                       break
+               }
+
+               h.Log.ERROR.Println(err)
+       }
+
+       return errors[i]
+}
+
 func (h *HugoSites) IsMultihost() bool {
        return h != nil && h.multihost
 }
@@ -636,6 +671,7 @@ func handleShortcodes(p *PageWithoutContent, rawContentCopy []byte) ([]byte, err
                err := p.shortcodeState.executeShortcodesForDelta(p)
 
                if err != nil {
+
                        return rawContentCopy, err
                }
 
index 13fbfd57eff52157adec2162effb4a43cbe5b84b..4c275f55bab0ce6572cd75c13fd60acbbb66c56a 100644 (file)
@@ -26,13 +26,29 @@ import (
 // Build builds all sites. If filesystem events are provided,
 // this is considered to be a potential partial rebuild.
 func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
+       errCollector := h.StartErrorCollector()
+       errs := make(chan error)
+
+       go func(from, to chan error) {
+               var errors []error
+               i := 0
+               for e := range from {
+                       i++
+                       if i > 50 {
+                               break
+                       }
+                       errors = append(errors, e)
+               }
+               to <- h.pickOneAndLogTheRest(errors)
+
+               close(to)
+
+       }(errCollector, errs)
 
        if h.Metrics != nil {
                h.Metrics.Reset()
        }
 
-       //t0 := time.Now()
-
        // Need a pointer as this may be modified.
        conf := &config
 
@@ -41,33 +57,46 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
                conf.whatChanged = &whatChanged{source: true, other: true}
        }
 
+       var prepareErr error
+
        if !config.PartialReRender {
-               for _, s := range h.Sites {
-                       s.Deps.BuildStartListeners.Notify()
-               }
+               prepare := func() error {
+                       for _, s := range h.Sites {
+                               s.Deps.BuildStartListeners.Notify()
+                       }
+
+                       if len(events) > 0 {
+                               // Rebuild
+                               if err := h.initRebuild(conf); err != nil {
+                                       return err
+                               }
+                       } else {
+                               if err := h.init(conf); err != nil {
+                                       return err
+                               }
+                       }
 
-               if len(events) > 0 {
-                       // Rebuild
-                       if err := h.initRebuild(conf); err != nil {
+                       if err := h.process(conf, events...); err != nil {
                                return err
                        }
-               } else {
-                       if err := h.init(conf); err != nil {
+
+                       if err := h.assemble(conf); err != nil {
                                return err
                        }
+                       return nil
                }
 
-               if err := h.process(conf, events...); err != nil {
-                       return err
+               prepareErr = prepare()
+               if prepareErr != nil {
+                       h.SendError(prepareErr)
                }
 
-               if err := h.assemble(conf); err != nil {
-                       return err
-               }
        }
 
-       if err := h.render(conf); err != nil {
-               return err
+       if prepareErr == nil {
+               if err := h.render(conf); err != nil {
+                       h.SendError(err)
+               }
        }
 
        if h.Metrics != nil {
@@ -79,6 +108,18 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
                h.Log.FEEDBACK.Println()
        }
 
+       select {
+       // Make sure the channel always gets something.
+       case errCollector <- nil:
+       default:
+       }
+       close(errCollector)
+
+       err := <-errs
+       if err != nil {
+               return err
+       }
+
        errorCount := h.Log.ErrorCounter.Count()
        if errorCount > 0 {
                return fmt.Errorf("logged %d error(s)", errorCount)
index 6b44bea8867173fd3fe49522d34ad15015169064..2e8eb99eae3cc46c7b4e1ad9beb0fb3ecc9feb41 100644 (file)
@@ -2,6 +2,7 @@ package hugolib
 
 import (
        "fmt"
+       "path/filepath"
        "strings"
        "testing"
 
@@ -17,13 +18,20 @@ type testSiteBuildErrorAsserter struct {
 func (t testSiteBuildErrorAsserter) getFileError(err error) *herrors.ErrorWithFileContext {
        t.assert.NotNil(err, t.name)
        ferr := herrors.UnwrapErrorWithFileContext(err)
-       t.assert.NotNil(ferr, fmt.Sprintf("[%s] got %T: %+v", t.name, err, err))
+       t.assert.NotNil(ferr, fmt.Sprintf("[%s] got %T: %+v\n%s", t.name, err, err, trace()))
        return ferr
 }
 
 func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) {
        fe := t.getFileError(err)
-       t.assert.Equal(lineNumber, fe.LineNumber, fmt.Sprintf("[%s]  got => %s", t.name, fe))
+       t.assert.Equal(lineNumber, fe.LineNumber, fmt.Sprintf("[%s]  got => %s\n%s", t.name, fe, trace()))
+}
+
+func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) {
+       // The error message will contain filenames with OS slashes. Normalize before compare.
+       e1, e2 = filepath.ToSlash(e1), filepath.ToSlash(e2)
+       t.assert.Equal(e1, e2, trace())
+
 }
 
 func TestSiteBuildErrors(t *testing.T) {
@@ -32,6 +40,7 @@ func TestSiteBuildErrors(t *testing.T) {
 
        const (
                yamlcontent = "yamlcontent"
+               tomlcontent = "tomlcontent"
                shortcode   = "shortcode"
                base        = "base"
                single      = "single"
@@ -55,7 +64,7 @@ func TestSiteBuildErrors(t *testing.T) {
                                return strings.Replace(content, ".Title }}", ".Title }", 1)
                        },
                        assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
-                               a.assertLineNumber(2, err)
+                               a.assertLineNumber(4, err)
                        },
                },
                {
@@ -65,7 +74,7 @@ func TestSiteBuildErrors(t *testing.T) {
                                return strings.Replace(content, ".Title", ".Titles", 1)
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
-                               a.assertLineNumber(2, err)
+                               a.assertLineNumber(4, err)
                        },
                },
                {
@@ -75,7 +84,12 @@ func TestSiteBuildErrors(t *testing.T) {
                                return strings.Replace(content, ".Title }}", ".Title }", 1)
                        },
                        assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
-                               a.assertLineNumber(3, err)
+                               fe := a.getFileError(err)
+                               assert.Equal(5, fe.LineNumber)
+                               assert.Equal(1, fe.ColumnNumber)
+                               assert.Equal("go-html-template", fe.ChromaLexer)
+                               a.assertErrorMessage("\"layouts/_default/single.html:5:1\": parse failed: template: _default/single.html:5: unexpected \"}\" in operand", fe.Error())
+
                        },
                },
                {
@@ -85,7 +99,12 @@ func TestSiteBuildErrors(t *testing.T) {
                                return strings.Replace(content, ".Title", ".Titles", 1)
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
-                               a.assertLineNumber(3, err)
+                               fe := a.getFileError(err)
+                               assert.Equal(5, fe.LineNumber)
+                               assert.Equal(14, fe.ColumnNumber)
+                               assert.Equal("md", fe.ChromaLexer)
+                               a.assertErrorMessage("asdfadf", fe.Error())
+
                        },
                },
                {
@@ -95,7 +114,7 @@ func TestSiteBuildErrors(t *testing.T) {
                                return strings.Replace(content, ".Title }}", ".Title }", 1)
                        },
                        assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
-                               a.assertLineNumber(2, err)
+                               a.assertLineNumber(4, err)
                        },
                },
                {
@@ -105,10 +124,47 @@ func TestSiteBuildErrors(t *testing.T) {
                                return strings.Replace(content, ".Title", ".Titles", 1)
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
-                               a.assertLineNumber(25, err)
+                               a.assertLineNumber(4, err)
                        },
                },
+               {
+                       name:     "Shortode does not exist",
+                       fileType: yamlcontent,
+                       fileFixer: func(content string) string {
+                               return strings.Replace(content, "{{< sc >}}", "{{< nono >}}", 1)
+                       },
+                       assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
+                               fe := a.getFileError(err)
+                               assert.Equal(7, fe.LineNumber)
+                               assert.Equal(14, fe.ColumnNumber)
+                               assert.Equal("md", fe.ChromaLexer)
+                               a.assertErrorMessage("\"content/myyaml.md:7:14\": failed to extract shortcode: template for shortcode \"nono\" not found", fe.Error())
+                       },
+               },
+               {
+                       name:     "Invalid YAML front matter",
+                       fileType: yamlcontent,
+                       fileFixer: func(content string) string {
+                               // TODO(bep) 2errors YAML line numbers seems to be off by one for > 1 line.
+                               return strings.Replace(content, "title:", "title", 1)
+                       },
+                       assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
+                               a.assertLineNumber(2, err)
+                       },
+               },
+               {
+                       name:     "Invalid TOML front matter",
+                       fileType: tomlcontent,
+                       fileFixer: func(content string) string {
+                               return strings.Replace(content, "description = ", "description &", 1)
+                       },
+                       assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
+                               fe := a.getFileError(err)
+                               assert.Equal(6, fe.LineNumber)
+                               assert.Equal("toml", fe.ErrorContext.ChromaLexer)
 
+                       },
+               },
                {
                        name:     "Panic in template Execute",
                        fileType: single,
@@ -166,12 +222,25 @@ title: "The YAML"
 
 Some content.
 
-{{< sc >}}
+         {{< sc >}}
 
 Some more text.
 
 The end.
 
+`))
+
+               b.WithContent("mytoml.md", f(tomlcontent, `+++
+title = "The TOML"
+p1 = "v"
+p2 = "v"
+p3 = "v"
+description = "Descriptioon"
++++
+
+Some content.
+
+
 `))
 
                createErr := b.CreateSitesE()
index 74005e5a8c1b2ab454f4e766595883e75facb664..df6f88b01eae867159a415d5ade31d56b5abb0ad 100644 (file)
@@ -20,11 +20,10 @@ import (
        "fmt"
        "reflect"
 
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/media"
        _errors "github.com/pkg/errors"
 
-       "github.com/gohugoio/hugo/common/maps"
-
        "github.com/gohugoio/hugo/langs"
 
        "github.com/gohugoio/hugo/related"
@@ -304,7 +303,7 @@ func (p *Page) initContent() {
 
                        if len(p.summary) == 0 {
                                if err = p.setAutoSummary(); err != nil {
-                                       err = _errors.Wrapf(err, "Failed to set user auto summary for page %q:", p.pathOrTitle())
+                                       err = p.errorf(err, "failed to set auto summary")
                                }
                        }
                        c <- err
@@ -315,11 +314,11 @@ func (p *Page) initContent() {
                        p.s.Log.WARN.Printf("WARNING: Timed out creating content for page %q (.Content will be empty). This is most likely a circular shortcode content loop that should be fixed. If this is just a shortcode calling a slow remote service, try to set \"timeout=20000\" (or higher, value is in milliseconds) in config.toml.\n", p.pathOrTitle())
                case err := <-c:
                        if err != nil {
-                               // TODO(bep) 2errors needs to be transported to the caller.
-                               p.s.Log.ERROR.Println(err)
+                               p.s.SendError(err)
                        }
                }
        })
+
 }
 
 // This is sent to the shortcodes for this page. Not doing that will create an infinite regress. So,
@@ -560,11 +559,6 @@ func (ps Pages) findPagePos(page *Page) int {
        return -1
 }
 
-func (p *Page) createWorkContentCopy() {
-       p.workContent = make([]byte, len(p.rawContent))
-       copy(p.workContent, p.rawContent)
-}
-
 func (p *Page) Plain() string {
        p.initContent()
        p.initPlain(true)
@@ -697,12 +691,6 @@ func (p *Page) UniqueID() string {
        return p.File.UniqueID()
 }
 
-// for logging
-// TODO(bep) 2errors remove
-func (p *Page) lineNumRawContentStart() int {
-       return bytes.Count(p.frontmatter, []byte("\n")) + 1
-}
-
 // Returns the page as summary and main.
 func (p *Page) setUserDefinedSummary(rawContentCopy []byte) (*summaryContent, error) {
 
@@ -936,31 +924,18 @@ func (s *Site) NewPage(name string) (*Page, error) {
        return p, nil
 }
 
-func (p *Page) errorf(err error, format string, a ...interface{}) error {
-       args := append([]interface{}{p.Lang(), p.pathOrTitle()}, a...)
-       format = "[%s] Page %q: " + format
-       if err == nil {
-               return fmt.Errorf(format, args...)
-       }
-       return _errors.Wrapf(err, format, args...)
-}
-
 func (p *Page) ReadFrom(buf io.Reader) (int64, error) {
        // Parse for metadata & body
        if err := p.parse(buf); err != nil {
-               return 0, p.errorf(err, "parse failed")
+               return 0, p.errWithFileContext(err)
 
        }
 
-       // Work on a copy of the raw content from now on.
-       // TODO(bep) 2errors
-       //p.createWorkContentCopy()
-
        if err := p.mapContent(); err != nil {
-               return 0, err
+               return 0, p.errWithFileContext(err)
        }
 
-       return int64(len(p.rawContent)), nil
+       return int64(len(p.source.parsed.Input())), nil
 }
 
 func (p *Page) WordCount() int {
@@ -1169,7 +1144,7 @@ func (p *Page) initMainOutputFormat() error {
        pageOutput, err := newPageOutput(p, false, false, outFormat)
 
        if err != nil {
-               return _errors.Wrapf(err, "Failed to create output page for type %q for page %q:", outFormat.Name, p.pathOrTitle())
+               return p.errorf(err, "failed to create output page for type %q", outFormat.Name)
        }
 
        p.mainPageOutput = pageOutput
@@ -1485,7 +1460,7 @@ func (p *Page) updateMetaData(frontmatter map[string]interface{}) error {
        if isCJKLanguage != nil {
                p.isCJKLanguage = *isCJKLanguage
        } else if p.s.Cfg.GetBool("hasCJKLanguage") {
-               if cjk.Match(p.rawContent) {
+               if cjk.Match(p.source.parsed.Input()) {
                        p.isCJKLanguage = true
                } else {
                        p.isCJKLanguage = false
@@ -1711,7 +1686,8 @@ func (p *Page) shouldRenderTo(f output.Format) bool {
 }
 
 func (p *Page) RawContent() string {
-       return string(p.rawContent)
+       // TODO(bep) 2errors
+       return string(p.source.parsed.Input())
 }
 
 func (p *Page) FullFilePath() string {
@@ -2145,12 +2121,7 @@ func (p *Page) setValuesForKind(s *Site) {
 // Used in error logs.
 func (p *Page) pathOrTitle() string {
        if p.Filename() != "" {
-               // Make a path relative to the working dir if possible.
-               filename := strings.TrimPrefix(p.Filename(), p.s.WorkingDir)
-               if filename != p.Filename() {
-                       filename = strings.TrimPrefix(filename, helpers.FilePathSeparator)
-               }
-               return filename
+               return p.Filename()
        }
        return p.title
 }
index 39abd09814de4b1fe27e8e40ffe06c03d1ad3e5d..8c20db7613dd1741be1511f0135f613a566fc276 100644 (file)
 package hugolib
 
 import (
-       "fmt"
+       "bytes"
        "io"
 
+       errors "github.com/pkg/errors"
+
        bp "github.com/gohugoio/hugo/bufferpool"
 
+       "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/parser/metadecoders"
        "github.com/gohugoio/hugo/parser/pageparser"
 )
@@ -31,11 +34,6 @@ var (
 type pageContent struct {
        renderable bool
 
-       frontmatter []byte
-
-       // rawContent is the raw content read from the content file.
-       rawContent []byte
-
        // workContent is a copy of rawContent that may be mutated during site build.
        workContent []byte
 
@@ -66,6 +64,10 @@ func (p *Page) mapContent() error {
 
        iter := p.source.parsed.Iterator()
 
+       fail := func(err error, i pageparser.Item) error {
+               return parseError(err, iter.Input(), i.Pos)
+       }
+
        // the parser is guaranteed to return items in proper order or fail, so …
        // … it's safe to keep some "global" state
        var currShortcode shortcode
@@ -87,7 +89,7 @@ Loop:
                        f := metadecoders.FormatFromFrontMatterType(it.Type)
                        m, err := metadecoders.UnmarshalToMap(it.Val, f)
                        if err != nil {
-                               return err
+                               return herrors.ToFileErrorWithOffset(string(f), err, iter.LineNumber()-1)
                        }
                        if err := p.updateMetaData(m); err != nil {
                                return err
@@ -125,7 +127,7 @@ Loop:
                        }
 
                        if err != nil {
-                               return err
+                               return fail(errors.Wrap(err, "failed to extract shortcode"), it)
                        }
 
                        if currShortcode.params == nil {
@@ -139,10 +141,10 @@ Loop:
                case it.IsEOF():
                        break Loop
                case it.IsError():
-                       err := fmt.Errorf("%s:shortcode:%d: %s",
-                               p.pathOrTitle(), iter.LineNumber(), it)
+                       err := fail(errors.WithStack(errors.New(it.ValStr())), it)
                        currShortcode.err = err
                        return err
+
                default:
                        result.Write(it.Val)
                }
@@ -180,3 +182,16 @@ func (p *Page) parse(reader io.Reader) error {
 
        return nil
 }
+
+func parseError(err error, input []byte, pos int) error {
+       if herrors.UnwrapFileError(err) != nil {
+               // Use the most specific location.
+               return err
+       }
+       lf := []byte("\n")
+       input = input[:pos]
+       lineNumber := bytes.Count(input, lf) + 1
+       endOfLastLine := bytes.LastIndex(input, lf)
+       return herrors.NewFileError("md", lineNumber, pos-endOfLastLine, err)
+
+}
diff --git a/hugolib/page_errors.go b/hugolib/page_errors.go
new file mode 100644 (file)
index 0000000..42e2a88
--- /dev/null
@@ -0,0 +1,47 @@
+// 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.
+// 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 hugolib
+
+import (
+       "fmt"
+
+       "github.com/gohugoio/hugo/common/herrors"
+       errors "github.com/pkg/errors"
+)
+
+func (p *Page) errorf(err error, format string, a ...interface{}) error {
+       if herrors.UnwrapErrorWithFileContext(err) != nil {
+               // More isn't always better.
+               return err
+       }
+       args := append([]interface{}{p.Lang(), p.pathOrTitle()}, a...)
+       format = "[%s] page %q: " + format
+       if err == nil {
+               errors.Errorf(format, args...)
+               return fmt.Errorf(format, args...)
+       }
+       return errors.Wrapf(err, format, args...)
+}
+
+func (p *Page) errWithFileContext(err error) error {
+
+       err, _ = herrors.WithFileContextForFile(
+               err,
+               p.Filename(),
+               p.Filename(),
+               p.s.SourceSpec.Fs.Source,
+               herrors.SimpleLineMatcher)
+
+       return err
+}
index 7497302364748cbc004db0fc0ca69eb7c674ad21..024a919ed45970c0398ba98fba684900c135ba67 100644 (file)
@@ -18,7 +18,9 @@ import (
        "errors"
        "fmt"
        "html/template"
+
        "reflect"
+
        "regexp"
        "sort"
 
@@ -139,6 +141,7 @@ type shortcode struct {
        ordinal  int
        err      error
        doMarkup bool
+       pos      int // the position in bytes in the source file
 }
 
 func (sc shortcode) String() string {
@@ -458,7 +461,13 @@ func (s *shortcodeHandler) executeShortcodesForDelta(p *PageWithoutContent) erro
                render := s.contentShortcodesDelta.getShortcodeRenderer(k)
                renderedShortcode, err := render()
                if err != nil {
-                       return _errors.Wrapf(err, "Failed to execute shortcode in page %q:", p.Path())
+                       sc := s.shortcodes.getShortcode(k.(scKey).ShortcodePlaceholder)
+                       if sc != nil {
+                               err = p.errWithFileContext(parseError(_errors.Wrapf(err, "failed to render shortcode %q", sc.name), p.source.parsed.Input(), sc.pos))
+                       }
+
+                       p.s.SendError(err)
+                       continue
                }
 
                s.renderedShortcodes[k.(scKey).ShortcodePlaceholder] = renderedShortcode
@@ -495,15 +504,8 @@ func (s *shortcodeHandler) extractShortcode(ordinal int, pt *pageparser.Iterator
        var cnt = 0
        var nestedOrdinal = 0
 
-       // TODO(bep) 2errors revisit after https://github.com/gohugoio/hugo/issues/5324
-       msgf := func(i pageparser.Item, format string, args ...interface{}) string {
-               format = format + ":%d:"
-               // TODO(bep) 2errors
-               c1 := 32 // strings.Count(pt.lexer.input[:i.pos], "\n") + 1
-               c2 := bytes.Count(p.frontmatter, []byte{'\n'})
-               args = append(args, c1+c2)
-               return fmt.Sprintf(format, args...)
-
+       fail := func(err error, i pageparser.Item) error {
+               return parseError(err, pt.Input(), i.Pos)
        }
 
 Loop:
@@ -511,6 +513,7 @@ Loop:
                currItem := pt.Next()
                switch {
                case currItem.IsLeftShortcodeDelim():
+                       sc.pos = currItem.Pos
                        next := pt.Peek()
                        if next.IsShortcodeClose() {
                                continue
@@ -550,7 +553,8 @@ Loop:
                                        // return that error, more specific
                                        continue
                                }
-                               return sc, errors.New(msgf(next, "shortcode %q has no .Inner, yet a closing tag was provided", next.Val))
+
+                               return sc, fail(_errors.Errorf("shortcode %q has no .Inner, yet a closing tag was provided", next.Val), next)
                        }
                        if next.IsRightShortcodeDelim() {
                                // self-closing
@@ -568,13 +572,13 @@ Loop:
                        // if more than one. It is "all inner or no inner".
                        tmpl := getShortcodeTemplateForTemplateKey(scKey{}, sc.name, p.s.Tmpl)
                        if tmpl == nil {
-                               return sc, errors.New(msgf(currItem, "unable to locate template for shortcode %q", sc.name))
+                               return sc, fail(_errors.Errorf("template for shortcode %q not found", sc.name), currItem)
                        }
 
                        var err error
                        isInner, err = isInnerShortcode(tmpl.(tpl.TemplateExecutor))
                        if err != nil {
-                               return sc, _errors.Wrap(err, msgf(currItem, "failed to handle template for shortcode %q", sc.name))
+                               return sc, fail(_errors.Wrapf(err, "failed to handle template for shortcode %q", sc.name), currItem)
                        }
 
                case currItem.IsShortcodeParam():
index 8358cf6104a15bbdd41af47b985d5e70c2180dc4..78a0070ee912e40c544b39e792cb4a1cb144b87d 100644 (file)
@@ -30,7 +30,6 @@ import (
 
        _errors "github.com/pkg/errors"
 
-       "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/publisher"
        "github.com/gohugoio/hugo/resource"
@@ -1552,7 +1551,7 @@ func (s *Site) preparePages() error {
                }
        }
 
-       return s.pickOneAndLogTheRest(errors)
+       return s.owner.pickOneAndLogTheRest(errors)
 }
 
 func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
@@ -1561,45 +1560,11 @@ func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
                errors = append(errors, e)
        }
 
-       errs <- s.pickOneAndLogTheRest(errors)
+       errs <- s.owner.pickOneAndLogTheRest(errors)
 
        close(errs)
 }
 
-func (s *Site) pickOneAndLogTheRest(errors []error) error {
-       if len(errors) == 0 {
-               return nil
-       }
-
-       var i int
-
-       for j, err := range errors {
-               // If this is in server mode, we want to return an error to the client
-               // with a file context, if possible.
-               if herrors.UnwrapErrorWithFileContext(err) != nil {
-                       i = j
-                       break
-               }
-       }
-
-       // Log the rest, but add a threshold to avoid flooding the log.
-       const errLogThreshold = 5
-
-       for j, err := range errors {
-               if j == i {
-                       continue
-               }
-
-               if j >= errLogThreshold {
-                       break
-               }
-
-               s.Log.ERROR.Println(err)
-       }
-
-       return errors[i]
-}
-
 func (s *Site) appendThemeTemplates(in []string) []string {
        if !s.PathSpec.ThemeSet() {
                return in
index 70c9263b345625e290a7fd099df18c0d00ec2ca7..d37d83ed3514ee6197653cad3feea028d447f16c 100644 (file)
@@ -465,12 +465,16 @@ func (s *sitesBuilder) Fatalf(format string, args ...interface{}) {
 }
 
 func Fatalf(t testing.TB, format string, args ...interface{}) {
-       trace := strings.Join(assert.CallerInfo(), "\n\r\t\t\t")
+       trace := trace()
        format = format + "\n%s"
        args = append(args, trace)
        t.Fatalf(format, args...)
 }
 
+func trace() string {
+       return strings.Join(assert.CallerInfo(), "\n\r\t\t\t")
+}
+
 func (s *sitesBuilder) AssertFileContent(filename string, matches ...string) {
        content := readDestination(s.T, s.Fs, filename)
        for _, match := range matches {
index b68850b108197e295f9168b34e5117db7d9891df..e02475d420eef292cd1fe3630cda68feb798fd59 100644 (file)
@@ -408,15 +408,22 @@ func (l *pageLexer) lexFrontMatterSection(tp ItemType, delimr rune, name string,
                }
        }
 
+       // Let front matter start at line 1
+       wasEndOfLine := l.consumeCRLF()
        // We don't care about the delimiters.
        l.ignore()
 
+       var r rune
+
        for {
-               r := l.next()
-               if r == eof {
-                       return l.errorf("EOF looking for end %s front matter delimiter", name)
+               if !wasEndOfLine {
+                       r = l.next()
+                       if r == eof {
+                               return l.errorf("EOF looking for end %s front matter delimiter", name)
+                       }
                }
-               if isEndOfLine(r) {
+
+               if wasEndOfLine || isEndOfLine(r) {
                        if l.hasPrefix(delim) {
                                l.emit(tp)
                                l.pos += 3
@@ -425,6 +432,8 @@ func (l *pageLexer) lexFrontMatterSection(tp ItemType, delimr rune, name string,
                                break
                        }
                }
+
+               wasEndOfLine = false
        }
 
        return lexMainSection
index 2cd141d376a65280cd8282833e31e21a7efe1386..6e75f195adeb55cd7e571e1c0f852c82fc18fdd8 100644 (file)
@@ -66,6 +66,11 @@ func (t *Iterator) Next() Item {
        return t.current()
 }
 
+// Input returns the input source.
+func (t *Iterator) Input() []byte {
+       return t.l.Input()
+}
+
 var errIndexOutOfBounds = Item{tError, 0, []byte("no more tokens")}
 
 func (t *Iterator) current() Item {
index 1a8c2d23775e6b0ff81ce16f8451abe9470cf834..32de6dc444698864ab4b91c926c0bf0a40411640 100644 (file)
@@ -32,9 +32,9 @@ func nti(tp ItemType, val string) Item {
 
 var (
        tstJSON                = `{ "a": { "b": "\"Hugo\"}" } }`
-       tstFrontMatterTOML     = nti(TypeFrontMatterTOML, "\nfoo = \"bar\"\n")
-       tstFrontMatterYAML     = nti(TypeFrontMatterYAML, "\nfoo: \"bar\"\n")
-       tstFrontMatterYAMLCRLF = nti(TypeFrontMatterYAML, "\r\nfoo: \"bar\"\r\n")
+       tstFrontMatterTOML     = nti(TypeFrontMatterTOML, "foo = \"bar\"\n")
+       tstFrontMatterYAML     = nti(TypeFrontMatterYAML, "foo: \"bar\"\n")
+       tstFrontMatterYAMLCRLF = nti(TypeFrontMatterYAML, "foo: \"bar\"\r\n")
        tstFrontMatterJSON     = nti(TypeFrontMatterJSON, tstJSON+"\r\n")
        tstSomeText            = nti(tText, "\nSome text.\n")
        tstSummaryDivider      = nti(TypeLeadSummaryDivider, "<!--more-->")
@@ -58,7 +58,7 @@ var frontMatterTests = []lexerTest{
        {"HTML Document 2", `<html><h1>Hugo Rocks</h1></html>`, []Item{nti(TypeHTMLDocument, "<html><h1>Hugo Rocks</h1></html>"), tstEOF}},
        {"No front matter", "\nSome text.\n", []Item{tstSomeText, tstEOF}},
        {"YAML front matter", "---\nfoo: \"bar\"\n---\n\nSome text.\n", []Item{tstFrontMatterYAML, tstSomeText, tstEOF}},
-       {"YAML empty front matter", "---\n---\n\nSome text.\n", []Item{nti(TypeFrontMatterYAML, "\n"), tstSomeText, tstEOF}},
+       {"YAML empty front matter", "---\n---\n\nSome text.\n", []Item{nti(TypeFrontMatterYAML, ""), tstSomeText, tstEOF}},
        {"YAML commented out front matter", "<!--\n---\nfoo: \"bar\"\n---\n-->\nSome text.\n", []Item{nti(TypeHTMLComment, "<!--\n---\nfoo: \"bar\"\n---\n-->"), tstSomeText, tstEOF}},
        // Note that we keep all bytes as they are, but we need to handle CRLF
        {"YAML front matter CRLF", "---\r\nfoo: \"bar\"\r\n---\n\nSome text.\n", []Item{tstFrontMatterYAMLCRLF, tstSomeText, tstEOF}},
index 3f87eda31c2c194c15e4edcf9d314bf24ee30eb2..03fd27606011a6864fa2f2722eab4a8e62b83ed4 100644 (file)
@@ -59,7 +59,7 @@ func (ns *Namespace) GetCSV(sep string, urlParts ...string) (d [][]string, err e
                var req *http.Request
                req, err = http.NewRequest("GET", url, nil)
                if err != nil {
-                       return nil, _errors.Wrapf(err, "Failed to create request for getCSV for resource %s:", url)
+                       return nil, _errors.Wrapf(err, "failed to create request for getCSV for resource %s", url)
                }
 
                req.Header.Add("Accept", "text/csv")
@@ -68,28 +68,22 @@ func (ns *Namespace) GetCSV(sep string, urlParts ...string) (d [][]string, err e
                var c []byte
                c, err = ns.getResource(req)
                if err != nil {
-                       ns.deps.Log.ERROR.Printf("Failed to read CSV resource %q: %s", url, err)
-                       return nil, nil
+                       return nil, _errors.Wrapf(err, "failed to read CSV resource %q", url)
                }
 
                if !bytes.Contains(c, []byte(sep)) {
-                       ns.deps.Log.ERROR.Printf("Cannot find separator %s in CSV for %s", sep, url)
-                       return nil, nil
+                       return nil, _errors.Errorf("cannot find separator %s in CSV for %s", sep, url)
                }
 
                if d, err = parseCSV(c, sep); err != nil {
-                       ns.deps.Log.WARN.Printf("Failed to parse CSV file %s: %s", url, err)
+                       err = _errors.Wrapf(err, "failed to parse CSV file %s", url)
+
                        clearCacheSleep(i, url)
                        continue
                }
                break
        }
 
-       if err != nil {
-               ns.deps.Log.ERROR.Printf("Failed to read CSV resource %q: %s", url, err)
-               return nil, nil
-       }
-
        return
 }
 
@@ -103,7 +97,7 @@ func (ns *Namespace) GetJSON(urlParts ...string) (v interface{}, err error) {
                var req *http.Request
                req, err = http.NewRequest("GET", url, nil)
                if err != nil {
-                       return nil, _errors.Wrapf(err, "Failed to create request for getJSON resource %s:", url)
+                       return nil, _errors.Wrapf(err, "Failed to create request for getJSON resource %s", url)
                }
 
                req.Header.Add("Accept", "application/json")
@@ -111,10 +105,8 @@ func (ns *Namespace) GetJSON(urlParts ...string) (v interface{}, err error) {
                var c []byte
                c, err = ns.getResource(req)
                if err != nil {
-                       ns.deps.Log.ERROR.Printf("Failed to get JSON resource %s: %s", url, err)
-                       return nil, nil
+                       return nil, _errors.Wrapf(err, "failed to get getJSON resource %q", url)
                }
-
                err = json.Unmarshal(c, &v)
                if err != nil {
                        ns.deps.Log.WARN.Printf("Cannot read JSON from resource %s: %s", url, err)
@@ -127,7 +119,7 @@ func (ns *Namespace) GetJSON(urlParts ...string) (v interface{}, err error) {
        }
 
        if err != nil {
-               ns.deps.Log.ERROR.Printf("Failed to get JSON resource %s: %s", url, err)
+               return nil, _errors.Wrapf(err, "failed to get getJSON resource %q", url)
                return nil, nil
        }
        return
index 9ef969244a9d4179072e65b03b930cf85ea31c90..7a0640e95254c27a67e111394c926edb60b96514 100644 (file)
@@ -21,8 +21,6 @@ import (
        "strings"
        "testing"
 
-       jww "github.com/spf13/jwalterweatherman"
-
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/require"
 )
@@ -110,13 +108,13 @@ func TestGetCSV(t *testing.T) {
                // Get on with it
                got, err := ns.GetCSV(test.sep, test.url)
 
-               require.NoError(t, err, msg)
-
                if _, ok := test.expect.(bool); ok {
-                       require.Equal(t, 1, int(ns.deps.Log.ErrorCounter.Count()))
+                       require.Error(t, err, msg)
                        require.Nil(t, got)
                        continue
                }
+
+               require.NoError(t, err, msg)
                require.Equal(t, 0, int(ns.deps.Log.ErrorCounter.Count()))
                require.NotNil(t, got, msg)
 
@@ -140,12 +138,12 @@ func TestGetJSON(t *testing.T) {
                {
                        `http://malformed/`,
                        `{gomeetup:["Sydney","San Francisco","Stockholm"]}`,
-                       jww.LevelError,
+                       false,
                },
                {
                        `http://nofound/404`,
                        ``,
-                       jww.LevelError,
+                       false,
                },
                // Locals
                {
@@ -156,7 +154,7 @@ func TestGetJSON(t *testing.T) {
                {
                        "fail/no-file",
                        "",
-                       jww.LevelError,
+                       false,
                },
        } {
 
@@ -198,13 +196,6 @@ func TestGetJSON(t *testing.T) {
                        continue
                }
 
-               if errLevel, ok := test.expect.(jww.Threshold); ok && errLevel >= jww.LevelError {
-                       logCount := ns.deps.Log.ErrorCounter.Count()
-                       require.True(t, logCount >= 1, fmt.Sprintf("got log count %d", logCount))
-                       continue
-               }
-               require.NoError(t, err, msg)
-
                require.Equal(t, 0, int(ns.deps.Log.ErrorCounter.Count()), msg)
                require.NotNil(t, got, msg)
 
index 68673a1fc91ca04ac8f4894bea7467cd52fd2681..09710206e1fb88ca8163254d34ed1396bde626ef 100644 (file)
@@ -145,15 +145,20 @@ func (t *TemplateAdapter) extractIdentifiers(line string) []string {
 }
 
 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 err
+               return inerr
+
        }
        defer f.Close()
 
        master, hasMaster := t.NameBaseTemplateName[name]
 
-       ferr1 := errors.Wrapf(inerr, "execute of template %q failed", realFilename)
+       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.
@@ -174,9 +179,8 @@ func (t *TemplateAdapter) addFileContext(name string, inerr error) error {
                }
                return false
        }
-
        // TODO(bep) 2errors text vs HTML
-       fe, ok := herrors.WithFileContext(ferr1, f, "go-html-template", lineMatcher)
+       fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
        if ok || !hasMaster {
                return fe
        }
@@ -188,12 +192,11 @@ func (t *TemplateAdapter) addFileContext(name string, inerr error) error {
        }
        defer f.Close()
 
-       ferr2 := errors.Wrapf(inerr, "execute of template %q failed", realFilename)
-       fe, ok = herrors.WithFileContext(ferr2, f, "go-html-template", lineMatcher)
+       fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
 
        if !ok {
                // Return the most specific.
-               return ferr1
+               return ferr
 
        }
        return fe
@@ -206,7 +209,7 @@ func (t *TemplateAdapter) fileAndFilename(name string) (afero.File, string, erro
 
        fi, err := fs.Stat(filename)
        if err != nil {
-               return nil, "", errors.Wrapf(err, "failed to Stat %q", filename)
+               return nil, "", err
        }
        f, err := fs.Open(filename)
        if err != nil {
index a422d77f1dd236a142cb3b1453750cb955f04051..63695c5f66fc3d55975f412625940592dc938a77 100644 (file)
@@ -33,13 +33,13 @@ type templateInfo struct {
 }
 
 func (info templateInfo) errWithFileContext(what string, err error) error {
-       err = errors.Wrapf(err, "file %q: %s:", info.realFilename, what)
+       err = errors.Wrapf(err, what)
 
        err, _ = herrors.WithFileContextForFile(
                err,
+               info.realFilename,
                info.filename,
                info.fs,
-               "go-html-template",
                herrors.SimpleLineMatcher)
 
        return err