commit f2946da9e806c2bafbdd26707fe339db79bd980b parent 6eea32bd6bc8e7a7dd07a8cb6a8343ae2c74aba0 Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> Date: Mon, 2 May 2022 16:07:52 +0200 Improve error messages, esp. when the server is running * Add file context to minifier errors when publishing * Misc fixes (see issues) * Allow custom server error template in layouts/server/error.html To get to this, this commit also cleans up and simplifies the code surrounding errors and files. This also removes the usage of `github.com/pkg/errors`, mostly because of https://github.com/pkg/errors/issues/223 -- but also because most of this is now built-in to Go. Fixes #9852 Fixes #9857 Fixes #9863 Diffstat:
109 files changed, 858 insertions(+), 777 deletions(-) diff --git a/cache/filecache/filecache_config.go b/cache/filecache/filecache_config.go @@ -14,6 +14,7 @@ package filecache import ( + "fmt" "path" "path/filepath" "strings" @@ -25,8 +26,9 @@ import ( "github.com/gohugoio/hugo/helpers" + "errors" + "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" "github.com/spf13/afero" ) @@ -153,7 +155,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) { } if err := decoder.Decode(v); err != nil { - return nil, errors.Wrap(err, "failed to decode filecache config") + return nil, fmt.Errorf("failed to decode filecache config: %w", err) } if cc.Dir == "" { @@ -162,7 +164,7 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) { name := strings.ToLower(k) if !valid[name] { - return nil, errors.Errorf("%q is not a valid cache name", name) + return nil, fmt.Errorf("%q is not a valid cache name", name) } c[name] = cc @@ -197,12 +199,12 @@ func DecodeConfig(fs afero.Fs, cfg config.Provider) (Configs, error) { if !v.isResourceDir { if isOsFs && !filepath.IsAbs(v.Dir) { - return c, errors.Errorf("%q must resolve to an absolute directory", v.Dir) + return c, fmt.Errorf("%q must resolve to an absolute directory", v.Dir) } // Avoid cache in root, e.g. / (Unix) or c:\ (Windows) if len(strings.TrimPrefix(v.Dir, filepath.VolumeName(v.Dir))) == 1 { - return c, errors.Errorf("%q is a root folder and not allowed as cache dir", v.Dir) + return c, fmt.Errorf("%q is a root folder and not allowed as cache dir", v.Dir) } } @@ -242,5 +244,5 @@ func resolveDirPlaceholder(fs afero.Fs, cfg config.Provider, placeholder string) return filepath.Base(workingDir), false, nil } - return "", false, errors.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder) + return "", false, fmt.Errorf("%q is not a valid placeholder (valid values are :cacheDir or :resourceDir)", placeholder) } diff --git a/cache/filecache/filecache_pruner.go b/cache/filecache/filecache_pruner.go @@ -14,12 +14,12 @@ package filecache import ( + "fmt" "io" "os" "github.com/gohugoio/hugo/hugofs" - "github.com/pkg/errors" "github.com/spf13/afero" ) @@ -39,7 +39,7 @@ func (c Caches) Prune() (int, error) { if os.IsNotExist(err) { continue } - return counter, errors.Wrapf(err, "failed to prune cache %q", k) + return counter, fmt.Errorf("failed to prune cache %q: %w", k, err) } } diff --git a/commands/commandeer.go b/commands/commandeer.go @@ -14,7 +14,6 @@ package commands import ( - "bytes" "errors" "io/ioutil" "net" @@ -141,19 +140,11 @@ func (c *commandeer) getErrorWithContext() any { m := make(map[string]any) - m["Error"] = errors.New(removeErrorPrefixFromLog(c.logger.Errors())) + //xwm["Error"] = errors.New(cleanErrorLog(removeErrorPrefixFromLog(c.logger.Errors()))) + m["Error"] = errors.New(cleanErrorLog(removeErrorPrefixFromLog(c.logger.Errors()))) m["Version"] = hugo.BuildVersionString() - - fe := herrors.UnwrapErrorWithFileContext(c.buildErr) - if fe != nil { - m["File"] = fe - } - - if c.h.verbose { - var b bytes.Buffer - herrors.FprintStackTraceFromErr(&b, c.buildErr) - m["StackTrace"] = b.String() - } + ferrors := herrors.UnwrapFileErrorsWithErrorContext(c.buildErr) + m["Files"] = ferrors return m } diff --git a/commands/convert.go b/commands/convert.go @@ -31,8 +31,6 @@ import ( "github.com/gohugoio/hugo/parser" "github.com/gohugoio/hugo/parser/metadecoders" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/hugolib" "github.com/spf13/cobra" @@ -193,7 +191,7 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target fs := hugofs.Os if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil { - return errors.Wrapf(err, "Failed to save file %q:", newFilename) + return fmt.Errorf("Failed to save file %q:: %w", newFilename, err) } return nil diff --git a/commands/hugo.go b/commands/hugo.go @@ -39,9 +39,6 @@ import ( "github.com/gohugoio/hugo/resources/page" - "github.com/pkg/errors" - - "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/terminal" @@ -300,14 +297,14 @@ func (c *commandeer) fullBuild(noBuildLock bool) error { copyStaticFunc := func() error { cnt, err := c.copyStatic() if err != nil { - return errors.Wrap(err, "Error copying static files") + return fmt.Errorf("Error copying static files: %w", err) } langCount = cnt return nil } buildSitesFunc := func() error { if err := c.buildSites(noBuildLock); err != nil { - return errors.Wrap(err, "Error building site") + return fmt.Errorf("Error building site: %w", err) } return nil } @@ -354,10 +351,10 @@ func (c *commandeer) initCPUProfile() (func(), error) { f, err := os.Create(c.h.cpuprofile) if err != nil { - return nil, errors.Wrap(err, "failed to create CPU profile") + return nil, fmt.Errorf("failed to create CPU profile: %w", err) } if err := pprof.StartCPUProfile(f); err != nil { - return nil, errors.Wrap(err, "failed to start CPU profile") + return nil, fmt.Errorf("failed to start CPU profile: %w", err) } return func() { pprof.StopCPUProfile() @@ -388,11 +385,11 @@ func (c *commandeer) initTraceProfile() (func(), error) { f, err := os.Create(c.h.traceprofile) if err != nil { - return nil, errors.Wrap(err, "failed to create trace file") + return nil, fmt.Errorf("failed to create trace file: %w", err) } if err := trace.Start(f); err != nil { - return nil, errors.Wrap(err, "failed to start trace") + return nil, fmt.Errorf("failed to start trace: %w", err) } return func() { @@ -735,9 +732,7 @@ func (c *commandeer) handleBuildErr(err error, msg string) { c.logger.Errorln(msg + ":\n") c.logger.Errorln(helpers.FirstUpper(err.Error())) - if !c.h.quiet && c.h.verbose { - herrors.PrintStackTraceFromErr(err) - } + } func (c *commandeer) rebuildSites(events []fsnotify.Event) error { diff --git a/commands/new_site.go b/commands/new_site.go @@ -16,14 +16,13 @@ package commands import ( "bytes" "errors" + "fmt" "path/filepath" "strings" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/parser/metadecoders" - _errors "github.com/pkg/errors" - "github.com/gohugoio/hugo/create" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" @@ -94,7 +93,7 @@ func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error for _, dir := range dirs { if err := fs.Source.MkdirAll(dir, 0777); err != nil { - return _errors.Wrap(err, "Failed to create dir") + return fmt.Errorf("Failed to create dir: %w", err) } } diff --git a/commands/server.go b/commands/server.go @@ -36,8 +36,6 @@ import ( "github.com/gohugoio/hugo/common/paths" "golang.org/x/sync/errgroup" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/livereload" "github.com/gohugoio/hugo/config" @@ -366,7 +364,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string // We're only interested in the path u, err := url.Parse(baseURL) if err != nil { - return nil, nil, "", "", errors.Wrap(err, "Invalid baseURL") + return nil, nil, "", "", fmt.Errorf("Invalid baseURL: %w", err) } decorate := func(h http.Handler) http.Handler { @@ -480,6 +478,21 @@ var logErrorRe = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{ func removeErrorPrefixFromLog(content string) string { return logErrorRe.ReplaceAllLiteralString(content, "") } +func cleanErrorLog(content string) string { + content = strings.ReplaceAll(content, "Rebuild failed:\n", "") + content = strings.ReplaceAll(content, "\n", "") + seen := make(map[string]bool) + parts := strings.Split(content, ": ") + keep := make([]string, 0, len(parts)) + for _, part := range parts { + if seen[part] { + continue + } + seen[part] = true + keep = append(keep, part) + } + return strings.Join(keep, ": ") +} func (c *commandeer) serve(s *serverCmd) error { isMultiHost := c.hugo().IsMultihost() @@ -500,17 +513,16 @@ func (c *commandeer) serve(s *serverCmd) error { roots = []string{""} } - templ, err := c.hugo().TextTmpl().Parse("__default_server_error", buildErrorTemplate) - if err != nil { - return err - } - srv := &fileServer{ baseURLs: baseURLs, roots: roots, c: c, s: s, errorTemplate: func(ctx any) (io.Reader, error) { + templ, found := c.hugo().Tmpl().Lookup("server/error.html") + if !found { + panic("template server/error.html not found") + } b := &bytes.Buffer{} err := c.hugo().Tmpl().Execute(templ, b, ctx) return b, err @@ -627,7 +639,7 @@ func (sc *serverCmd) fixURL(cfg config.Provider, s string, port int) (string, er if strings.Contains(u.Host, ":") { u.Host, _, err = net.SplitHostPort(u.Host) if err != nil { - return "", errors.Wrap(err, "Failed to split baseURL hostpost") + return "", fmt.Errorf("Failed to split baseURL hostpost: %w", err) } } u.Host += fmt.Sprintf(":%d", port) diff --git a/commands/server_errors.go b/commands/server_errors.go @@ -22,67 +22,6 @@ import ( "github.com/gohugoio/hugo/transform/livereloadinject" ) -var buildErrorTemplate = `<!doctype html> -<html class="no-js" lang=""> - <head> - <meta charset="utf-8"> - <title>Hugo Server: Error</title> - <style type="text/css"> - body { - font-family: "Muli",avenir, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 16px; - background-color: #2f1e2e; - } - main { - margin: auto; - width: 95%; - padding: 1rem; - } - .version { - color: #ccc; - padding: 1rem 0; - } - .stack { - margin-top: 4rem; - } - pre { - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; - } - .highlight { - overflow-x: auto; - margin-bottom: 1rem; - } - a { - color: #0594cb; - text-decoration: none; - } - a:hover { - color: #ccc; - } - </style> - </head> - <body> - <main> - {{ highlight .Error "apl" "linenos=false,noclasses=true,style=paraiso-dark" }} - {{ with .File }} - {{ $params := printf "noclasses=true,style=paraiso-dark,linenos=table,hl_lines=%d,linenostart=%d" (add .LinesPos 1) (sub .Position.LineNumber .LinesPos) }} - {{ $lexer := .ChromaLexer | default "go-html-template" }} - {{ highlight (delimit .Lines "\n") $lexer $params }} - {{ end }} - {{ with .StackTrace }} - {{ highlight . "apl" "noclasses=true,style=paraiso-dark" }} - {{ end }} - <p class="version">{{ .Version }}</p> - <a href="">Reload Page</a> - </main> -</body> -</html> -` - func injectLiveReloadScript(src io.Reader, baseURL url.URL) string { var b bytes.Buffer chain := transform.Chain{livereloadinject.New(baseURL)} diff --git a/common/herrors/error_locator.go b/common/herrors/error_locator.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Hugo Authors. All rights reserved. +// Copyright 2022 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. @@ -21,8 +21,6 @@ import ( "strings" "github.com/gohugoio/hugo/common/text" - - "github.com/spf13/afero" ) // LineMatcher contains the elements used to match an error to a line @@ -43,8 +41,6 @@ var SimpleLineMatcher = func(m LineMatcher) bool { return m.Position.LineNumber == m.LineNumber } -var _ text.Positioner = ErrorContext{} - // ErrorContext contains contextual information about an error. This will // typically be the lines surrounding some problem in a file. type ErrorContext struct { @@ -56,125 +52,11 @@ type ErrorContext struct { // The position of the error in the Lines above. 0 based. LinesPos int - position text.Position - // The lexer to use for syntax highlighting. // https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages ChromaLexer string } -// Position returns the text position of this error. -func (e ErrorContext) Position() text.Position { - return e.position -} - -var _ causer = (*ErrorWithFileContext)(nil) - -// ErrorWithFileContext is an error with some additional file context related -// to that error. -type ErrorWithFileContext struct { - cause error - ErrorContext -} - -func (e *ErrorWithFileContext) Error() string { - pos := e.Position() - if pos.IsValid() { - return pos.String() + ": " + e.cause.Error() - } - return e.cause.Error() -} - -func (e *ErrorWithFileContext) Cause() error { - return e.cause -} - -// 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, realFilename, filename string, fs afero.Fs, matcher LineMatcherFn) (error, bool) { - f, err := fs.Open(filename) - if err != nil { - return e, false - } - defer f.Close() - return WithFileContext(e, realFilename, f, matcher) -} - -// WithFileContextForFileDefault tries to add file context using the default line matcher. -func WithFileContextForFileDefault(err error, filename string, fs afero.Fs) error { - err, _ = WithFileContextForFile( - err, - filename, - filename, - fs, - SimpleLineMatcher) - return err -} - -// 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, realFilename string, r io.Reader, matcher LineMatcherFn) (error, bool) { - if e == nil { - panic("error missing") - } - le := UnwrapFileError(e) - - if le == nil { - var ok bool - if le, ok = ToFileError("", e).(FileError); !ok { - return e, false - } - } - - var errCtx ErrorContext - - posle := le.Position() - - if posle.Offset != -1 { - errCtx = locateError(r, le, func(m LineMatcher) bool { - if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) { - lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber - m.Position = text.Position{LineNumber: lno} - } - return matcher(m) - }) - } else { - errCtx = locateError(r, le, matcher) - } - - pos := &errCtx.position - - if pos.LineNumber == -1 { - return e, false - } - - pos.Filename = realFilename - - if le.Type() != "" { - errCtx.ChromaLexer = chromaLexerFromType(le.Type()) - } else { - errCtx.ChromaLexer = chromaLexerFromFilename(realFilename) - } - - return &ErrorWithFileContext{cause: e, ErrorContext: errCtx}, true -} - -// UnwrapErrorWithFileContext tries to unwrap an ErrorWithFileContext from err. -// It returns nil if this is not possible. -func UnwrapErrorWithFileContext(err error) *ErrorWithFileContext { - for err != nil { - switch v := err.(type) { - case *ErrorWithFileContext: - return v - case causer: - err = v.Cause() - default: - return nil - } - } - return nil -} - func chromaLexerFromType(fileType string) string { switch fileType { case "html", "htm": @@ -196,23 +78,23 @@ func chromaLexerFromFilename(filename string) string { return chromaLexerFromType(ext) } -func locateErrorInString(src string, matcher LineMatcherFn) ErrorContext { +func locateErrorInString(src string, matcher LineMatcherFn) (*ErrorContext, text.Position) { return locateError(strings.NewReader(src), &fileError{}, matcher) } -func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext { +func locateError(r io.Reader, le FileError, matches LineMatcherFn) (*ErrorContext, text.Position) { if le == nil { panic("must provide an error") } - errCtx := ErrorContext{position: text.Position{LineNumber: -1, ColumnNumber: 1, Offset: -1}, LinesPos: -1} + errCtx := &ErrorContext{LinesPos: -1} + pos := text.Position{LineNumber: -1, ColumnNumber: 1, Offset: -1} b, err := ioutil.ReadAll(r) if err != nil { - return errCtx + return errCtx, pos } - pos := &errCtx.position lepos := le.Position() lines := strings.Split(string(b), "\n") @@ -262,5 +144,5 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext } - return errCtx + return errCtx, pos } diff --git a/common/herrors/error_locator_test.go b/common/herrors/error_locator_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Hugo Authors. All rights reserved. +// Copyright 2022 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. @@ -38,44 +38,48 @@ LINE 7 LINE 8 ` - location := locateErrorInString(lines, lineMatcher) + location, pos := locateErrorInString(lines, lineMatcher) c.Assert(location.Lines, qt.DeepEquals, []string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"}) - pos := location.Position() c.Assert(pos.LineNumber, qt.Equals, 5) c.Assert(location.LinesPos, qt.Equals, 2) - c.Assert(locateErrorInString(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"}) + locate := func(s string, m LineMatcherFn) *ErrorContext { + ctx, _ := locateErrorInString(s, m) + return ctx + } + + c.Assert(locate(`This is THEONE`, lineMatcher).Lines, qt.DeepEquals, []string{"This is THEONE"}) - location = locateErrorInString(`L1 + location, pos = locateErrorInString(`L1 This is THEONE L2 `, lineMatcher) - c.Assert(location.Position().LineNumber, qt.Equals, 2) + c.Assert(pos.LineNumber, qt.Equals, 2) c.Assert(location.LinesPos, qt.Equals, 1) c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This is THEONE", "L2", ""}) - location = locateErrorInString(`This is THEONE + location = locate(`This is THEONE L2 `, lineMatcher) c.Assert(location.LinesPos, qt.Equals, 0) c.Assert(location.Lines, qt.DeepEquals, []string{"This is THEONE", "L2", ""}) - location = locateErrorInString(`L1 + location = locate(`L1 This THEONE `, lineMatcher) c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "This THEONE", ""}) c.Assert(location.LinesPos, qt.Equals, 1) - location = locateErrorInString(`L1 + location = locate(`L1 L2 This THEONE `, lineMatcher) c.Assert(location.Lines, qt.DeepEquals, []string{"L1", "L2", "This THEONE", ""}) c.Assert(location.LinesPos, qt.Equals, 2) - location = locateErrorInString("NO MATCH", lineMatcher) - c.Assert(location.Position().LineNumber, qt.Equals, -1) + location, pos = locateErrorInString("NO MATCH", lineMatcher) + c.Assert(pos.LineNumber, qt.Equals, -1) c.Assert(location.LinesPos, qt.Equals, -1) c.Assert(len(location.Lines), qt.Equals, 0) @@ -83,7 +87,7 @@ This THEONE return m.LineNumber == 6 } - location = locateErrorInString(`A + location, pos = locateErrorInString(`A B C D @@ -95,7 +99,7 @@ I J`, lineMatcher) c.Assert(location.Lines, qt.DeepEquals, []string{"D", "E", "F", "G", "H"}) - c.Assert(location.Position().LineNumber, qt.Equals, 6) + c.Assert(pos.LineNumber, qt.Equals, 6) c.Assert(location.LinesPos, qt.Equals, 2) // Test match EOF @@ -103,26 +107,26 @@ J`, lineMatcher) return m.LineNumber == 4 } - location = locateErrorInString(`A + location, pos = locateErrorInString(`A B C `, lineMatcher) c.Assert(location.Lines, qt.DeepEquals, []string{"B", "C", ""}) - c.Assert(location.Position().LineNumber, qt.Equals, 4) + c.Assert(pos.LineNumber, qt.Equals, 4) c.Assert(location.LinesPos, qt.Equals, 2) offsetMatcher := func(m LineMatcher) bool { return m.Offset == 1 } - location = locateErrorInString(`A + location, pos = locateErrorInString(`A B C D E`, offsetMatcher) c.Assert(location.Lines, qt.DeepEquals, []string{"A", "B", "C", "D"}) - c.Assert(location.Position().LineNumber, qt.Equals, 2) + c.Assert(pos.LineNumber, qt.Equals, 2) c.Assert(location.LinesPos, qt.Equals, 1) } diff --git a/common/herrors/errors.go b/common/herrors/errors.go @@ -19,37 +19,11 @@ import ( "errors" "fmt" "io" - "os" "runtime" "runtime/debug" "strconv" - - _errors "github.com/pkg/errors" ) -// As defined in https://godoc.org/github.com/pkg/errors -type causer interface { - Cause() error -} - -type stackTracer interface { - StackTrace() _errors.StackTrace -} - -// PrintStackTraceFromErr prints the error's stack trace to stdoud. -func PrintStackTraceFromErr(err error) { - FprintStackTraceFromErr(os.Stdout, err) -} - -// FprintStackTraceFromErr prints the error's stack trace to w. -func FprintStackTraceFromErr(w io.Writer, err error) { - if err, ok := err.(stackTracer); ok { - for _, f := range err.StackTrace() { - fmt.Fprintf(w, "%+s:%d\n", f, f) - } - } -} - // PrintStackTrace prints the current stacktrace to w. func PrintStackTrace(w io.Writer) { buf := make([]byte, 1<<16) diff --git a/common/herrors/file_error.go b/common/herrors/file_error.go @@ -1,11 +1,11 @@ -// Copyright 2018 The Hugo Authors. All rights reserved. +// Copyright 2022 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 +// Unless required by applicable lfmtaw 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 @@ -15,59 +15,217 @@ package herrors import ( "encoding/json" + "fmt" + "io" + "path/filepath" + "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/common/text" + "github.com/pelletier/go-toml/v2" + "github.com/spf13/afero" + "github.com/tdewolff/parse/v2" - "github.com/pkg/errors" + "errors" ) -var _ causer = (*fileError)(nil) - // FileError represents an error when handling a file: Parsing a config file, // execute a template etc. type FileError interface { error + // ErroContext holds some context information about the error. + ErrorContext() *ErrorContext + text.Positioner - // A string identifying the type of file, e.g. JSON, TOML, markdown etc. - Type() string + // UpdatePosition updates the position of the error. + UpdatePosition(pos text.Position) FileError + + // UpdateContent updates the error with a new ErrorContext from the content of the file. + UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError +} + +// Unwrapper can unwrap errors created with fmt.Errorf. +type Unwrapper interface { + Unwrap() error } -var _ FileError = (*fileError)(nil) +var ( + _ FileError = (*fileError)(nil) + _ Unwrapper = (*fileError)(nil) +) + +func (fe *fileError) UpdatePosition(pos text.Position) FileError { + oldFilename := fe.Position().Filename + if pos.Filename != "" && fe.fileType == "" { + _, fe.fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename)) + } + if pos.Filename == "" { + pos.Filename = oldFilename + } + fe.position = pos + return fe +} + +func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError { + if linematcher == nil { + linematcher = SimpleLineMatcher + } + + var ( + contentPos text.Position + posle = fe.position + errorContext *ErrorContext + ) + + if posle.LineNumber <= 1 && posle.Offset > 0 { + // Try to locate the line number from the content if offset is set. + errorContext, contentPos = locateError(r, fe, func(m LineMatcher) bool { + if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) { + lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber + m.Position = text.Position{LineNumber: lno} + return linematcher(m) + } + return false + }) + } else { + errorContext, contentPos = locateError(r, fe, linematcher) + } + + if errorContext.ChromaLexer == "" { + if fe.fileType != "" { + errorContext.ChromaLexer = chromaLexerFromType(fe.fileType) + } else { + errorContext.ChromaLexer = chromaLexerFromFilename(fe.Position().Filename) + } + } + + fe.errorContext = errorContext + + if contentPos.LineNumber > 0 { + fe.position.LineNumber = contentPos.LineNumber + } + + return fe + +} type fileError struct { - position text.Position + position text.Position + errorContext *ErrorContext fileType string cause error } +type fileErrorWithErrorContext struct { + *fileError +} + +func (e *fileError) ErrorContext() *ErrorContext { + return e.errorContext +} + // Position returns the text position of this error. func (e fileError) Position() text.Position { return e.position } -func (e *fileError) Type() string { - return e.fileType +func (e *fileError) Error() string { + return fmt.Sprintf("%s: %s", e.position, e.cause) } -func (e *fileError) Error() string { - if e.cause == nil { - return "" +func (e *fileError) Unwrap() error { + return e.cause +} + +// NewFileError creates a new FileError that wraps err. +// The value for name should identify the file, the best +// being the full filename to the file on disk. +func NewFileError(name string, err error) FileError { + if err == nil { + panic("err is nil") + } + + // Filetype is used to determine the Chroma lexer to use. + fileType, pos := extractFileTypePos(err) + pos.Filename = name + if fileType == "" { + _, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(name)) } - return e.cause.Error() + + if pos.LineNumber < 0 { + panic(fmt.Sprintf("invalid line number: %d", pos.LineNumber)) + } + + return &fileError{cause: err, fileType: fileType, position: pos} + } -func (f *fileError) Cause() error { - return f.cause +// NewFileErrorFromFile is a convenience method to create a new FileError from a file. +func NewFileErrorFromFile(err error, filename, realFilename string, fs afero.Fs, linematcher LineMatcherFn) FileError { + if err == nil { + panic("err is nil") + } + if linematcher == nil { + linematcher = SimpleLineMatcher + } + f, err2 := fs.Open(filename) + if err2 != nil { + return NewFileError(realFilename, err) + } + defer f.Close() + return NewFileError(realFilename, err).UpdateContent(f, linematcher) } -// NewFileError creates a new FileError. -func NewFileError(fileType string, offset, lineNumber, columnNumber int, err error) FileError { - pos := text.Position{Offset: offset, LineNumber: lineNumber, ColumnNumber: columnNumber} - return &fileError{cause: err, fileType: fileType, position: pos} +// Cause returns the underlying error or itself if it does not implement Unwrap. +func Cause(err error) error { + if u := errors.Unwrap(err); u != nil { + return u + } + return err +} + +func extractFileTypePos(err error) (string, text.Position) { + err = Cause(err) + var fileType string + + // Fall back to line/col 1:1 if we cannot find any better information. + pos := text.Position{ + Offset: -1, + LineNumber: 1, + ColumnNumber: 1, + } + + // JSON errors. + offset, typ := extractOffsetAndType(err) + if fileType == "" { + fileType = typ + } + + if offset >= 0 { + pos.Offset = offset + } + + // The error type from the minifier contains line number and column number. + if line, col := exctractLineNumberAndColumnNumber(err); line >= 0 { + pos.LineNumber = line + pos.ColumnNumber = col + return fileType, pos + } + + // Look in the error message for the line number. + for _, handle := range lineNumberExtractors { + lno, col := handle(err) + if lno > 0 { + pos.ColumnNumber = col + pos.LineNumber = lno + break + } + } + + return fileType, pos } // UnwrapFileError tries to unwrap a FileError from err. @@ -77,49 +235,26 @@ func UnwrapFileError(err error) FileError { switch v := err.(type) { case FileError: return v - case causer: - err = v.Cause() default: - return nil + err = errors.Unwrap(err) } } return nil } -// ToFileErrorWithOffset will return a new FileError with a line number -// with the given offset from the original. -func ToFileErrorWithOffset(fe FileError, offset int) FileError { - pos := fe.Position() - return ToFileErrorWithLineNumber(fe, pos.LineNumber+offset) -} - -// ToFileErrorWithOffset will return a new FileError with the given line number. -func ToFileErrorWithLineNumber(fe FileError, lineNumber int) FileError { - pos := fe.Position() - pos.LineNumber = lineNumber - return &fileError{cause: fe, fileType: fe.Type(), position: pos} -} - -// ToFileError will convert the given error to an error supporting -// the FileError interface. -func ToFileError(fileType string, err error) FileError { - for _, handle := range lineNumberExtractors { - lno, col := handle(err) - offset, typ := extractOffsetAndType(err) - if fileType == "" { - fileType = typ - } - - if lno > 0 || offset != -1 { - return NewFileError(fileType, offset, lno, col, err) +// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext. +func UnwrapFileErrorsWithErrorContext(err error) []FileError { + var errs []FileError + for err != nil { + if v, ok := err.(FileError); ok && v.ErrorContext() != nil { + errs = append(errs, v) } + err = errors.Unwrap(err) } - // Fall back to the pointing to line number 1. - return NewFileError(fileType, -1, 1, 1, err) + return errs } func extractOffsetAndType(e error) (int, string) { - e = errors.Cause(e) switch v := e.(type) { case *json.UnmarshalTypeError: return int(v.Offset), "json" @@ -129,3 +264,15 @@ func extractOffsetAndType(e error) (int, string) { return -1, "" } } + +func exctractLineNumberAndColumnNumber(e error) (int, int) { + switch v := e.(type) { + case *parse.Error: + return v.Line, v.Column + case *toml.DecodeError: + return v.Position() + + } + + return -1, -1 +} diff --git a/common/herrors/file_error_test.go b/common/herrors/file_error_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Hugo Authors. All rights reserved. +// Copyright 2022 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,14 +14,44 @@ package herrors import ( + "fmt" + "strings" "testing" - "github.com/pkg/errors" + "errors" + + "github.com/gohugoio/hugo/common/text" qt "github.com/frankban/quicktest" ) -func TestToLineNumberError(t *testing.T) { +func TestNewFileError(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + fe := NewFileError("foo.html", errors.New("bar")) + c.Assert(fe.Error(), qt.Equals, `"foo.html:1:1": bar`) + + lines := "" + for i := 1; i <= 100; i++ { + lines += fmt.Sprintf("line %d\n", i) + } + + fe.UpdatePosition(text.Position{LineNumber: 32, ColumnNumber: 2}) + c.Assert(fe.Error(), qt.Equals, `"foo.html:32:2": bar`) + fe.UpdatePosition(text.Position{LineNumber: 0, ColumnNumber: 0, Offset: 212}) + fe.UpdateContent(strings.NewReader(lines), SimpleLineMatcher) + c.Assert(fe.Error(), qt.Equals, `"foo.html:32:0": bar`) + errorContext := fe.ErrorContext() + c.Assert(errorContext, qt.IsNotNil) + c.Assert(errorContext.Lines, qt.DeepEquals, []string{"line 30", "line 31", "line 32", "line 33", "line 34"}) + c.Assert(errorContext.LinesPos, qt.Equals, 2) + c.Assert(errorContext.ChromaLexer, qt.Equals, "go-html-template") + +} + +func TestNewFileErrorExtractFromMessage(t *testing.T) { t.Parallel() c := qt.New(t) @@ -37,18 +67,16 @@ func TestToLineNumberError(t *testing.T) { {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(`failed to load translations: (6, 7): was expecting token =, but got "g" instead`), 0, 6, 7}, + {errors.New(`execute of template failed: template: index.html:2:5: executing "index.html" at <partial "foo.html" .>: error calling partial: "/layouts/partials/foo.html:3:6": execute of template failed: template: partials/foo.html:3:6: executing "partials/foo.html" at <.ThisDoesNotExist>: can't evaluate field ThisDoesNotExist in type *hugolib.pageStat`), 0, 2, 5}, } { - got := ToFileError("template", test.in) + got := NewFileError("test.txt", test.in) errMsg := qt.Commentf("[%d][%T]", i, got) - le, ok := got.(FileError) - c.Assert(ok, qt.Equals, true) - c.Assert(ok, qt.Equals, true, errMsg) - pos := le.Position() + pos := got.Position() c.Assert(pos.LineNumber, qt.Equals, test.lineNumber, errMsg) c.Assert(pos.ColumnNumber, qt.Equals, test.columnNumber, errMsg) - c.Assert(errors.Cause(got), qt.Not(qt.IsNil)) + c.Assert(errors.Unwrap(got), qt.Not(qt.IsNil)) } } diff --git a/common/herrors/line_number_extractors.go b/common/herrors/line_number_extractors.go @@ -16,36 +16,22 @@ package herrors import ( "regexp" "strconv" - - "github.com/pkg/errors" - - "github.com/pelletier/go-toml/v2" ) var lineNumberExtractors = []lineNumberExtractor{ // Template/shortcode parse errors - newLineNumberErrHandlerFromRegexp(".*:(\\d+):(\\d*):"), - newLineNumberErrHandlerFromRegexp(".*:(\\d+):"), + newLineNumberErrHandlerFromRegexp(`:(\d+):(\d*):`), + newLineNumberErrHandlerFromRegexp(`:(\d+):`), - // TOML parse errors - tomlLineNumberExtractor, // YAML parse errors - newLineNumberErrHandlerFromRegexp("line (\\d+):"), + newLineNumberErrHandlerFromRegexp(`line (\d+):`), // i18n bundle errors - newLineNumberErrHandlerFromRegexp("\\((\\d+),\\s(\\d*)"), + newLineNumberErrHandlerFromRegexp(`\((\d+),\s(\d*)`), } type lineNumberExtractor func(e error) (int, int) -var tomlLineNumberExtractor = func(e error) (int, int) { - e = errors.Cause(e) - if terr, ok := e.(*toml.DecodeError); ok { - return terr.Position() - } - return -1, -1 -} - func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor { re := regexp.MustCompile(expression) return extractLineNo(re) @@ -72,6 +58,6 @@ func extractLineNo(re *regexp.Regexp) lineNumberExtractor { return lno, col } - return -1, col + return 0, col } } diff --git a/common/hugio/copy.go b/common/hugio/copy.go @@ -14,13 +14,12 @@ package hugio import ( + "fmt" "io" "io/ioutil" "os" "path/filepath" - "github.com/pkg/errors" - "github.com/spf13/afero" ) @@ -60,7 +59,7 @@ func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool } if !fi.IsDir() { - return errors.Errorf("%q is not a directory", from) + return fmt.Errorf("%q is not a directory", from) } err = fs.MkdirAll(to, 0777) // before umask diff --git a/config/commonConfig.go b/config/commonConfig.go @@ -14,12 +14,11 @@ package config import ( + "fmt" "sort" "strings" "sync" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/common/types" "github.com/gobwas/glob" @@ -207,7 +206,7 @@ func DecodeServer(cfg Provider) (*Server, error) { // There are some tricky infinite loop situations when dealing // when the target does not have a trailing slash. // This can certainly be handled better, but not time for that now. - return nil, errors.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To) + return nil, fmt.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To) } s.Redirects[i] = redir } diff --git a/config/configLoader.go b/config/configLoader.go @@ -14,14 +14,13 @@ package config import ( + "fmt" "os" "path/filepath" "strings" "github.com/gohugoio/hugo/common/herrors" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/common/maps" @@ -60,7 +59,7 @@ func FromConfigString(config, configType string) (Provider, error) { func FromFile(fs afero.Fs, filename string) (Provider, error) { m, err := loadConfigFromFile(fs, filename) if err != nil { - return nil, herrors.WithFileContextForFileDefault(err, filename, fs) + return nil, herrors.NewFileErrorFromFile(err, filename, filename, fs, herrors.SimpleLineMatcher) } return NewFrom(m), nil } @@ -132,7 +131,7 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid if err != nil { // This will be used in error reporting, use the most specific value. dirnames = []string{path} - return errors.Wrapf(err, "failed to unmarshl config for path %q", path) + return fmt.Errorf("failed to unmarshl config for path %q: %w", path, err) } var keyPath []string diff --git a/create/content.go b/create/content.go @@ -27,7 +27,7 @@ import ( "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/paths" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/hugofs/files" @@ -94,11 +94,11 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string) error { } if ext == "" { - return "", errors.Errorf("failed to resolve %q to a archetype template", targetPath) + return "", fmt.Errorf("failed to resolve %q to a archetype template", targetPath) } if !files.IsContentFile(b.targetPath) { - return "", errors.Errorf("target path %q is not a known content format", b.targetPath) + return "", fmt.Errorf("target path %q is not a known content format", b.targetPath) } return b.buildFile() @@ -188,14 +188,14 @@ func (b *contentBuilder) buildDir() error { in, err := meta.Open() if err != nil { - return errors.Wrap(err, "failed to open non-content file") + return fmt.Errorf("failed to open non-content file: %w", err) } targetFilename := filepath.Join(baseDir, b.targetPath, strings.TrimPrefix(filename, b.archetypeFilename)) targetDir := filepath.Dir(targetFilename) if err := b.sourceFs.MkdirAll(targetDir, 0o777); err != nil && !os.IsExist(err) { - return errors.Wrapf(err, "failed to create target directory for %q", targetDir) + return fmt.Errorf("failed to create target directory for %q: %w", targetDir, err) } out, err := b.sourceFs.Create(targetFilename) @@ -329,7 +329,7 @@ func (b *contentBuilder) mapArcheTypeDir() error { w := hugofs.NewWalkway(walkCfg) if err := w.Walk(); err != nil { - return errors.Wrapf(err, "failed to walk archetype dir %q", b.archetypeFilename) + return fmt.Errorf("failed to walk archetype dir %q: %w", b.archetypeFilename, err) } b.dirMap = m @@ -374,7 +374,7 @@ func (b *contentBuilder) usesSiteVar(filename string) (bool, error) { } bb, err := afero.ReadFile(b.archeTypeFs, filename) if err != nil { - return false, errors.Wrap(err, "failed to open archetype file") + return false, fmt.Errorf("failed to open archetype file: %w", err) } return bytes.Contains(bb, []byte(".Site")) || bytes.Contains(bb, []byte("site.")), nil diff --git a/deploy/deploy.go b/deploy/deploy.go @@ -34,11 +34,12 @@ import ( "strings" "sync" + "errors" + "github.com/dustin/go-humanize" "github.com/gobwas/glob" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/media" - "github.com/pkg/errors" "github.com/spf13/afero" jww "github.com/spf13/jwalterweatherman" "golang.org/x/text/unicode/norm" diff --git a/deploy/deployConfig.go b/deploy/deployConfig.go @@ -20,12 +20,13 @@ import ( "fmt" "regexp" + "errors" + "github.com/gobwas/glob" "github.com/gohugoio/hugo/config" hglob "github.com/gohugoio/hugo/hugofs/glob" "github.com/gohugoio/hugo/media" "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" ) const deploymentConfigKey = "deployment" diff --git a/deps/deps.go b/deps/deps.go @@ -1,12 +1,11 @@ package deps import ( + "fmt" "sync" "sync/atomic" "time" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/cache/filecache" "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/loggers" @@ -186,11 +185,11 @@ func (d *Deps) SetTextTmpl(tmpl tpl.TemplateParseFinder) { func (d *Deps) LoadResources() error { // Note that the translations need to be loaded before the templates. if err := d.translationProvider.Update(d); err != nil { - return errors.Wrap(err, "loading translations") + return fmt.Errorf("loading translations: %w", err) } if err := d.templateProvider.Update(d); err != nil { - return errors.Wrap(err, "loading templates") + return fmt.Errorf("loading templates: %w", err) } return nil @@ -236,18 +235,18 @@ func New(cfg DepsCfg) (*Deps, error) { securityConfig, err := security.DecodeConfig(cfg.Cfg) if err != nil { - return nil, errors.WithMessage(err, "failed to create security config from configuration") + return nil, fmt.Errorf("failed to create security config from configuration: %w", err) } execHelper := hexec.New(securityConfig) ps, err := helpers.NewPathSpec(fs, cfg.Language, logger) if err != nil { - return nil, errors.Wrap(err, "create PathSpec") + return nil, fmt.Errorf("create PathSpec: %w", err) } fileCaches, err := filecache.NewCaches(ps) if err != nil { - return nil, errors.WithMessage(err, "failed to create file caches from configuration") + return nil, fmt.Errorf("failed to create file caches from configuration: %w", err) } errorHandler := &globalErrHandler{} diff --git a/helpers/path.go b/helpers/path.go @@ -31,7 +31,6 @@ import ( "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/common/hugio" - _errors "github.com/pkg/errors" "github.com/spf13/afero" ) @@ -403,7 +402,7 @@ func GetCacheDir(fs afero.Fs, cfg config.Provider) (string, error) { if !exists { err := fs.MkdirAll(cacheDir, 0777) // Before umask if err != nil { - return "", _errors.Wrap(err, "failed to create cache dir") + return "", fmt.Errorf("failed to create cache dir: %w", err) } } return cacheDir, nil diff --git a/hugofs/decorators.go b/hugofs/decorators.go @@ -14,12 +14,11 @@ package hugofs import ( + "fmt" "os" "path/filepath" "strings" - "github.com/pkg/errors" - "github.com/spf13/afero" ) @@ -232,7 +231,7 @@ func (l *baseFileDecoratorFile) Readdir(c int) (ofi []os.FileInfo, err error) { } fi, err = l.fs.decorate(fi, filename) if err != nil { - return nil, errors.Wrap(err, "decorate") + return nil, fmt.Errorf("decorate: %w", err) } fisp = append(fisp, fi) } diff --git a/hugofs/fileinfo.go b/hugofs/fileinfo.go @@ -28,7 +28,7 @@ import ( "github.com/gohugoio/hugo/hugofs/files" "golang.org/x/text/unicode/norm" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/common/hreflect" diff --git a/hugofs/rootmapping_fs.go b/hugofs/rootmapping_fs.go @@ -21,8 +21,6 @@ import ( "github.com/gohugoio/hugo/hugofs/files" - "github.com/pkg/errors" - radix "github.com/armon/go-radix" "github.com/spf13/afero" ) @@ -191,7 +189,7 @@ func (fs *RootMappingFs) Dirs(base string) ([]FileMetaInfo, error) { fs = decorateDirs(fs, r.Meta) fi, err := fs.Stat("") if err != nil { - return nil, errors.Wrap(err, "RootMappingFs.Dirs") + return nil, fmt.Errorf("RootMappingFs.Dirs: %w", err) } if !fi.IsDir() { @@ -560,7 +558,7 @@ func (fs *RootMappingFs) doLstat(name string) ([]FileMetaInfo, error) { if fileCount > 1 { // Not supported by this filesystem. - return nil, errors.Errorf("found multiple files with name %q, use .Readdir or the source filesystem directly", name) + return nil, fmt.Errorf("found multiple files with name %q, use .Readdir or the source filesystem directly", name) } return []FileMetaInfo{roots[0].fi}, nil diff --git a/hugofs/slice_fs.go b/hugofs/slice_fs.go @@ -14,11 +14,12 @@ package hugofs import ( + "fmt" "os" "syscall" "time" - "github.com/pkg/errors" + "errors" "github.com/spf13/afero" ) @@ -83,7 +84,7 @@ func (fs *SliceFs) LstatIfPossible(name string) (os.FileInfo, bool, error) { return decorateFileInfo(fi, fs, fs.getOpener(name), "", "", nil), false, nil } - return nil, false, errors.Errorf("lstat: files not supported: %q", name) + return nil, false, fmt.Errorf("lstat: files not supported: %q", name) } func (fs *SliceFs) Mkdir(n string, p os.FileMode) error { diff --git a/hugofs/walk.go b/hugofs/walk.go @@ -22,7 +22,7 @@ import ( "github.com/gohugoio/hugo/common/loggers" - "github.com/pkg/errors" + "errors" "github.com/spf13/afero" ) @@ -125,7 +125,7 @@ func (w *Walkway) Walk() error { if w.checkErr(w.root, err) { return nil } - return w.walkFn(w.root, nil, errors.Wrapf(err, "walk: %q", w.root)) + return w.walkFn(w.root, nil, fmt.Errorf("walk: %q: %w", w.root, err)) } fi = info.(FileMetaInfo) } @@ -192,7 +192,7 @@ func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo if w.checkErr(path, err) { return nil } - return walkFn(path, info, errors.Wrapf(err, "walk: open %q (%q)", path, w.root)) + return walkFn(path, info, fmt.Errorf("walk: open %q (%q): %w", path, w.root, err)) } fis, err := f.Readdir(-1) @@ -201,7 +201,7 @@ func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo if w.checkErr(filename, err) { return nil } - return walkFn(path, info, errors.Wrap(err, "walk: Readdir")) + return walkFn(path, info, fmt.Errorf("walk: Readdir: %w", err)) } dirEntries = fileInfosToFileMetaInfos(fis) diff --git a/hugofs/walk_test.go b/hugofs/walk_test.go @@ -22,7 +22,7 @@ import ( "strings" "testing" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/common/para" "github.com/gohugoio/hugo/htesting" diff --git a/hugolib/config.go b/hugolib/config.go @@ -33,11 +33,12 @@ import ( "github.com/gohugoio/hugo/parser/metadecoders" + "errors" + "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/modules" - "github.com/pkg/errors" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config/privacy" @@ -510,5 +511,5 @@ func (configLoader) loadSiteConfig(cfg config.Provider) (scfg SiteConfig, err er } func (l configLoader) wrapFileError(err error, filename string) error { - return herrors.WithFileContextForFileDefault(err, filename, l.Fs) + return herrors.NewFileErrorFromFile(err, filename, filename, l.Fs, herrors.SimpleLineMatcher) } diff --git a/hugolib/configdir_test.go b/hugolib/configdir_test.go @@ -146,7 +146,7 @@ baseURL = "https://example.org" _, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Environment: "development", Filename: "hugo.toml", AbsConfigDir: "config"}) c.Assert(err, qt.Not(qt.IsNil)) - fe := herrors.UnwrapErrorWithFileContext(err) + fe := herrors.UnwrapFileError(err) c.Assert(fe, qt.Not(qt.IsNil)) c.Assert(fe.Position().Filename, qt.Equals, filepath.FromSlash("config/development/config.toml")) } diff --git a/hugolib/content_factory.go b/hugolib/content_factory.go @@ -14,6 +14,7 @@ package hugolib import ( + "fmt" "io" "path/filepath" "strings" @@ -25,7 +26,6 @@ import ( "github.com/gohugoio/hugo/resources/page" - "github.com/pkg/errors" "github.com/spf13/afero" ) @@ -48,12 +48,12 @@ func (f ContentFactory) ApplyArchetypeFilename(w io.Writer, p page.Page, archety } if fi.IsDir() { - return errors.Errorf("archetype directory (%q) not supported", archetypeFilename) + return fmt.Errorf("archetype directory (%q) not supported", archetypeFilename) } templateSource, err := afero.ReadFile(f.h.SourceFilesystems.Archetypes.Fs, archetypeFilename) if err != nil { - return errors.Wrapf(err, "failed to read archetype file %q: %s", archetypeFilename, err) + return fmt.Errorf("failed to read archetype file %q: %s: %w", archetypeFilename, err, err) } @@ -79,12 +79,12 @@ func (f ContentFactory) ApplyArchetypeTemplate(w io.Writer, p page.Page, archety templ, err := ps.s.TextTmpl().Parse("archetype.md", string(templateSource)) if err != nil { - return errors.Wrapf(err, "failed to parse archetype template: %s", err) + return fmt.Errorf("failed to parse archetype template: %s: %w", err, err) } result, err := executeToString(ps.s.Tmpl(), templ, d) if err != nil { - return errors.Wrapf(err, "failed to execute archetype template: %s", err) + return fmt.Errorf("failed to execute archetype template: %s: %w", err, err) } _, err = io.WriteString(w, f.shortcodeReplacerPost.Replace(result)) diff --git a/hugolib/content_map.go b/hugolib/content_map.go @@ -23,7 +23,6 @@ import ( "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/resources/page" - "github.com/pkg/errors" "github.com/gohugoio/hugo/hugofs/files" @@ -207,7 +206,7 @@ func (b *cmInsertKeyBuilder) WithFile(fi hugofs.FileMetaInfo) *cmInsertKeyBuilde p, k := b.getBundle(p) if k == "" { - b.err = errors.Errorf("no bundle header found for %q", bundlePath) + b.err = fmt.Errorf("no bundle header found for %q", bundlePath) return b } diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go @@ -35,7 +35,6 @@ import ( "github.com/spf13/cast" "github.com/gohugoio/hugo/common/para" - "github.com/pkg/errors" ) func newPageMaps(h *HugoSites) *pageMaps { @@ -131,13 +130,13 @@ func (m *pageMap) newPageFromContentNode(n *contentNode, parentBucket *pagesMapB gi, err := s.h.gitInfoForPage(ps) if err != nil { - return nil, errors.Wrap(err, "failed to load Git data") + return nil, fmt.Errorf("failed to load Git data: %w", err) } ps.gitInfo = gi owners, err := s.h.codeownersForPage(ps) if err != nil { - return nil, errors.Wrap(err, "failed to load CODEOWNERS") + return nil, fmt.Errorf("failed to load CODEOWNERS: %w", err) } ps.codeowners = owners @@ -282,7 +281,7 @@ func (m *pageMap) createSiteTaxonomies() error { } else { taxonomy := m.s.taxonomies[viewName.plural] if taxonomy == nil { - walkErr = errors.Errorf("missing taxonomy: %s", viewName.plural) + walkErr = fmt.Errorf("missing taxonomy: %s", viewName.plural) return true } m.taxonomyEntries.WalkPrefix(s, func(ss string, v any) bool { diff --git a/hugolib/fileInfo.go b/hugolib/fileInfo.go @@ -14,12 +14,11 @@ package hugolib import ( + "fmt" "strings" "github.com/gohugoio/hugo/hugofs/files" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/hugofs" "github.com/spf13/afero" @@ -41,7 +40,7 @@ type fileInfo struct { func (fi *fileInfo) Open() (afero.File, error) { f, err := fi.FileInfo().Meta().Open() if err != nil { - err = errors.Wrap(err, "fileInfo") + err = fmt.Errorf("fileInfo: %w", err) } return f, err diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go @@ -35,8 +35,6 @@ import ( "github.com/gohugoio/hugo/hugofs/files" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/modules" hpaths "github.com/gohugoio/hugo/common/paths" @@ -176,7 +174,7 @@ func (b *BaseFs) AbsProjectContentDir(filename string) (string, string, error) { } - return "", "", errors.Errorf("could not determine content directory for %q", filename) + return "", "", fmt.Errorf("could not determine content directory for %q", filename) } // ResolveJSConfigFile resolves the JS-related config file to a absolute @@ -468,7 +466,7 @@ func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) err builder := newSourceFilesystemsBuilder(p, logger, b) sourceFilesystems, err := builder.Build() if err != nil { - return nil, errors.Wrap(err, "build filesystems") + return nil, fmt.Errorf("build filesystems: %w", err) } b.SourceFilesystems = sourceFilesystems @@ -502,7 +500,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) { if b.theBigFs == nil { theBigFs, err := b.createMainOverlayFs(b.p) if err != nil { - return nil, errors.Wrap(err, "create main fs") + return nil, fmt.Errorf("create main fs: %w", err) } b.theBigFs = theBigFs @@ -544,7 +542,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) { contentFs, err := hugofs.NewLanguageFs(b.p.LanguagesDefaultFirst.AsOrdinalSet(), contentBfs) if err != nil { - return nil, errors.Wrap(err, "create content filesystem") + return nil, fmt.Errorf("create content filesystem: %w", err) } b.result.Content = b.newSourceFilesystem(files.ComponentFolderContent, contentFs, contentDirs) diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go @@ -15,6 +15,7 @@ package hugolib import ( "context" + "fmt" "io" "path/filepath" "sort" @@ -33,9 +34,10 @@ import ( "github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/parser/metadecoders" + "errors" + "github.com/gohugoio/hugo/common/para" "github.com/gohugoio/hugo/hugofs" - "github.com/pkg/errors" "github.com/gohugoio/hugo/source" @@ -194,7 +196,7 @@ func (h *hugoSitesInit) Reset() { func (h *HugoSites) Data() map[string]any { if _, err := h.init.data.Do(); err != nil { - h.SendError(errors.Wrap(err, "failed to load data")) + h.SendError(fmt.Errorf("failed to load data: %w", err)) return nil } return h.data @@ -242,7 +244,7 @@ func (h *HugoSites) pickOneAndLogTheRest(errors []error) error { 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 { + if herrors.UnwrapFileError(err) != nil { i = j break } @@ -327,7 +329,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { langConfig, err := newMultiLingualFromSites(cfg.Cfg, sites...) if err != nil { - return nil, errors.Wrap(err, "failed to create language config") + return nil, fmt.Errorf("failed to create language config: %w", err) } var contentChangeTracker *contentChangeMap @@ -365,7 +367,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { h.init.data.Add(func() (any, error) { err := h.loadData(h.PathSpec.BaseFs.Data.Dirs) if err != nil { - return nil, errors.Wrap(err, "failed to load data") + return nil, fmt.Errorf("failed to load data: %w", err) } return nil, nil }) @@ -391,7 +393,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { h.init.gitInfo.Add(func() (any, error) { err := h.loadGitInfo() if err != nil { - return nil, errors.Wrap(err, "failed to load Git info") + return nil, fmt.Errorf("failed to load Git info: %w", err) } return nil, nil }) @@ -402,7 +404,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { var l configLoader if err := l.applyDeps(cfg, sites...); err != nil { - initErr = errors.Wrap(err, "add site dependencies") + initErr = fmt.Errorf("add site dependencies: %w", err) } h.Deps = sites[0].Deps @@ -485,7 +487,7 @@ func (l configLoader) applyDeps(cfg deps.DepsCfg, sites ...*Site) error { siteConfig, err := l.loadSiteConfig(s.language) if err != nil { - return errors.Wrap(err, "load site config") + return fmt.Errorf("load site config: %w", err) } s.siteConfigConfig = siteConfig @@ -516,17 +518,17 @@ func (l configLoader) applyDeps(cfg deps.DepsCfg, sites ...*Site) error { var err error d, err = deps.New(cfg) if err != nil { - return errors.Wrap(err, "create deps") + return fmt.Errorf("create deps: %w", err) } d.OutputFormatsConfig = s.outputFormatsConfig if err := onCreated(d); err != nil { - return errors.Wrap(err, "on created") + return fmt.Errorf("on created: %w", err) } if err = d.LoadResources(); err != nil { - return errors.Wrap(err, "load resources") + return fmt.Errorf("load resources: %w", err) } } else { @@ -548,7 +550,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { } sites, err := createSitesFromConfig(cfg) if err != nil { - return nil, errors.Wrap(err, "from config") + return nil, fmt.Errorf("from config: %w", err) } return newHugoSites(cfg, sites...) } @@ -882,7 +884,7 @@ func (h *HugoSites) handleDataFile(r source.File) error { f, err := r.FileInfo().Meta().Open() if err != nil { - return errors.Wrapf(err, "data: failed to open %q:", r.LogicalName()) + return fmt.Errorf("data: failed to open %q: %w", r.LogicalName(), err) } defer f.Close() @@ -960,23 +962,16 @@ func (h *HugoSites) errWithFileContext(err error, f source.File) error { if !ok { return err } - realFilename := fim.Meta().Filename - err, _ = herrors.WithFileContextForFile( - err, - realFilename, - realFilename, - h.SourceSpec.Fs.Source, - herrors.SimpleLineMatcher) + return herrors.NewFileErrorFromFile(err, realFilename, realFilename, h.SourceSpec.Fs.Source, herrors.SimpleLineMatcher) - return err } func (h *HugoSites) readData(f source.File) (any, error) { file, err := f.FileInfo().Meta().Open() if err != nil { - return nil, errors.Wrap(err, "readData: failed to open data file") + return nil, fmt.Errorf("readData: failed to open data file: %w", err) } defer file.Close() content := helpers.ReaderToBytes(file) diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go @@ -35,7 +35,7 @@ import ( "github.com/gohugoio/hugo/output" - "github.com/pkg/errors" + "errors" "github.com/fsnotify/fsnotify" "github.com/gohugoio/hugo/helpers" @@ -50,7 +50,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { if !config.NoBuildLock { unlock, err := h.BaseFs.LockBuild() if err != nil { - return errors.Wrap(err, "failed to acquire a build lock") + return fmt.Errorf("failed to acquire a build lock: %w", err) } defer unlock() } @@ -99,11 +99,11 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { if len(events) > 0 { // Rebuild if err := h.initRebuild(conf); err != nil { - return errors.Wrap(err, "initRebuild") + return fmt.Errorf("initRebuild: %w", err) } } else { if err := h.initSites(conf); err != nil { - return errors.Wrap(err, "initSites") + return fmt.Errorf("initSites: %w", err) } } @@ -117,7 +117,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { } trace.WithRegion(ctx, "process", f) if err != nil { - return errors.Wrap(err, "process") + return fmt.Errorf("process: %w", err) } f = func() { diff --git a/hugolib/hugo_sites_build_errors_test.go b/hugolib/hugo_sites_build_errors_test.go @@ -2,6 +2,7 @@ package hugolib import ( "fmt" + "os" "path/filepath" "strings" "testing" @@ -17,14 +18,15 @@ type testSiteBuildErrorAsserter struct { c *qt.C } -func (t testSiteBuildErrorAsserter) getFileError(err error) *herrors.ErrorWithFileContext { +func (t testSiteBuildErrorAsserter) getFileError(err error) herrors.FileError { t.c.Assert(err, qt.Not(qt.IsNil), qt.Commentf(t.name)) - ferr := herrors.UnwrapErrorWithFileContext(err) - t.c.Assert(ferr, qt.Not(qt.IsNil)) - return ferr + fe := herrors.UnwrapFileError(err) + t.c.Assert(fe, qt.Not(qt.IsNil)) + return fe } func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) { + t.c.Helper() fe := t.getFileError(err) t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber, qt.Commentf(err.Error())) } @@ -87,7 +89,6 @@ func TestSiteBuildErrors(t *testing.T) { fe := a.getFileError(err) a.c.Assert(fe.Position().LineNumber, qt.Equals, 5) a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1) - a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template") a.assertErrorMessage("\"layouts/foo/single.html:5:1\": parse failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error()) }, }, @@ -101,7 +102,6 @@ func TestSiteBuildErrors(t *testing.T) { fe := a.getFileError(err) a.c.Assert(fe.Position().LineNumber, qt.Equals, 5) a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 14) - a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template") a.assertErrorMessage("\"layouts/_default/single.html:5:14\": execute of template failed", fe.Error()) }, }, @@ -115,7 +115,6 @@ func TestSiteBuildErrors(t *testing.T) { fe := a.getFileError(err) a.c.Assert(fe.Position().LineNumber, qt.Equals, 5) a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 14) - a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template") a.assertErrorMessage("\"layouts/_default/single.html:5:14\": execute of template failed", fe.Error()) }, }, @@ -130,18 +129,17 @@ func TestSiteBuildErrors(t *testing.T) { }, }, { - name: "Shortode execute failed", + name: "Shortcode execute failed", fileType: shortcode, fileFixer: func(content string) string { return strings.Replace(content, ".Title", ".Titles", 1) }, assertBuildError: func(a testSiteBuildErrorAsserter, err error) { fe := a.getFileError(err) - a.c.Assert(fe.Position().LineNumber, qt.Equals, 7) - a.c.Assert(fe.ChromaLexer, qt.Equals, "md") // Make sure that it contains both the content file and template - a.assertErrorMessage(`content/myyaml.md:7:10": failed to render shortcode "sc"`, fe.Error()) - a.assertErrorMessage(`shortcodes/sc.html:4:22: executing "shortcodes/sc.html" at <.Page.Titles>: can't evaluate`, fe.Error()) + a.assertErrorMessage(`"content/myyaml.md:7:10": failed to render shortcode "sc": failed to process shortcode: "layouts/shortcodes/sc.html:4:22": execute of template failed: template: shortcodes/sc.html:4:22: executing "shortcodes/sc.html" at <.Page.Titles>: can't evaluate field Titles in type page.Page`, fe.Error()) + a.c.Assert(fe.Position().LineNumber, qt.Equals, 7) + }, }, { @@ -154,7 +152,6 @@ func TestSiteBuildErrors(t *testing.T) { fe := a.getFileError(err) a.c.Assert(fe.Position().LineNumber, qt.Equals, 7) a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 10) - a.c.Assert(fe.ChromaLexer, qt.Equals, "md") a.assertErrorMessage(`"content/myyaml.md:7:10": failed to extract shortcode: template for shortcode "nono" not found`, fe.Error()) }, }, @@ -162,10 +159,14 @@ func TestSiteBuildErrors(t *testing.T) { name: "Invalid YAML front matter", fileType: yamlcontent, fileFixer: func(content string) string { - return strings.Replace(content, "title:", "title: %foo", 1) + return `--- +title: "My YAML Content" +foo bar +--- +` }, assertBuildError: func(a testSiteBuildErrorAsserter, err error) { - a.assertLineNumber(2, err) + a.assertLineNumber(3, err) }, }, { @@ -177,7 +178,6 @@ func TestSiteBuildErrors(t *testing.T) { assertBuildError: func(a testSiteBuildErrorAsserter, err error) { fe := a.getFileError(err) a.c.Assert(fe.Position().LineNumber, qt.Equals, 6) - a.c.Assert(fe.ErrorContext.ChromaLexer, qt.Equals, "toml") }, }, { @@ -188,9 +188,7 @@ func TestSiteBuildErrors(t *testing.T) { }, assertBuildError: func(a testSiteBuildErrorAsserter, err error) { fe := a.getFileError(err) - a.c.Assert(fe.Position().LineNumber, qt.Equals, 3) - a.c.Assert(fe.ErrorContext.ChromaLexer, qt.Equals, "json") }, }, { @@ -211,6 +209,9 @@ func TestSiteBuildErrors(t *testing.T) { } for _, test := range tests { + if test.name != "Invalid JSON front matter" { + continue + } test := test t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -311,6 +312,77 @@ Some content. } }) } + +} + +// Issue 9852 +func TestErrorMinify(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +minify = true + +-- layouts/index.html -- +<body> +<script>=;</script> +</body> + +` + + b, err := NewIntegrationTestBuilder( + IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + + fe := herrors.UnwrapFileError(err) + b.Assert(fe, qt.IsNotNil) + b.Assert(fe.Position().LineNumber, qt.Equals, 2) + b.Assert(fe.Position().ColumnNumber, qt.Equals, 9) + b.Assert(fe.Error(), qt.Contains, "unexpected = in expression on line 2 and column 9") + b.Assert(filepath.ToSlash(fe.Position().Filename), qt.Contains, "hugo-transform-error") + b.Assert(os.Remove(fe.Position().Filename), qt.IsNil) + +} + +func TestErrorNested(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- layouts/index.html -- +line 1 +12{{ partial "foo.html" . }} +line 4 +line 5 +-- layouts/partials/foo.html -- +line 1 +line 2 +123{{ .ThisDoesNotExist }} +line 4 +` + + b, err := NewIntegrationTestBuilder( + IntegrationTestConfig{ + T: t, + TxtarString: files, + }, + ).BuildE() + + b.Assert(err, qt.IsNotNil) + errors := herrors.UnwrapFileErrorsWithErrorContext(err) + b.Assert(errors, qt.HasLen, 2) + fmt.Println(errors[0]) + b.Assert(errors[0].Position().LineNumber, qt.Equals, 2) + b.Assert(errors[0].Position().ColumnNumber, qt.Equals, 5) + b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/layouts/index.html:2:5": execute of template failed`)) + b.Assert(errors[0].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"}) + b.Assert(errors[1].Position().LineNumber, qt.Equals, 3) + b.Assert(errors[1].Position().ColumnNumber, qt.Equals, 6) + b.Assert(errors[1].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"}) + } // https://github.com/gohugoio/hugo/issues/5375 diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go @@ -169,8 +169,7 @@ func (s *IntegrationTestBuilder) destinationExists(filename string) bool { } func (s *IntegrationTestBuilder) AssertIsFileError(err error) { - var ferr *herrors.ErrorWithFileContext - s.Assert(err, qt.ErrorAs, &ferr) + s.Assert(err, qt.ErrorAs, new(herrors.FileError)) } func (s *IntegrationTestBuilder) AssertRenderCountContent(count int) { diff --git a/hugolib/page.go b/hugolib/page.go @@ -39,8 +39,9 @@ import ( "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/parser/metadecoders" + "errors" + "github.com/gohugoio/hugo/parser/pageparser" - "github.com/pkg/errors" "github.com/gohugoio/hugo/output" @@ -482,7 +483,7 @@ func (p *pageState) renderResources() (err error) { src, ok := r.(resource.Source) if !ok { - err = errors.Errorf("Resource %T does not support resource.Source", src) + err = fmt.Errorf("Resource %T does not support resource.Source", src) return } @@ -560,23 +561,37 @@ func (p *pageState) addDependency(dep identity.Provider) { // wrapError adds some more context to the given error if possible/needed func (p *pageState) wrapError(err error) error { - if _, ok := err.(*herrors.ErrorWithFileContext); ok { - // Preserve the first file context. - return err + if err == nil { + panic("wrapError with nil") } - var filename string - if !p.File().IsZero() { - filename = p.File().Filename() + + if p.File().IsZero() { + // No more details to add. + return fmt.Errorf("%q: %w", p.Pathc(), err) } - err, _ = herrors.WithFileContextForFile( - err, - filename, - filename, - p.s.SourceSpec.Fs.Source, - herrors.SimpleLineMatcher) + filename := p.File().Filename() + + if ferr := herrors.UnwrapFileError(err); ferr != nil { + errfilename := ferr.Position().Filename + if ferr.ErrorContext() != nil || errfilename == "" || !(errfilename == pageFileErrorName || filepath.IsAbs(errfilename)) { + return err + } + if filepath.IsAbs(errfilename) { + filename = errfilename + } + f, ferr2 := p.s.SourceSpec.Fs.Source.Open(filename) + if ferr2 != nil { + return err + } + defer f.Close() + pos := ferr.Position() + pos.Filename = filename + return ferr.UpdatePosition(pos).UpdateContent(f, herrors.SimpleLineMatcher) + } + + return herrors.NewFileErrorFromFile(err, filename, filename, p.s.SourceSpec.Fs.Source, herrors.SimpleLineMatcher) - return err } func (p *pageState) getContentConverter() converter.Converter { @@ -606,6 +621,9 @@ func (p *pageState) mapContent(bucket *pagesMapBucket, meta *pageMeta) error { iter := p.source.parsed.Iterator() fail := func(err error, i pageparser.Item) error { + if fe, ok := err.(herrors.FileError); ok { + return fe + } return p.parseError(err, iter.Input(), i.Pos) } @@ -626,7 +644,17 @@ Loop: m, err := metadecoders.Default.UnmarshalToMap(it.Val, f) if err != nil { if fe, ok := err.(herrors.FileError); ok { - return herrors.ToFileErrorWithOffset(fe, iter.LineNumber()-1) + // Offset the starting position of front matter. + pos := fe.Position() + offset := iter.LineNumber() - 1 + if f == metadecoders.YAML { + offset -= 1 + } + pos.LineNumber += offset + + fe.UpdatePosition(pos) + + return fe } else { return err } @@ -682,7 +710,7 @@ Loop: currShortcode, err := s.extractShortcode(ordinal, 0, iter) if err != nil { - return fail(errors.Wrap(err, "failed to extract shortcode"), it) + return fail(err, it) } currShortcode.pos = it.Pos @@ -715,7 +743,7 @@ Loop: case it.IsEOF(): break Loop case it.IsError(): - err := fail(errors.WithStack(errors.New(it.ValStr())), it) + err := fail(errors.New(it.ValStr()), it) currShortcode.err = err return err @@ -738,17 +766,17 @@ Loop: } func (p *pageState) errorf(err error, format string, a ...any) error { - if herrors.UnwrapErrorWithFileContext(err) != nil { + if herrors.UnwrapFileError(err) != nil { // More isn't always better. return err } args := append([]any{p.Language().Lang, p.pathOrTitle()}, a...) - format = "[%s] page %q: " + format + args = append(args, err) + format = "[%s] page %q: " + format + ": %w" if err == nil { - errors.Errorf(format, args...) return fmt.Errorf(format, args...) } - return errors.Wrapf(err, format, args...) + return fmt.Errorf(format, args...) } func (p *pageState) outputFormat() (f output.Format) { @@ -759,12 +787,8 @@ func (p *pageState) outputFormat() (f output.Format) { } func (p *pageState) parseError(err error, input []byte, offset int) error { - if herrors.UnwrapFileError(err) != nil { - // Use the most specific location. - return err - } pos := p.posFromInput(input, offset) - return herrors.NewFileError("md", -1, pos.LineNumber, pos.ColumnNumber, err) + return herrors.NewFileError("page.md", err).UpdatePosition(pos) } func (p *pageState) pathOrTitle() string { diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go @@ -34,7 +34,6 @@ import ( "github.com/gohugoio/hugo/related" "github.com/gohugoio/hugo/source" - "github.com/pkg/errors" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/config" @@ -765,7 +764,7 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string, renderingCo } cp := p.s.ContentSpec.Converters.Get(markup) if cp == nil { - return converter.NopConverter, errors.Errorf("no content renderer found for markup %q", p.markup) + return converter.NopConverter, fmt.Errorf("no content renderer found for markup %q", p.markup) } var id string diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go @@ -23,11 +23,12 @@ import ( "sync" "unicode/utf8" + "errors" + "github.com/gohugoio/hugo/common/text" "github.com/gohugoio/hugo/common/types/hstring" "github.com/gohugoio/hugo/identity" "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" "github.com/spf13/cast" "github.com/gohugoio/hugo/markup/converter/hooks" @@ -348,7 +349,7 @@ func (p *pageContentOutput) RenderString(args ...any) (template.HTML, error) { } if err := mapstructure.WeakDecode(m, &opts); err != nil { - return "", errors.WithMessage(err, "failed to decode options") + return "", fmt.Errorf("failed to decode options: %w", err) } } @@ -416,7 +417,7 @@ func (p *pageContentOutput) Render(layout ...string) (template.HTML, error) { // Make sure to send the *pageState and not the *pageContentOutput to the template. res, err := executeToString(p.p.s.Tmpl(), templ, p.p) if err != nil { - return "", p.p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout)) + return "", p.p.wrapError(fmt.Errorf("failed to execute template %q v: %w", layout, err)) } return template.HTML(res), nil } diff --git a/hugolib/page__ref.go b/hugolib/page__ref.go @@ -19,7 +19,6 @@ import ( "github.com/gohugoio/hugo/common/text" "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" ) func newPageRef(p *pageState) pageRef { @@ -77,7 +76,7 @@ func (p pageRef) decodeRefArgs(args map[string]any) (refArgs, *Site, error) { func (p pageRef) ref(argsm map[string]any, source any) (string, error) { args, s, err := p.decodeRefArgs(argsm) if err != nil { - return "", errors.Wrap(err, "invalid arguments to Ref") + return "", fmt.Errorf("invalid arguments to Ref: %w", err) } if s == nil { @@ -94,7 +93,7 @@ func (p pageRef) ref(argsm map[string]any, source any) (string, error) { func (p pageRef) relRef(argsm map[string]any, source any) (string, error) { args, s, err := p.decodeRefArgs(argsm) if err != nil { - return "", errors.Wrap(err, "invalid arguments to Ref") + return "", fmt.Errorf("invalid arguments to Ref: %w", err) } if s == nil { diff --git a/hugolib/page_unwrap.go b/hugolib/page_unwrap.go @@ -14,7 +14,7 @@ package hugolib import ( - "github.com/pkg/errors" + "fmt" "github.com/gohugoio/hugo/resources/page" ) @@ -36,7 +36,7 @@ func unwrapPage(in any) (page.Page, error) { case nil: return nil, nil default: - return nil, errors.Errorf("unwrapPage: %T not supported", in) + return nil, fmt.Errorf("unwrapPage: %T not supported", in) } } diff --git a/hugolib/pages_process.go b/hugolib/pages_process.go @@ -22,7 +22,6 @@ import ( "github.com/gohugoio/hugo/source" "github.com/gohugoio/hugo/hugofs/files" - "github.com/pkg/errors" "golang.org/x/sync/errgroup" "github.com/gohugoio/hugo/common/herrors" @@ -156,7 +155,7 @@ func (p *sitePagesProcessor) copyFile(fim hugofs.FileMetaInfo) error { meta := fim.Meta() f, err := meta.Open() if err != nil { - return errors.Wrap(err, "copyFile: failed to open") + return fmt.Errorf("copyFile: failed to open: %w", err) } s := p.m.s diff --git a/hugolib/paths/paths.go b/hugolib/paths/paths.go @@ -23,7 +23,6 @@ import ( "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/modules" - "github.com/pkg/errors" "github.com/gohugoio/hugo/hugofs" ) @@ -83,7 +82,7 @@ func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) { baseURLstr := cfg.GetString("baseURL") baseURL, err := newBaseURLFromString(baseURLstr) if err != nil { - return nil, errors.Wrapf(err, "Failed to create baseURL from %q:", baseURLstr) + return nil, fmt.Errorf("Failed to create baseURL from %q:: %w", baseURLstr, err) } contentDir := filepath.Clean(cfg.GetString("contentDir")) diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go @@ -27,8 +27,9 @@ import ( "github.com/gohugoio/hugo/helpers" + "errors" + "github.com/gohugoio/hugo/common/herrors" - "github.com/pkg/errors" "github.com/gohugoio/hugo/parser/pageparser" "github.com/gohugoio/hugo/resources/page" @@ -269,6 +270,7 @@ const ( innerNewlineRegexp = "\n" innerCleanupRegexp = `\A<p>(.*)</p>\n\z` innerCleanupExpand = "$1" + pageFileErrorName = "page.md" ) func renderShortcode( @@ -297,9 +299,10 @@ func renderShortcode( var err error tmpl, err = s.TextTmpl().Parse(templName, templStr) if err != nil { - fe := herrors.ToFileError("html", err) - l1, l2 := p.posOffset(sc.pos).LineNumber, fe.Position().LineNumber - fe = herrors.ToFileErrorWithLineNumber(fe, l1+l2-1) + fe := herrors.NewFileError(pageFileErrorName, err) + pos := fe.Position() + pos.LineNumber += p.posOffset(sc.pos).LineNumber + fe = fe.UpdatePosition(pos) return "", false, p.wrapError(fe) } @@ -308,7 +311,7 @@ func renderShortcode( var found bool tmpl, found = s.TextTmpl().Lookup(templName) if !found { - return "", false, errors.Errorf("no earlier definition of shortcode %q found", sc.name) + return "", false, fmt.Errorf("no earlier definition of shortcode %q found", sc.name) } } } else { @@ -389,9 +392,10 @@ func renderShortcode( result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data) if err != nil && sc.isInline { - fe := herrors.ToFileError("html", err) - l1, l2 := p.posFromPage(sc.pos).LineNumber, fe.Position().LineNumber - fe = herrors.ToFileErrorWithLineNumber(fe, l1+l2-1) + fe := herrors.NewFileError("shortcode.md", err) + pos := fe.Position() + pos.LineNumber += p.posOffset(sc.pos).LineNumber + fe = fe.UpdatePosition(pos) return "", false, fe } @@ -415,7 +419,7 @@ func (s *shortcodeHandler) renderShortcodesForPage(p *pageState, f output.Format for _, v := range s.shortcodes { s, more, err := renderShortcode(0, s.s, tplVariants, v, nil, p) if err != nil { - err = p.parseError(errors.Wrapf(err, "failed to render shortcode %q", v.name), p.source.parsed.Input(), v.pos) + err = p.parseError(fmt.Errorf("failed to render shortcode %q: %w", v.name, err), p.source.parsed.Input(), v.pos) return nil, false, err } hasVariants = hasVariants || more @@ -447,9 +451,10 @@ func (s *shortcodeHandler) extractShortcode(ordinal, level int, pt *pageparser.I cnt := 0 nestedOrdinal := 0 nextLevel := level + 1 + const errorPrefix = "failed to extract shortcode" fail := func(err error, i pageparser.Item) error { - return s.parseError(err, pt.Input(), i.Pos) + return s.parseError(fmt.Errorf("%s: %w", errorPrefix, err), pt.Input(), i.Pos) } Loop: @@ -508,7 +513,7 @@ Loop: // return that error, more specific continue } - return sc, fail(errors.Errorf("shortcode %q has no .Inner, yet a closing tag was provided", next.Val), next) + return sc, fail(fmt.Errorf("shortcode %q has no .Inner, yet a closing tag was provided", next.Val), next) } } if next.IsRightShortcodeDelim() { @@ -538,7 +543,7 @@ Loop: // Used to check if the template expects inner content. templs := s.s.Tmpl().LookupVariants(sc.name) if templs == nil { - return nil, errors.Errorf("template for shortcode %q not found", sc.name) + return nil, fmt.Errorf("%s: template for shortcode %q not found", errorPrefix, sc.name) } sc.info = templs[0].(tpl.Info) @@ -639,7 +644,7 @@ func renderShortcodeWithPage(h tpl.TemplateHandler, tmpl tpl.Template, data *Sho err := h.Execute(tmpl, buffer, data) if err != nil { - return "", errors.Wrap(err, "failed to process shortcode") + return "", fmt.Errorf("failed to process shortcode: %w", err) } return buffer.String(), nil } diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go @@ -1340,7 +1340,7 @@ func TestShortcodeNoInner(t *testing.T) { b := newTestSitesBuilder(t) - b.WithContent("page.md", `--- + b.WithContent("mypage.md", `--- title: "No Inner!" --- {{< noinner >}}{{< /noinner >}} @@ -1350,7 +1350,7 @@ title: "No Inner!" "layouts/shortcodes/noinner.html", `No inner here.`) err := b.BuildE(BuildCfg{}) - b.Assert(err.Error(), qt.Contains, `failed to extract shortcode: shortcode "noinner" has no .Inner, yet a closing tag was provided`) + b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`"content/mypage.md:4:21": failed to extract shortcode: shortcode "noinner" has no .Inner, yet a closing tag was provided`)) } func TestShortcodeStableOutputFormatTemplates(t *testing.T) { diff --git a/hugolib/site.go b/hugolib/site.go @@ -59,8 +59,6 @@ import ( "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/publisher" - "github.com/pkg/errors" - _errors "github.com/pkg/errors" "github.com/gohugoio/hugo/langs" @@ -508,7 +506,7 @@ But this also means that your site configuration may not do what you expect. If if cfg.Language.IsSet("related") { relatedContentConfig, err = related.DecodeConfig(cfg.Language.GetParams("related")) if err != nil { - return nil, errors.Wrap(err, "failed to decode related config") + return nil, fmt.Errorf("failed to decode related config: %w", err) } } else { relatedContentConfig = related.DefaultConfig @@ -546,7 +544,7 @@ But this also means that your site configuration may not do what you expect. If var err error cascade, err := page.DecodeCascade(cfg.Language.Get("cascade")) if err != nil { - return nil, errors.Errorf("failed to decode cascade config: %s", err) + return nil, fmt.Errorf("failed to decode cascade config: %s", err) } siteBucket = &pagesMapBucket{ @@ -1211,11 +1209,11 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro func (s *Site) process(config BuildCfg) (err error) { if err = s.initialize(); err != nil { - err = errors.Wrap(err, "initialize") + err = fmt.Errorf("initialize: %w", err) return } if err = s.readAndProcessContent(config); err != nil { - err = errors.Wrap(err, "readAndProcessContent") + err = fmt.Errorf("readAndProcessContent: %w", err) return } return err @@ -1534,7 +1532,7 @@ func (s *Site) assembleMenus() { for name, me := range p.pageMenus.menus() { if _, ok := flat[twoD{name, me.KeyName()}]; ok { - err := p.wrapError(errors.Errorf("duplicate menu entry with identifier %q in menu %q", me.KeyName(), name)) + err := p.wrapError(fmt.Errorf("duplicate menu entry with identifier %q in menu %q", me.KeyName(), name)) s.Log.Warnln(err) continue } @@ -1819,7 +1817,7 @@ func (s *Site) renderForTemplate(name, outputFormat string, d any, w io.Writer, } if err = s.Tmpl().Execute(templ, w, d); err != nil { - return _errors.Wrapf(err, "render of %q failed", name) + return fmt.Errorf("render of %q failed: %w", name, err) } return } diff --git a/hugolib/site_render.go b/hugolib/site_render.go @@ -23,8 +23,9 @@ import ( "github.com/gohugoio/hugo/config" + "errors" + "github.com/gohugoio/hugo/output" - "github.com/pkg/errors" "github.com/gohugoio/hugo/resources/page" "github.com/gohugoio/hugo/resources/page/pagemeta" @@ -95,7 +96,7 @@ func (s *Site) renderPages(ctx *siteRenderContext) error { err := <-errs if err != nil { - return errors.Wrap(err, "failed to render pages") + return fmt.Errorf("failed to render pages: %w", err) } return nil } diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go @@ -28,10 +28,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/gohugoio/hugo/parser" - "github.com/pkg/errors" "github.com/fsnotify/fsnotify" - "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/config" @@ -471,7 +469,6 @@ func (s *sitesBuilder) writeFilePairs(folder string, files []filenameContent) *s func (s *sitesBuilder) CreateSites() *sitesBuilder { if err := s.CreateSitesE(); err != nil { - herrors.PrintStackTraceFromErr(err) s.Fatalf("Failed to create sites: %s", err) } @@ -517,7 +514,7 @@ func (s *sitesBuilder) CreateSitesE() error { "i18n", } { if err := os.MkdirAll(filepath.Join(s.workingDir, dir), 0777); err != nil { - return errors.Wrapf(err, "failed to create %q", dir) + return fmt.Errorf("failed to create %q: %w", dir, err) } } } @@ -536,7 +533,7 @@ func (s *sitesBuilder) CreateSitesE() error { } if err := s.LoadConfig(); err != nil { - return errors.Wrap(err, "failed to load config") + return fmt.Errorf("failed to load config: %w", err) } s.Fs.PublishDir = hugofs.NewCreateCountingFs(s.Fs.PublishDir) @@ -549,7 +546,7 @@ func (s *sitesBuilder) CreateSitesE() error { sites, err := NewHugoSites(depsCfg) if err != nil { - return errors.Wrap(err, "failed to create sites") + return fmt.Errorf("failed to create sites: %w", err) } s.H = sites @@ -612,7 +609,6 @@ func (s *sitesBuilder) build(cfg BuildCfg, shouldFail bool) *sitesBuilder { } } if err != nil && !shouldFail { - herrors.PrintStackTraceFromErr(err) s.Fatalf("Build failed: %s", err) } else if err == nil && shouldFail { s.Fatalf("Expected error") diff --git a/langs/config.go b/langs/config.go @@ -23,7 +23,7 @@ import ( "github.com/spf13/cast" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/config" ) @@ -72,7 +72,7 @@ func LoadLanguageSettings(cfg config.Provider, oldLangs Languages) (c LanguagesC } else { languages2, err = toSortedLanguages(cfg, languages) if err != nil { - return c, errors.Wrap(err, "Failed to parse multilingual config") + return c, fmt.Errorf("Failed to parse multilingual config: %w", err) } } diff --git a/langs/i18n/translationProvider.go b/langs/i18n/translationProvider.go @@ -15,6 +15,7 @@ package i18n import ( "encoding/json" + "fmt" "strings" "github.com/gohugoio/hugo/common/paths" @@ -30,7 +31,6 @@ import ( "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/source" - _errors "github.com/pkg/errors" ) // TranslationProvider provides translation handling, i.e. loading @@ -83,7 +83,7 @@ const artificialLangTagPrefix = "art-x-" func addTranslationFile(bundle *i18n.Bundle, r source.File) error { f, err := r.FileInfo().Meta().Open() if err != nil { - return _errors.Wrapf(err, "failed to open translations file %q:", r.LogicalName()) + return fmt.Errorf("failed to open translations file %q:: %w", r.LogicalName(), err) } b := helpers.ReaderToBytes(f) @@ -96,7 +96,7 @@ func addTranslationFile(bundle *i18n.Bundle, r source.File) error { try := artificialLangTagPrefix + lang _, err = language.Parse(try) if err != nil { - return _errors.Errorf("%q %s.", try, err) + return fmt.Errorf("%q: %s", try, err) } name = artificialLangTagPrefix + name } @@ -111,7 +111,7 @@ func addTranslationFile(bundle *i18n.Bundle, r source.File) error { return nil } } - return errWithFileContext(_errors.Wrapf(err, "failed to load translations"), r) + return errWithFileContext(fmt.Errorf("failed to load translations: %w", err), r) } return nil @@ -138,11 +138,6 @@ func errWithFileContext(inerr error, r source.File) error { } defer f.Close() - err, _ = herrors.WithFileContext( - inerr, - realFilename, - f, - herrors.SimpleLineMatcher) + return herrors.NewFileError(realFilename, inerr).UpdateContent(f, herrors.SimpleLineMatcher) - return err } diff --git a/langs/language.go b/langs/language.go @@ -14,6 +14,7 @@ package langs import ( + "fmt" "sort" "strings" "sync" @@ -22,8 +23,6 @@ import ( "golang.org/x/text/collate" "golang.org/x/text/language" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/common/htime" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/config" @@ -311,7 +310,7 @@ func GetCollator(l *Language) *Collator { func (l *Language) loadLocation(tzStr string) error { location, err := time.LoadLocation(tzStr) if err != nil { - return errors.Wrapf(err, "invalid timeZone for language %q", l.Lang) + return fmt.Errorf("invalid timeZone for language %q: %w", l.Lang, err) } l.location = location diff --git a/lazy/init.go b/lazy/init.go @@ -19,7 +19,7 @@ import ( "sync/atomic" "time" - "github.com/pkg/errors" + "errors" ) // New creates a new empty Init. diff --git a/markup/blackfriday/blackfriday_config/config.go b/markup/blackfriday/blackfriday_config/config.go @@ -19,8 +19,9 @@ package blackfriday_config import ( + "fmt" + "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" ) // Default holds the default BlackFriday config. @@ -64,7 +65,7 @@ type Config struct { func UpdateConfig(b Config, m map[string]any) (Config, error) { if err := mapstructure.Decode(m, &b); err != nil { - return b, errors.WithMessage(err, "failed to decode rendering config") + return b, fmt.Errorf("failed to decode rendering config: %w", err) } return b, nil } diff --git a/modules/client.go b/modules/client.go @@ -47,7 +47,8 @@ import ( "github.com/gohugoio/hugo/common/hugio" - "github.com/pkg/errors" + "errors" + "github.com/spf13/afero" ) @@ -241,7 +242,7 @@ func (c *Client) Vendor() error { // See https://github.com/gohugoio/hugo/issues/8239 // This is an error situation. We need something to vendor. if t.Mounts() == nil { - return errors.Errorf("cannot vendor module %q, need at least one mount", t.Path()) + return fmt.Errorf("cannot vendor module %q, need at least one mount", t.Path()) } fmt.Fprintln(&modulesContent, "# "+t.Path()+" "+t.Version()) @@ -253,22 +254,22 @@ func (c *Client) Vendor() error { targetFilename := filepath.Join(vendorDir, t.Path(), mount.Source) fi, err := c.fs.Stat(sourceFilename) if err != nil { - return errors.Wrap(err, "failed to vendor module") + return fmt.Errorf("failed to vendor module: %w", err) } if fi.IsDir() { if err := hugio.CopyDir(c.fs, sourceFilename, targetFilename, nil); err != nil { - return errors.Wrap(err, "failed to copy module to vendor dir") + return fmt.Errorf("failed to copy module to vendor dir: %w", err) } } else { targetDir := filepath.Dir(targetFilename) if err := c.fs.MkdirAll(targetDir, 0755); err != nil { - return errors.Wrap(err, "failed to make target dir") + return fmt.Errorf("failed to make target dir: %w", err) } if err := hugio.CopyFile(c.fs, sourceFilename, targetFilename); err != nil { - return errors.Wrap(err, "failed to copy module file to vendor") + return fmt.Errorf("failed to copy module file to vendor: %w", err) } } } @@ -278,7 +279,7 @@ func (c *Client) Vendor() error { _, err := c.fs.Stat(resourcesDir) if err == nil { if err := hugio.CopyDir(c.fs, resourcesDir, filepath.Join(vendorDir, t.Path(), files.FolderResources), nil); err != nil { - return errors.Wrap(err, "failed to copy resources to vendor dir") + return fmt.Errorf("failed to copy resources to vendor dir: %w", err) } } @@ -287,7 +288,7 @@ func (c *Client) Vendor() error { _, err = c.fs.Stat(configDir) if err == nil { if err := hugio.CopyDir(c.fs, configDir, filepath.Join(vendorDir, t.Path(), "config"), nil); err != nil { - return errors.Wrap(err, "failed to copy config dir to vendor dir") + return fmt.Errorf("failed to copy config dir to vendor dir: %w", err) } } @@ -361,7 +362,7 @@ func (c *Client) get(args ...string) error { args = append([]string{"-d"}, args...) } if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil { - errors.Wrapf(err, "failed to get %q", args) + return fmt.Errorf("failed to get %q: %w", args, err) } return nil } @@ -372,7 +373,7 @@ func (c *Client) get(args ...string) error { func (c *Client) Init(path string) error { err := c.runGo(context.Background(), c.logger.Out(), "mod", "init", path) if err != nil { - return errors.Wrap(err, "failed to init modules") + return fmt.Errorf("failed to init modules: %w", err) } c.GoModulesFilename = filepath.Join(c.ccfg.WorkingDir, goModFilename) @@ -458,7 +459,7 @@ func (c *Client) listGoMods() (goModules, error) { out := ioutil.Discard err := c.runGo(context.Background(), out, args...) if err != nil { - return errors.Wrap(err, "failed to download modules") + return fmt.Errorf("failed to download modules: %w", err) } return nil } @@ -477,7 +478,7 @@ func (c *Client) listGoMods() (goModules, error) { } err := c.runGo(context.Background(), b, args...) if err != nil { - return errors.Wrap(err, "failed to list modules") + return fmt.Errorf("failed to list modules: %w", err) } dec := json.NewDecoder(b) @@ -487,7 +488,7 @@ func (c *Client) listGoMods() (goModules, error) { if err == io.EOF { break } - return errors.Wrap(err, "failed to decode modules list") + return fmt.Errorf("failed to decode modules list: %w", err) } if err := handle(m); err != nil { @@ -657,7 +658,7 @@ If you then run 'hugo mod graph' it should resolve itself to the most recent ver _, ok := err.(*exec.ExitError) if !ok { - return errors.Errorf("failed to execute 'go %v': %s %T", args, err, err) + return fmt.Errorf("failed to execute 'go %v': %s %T", args, err, err) } // Too old Go version @@ -666,7 +667,7 @@ If you then run 'hugo mod graph' it should resolve itself to the most recent ver return nil } - return errors.Errorf("go command failed: %s", stderr) + return fmt.Errorf("go command failed: %s", stderr) } @@ -706,7 +707,7 @@ func (c *Client) shouldVendor(path string) bool { } func (c *Client) createThemeDirname(modulePath string, isProjectMod bool) (string, error) { - invalid := errors.Errorf("invalid module path %q; must be relative to themesDir when defined outside of the project", modulePath) + invalid := fmt.Errorf("invalid module path %q; must be relative to themesDir when defined outside of the project", modulePath) modulePath = filepath.Clean(modulePath) if filepath.IsAbs(modulePath) { diff --git a/modules/collect.go b/modules/collect.go @@ -36,7 +36,7 @@ import ( "github.com/rogpeppe/go-internal/module" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/config" "github.com/spf13/afero" @@ -48,7 +48,7 @@ const vendorModulesFilename = "modules.txt" // IsNotExist returns whether an error means that a module could not be found. func IsNotExist(err error) bool { - return errors.Cause(err) == ErrNotExist + return errors.Is(err, os.ErrNotExist) } // CreateProjectModule creates modules from the given config. @@ -289,7 +289,7 @@ func (c *collector) add(owner *moduleAdapter, moduleImport Import, disabled bool return nil, nil } if found, _ := afero.Exists(c.fs, moduleDir); !found { - c.err = c.wrapModuleNotFound(errors.Errorf(`module %q not found; either add it as a Hugo Module or store it in %q.`, modulePath, c.ccfg.ThemesDir)) + c.err = c.wrapModuleNotFound(fmt.Errorf(`module %q not found; either add it as a Hugo Module or store it in %q.`, modulePath, c.ccfg.ThemesDir)) return nil, nil } } @@ -297,7 +297,7 @@ func (c *collector) add(owner *moduleAdapter, moduleImport Import, disabled bool } if found, _ := afero.Exists(c.fs, moduleDir); !found { - c.err = c.wrapModuleNotFound(errors.Errorf("%q not found", moduleDir)) + c.err = c.wrapModuleNotFound(fmt.Errorf("%q not found", moduleDir)) return nil, nil } @@ -557,7 +557,7 @@ func (c *collector) collectModulesTXT(owner Module) error { line = strings.TrimSpace(line) parts := strings.Fields(line) if len(parts) != 2 { - return errors.Errorf("invalid modules list: %q", filename) + return fmt.Errorf("invalid modules list: %q", filename) } path := parts[0] @@ -662,7 +662,7 @@ func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mou targetBase = mnt.Target[0:idxPathSep] } if !files.IsComponentFolder(targetBase) { - return nil, errors.Errorf("%s: mount target must be one of: %v", errMsg, files.ComponentFolders) + return nil, fmt.Errorf("%s: mount target must be one of: %v", errMsg, files.ComponentFolders) } out = append(out, mnt) @@ -672,7 +672,7 @@ func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mou } func (c *collector) wrapModuleNotFound(err error) error { - err = errors.Wrap(ErrNotExist, err.Error()) + err = fmt.Errorf(err.Error()+": %w", ErrNotExist) if c.GoModulesFilename == "" { return err } @@ -681,9 +681,9 @@ func (c *collector) wrapModuleNotFound(err error) error { switch c.goBinaryStatus { case goBinaryStatusNotFound: - return errors.Wrap(err, baseMsg+" you need to install Go to use it. See https://golang.org/dl/.") + return fmt.Errorf(baseMsg+" you need to install Go to use it. See https://golang.org/dl/ : %q", err) case goBinaryStatusTooOld: - return errors.Wrap(err, baseMsg+" you need to a newer version of Go to use it. See https://golang.org/dl/.") + return fmt.Errorf(baseMsg+" you need to a newer version of Go to use it. See https://golang.org/dl/ : %w", err) } return err diff --git a/modules/config.go b/modules/config.go @@ -18,8 +18,6 @@ import ( "path/filepath" "strings" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/config" @@ -226,7 +224,7 @@ func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Conf for _, repl := range c.Replacements { parts := strings.Split(repl, "->") if len(parts) != 2 { - return c, errors.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl) + return c, fmt.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl) } c.replacementsMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) diff --git a/modules/npm/package_builder.go b/modules/npm/package_builder.go @@ -24,8 +24,6 @@ import ( "github.com/gohugoio/hugo/hugofs/files" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/hugofs" "github.com/spf13/afero" @@ -57,7 +55,7 @@ func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error { if err == nil { // Preserve the original in package.hugo.json. if err = hugio.CopyFile(fs, packageJSONName, files.FilenamePackageHugoJSON); err != nil { - return errors.Wrap(err, "npm pack: failed to copy package file") + return fmt.Errorf("npm pack: failed to copy package file: %w", err) } } else { // Create one. @@ -83,7 +81,7 @@ func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error { masterFilename := meta.Filename f, err := meta.Open() if err != nil { - return errors.Wrap(err, "npm pack: failed to open package file") + return fmt.Errorf("npm pack: failed to open package file: %w", err) } b = newPackageBuilder(meta.Module, f) f.Close() @@ -106,14 +104,14 @@ func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error { f, err := meta.Open() if err != nil { - return errors.Wrap(err, "npm pack: failed to open package file") + return fmt.Errorf("npm pack: failed to open package file: %w", err) } b.Add(meta.Module, f) f.Close() } if b.Err() != nil { - return errors.Wrap(b.Err(), "npm pack: failed to build") + return fmt.Errorf("npm pack: failed to build: %w", b.Err()) } // Replace the dependencies in the original template with the merged set. @@ -136,11 +134,11 @@ func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error { encoder.SetEscapeHTML(false) encoder.SetIndent("", strings.Repeat(" ", 2)) if err := encoder.Encode(b.originalPackageJSON); err != nil { - return errors.Wrap(err, "npm pack: failed to marshal JSON") + return fmt.Errorf("npm pack: failed to marshal JSON: %w", err) } if err := afero.WriteFile(fs, packageJSONName, packageJSONData.Bytes(), 0666); err != nil { - return errors.Wrap(err, "npm pack: failed to write package.json") + return fmt.Errorf("npm pack: failed to write package.json: %w", err) } return nil diff --git a/navigation/menu.go b/navigation/menu.go @@ -19,8 +19,6 @@ import ( "sort" "strings" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/compare" @@ -190,7 +188,7 @@ func (m *MenuEntry) MarshallMap(ime map[string]any) error { } if err != nil { - return errors.Wrapf(err, "failed to marshal menu entry %q", m.KeyName()) + return fmt.Errorf("failed to marshal menu entry %q: %w", m.KeyName(), err) } return nil diff --git a/navigation/pagemenus.go b/navigation/pagemenus.go @@ -14,10 +14,11 @@ package navigation import ( + "fmt" + "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/types" - "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -76,7 +77,7 @@ func PageMenusFromPage(p Page) (PageMenus, error) { } var wrapErr = func(err error) error { - return errors.Wrapf(err, "unable to process menus for page %q", p.Path()) + return fmt.Errorf("unable to process menus for page %q: %w", p.Path(), err) } // Could be a structured menu entry diff --git a/output/outputFormat.go b/output/outputFormat.go @@ -20,8 +20,6 @@ import ( "sort" "strings" - "github.com/pkg/errors" - "github.com/mitchellh/mapstructure" "github.com/gohugoio/hugo/media" @@ -364,7 +362,7 @@ func decode(mediaTypes media.Types, input any, output *Format) error { } dataVal.SetMapIndex(key, reflect.ValueOf(mediaType)) default: - return nil, errors.Errorf("invalid output format configuration; wrong type for media type, expected string (e.g. text/html), got %T", vvi) + return nil, fmt.Errorf("invalid output format configuration; wrong type for media type, expected string (e.g. text/html), got %T", vvi) } } } @@ -379,7 +377,7 @@ func decode(mediaTypes media.Types, input any, output *Format) error { } if err = decoder.Decode(input); err != nil { - return errors.Wrap(err, "failed to decode output format configuration") + return fmt.Errorf("failed to decode output format configuration: %w", err) } return nil diff --git a/parser/metadecoders/decoder.go b/parser/metadecoders/decoder.go @@ -26,7 +26,6 @@ import ( xml "github.com/clbanning/mxj/v2" toml "github.com/pelletier/go-toml/v2" - "github.com/pkg/errors" "github.com/spf13/afero" "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" @@ -74,7 +73,7 @@ func (d Decoder) UnmarshalToMap(data []byte, f Format) (map[string]any, error) { func (d Decoder) UnmarshalFileToMap(fs afero.Fs, filename string) (map[string]any, error) { format := FormatFromString(filename) if format == "" { - return nil, errors.Errorf("%q is not a valid configuration format", filename) + return nil, fmt.Errorf("%q is not a valid configuration format", filename) } data, err := afero.ReadFile(fs, filename) @@ -106,7 +105,7 @@ func (d Decoder) UnmarshalStringTo(data string, typ any) (any, error) { case float64: return cast.ToFloat64E(data) default: - return nil, errors.Errorf("unmarshal: %T not supported", typ) + return nil, fmt.Errorf("unmarshal: %T not supported", typ) } } @@ -144,7 +143,7 @@ func (d Decoder) UnmarshalTo(data []byte, f Format, v any) error { if err == nil { xmlRootName, err := xmlRoot.Root() if err != nil { - return toFileError(f, errors.Wrap(err, "failed to unmarshal XML")) + return toFileError(f, data, fmt.Errorf("failed to unmarshal XML: %w", err)) } xmlValue = xmlRoot[xmlRootName].(map[string]any) } @@ -160,7 +159,7 @@ func (d Decoder) UnmarshalTo(data []byte, f Format, v any) error { case YAML: err = yaml.Unmarshal(data, v) if err != nil { - return toFileError(f, errors.Wrap(err, "failed to unmarshal YAML")) + return toFileError(f, data, fmt.Errorf("failed to unmarshal YAML: %w", err)) } // To support boolean keys, the YAML package unmarshals maps to @@ -191,14 +190,14 @@ func (d Decoder) UnmarshalTo(data []byte, f Format, v any) error { return d.unmarshalCSV(data, v) default: - return errors.Errorf("unmarshal of format %q is not supported", f) + return fmt.Errorf("unmarshal of format %q is not supported", f) } if err == nil { return nil } - return toFileError(f, errors.Wrap(err, "unmarshal failed")) + return toFileError(f, data, fmt.Errorf("unmarshal failed: %w", err)) } func (d Decoder) unmarshalCSV(data []byte, v any) error { @@ -215,7 +214,7 @@ func (d Decoder) unmarshalCSV(data []byte, v any) error { case *any: *v.(*any) = records default: - return errors.Errorf("CSV cannot be unmarshaled into %T", v) + return fmt.Errorf("CSV cannot be unmarshaled into %T", v) } @@ -260,8 +259,8 @@ func (d Decoder) unmarshalORG(data []byte, v any) error { return nil } -func toFileError(f Format, err error) error { - return herrors.ToFileError(string(f), err) +func toFileError(f Format, data []byte, err error) error { + return herrors.NewFileError(fmt.Sprintf("_stream.%s", f), err).UpdateContent(bytes.NewReader(data), nil) } // stringifyMapKeys recurses into in and changes all instances of diff --git a/parser/pageparser/pageparser.go b/parser/pageparser/pageparser.go @@ -15,11 +15,11 @@ package pageparser import ( "bytes" + "fmt" "io" "io/ioutil" "github.com/gohugoio/hugo/parser/metadecoders" - "github.com/pkg/errors" ) // Result holds the parse result. @@ -102,7 +102,7 @@ func ParseMain(r io.Reader, cfg Config) (Result, error) { func parseSection(r io.Reader, cfg Config, start stateFunc) (Result, error) { b, err := ioutil.ReadAll(r) if err != nil { - return nil, errors.Wrap(err, "failed to read page content") + return nil, fmt.Errorf("failed to read page content: %w", err) } return parseBytes(b, cfg, start) } diff --git a/publisher/publisher.go b/publisher/publisher.go @@ -15,6 +15,7 @@ package publisher import ( "errors" + "fmt" "io" "net/url" "sync/atomic" @@ -104,7 +105,7 @@ func (p DestinationPublisher) Publish(d Descriptor) error { defer bp.PutBuffer(b) if err := transformers.Apply(b, d.Src); err != nil { - return err + return fmt.Errorf("failed to process %q: %w", d.TargetPath, err) } // This is now what we write to disk. diff --git a/releaser/releaser.go b/releaser/releaser.go @@ -26,8 +26,9 @@ import ( "github.com/gohugoio/hugo/common/hexec" + "errors" + "github.com/gohugoio/hugo/common/hugo" - "github.com/pkg/errors" ) const commitPrefix = "releaser:" @@ -217,7 +218,7 @@ func (r *ReleaseHandler) release(releaseNotesFile string) error { cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { - return errors.Wrap(err, "goreleaser failed") + return fmt.Errorf("goreleaser failed: %w", err) } return nil } diff --git a/resources/image.go b/resources/image.go @@ -38,9 +38,6 @@ import ( "github.com/gohugoio/hugo/resources/resource" - "github.com/pkg/errors" - _errors "github.com/pkg/errors" - "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/resources/images" @@ -325,7 +322,7 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im }) if err != nil { if i.root != nil && i.root.getFileInfo() != nil { - return nil, errors.Wrapf(err, "image %q", i.root.getFileInfo().Meta().Filename) + return nil, fmt.Errorf("image %q: %w", i.root.getFileInfo().Meta().Filename, err) } } return img, nil @@ -345,7 +342,7 @@ func (i *imageResource) decodeImageConfig(action, spec string) (images.ImageConf func (i *imageResource) DecodeImage() (image.Image, error) { f, err := i.ReadSeekCloser() if err != nil { - return nil, _errors.Wrap(err, "failed to open image for decode") + return nil, fmt.Errorf("failed to open image for decode: %w", err) } defer f.Close() img, _, err := image.Decode(f) diff --git a/resources/images/color.go b/resources/images/color.go @@ -15,10 +15,9 @@ package images import ( "encoding/hex" + "fmt" "image/color" "strings" - - "github.com/pkg/errors" ) // AddColorToPalette adds c as the first color in p if not already there. @@ -50,7 +49,7 @@ func hexStringToColor(s string) (color.Color, error) { s = strings.TrimPrefix(s, "#") if len(s) != 3 && len(s) != 6 { - return nil, errors.Errorf("invalid color code: %q", s) + return nil, fmt.Errorf("invalid color code: %q", s) } s = strings.ToLower(s) diff --git a/resources/images/config.go b/resources/images/config.go @@ -22,7 +22,7 @@ import ( "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/media" - "github.com/pkg/errors" + "errors" "github.com/bep/gowebp/libwebp/webpoptions" @@ -158,7 +158,7 @@ func DecodeConfig(m map[string]any) (ImagingConfig, error) { if i.Cfg.Anchor != "" && i.Cfg.Anchor != smartCropIdentifier { anchor, found := anchorPositions[i.Cfg.Anchor] if !found { - return i, errors.Errorf("invalid anchor value %q in imaging config", i.Anchor) + return i, fmt.Errorf("invalid anchor value %q in imaging config", i.Anchor) } i.Anchor = anchor } else { @@ -263,7 +263,7 @@ func DecodeImageConfig(action, config string, defaults ImagingConfig, sourceForm return c, errors.New("must provide Width or Height") } default: - return c, errors.Errorf("BUG: unknown action %q encountered while decoding image configuration", c.Action) + return c, fmt.Errorf("BUG: unknown action %q encountered while decoding image configuration", c.Action) } if c.FilterStr == "" { diff --git a/resources/images/image.go b/resources/images/image.go @@ -34,8 +34,9 @@ import ( "golang.org/x/image/bmp" "golang.org/x/image/tiff" + "errors" + "github.com/gohugoio/hugo/common/hugio" - "github.com/pkg/errors" ) func NewImage(f Format, proc *ImageProcessor, img image.Image, s Spec) *Image { @@ -163,7 +164,7 @@ func (i *Image) initConfig() error { }) if err != nil { - return errors.Wrap(err, "failed to load image config") + return fmt.Errorf("failed to load image config: %w", err) } return nil @@ -239,7 +240,7 @@ func (p *ImageProcessor) ApplyFiltersFromConfig(src image.Image, conf ImageConfi case "fit": filters = append(filters, gift.ResizeToFit(conf.Width, conf.Height, conf.Filter)) default: - return nil, errors.Errorf("unsupported action: %q", conf.Action) + return nil, fmt.Errorf("unsupported action: %q", conf.Action) } img, err := p.Filter(src, filters...) diff --git a/resources/page/page_generate/generate_page_wrappers.go b/resources/page/page_generate/generate_page_wrappers.go @@ -20,7 +20,7 @@ import ( "path/filepath" "reflect" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/common/maps" @@ -55,15 +55,15 @@ var ( func Generate(c *codegen.Inspector) error { if err := generateMarshalJSON(c); err != nil { - return errors.Wrap(err, "failed to generate JSON marshaler") + return fmt.Errorf("failed to generate JSON marshaler: %w", err) } if err := generateDeprecatedWrappers(c); err != nil { - return errors.Wrap(err, "failed to generate deprecate wrappers") + return fmt.Errorf("failed to generate deprecate wrappers: %w", err) } if err := generateFileIsZeroWrappers(c); err != nil { - return errors.Wrap(err, "failed to generate file wrappers") + return fmt.Errorf("failed to generate file wrappers: %w", err) } return nil diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go @@ -14,13 +14,13 @@ package page import ( + "fmt" "path/filepath" "strings" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/hugofs/glob" "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" ) // A PageMatcher can be used to match a Page with Glob patterns. @@ -132,7 +132,7 @@ func DecodePageMatcher(m any, v *PageMatcher) error { } } if !found { - return errors.Errorf("%q did not match a valid Page Kind", v.Kind) + return fmt.Errorf("%q did not match a valid Page Kind", v.Kind) } } diff --git a/resources/page/pages_related.go b/resources/page/pages_related.go @@ -14,11 +14,11 @@ package page import ( + "fmt" "sync" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/related" - "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -108,7 +108,7 @@ func (p Pages) withInvertedIndex(search func(idx *related.InvertedIndex) ([]rela d, ok := p[0].(InternalDependencies) if !ok { - return nil, errors.Errorf("invalid type %T in related search", p[0]) + return nil, fmt.Errorf("invalid type %T in related search", p[0]) } cache := d.GetRelatedDocsHandler() diff --git a/resources/page/permalinks.go b/resources/page/permalinks.go @@ -23,7 +23,7 @@ import ( "strings" "time" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/helpers" ) diff --git a/resources/resource.go b/resources/resource.go @@ -31,7 +31,7 @@ import ( "github.com/gohugoio/hugo/media" "github.com/gohugoio/hugo/source" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/common/maps" @@ -633,7 +633,7 @@ func (fi *resourceFileInfo) hash() (string, error) { var f hugio.ReadSeekCloser f, err = fi.ReadSeekCloser() if err != nil { - err = errors.Wrap(err, "failed to open source file") + err = fmt.Errorf("failed to open source file: %w", err) return } defer f.Close() diff --git a/resources/resource_factories/create/remote.go b/resources/resource_factories/create/remote.go @@ -16,6 +16,7 @@ package create import ( "bufio" "bytes" + "fmt" "io" "io/ioutil" "mime" @@ -34,7 +35,6 @@ import ( "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources/resource" "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" ) type HTTPError struct { @@ -77,7 +77,7 @@ func toHTTPError(err error, res *http.Response) *HTTPError { func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resource, error) { rURL, err := url.Parse(uri) if err != nil { - return nil, errors.Wrapf(err, "failed to parse URL for resource %s", uri) + return nil, fmt.Errorf("failed to parse URL for resource %s: %w", uri, err) } resourceID := calculateResourceID(uri, optionsm) @@ -85,7 +85,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou _, httpResponse, err := c.cacheGetResource.GetOrCreate(resourceID, func() (io.ReadCloser, error) { options, err := decodeRemoteOptions(optionsm) if err != nil { - return nil, errors.Wrapf(err, "failed to decode options for resource %s", uri) + return nil, fmt.Errorf("failed to decode options for resource %s: %w", uri, err) } if err := c.validateFromRemoteArgs(uri, options); err != nil { return nil, err @@ -93,7 +93,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou req, err := http.NewRequest(options.Method, uri, options.BodyReader()) if err != nil { - return nil, errors.Wrapf(err, "failed to create request for resource %s", uri) + return nil, fmt.Errorf("failed to create request for resource %s: %w", uri, err) } addDefaultHeaders(req) @@ -113,7 +113,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou if res.StatusCode != http.StatusNotFound { if res.StatusCode < 200 || res.StatusCode > 299 { - return nil, toHTTPError(errors.Errorf("failed to fetch remote resource: %s", http.StatusText(res.StatusCode)), res) + return nil, toHTTPError(fmt.Errorf("failed to fetch remote resource: %s", http.StatusText(res.StatusCode)), res) } } @@ -137,7 +137,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou body, err := ioutil.ReadAll(res.Body) if err != nil { - return nil, errors.Wrapf(err, "failed to read remote resource %q", uri) + return nil, fmt.Errorf("failed to read remote resource %q: %w", uri, err) } filename := path.Base(rURL.Path) @@ -172,7 +172,7 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou // Now resolve the media type primarily using the content. mediaType := media.FromContent(c.rs.MediaTypes, extensionHints, body) if mediaType.IsZero() { - return nil, errors.Errorf("failed to resolve media type for remote resource %q", uri) + return nil, fmt.Errorf("failed to resolve media type for remote resource %q", uri) } resourceID = filename[:len(filename)-len(path.Ext(filename))] + "_" + resourceID + mediaType.FirstSuffix.FullSuffix diff --git a/resources/resource_metadata.go b/resources/resource_metadata.go @@ -22,7 +22,6 @@ import ( "github.com/gohugoio/hugo/media" "github.com/gohugoio/hugo/resources/resource" - "github.com/pkg/errors" "github.com/spf13/cast" "github.com/gohugoio/hugo/common/maps" @@ -85,7 +84,7 @@ func AssignMetadata(metadata []map[string]any, resources ...resource.Resource) e glob, err := glob.GetGlob(srcKey) if err != nil { - return errors.Wrap(err, "failed to match resource with metadata") + return fmt.Errorf("failed to match resource with metadata: %w", err) } match := glob.Match(resourceSrcKey) diff --git a/resources/resource_transformers/babel/babel.go b/resources/resource_transformers/babel/babel.go @@ -15,6 +15,7 @@ package babel import ( "bytes" + "fmt" "io" "io/ioutil" "os" @@ -34,7 +35,6 @@ import ( "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources/resource" - "github.com/pkg/errors" ) // Options from https://babeljs.io/docs/en/options @@ -141,7 +141,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx configFile = t.rs.BaseFs.ResolveJSConfigFile(configFile) if configFile == "" && t.options.Config != "" { // Only fail if the user specified config file is not found. - return errors.Errorf("babel config %q not found:", configFile) + return fmt.Errorf("babel config %q not found:", configFile) } } @@ -203,7 +203,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx if hexec.IsNotFound(err) { return herrors.ErrFeatureNotAvailable } - return errors.Wrap(err, errBuf.String()) + return fmt.Errorf(errBuf.String()+": %w", err) } content, err := ioutil.ReadAll(compileOutput) diff --git a/resources/resource_transformers/integrity/integrity.go b/resources/resource_transformers/integrity/integrity.go @@ -19,14 +19,13 @@ import ( "crypto/sha512" "encoding/base64" "encoding/hex" + "fmt" "hash" "html/template" "io" "github.com/gohugoio/hugo/resources/internal" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources/resource" ) @@ -92,7 +91,7 @@ func newHash(algo string) (hash.Hash, error) { case "sha512": return sha512.New(), nil default: - return nil, errors.Errorf("unsupported crypto algo: %q, use either md5, sha256, sha384 or sha512", algo) + return nil, fmt.Errorf("unsupported crypto algo: %q, use either md5, sha256, sha384 or sha512", algo) } } diff --git a/resources/resource_transformers/js/build.go b/resources/resource_transformers/js/build.go @@ -22,13 +22,14 @@ import ( "regexp" "strings" - "github.com/pkg/errors" + "errors" "github.com/spf13/afero" "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/text" "github.com/gohugoio/hugo/hugolib/filesystems" "github.com/gohugoio/hugo/media" @@ -109,13 +110,13 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx for i, ext := range opts.Inject { impPath := filepath.FromSlash(ext) if filepath.IsAbs(impPath) { - return errors.Errorf("inject: absolute paths not supported, must be relative to /assets") + return fmt.Errorf("inject: absolute paths not supported, must be relative to /assets") } m := resolveComponentInAssets(t.c.rs.Assets.Fs, impPath) if m == nil { - return errors.Errorf("inject: file %q not found", ext) + return fmt.Errorf("inject: file %q not found", ext) } opts.Inject[i] = m.Filename @@ -157,10 +158,12 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx } if err == nil { - fe := herrors.NewFileError("js", 0, loc.Line, loc.Column, errors.New(msg.Text)) - err, _ := herrors.WithFileContext(fe, path, f, herrors.SimpleLineMatcher) + fe := herrors.NewFileError(path, errors.New(msg.Text)). + UpdatePosition(text.Position{Offset: -1, LineNumber: loc.Line, ColumnNumber: loc.Column}). + UpdateContent(f, herrors.SimpleLineMatcher) + f.Close() - return err + return fe } return fmt.Errorf("%s", msg.Text) diff --git a/resources/resource_transformers/js/options.go b/resources/resource_transformers/js/options.go @@ -21,7 +21,6 @@ import ( "strings" "github.com/gohugoio/hugo/common/maps" - "github.com/pkg/errors" "github.com/spf13/afero" "github.com/evanw/esbuild/pkg/api" @@ -251,7 +250,7 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) { func(args api.OnLoadArgs) (api.OnLoadResult, error) { b, err := ioutil.ReadFile(args.Path) if err != nil { - return api.OnLoadResult{}, errors.Wrapf(err, "failed to read %q", args.Path) + return api.OnLoadResult{}, fmt.Errorf("failed to read %q: %w", args.Path, err) } c := string(b) return api.OnLoadResult{ @@ -274,7 +273,7 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) { b, err := json.Marshal(params) if err != nil { - return nil, errors.Wrap(err, "failed to marshal params") + return nil, fmt.Errorf("failed to marshal params: %w", err) } bs := string(b) paramsPlugin := api.Plugin{ diff --git a/resources/resource_transformers/postcss/postcss.go b/resources/resource_transformers/postcss/postcss.go @@ -17,6 +17,7 @@ import ( "bytes" "crypto/sha256" "encoding/hex" + "fmt" "io" "io/ioutil" "path" @@ -36,8 +37,9 @@ import ( "github.com/spf13/afero" "github.com/spf13/cast" + "errors" + "github.com/gohugoio/hugo/hugofs" - "github.com/pkg/errors" "github.com/mitchellh/mapstructure" @@ -161,7 +163,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC configFile = t.rs.BaseFs.ResolveJSConfigFile(configFile) if configFile == "" && t.options.Config != "" { // Only fail if the user specified config file is not found. - return errors.Errorf("postcss config %q not found:", configFile) + return fmt.Errorf("postcss config %q not found:", configFile) } } @@ -388,15 +390,9 @@ func (imp *importResolver) toFileError(output string) error { if err != nil { return inErr } - realFilename := fi.(hugofs.FileMetaInfo).Meta().Filename - - ferr := herrors.NewFileError("css", -1, file.Offset+1, 1, inErr) - werr, ok := herrors.WithFileContextForFile(ferr, realFilename, file.Filename, imp.fs, herrors.SimpleLineMatcher) + realFilename := fi.(hugofs.FileMetaInfo).Meta().Filename - if !ok { - return ferr - } + return herrors.NewFileErrorFromFile(inErr, file.Filename, realFilename, hugofs.Os, herrors.SimpleLineMatcher) - return werr } diff --git a/resources/resource_transformers/templates/execute_as_template.go b/resources/resource_transformers/templates/execute_as_template.go @@ -15,12 +15,13 @@ package templates import ( + "fmt" + "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources/internal" "github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/tpl" - "github.com/pkg/errors" ) // Client contains methods to perform template processing of Resource objects. @@ -55,7 +56,7 @@ func (t *executeAsTemplateTransform) Transform(ctx *resources.ResourceTransforma tplStr := helpers.ReaderToString(ctx.From) templ, err := t.t.TextTmpl().Parse(ctx.InPath, tplStr) if err != nil { - return errors.Wrapf(err, "failed to parse Resource %q as Template:", ctx.InPath) + return fmt.Errorf("failed to parse Resource %q as Template:: %w", ctx.InPath, err) } ctx.OutPath = t.targetPath diff --git a/resources/resource_transformers/tocss/dartsass/transform.go b/resources/resource_transformers/tocss/dartsass/transform.go @@ -120,18 +120,8 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error { return m.Offset+len(m.Line) >= start.Offset && strings.Contains(m.Line, context) } - ferr, ok := herrors.WithFileContextForFile( - herrors.NewFileError("scss", -1, -1, start.Column, sassErr), - filename, - filename, - hugofs.Os, - offsetMatcher) - - if !ok { - return sassErr - } + return herrors.NewFileErrorFromFile(sassErr, filename, filename, hugofs.Os, offsetMatcher) - return ferr } return err } diff --git a/resources/resource_transformers/tocss/scss/tocss.go b/resources/resource_transformers/tocss/scss/tocss.go @@ -28,7 +28,6 @@ import ( "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/media" "github.com/gohugoio/hugo/resources" - "github.com/pkg/errors" ) // Used in tests. This feature requires Hugo to be built with the extended tag. @@ -172,7 +171,7 @@ func (c *Client) toCSS(options libsass.Options, dst io.Writer, src io.Reader) (l in := helpers.ReaderToString(src) // See https://github.com/gohugoio/hugo/issues/7059 - // We need to preserver the regular CSS imports. This is by far + // We need to preserve the regular CSS imports. This is by far // a perfect solution, and only works for the main entry file, but // that should cover many use cases, e.g. using SCSS as a preprocessor // for Tailwind. @@ -181,7 +180,7 @@ func (c *Client) toCSS(options libsass.Options, dst io.Writer, src io.Reader) (l res, err = transpiler.Execute(in) if err != nil { - return res, errors.Wrap(err, "SCSS processing failed") + return res, fmt.Errorf("SCSS processing failed: %w", err) } out := res.CSS diff --git a/resources/transform.go b/resources/transform.go @@ -24,8 +24,6 @@ import ( "github.com/gohugoio/hugo/common/paths" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/resources/images" "github.com/gohugoio/hugo/resources/images/exif" "github.com/spf13/afero" @@ -431,10 +429,10 @@ func (r *resourceAdapter) transform(publish, setContent bool) error { errMsg = ". You need to install Babel, see https://gohugo.io/hugo-pipes/babel/" } - return errors.Wrap(err, msg+errMsg) + return fmt.Errorf(msg+errMsg+": %w", err) } - return errors.Wrap(err, msg) + return fmt.Errorf(msg+": %w", err) } var tryFileCache bool @@ -461,7 +459,7 @@ func (r *resourceAdapter) transform(publish, setContent bool) error { if err != nil { return newErr(err) } - return newErr(errors.Errorf("resource %q not found in file cache", key)) + return newErr(fmt.Errorf("resource %q not found in file cache", key)) } transformedContentr = f updates.sourceFs = cache.fileCache.Fs diff --git a/source/fileInfo.go b/source/fileInfo.go @@ -14,6 +14,7 @@ package source import ( + "fmt" "path/filepath" "strings" "sync" @@ -22,8 +23,6 @@ import ( "github.com/gohugoio/hugo/hugofs/files" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/hugofs" @@ -244,11 +243,11 @@ func (sp *SourceSpec) NewFileInfo(fi hugofs.FileMetaInfo) (*FileInfo, error) { relPath := m.Path if relPath == "" { - return nil, errors.Errorf("no Path provided by %v (%T)", m, m.Fs) + return nil, fmt.Errorf("no Path provided by %v (%T)", m, m.Fs) } if filename == "" { - return nil, errors.Errorf("no Filename provided by %v (%T)", m, m.Fs) + return nil, fmt.Errorf("no Filename provided by %v (%T)", m, m.Fs) } relDir := filepath.Dir(relPath) diff --git a/source/filesystem.go b/source/filesystem.go @@ -14,11 +14,10 @@ package source import ( + "fmt" "path/filepath" "sync" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/hugofs" ) @@ -49,7 +48,7 @@ func (f *Filesystem) Files() ([]File, error) { f.filesInit.Do(func() { err := f.captureFiles() if err != nil { - f.filesInitErr = errors.Wrap(err, "capture files") + f.filesInitErr = fmt.Errorf("capture files: %w", err) } }) return f.files, f.filesInitErr diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go @@ -24,12 +24,13 @@ import ( "strings" "time" + "errors" + "github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" - "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -736,7 +737,7 @@ func (ns *Namespace) Uniq(seq any) (any, error) { case reflect.Array: slice = reflect.MakeSlice(reflect.SliceOf(v.Type().Elem()), 0, 0) default: - return nil, errors.Errorf("type %T not supported", seq) + return nil, fmt.Errorf("type %T not supported", seq) } seen := make(map[any]bool) diff --git a/tpl/collections/merge.go b/tpl/collections/merge.go @@ -14,13 +14,14 @@ package collections import ( + "fmt" "reflect" "strings" "github.com/gohugoio/hugo/common/hreflect" "github.com/gohugoio/hugo/common/maps" - "github.com/pkg/errors" + "errors" ) // Merge creates a copy of the final parameter and merges the preceding @@ -49,7 +50,7 @@ func (ns *Namespace) merge(src, dst any) (any, error) { vdst, vsrc := reflect.ValueOf(dst), reflect.ValueOf(src) if vdst.Kind() != reflect.Map { - return nil, errors.Errorf("destination must be a map, got %T", dst) + return nil, fmt.Errorf("destination must be a map, got %T", dst) } if !hreflect.IsTruthfulValue(vsrc) { @@ -57,11 +58,11 @@ func (ns *Namespace) merge(src, dst any) (any, error) { } if vsrc.Kind() != reflect.Map { - return nil, errors.Errorf("source must be a map, got %T", src) + return nil, fmt.Errorf("source must be a map, got %T", src) } if vsrc.Type().Key() != vdst.Type().Key() { - return nil, errors.Errorf("incompatible map types, got %T to %T", src, dst) + return nil, fmt.Errorf("incompatible map types, got %T to %T", src, dst) } return mergeMap(vdst, vsrc).Interface(), nil diff --git a/tpl/collections/reflect_helpers.go b/tpl/collections/reflect_helpers.go @@ -18,8 +18,9 @@ import ( "reflect" "time" + "errors" + "github.com/mitchellh/hashstructure" - "github.com/pkg/errors" ) var ( @@ -103,7 +104,7 @@ func convertValue(v reflect.Value, to reflect.Type) (reflect.Value, error) { case isNumber(kind): return convertNumber(v, kind) default: - return reflect.Value{}, errors.Errorf("%s is not assignable to %s", v.Type(), to) + return reflect.Value{}, fmt.Errorf("%s is not assignable to %s", v.Type(), to) } } diff --git a/tpl/collections/symdiff.go b/tpl/collections/symdiff.go @@ -16,8 +16,6 @@ package collections import ( "fmt" "reflect" - - "github.com/pkg/errors" ) // SymDiff returns the symmetric difference of s1 and s2. @@ -54,7 +52,7 @@ func (ns *Namespace) SymDiff(s2, s1 any) (any, error) { if ids1[key] != ids2[key] { v, err := convertValue(ev, sliceElemType) if err != nil { - return nil, errors.WithMessage(err, "symdiff: failed to convert value") + return nil, fmt.Errorf("symdiff: failed to convert value: %w", err) } slice = reflect.Append(slice, v) } diff --git a/tpl/data/data.go b/tpl/data/data.go @@ -20,6 +20,7 @@ import ( "encoding/csv" "encoding/json" "errors" + "fmt" "net/http" "strings" @@ -35,7 +36,6 @@ import ( "github.com/gohugoio/hugo/cache/filecache" "github.com/gohugoio/hugo/deps" - _errors "github.com/pkg/errors" ) // New returns a new instance of the data-namespaced template functions. @@ -69,7 +69,7 @@ func (ns *Namespace) GetCSV(sep string, args ...any) (d [][]string, err error) { unmarshal := func(b []byte) (bool, error) { if d, err = parseCSV(b, sep); err != nil { - err = _errors.Wrapf(err, "failed to parse CSV file %s", url) + err = fmt.Errorf("failed to parse CSV file %s: %w", url, err) return true, err } @@ -80,7 +80,7 @@ func (ns *Namespace) GetCSV(sep string, args ...any) (d [][]string, 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 getCSV for resource %s", url) + return nil, fmt.Errorf("failed to create request for getCSV for resource %s: %w", url, err) } // Add custom user headers. @@ -109,7 +109,7 @@ func (ns *Namespace) GetJSON(args ...any) (any, error) { 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, fmt.Errorf("Failed to create request for getJSON resource %s: %w", url, err) } unmarshal := func(b []byte) (bool, error) { diff --git a/tpl/data/resources.go b/tpl/data/resources.go @@ -15,14 +15,13 @@ package data import ( "bytes" + "fmt" "io/ioutil" "net/http" "net/url" "path/filepath" "time" - "github.com/pkg/errors" - "github.com/gohugoio/hugo/cache/filecache" "github.com/gohugoio/hugo/config" @@ -70,7 +69,7 @@ func (ns *Namespace) getRemote(cache *filecache.Cache, unmarshal func([]byte) (b res.Body.Close() if isHTTPError(res) { - return nil, errors.Errorf("Failed to retrieve remote file: %s, body: %q", http.StatusText(res.StatusCode), b) + return nil, fmt.Errorf("Failed to retrieve remote file: %s, body: %q", http.StatusText(res.StatusCode), b) } retry, err = unmarshal(b) diff --git a/tpl/images/images.go b/tpl/images/images.go @@ -18,7 +18,7 @@ import ( "image" "sync" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/resources/images" diff --git a/tpl/internal/resourcehelpers/helpers.go b/tpl/internal/resourcehelpers/helpers.go @@ -19,8 +19,6 @@ import ( "errors" "fmt" - _errors "github.com/pkg/errors" - "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/resources" ) @@ -64,7 +62,7 @@ func ResolveArgs(args []any) (resources.ResourceTransformer, map[string]any, err m, err := maps.ToStringMapE(args[0]) if err != nil { - return nil, nil, _errors.Wrap(err, "invalid options type") + return nil, nil, fmt.Errorf("invalid options type: %w", err) } return r, m, nil diff --git a/tpl/lang/lang.go b/tpl/lang/lang.go @@ -20,9 +20,10 @@ import ( "strconv" "strings" + "errors" + "github.com/gohugoio/locales" translators "github.com/gohugoio/localescompressed" - "github.com/pkg/errors" "github.com/gohugoio/hugo/common/hreflect" "github.com/gohugoio/hugo/deps" @@ -49,7 +50,7 @@ func (ns *Namespace) Translate(id any, args ...any) (string, error) { if len(args) > 0 { if len(args) > 1 { - return "", errors.Errorf("wrong number of arguments, expecting at most 2, got %d", len(args)+1) + return "", fmt.Errorf("wrong number of arguments, expecting at most 2, got %d", len(args)+1) } templateData = args[0] } diff --git a/tpl/openapi/openapi3/openapi3.go b/tpl/openapi/openapi3/openapi3.go @@ -14,11 +14,12 @@ package openapi3 import ( + "fmt" "io/ioutil" gyaml "github.com/ghodss/yaml" - "github.com/pkg/errors" + "errors" kopenapi3 "github.com/getkin/kin-openapi/openapi3" "github.com/gohugoio/hugo/cache/namedmemcache" @@ -57,7 +58,7 @@ func (ns *Namespace) Unmarshal(r resource.UnmarshableResource) (*kopenapi3.T, er v, err := ns.cache.GetOrCreate(key, func() (any, error) { f := metadecoders.FormatFromMediaType(r.MediaType()) if f == "" { - return nil, errors.Errorf("MIME %q not supported", r.MediaType()) + return nil, fmt.Errorf("MIME %q not supported", r.MediaType()) } reader, err := r.ReadSeekCloser() diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go @@ -20,8 +20,9 @@ import ( "github.com/gohugoio/hugo/common/herrors" + "errors" + "github.com/gohugoio/hugo/common/maps" - "github.com/pkg/errors" "github.com/gohugoio/hugo/tpl/internal/resourcehelpers" @@ -162,7 +163,7 @@ func (ns *Namespace) GetRemote(args ...any) resource.Resource { case *create.HTTPError: return resources.NewErrorResource(resource.NewResourceError(v, v.Data)) default: - return resources.NewErrorResource(resource.NewResourceError(errors.Wrap(err, "error calling resources.GetRemote"), make(map[string]any))) + return resources.NewErrorResource(resource.NewResourceError(fmt.Errorf("error calling resources.GetRemote: %w", err), make(map[string]any))) } } @@ -354,7 +355,7 @@ func (ns *Namespace) ToCSS(args ...any) (resource.Resource, error) { case transpilerDart, transpilerLibSass: transpiler = cast.ToString(t) default: - return nil, errors.Errorf("unsupported transpiler %q; valid values are %q or %q", t, transpilerLibSass, transpilerDart) + return nil, fmt.Errorf("unsupported transpiler %q; valid values are %q or %q", t, transpilerLibSass, transpilerDart) } } } diff --git a/tpl/strings/strings.go b/tpl/strings/strings.go @@ -16,6 +16,7 @@ package strings import ( "errors" + "fmt" "html/template" "regexp" "strings" @@ -25,7 +26,6 @@ import ( "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" - _errors "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -48,7 +48,7 @@ type Namespace struct { func (ns *Namespace) CountRunes(s any) (int, error) { ss, err := cast.ToStringE(s) if err != nil { - return 0, _errors.Wrap(err, "Failed to convert content to string") + return 0, fmt.Errorf("Failed to convert content to string: %w", err) } counter := 0 @@ -65,7 +65,7 @@ func (ns *Namespace) CountRunes(s any) (int, error) { func (ns *Namespace) RuneCount(s any) (int, error) { ss, err := cast.ToStringE(s) if err != nil { - return 0, _errors.Wrap(err, "Failed to convert content to string") + return 0, fmt.Errorf("Failed to convert content to string: %w", err) } return utf8.RuneCountInString(ss), nil } @@ -74,12 +74,12 @@ func (ns *Namespace) RuneCount(s any) (int, error) { func (ns *Namespace) CountWords(s any) (int, error) { ss, err := cast.ToStringE(s) if err != nil { - return 0, _errors.Wrap(err, "Failed to convert content to string") + return 0, fmt.Errorf("Failed to convert content to string: %w", err) } isCJKLanguage, err := regexp.MatchString(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`, ss) if err != nil { - return 0, _errors.Wrap(err, "Failed to match regex pattern against string") + return 0, fmt.Errorf("Failed to match regex pattern against string: %w", err) } if !isCJKLanguage { @@ -104,11 +104,11 @@ func (ns *Namespace) CountWords(s any) (int, error) { func (ns *Namespace) Count(substr, s any) (int, error) { substrs, err := cast.ToStringE(substr) if err != nil { - return 0, _errors.Wrap(err, "Failed to convert substr to string") + return 0, fmt.Errorf("Failed to convert substr to string: %w", err) } ss, err := cast.ToStringE(s) if err != nil { - return 0, _errors.Wrap(err, "Failed to convert s to string") + return 0, fmt.Errorf("Failed to convert s to string: %w", err) } return strings.Count(ss, substrs), nil } diff --git a/tpl/tplimpl/embedded/templates/server/error.html b/tpl/tplimpl/embedded/templates/server/error.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html class="no-js" lang=""> + <head> + <meta charset="utf-8" /> + <title>Hugo Server: Error</title> + <style type="text/css"> + body { + font-family: "Muli", avenir, -apple-system, BlinkMacSystemFont, + "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 16px; + color: #48b685; + background-color: #2f1e2e; + } + main { + margin: auto; + width: 95%; + padding: 1rem; + } + .version { + color: #ccc; + padding: 1rem 0; + } + .stack { + margin-top: 4rem; + } + pre { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; + } + .highlight { + overflow-x: auto; + margin-bottom: 1rem; + } + a { + color: #0594cb; + text-decoration: none; + } + a:hover { + color: #ccc; + } + </style> + </head> + <body> + <main> + {{ highlight .Error "apl" "linenos=false,noclasses=true,style=paraiso-dark" }} + {{ range $i, $e := .Files }} + {{ if not .ErrorContext }} + {{ continue }} + {{ end }} + {{ $params := printf "noclasses=true,style=paraiso-dark,linenos=table,hl_lines=%d,linenostart=%d" (add .ErrorContext.LinesPos 1) (sub .Position.LineNumber .ErrorContext.LinesPos) }} + {{ $lexer := .ErrorContext.ChromaLexer | default "go-html-template" }} + <h3><code>{{ path.Base .Position.Filename }}:</code></h3> + {{ highlight (delimit .ErrorContext.Lines "\n") $lexer $params }} + {{ end }} + <p class="version">{{ .Version }}</p> + <a href="">Reload Page</a> + </main> + </body> +</html> diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go @@ -17,6 +17,7 @@ import ( "bytes" "context" "embed" + "fmt" "io" "io/fs" "os" @@ -42,7 +43,6 @@ import ( "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/hugofs/files" - "github.com/pkg/errors" htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" @@ -551,14 +551,10 @@ func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error } defer f.Close() - fe, ok := herrors.WithFileContext(inErr, info.realFilename, f, lineMatcher) - if ok { - return fe, true - } - return inErr, false + return herrors.NewFileError(info.realFilename, inErr).UpdateContent(f, lineMatcher), true } - inerr = errors.Wrap(inerr, "execute of template failed") + inerr = fmt.Errorf("execute of template failed: %w", inerr) if err, ok := checkFilename(ts.info, inerr); ok { return err @@ -736,6 +732,7 @@ func (t *templateHandler) extractIdentifiers(line string) []string { //go:embed embedded/templates/* //go:embed embedded/templates/_default/* +//go:embed embedded/templates/server/* var embededTemplatesFs embed.FS func (t *templateHandler) loadEmbedded() error { @@ -755,10 +752,10 @@ func (t *templateHandler) loadEmbedded() error { name := strings.TrimPrefix(filepath.ToSlash(path), "embedded/templates/") templateName := name - // For the render hooks it does not make sense to preseve the + // For the render hooks and the server templates it does not make sense to preseve the // double _indternal double book-keeping, // just add it if its now provided by the user. - if !strings.Contains(path, "_default/_markup") { + if !strings.Contains(path, "_default/_markup") && !strings.HasPrefix(name, "server/") { templateName = internalPathPrefix + name } diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go @@ -14,6 +14,7 @@ package tplimpl import ( + "fmt" "regexp" "strings" @@ -22,10 +23,11 @@ import ( "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse" + "errors" + "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/tpl" "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" ) type templateType int @@ -239,14 +241,14 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) { } if s, ok := cmd.Args[0].(*parse.StringNode); ok { - errMsg := "failed to decode $_hugo_config in template" + errMsg := "failed to decode $_hugo_config in template: %w" m, err := maps.ToStringMapE(s.Text) if err != nil { - c.err = errors.Wrap(err, errMsg) + c.err = fmt.Errorf(errMsg, err) return } if err := mapstructure.WeakDecode(m, &c.t.parseInfo.Config); err != nil { - c.err = errors.Wrap(err, errMsg) + c.err = fmt.Errorf(errMsg, err) } } } diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go @@ -14,8 +14,9 @@ package tplimpl import ( + "fmt" + "github.com/gohugoio/hugo/common/herrors" - "github.com/pkg/errors" "github.com/spf13/afero" ) @@ -51,14 +52,13 @@ func (t templateInfo) resolveType() templateType { } func (info templateInfo) errWithFileContext(what string, err error) error { - err = errors.Wrapf(err, what) - - err, _ = herrors.WithFileContextForFile( - err, - info.realFilename, - info.filename, - info.fs, - herrors.SimpleLineMatcher) + err = fmt.Errorf(what+": %w", err) + fe := herrors.NewFileError(info.realFilename, err) + f, err := info.fs.Open(info.filename) + if err != nil { + return err + } + defer f.Close() + return fe.UpdateContent(f, herrors.SimpleLineMatcher) - return err } diff --git a/tpl/transform/remarshal.go b/tpl/transform/remarshal.go @@ -4,7 +4,7 @@ import ( "bytes" "strings" - "github.com/pkg/errors" + "errors" "github.com/gohugoio/hugo/parser" "github.com/gohugoio/hugo/parser/metadecoders" diff --git a/tpl/transform/unmarshal.go b/tpl/transform/unmarshal.go @@ -14,6 +14,7 @@ package transform import ( + "fmt" "io/ioutil" "strings" @@ -23,9 +24,10 @@ import ( "github.com/mitchellh/mapstructure" + "errors" + "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/parser/metadecoders" - "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -54,7 +56,7 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) { data = args[1] decoder, err = decodeDecoder(m) if err != nil { - return nil, errors.WithMessage(err, "failed to decode options") + return nil, fmt.Errorf("failed to decode options: %w", err) } } @@ -72,7 +74,7 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) { return ns.cache.GetOrCreate(key, func() (any, error) { f := metadecoders.FormatFromMediaType(r.MediaType()) if f == "" { - return nil, errors.Errorf("MIME %q not supported", r.MediaType()) + return nil, fmt.Errorf("MIME %q not supported", r.MediaType()) } reader, err := r.ReadSeekCloser() @@ -92,7 +94,7 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) { dataStr, err := types.ToStringE(data) if err != nil { - return nil, errors.Errorf("type %T not supported", data) + return nil, fmt.Errorf("type %T not supported", data) } if dataStr == "" { @@ -160,7 +162,7 @@ func stringToRune(v any) (rune, error) { if i == 0 { r = rr } else { - return 0, errors.Errorf("invalid character: %q", v) + return 0, fmt.Errorf("invalid character: %q", v) } } diff --git a/tpl/urls/urls.go b/tpl/urls/urls.go @@ -22,7 +22,6 @@ import ( "github.com/gohugoio/hugo/common/urls" "github.com/gohugoio/hugo/deps" - _errors "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -55,7 +54,7 @@ func (ns *Namespace) AbsURL(s any) (template.HTML, error) { func (ns *Namespace) Parse(rawurl any) (*url.URL, error) { s, err := cast.ToStringE(rawurl) if err != nil { - return nil, _errors.Wrap(err, "Error in Parse") + return nil, fmt.Errorf("Error in Parse: %w", err) } return url.Parse(s) diff --git a/transform/chain.go b/transform/chain.go @@ -16,8 +16,11 @@ package transform import ( "bytes" "io" + "io/ioutil" bp "github.com/gohugoio/hugo/bufferpool" + "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/hugofs" ) // Transformer is the func that needs to be implemented by a transformation step. @@ -103,7 +106,17 @@ func (c *Chain) Apply(to io.Writer, from io.Reader) error { } if err := tr(fb); err != nil { - return err + // Write output to a temp file so it can be read by the user for trouble shooting. + filename := "output.html" + tempfile, ferr := ioutil.TempFile("", "hugo-transform-error") + if ferr == nil { + filename = tempfile.Name() + defer tempfile.Close() + _, _ = io.Copy(tempfile, fb.from) + return herrors.NewFileErrorFromFile(err, filename, filename, hugofs.Os, nil) + } + return herrors.NewFileError(filename, err).UpdateContent(fb.from, nil) + } }