From f2946da9e806c2bafbdd26707fe339db79bd980b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 2 May 2022 16:07:52 +0200 Subject: 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 --- hugolib/config.go | 5 +- hugolib/configdir_test.go | 2 +- hugolib/content_factory.go | 10 +-- hugolib/content_map.go | 3 +- hugolib/content_map_page.go | 7 +-- hugolib/fileInfo.go | 5 +- hugolib/filesystems/basefs.go | 10 ++- hugolib/hugo_sites.go | 39 +++++------- hugolib/hugo_sites_build.go | 10 +-- hugolib/hugo_sites_build_errors_test.go | 108 ++++++++++++++++++++++++++------ hugolib/integrationtest_builder.go | 3 +- hugolib/page.go | 78 +++++++++++++++-------- hugolib/page__meta.go | 3 +- hugolib/page__per_output.go | 7 ++- hugolib/page__ref.go | 5 +- hugolib/page_unwrap.go | 4 +- hugolib/pages_process.go | 3 +- hugolib/paths/paths.go | 3 +- hugolib/shortcode.go | 31 +++++---- hugolib/shortcode_test.go | 4 +- hugolib/site.go | 14 ++--- hugolib/site_render.go | 5 +- hugolib/testhelpers_test.go | 10 +-- 23 files changed, 226 insertions(+), 143 deletions(-) (limited to 'hugolib') diff --git a/hugolib/config.go b/hugolib/config.go index 5d2c6ddf7..b2713758a 100644 --- 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 index 998010318..7ac3f969d 100644 --- 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 index bea98894d..e6e82979f 100644 --- 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 index 330391dcb..6849998b6 100644 --- 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 index 21a4e8f2a..a16e4720d 100644 --- 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 index fdfd34b16..1cdd7041d 100644 --- 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 index d02f8c624..2447246d6 100644 --- 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 index d67652dab..e0d69a8af 100644 --- 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 index 4616b6dbb..1a191257c 100644 --- 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 index 8b23e7ac7..8f983075d 100644 --- 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 -- + + + + +` + + 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 index 58e751892..c3fb9fffc 100644 --- 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 index 77165c072..4faefa3cc 100644 --- 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 index 0c269d57a..1c8748869 100644 --- 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 index c77d7c32e..fdce8e802 100644 --- 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 index 242317973..e55a8a3e4 100644 --- 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 index 0e985953b..c3e1ce8dd 100644 --- 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 index 47687eaad..04ac0218a 100644 --- 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 index 9d5716e16..501665676 100644 --- 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 index ac9e4a699..42877b537 100644 --- 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

(.*)

\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 index c2c5abe87..d1a844423 100644 --- 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 index bbabf91a3..cf7f0ff82 100644 --- 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 index c09e5cc99..b572c443e 100644 --- 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 index 6c1aa6fd0..4a72836af 100644 --- 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") -- cgit v1.2.3