From e25aa655f4227ac064be5fe770d517a80acd46b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 18 Jul 2018 19:58:39 +0200 Subject: Add configurable ref/relref error handling and notFoundURL Two new settings: * refLinksErrorLevel: ERROR (default) or WARNING. ERROR will fail the build. * refLinksNotFoundURL: Used as a placeholder when page references cannot be found. Fixes #4964 --- hugolib/config_test.go | 2 +- hugolib/hugo_sites.go | 19 +++++- hugolib/page.go | 15 ++++- hugolib/shortcode_test.go | 18 +++--- hugolib/site.go | 146 ++++++++++++++++++++++---------------------- hugolib/site_test.go | 2 +- hugolib/testhelpers_test.go | 2 +- 7 files changed, 112 insertions(+), 92 deletions(-) diff --git a/hugolib/config_test.go b/hugolib/config_test.go index 0fe692805..16d07d1af 100644 --- a/hugolib/config_test.go +++ b/hugolib/config_test.go @@ -392,6 +392,6 @@ privacyEnhanced = true b.WithConfigFile("toml", tomlConfig) b.Build(BuildCfg{SkipRender: true}) - assert.True(b.H.Sites[0].Info.Config.Privacy.YouTube.PrivacyEnhanced) + assert.True(b.H.Sites[0].Info.Config().Privacy.YouTube.PrivacyEnhanced) } diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 8cb3cf2fd..859e0da7c 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -129,7 +129,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { s.owner = h } - if err := applyDepsIfNeeded(cfg, sites...); err != nil { + if err := applyDeps(cfg, sites...); err != nil { return nil, err } @@ -161,7 +161,7 @@ func (h *HugoSites) initGitInfo() error { return nil } -func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error { +func applyDeps(cfg deps.DepsCfg, sites ...*Site) error { if cfg.TemplateProvider == nil { cfg.TemplateProvider = tplimpl.DefaultTemplateProvider } @@ -208,6 +208,19 @@ func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error { s.Deps = d } + if err := s.initializeSiteInfo(); err != nil { + return err + } + + siteConfig, err := loadSiteConfig(s.Language) + if err != nil { + return err + } + s.siteConfig = siteConfig + s.siteRefLinker, err = newSiteRefLinker(s.Language, s) + if err != nil { + return err + } } return nil @@ -308,7 +321,7 @@ func (h *HugoSites) createSitesFromConfig() error { s.owner = h } - if err := applyDepsIfNeeded(depsCfg, sites...); err != nil { + if err := applyDeps(depsCfg, sites...); err != nil { return err } diff --git a/hugolib/page.go b/hugolib/page.go index 353e546d3..838791ab8 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -2063,7 +2063,8 @@ func (p *Page) decodeRefArgs(args map[string]interface{}) (refArgs, *SiteInfo, e } if !found { - return ra, nil, fmt.Errorf("no site found with lang %q", ra.Lang) + p.s.siteRefLinker.logNotFound(ra.Path, fmt.Sprintf("no site found with lang %q", ra.Lang), p) + return ra, nil, nil } } @@ -2076,6 +2077,10 @@ func (p *Page) Ref(argsm map[string]interface{}) (string, error) { return "", fmt.Errorf("invalid arguments to Ref: %s", err) } + if s == nil { + return p.s.siteRefLinker.notFoundURL, nil + } + if args.Path == "" { return "", nil } @@ -2092,6 +2097,10 @@ func (p *Page) RelRef(argsm map[string]interface{}) (string, error) { return "", fmt.Errorf("invalid arguments to Ref: %s", err) } + if s == nil { + return p.s.siteRefLinker.notFoundURL, nil + } + if args.Path == "" { return "", nil } @@ -2178,7 +2187,7 @@ func (p *Page) shouldAddLanguagePrefix() bool { return false } - if !p.Site.defaultContentLanguageInSubdir && p.Lang() == p.Site.multilingual.DefaultLang.Lang { + if !p.Site.defaultContentLanguageInSubdir && p.Lang() == p.s.multilingual().DefaultLang.Lang { return false } @@ -2191,7 +2200,7 @@ func (p *Page) initLanguage() { return } - ml := p.Site.multilingual + ml := p.s.multilingual() if ml == nil { panic("Multilanguage not set") } diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 1437ae0cf..f4935c6e9 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -34,7 +34,6 @@ import ( "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/tpl" "github.com/stretchr/testify/require" @@ -42,19 +41,16 @@ import ( // TODO(bep) remove func pageFromString(in, filename string, withTemplate ...func(templ tpl.TemplateHandler) error) (*Page, error) { - s := newTestSite(nil) - if len(withTemplate) > 0 { - // Have to create a new site - var err error - cfg, fs := newTestCfg() + var err error + cfg, fs := newTestCfg() - d := deps.DepsCfg{Language: langs.NewLanguage("en", cfg), Cfg: cfg, Fs: fs, WithTemplate: withTemplate[0]} + d := deps.DepsCfg{Cfg: cfg, Fs: fs, WithTemplate: withTemplate[0]} - s, err = NewSiteForCfg(d) - if err != nil { - return nil, err - } + s, err := NewSiteForCfg(d) + if err != nil { + return nil, err } + return s.NewPageFrom(strings.NewReader(in), filename) } diff --git a/hugolib/site.go b/hugolib/site.go index 08cdd2652..b460a0f81 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -18,6 +18,7 @@ import ( "fmt" "html/template" "io" + "log" "mime" "net/url" "os" @@ -127,6 +128,8 @@ type Site struct { outputFormatsConfig output.Formats mediaTypesConfig media.Types + siteConfig SiteConfig + // How to handle page front matter. frontmatterHandler pagemeta.FrontMatterHandler @@ -147,6 +150,7 @@ type Site struct { titleFunc func(s string) string relatedDocsHandler *relatedDocsHandler + siteRefLinker } type siteRenderingContext struct { @@ -183,6 +187,7 @@ func (s *Site) reset() *Site { disabledKinds: s.disabledKinds, titleFunc: s.titleFunc, relatedDocsHandler: newSearchIndexHandler(s.relatedDocsHandler.cfg), + siteRefLinker: s.siteRefLinker, outputFormats: s.outputFormats, rc: s.rc, outputFormatsConfig: s.outputFormatsConfig, @@ -190,7 +195,9 @@ func (s *Site) reset() *Site { mediaTypesConfig: s.mediaTypesConfig, Language: s.Language, owner: s.owner, + siteConfig: s.siteConfig, PageCollections: newPageCollections()} + } // newSite creates a new site with the given configuration. @@ -276,8 +283,6 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { frontmatterHandler: frontMatterHandler, } - s.Info = newSiteInfo(siteBuilderCfg{s: s, pageCollections: c, language: s.Language}) - return s, nil } @@ -291,7 +296,7 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) { return nil, err } - if err = applyDepsIfNeeded(cfg, s); err != nil { + if err = applyDeps(cfg, s); err != nil { return nil, err } @@ -333,7 +338,7 @@ func newSiteForLang(lang *langs.Language, withTemplate ...func(templ tpl.Templat return nil } - cfg := deps.DepsCfg{WithTemplate: withTemplates, Language: lang, Cfg: lang} + cfg := deps.DepsCfg{WithTemplate: withTemplates, Cfg: lang} return NewSiteForCfg(cfg) @@ -343,16 +348,12 @@ func newSiteForLang(lang *langs.Language, withTemplate ...func(templ tpl.Templat // The site will have a template system loaded and ready to use. // Note: This is mainly used in single site tests. func NewSiteForCfg(cfg deps.DepsCfg) (*Site, error) { - s, err := newSite(cfg) - + h, err := NewHugoSites(cfg) if err != nil { return nil, err } + return h.Sites[0], nil - if err := applyDepsIfNeeded(cfg, s); err != nil { - return nil, err - } - return s, nil } type SiteInfos []*SiteInfo @@ -370,28 +371,24 @@ type SiteInfo struct { Authors AuthorList Social SiteSocial *PageCollections - Menus *Menus - Hugo *HugoInfo - Title string - RSSLink string - Author map[string]interface{} - LanguageCode string - Copyright string - LastChange time.Time - Permalinks PermalinkOverrides - Params map[string]interface{} - BuildDrafts bool - canonifyURLs bool - relativeURLs bool - uglyURLs func(p *Page) bool - preserveTaxonomyNames bool - Data *map[string]interface{} - - Config SiteConfig - + Menus *Menus + Hugo *HugoInfo + Title string + RSSLink string + Author map[string]interface{} + LanguageCode string + Copyright string + LastChange time.Time + Permalinks PermalinkOverrides + Params map[string]interface{} + BuildDrafts bool + canonifyURLs bool + relativeURLs bool + uglyURLs func(p *Page) bool + preserveTaxonomyNames bool + Data *map[string]interface{} owner *HugoSites s *Site - multilingual *Multilingual Language *langs.Language LanguagePrefix string Languages langs.Languages @@ -399,6 +396,10 @@ type SiteInfo struct { sectionPagesMenu string } +func (s *SiteInfo) Config() SiteConfig { + return s.s.siteConfig +} + func (s *SiteInfo) String() string { return fmt.Sprintf("Site(%q)", s.Title) } @@ -422,34 +423,13 @@ func (s *SiteInfo) ServerPort() int { // GoogleAnalytics is kept here for historic reasons. func (s *SiteInfo) GoogleAnalytics() string { - return s.Config.Services.GoogleAnalytics.ID + return s.Config().Services.GoogleAnalytics.ID } // DisqusShortname is kept here for historic reasons. func (s *SiteInfo) DisqusShortname() string { - return s.Config.Services.Disqus.Shortname -} - -// Used in tests. - -type siteBuilderCfg struct { - language *langs.Language - s *Site - pageCollections *PageCollections -} - -// TODO(bep) get rid of this -func newSiteInfo(cfg siteBuilderCfg) SiteInfo { - return SiteInfo{ - s: cfg.s, - multilingual: newMultiLingualForLanguage(cfg.language), - PageCollections: cfg.pageCollections, - Params: make(map[string]interface{}), - uglyURLs: func(p *Page) bool { - return false - }, - } + return s.Config().Services.Disqus.Shortname } // SiteSocial is a place to put social details on a site level. These are the @@ -487,7 +467,35 @@ func (s *SiteInfo) IsServer() bool { return s.owner.running } -func (s *SiteInfo) refLink(ref string, page *Page, relative bool, outputFormat string) (string, error) { +type siteRefLinker struct { + s *Site + + errorLogger *log.Logger + notFoundURL string +} + +func newSiteRefLinker(cfg config.Provider, s *Site) (siteRefLinker, error) { + logger := s.Log.ERROR + + notFoundURL := cfg.GetString("refLinksNotFoundURL") + errLevel := cfg.GetString("refLinksErrorLevel") + if strings.EqualFold(errLevel, "warning") { + logger = s.Log.WARN + } + return siteRefLinker{s: s, errorLogger: logger, notFoundURL: notFoundURL}, nil +} + +func (s siteRefLinker) logNotFound(ref, what string, p *Page) { + if p != nil { + s.errorLogger.Printf("REF_NOT_FOUND: Ref %q: %s", ref, what) + } else { + s.errorLogger.Printf("REF_NOT_FOUND: Ref %q from page %q: %s", ref, p.absoluteSourceRef(), what) + } + +} + +func (s *siteRefLinker) refLink(ref string, page *Page, relative bool, outputFormat string) (string, error) { + var refURL *url.URL var err error @@ -496,21 +504,22 @@ func (s *SiteInfo) refLink(ref string, page *Page, relative bool, outputFormat s refURL, err = url.Parse(ref) if err != nil { - return "", err + return s.notFoundURL, err } var target *Page var link string if refURL.Path != "" { - target, err := s.getPageNew(page, refURL.Path) + target, err := s.s.getPageNew(page, refURL.Path) if err != nil { return "", err } if target == nil { - return "", fmt.Errorf("No page found with path or logical name \"%s\".\n", refURL.Path) + s.logNotFound(refURL.Path, "page not found", page) + return s.notFoundURL, nil } var permalinker Permalinker = target @@ -519,7 +528,8 @@ func (s *SiteInfo) refLink(ref string, page *Page, relative bool, outputFormat s o := target.OutputFormats().Get(outputFormat) if o == nil { - return "", fmt.Errorf("Output format %q not found for page %q", outputFormat, refURL.Path) + s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), page) + return s.notFoundURL, nil } permalinker = o } @@ -551,7 +561,7 @@ func (s *SiteInfo) Ref(ref string, page *Page, options ...string) (string, error outputFormat = options[0] } - return s.refLink(ref, page, false, outputFormat) + return s.s.refLink(ref, page, false, outputFormat) } // RelRef will give an relative URL to ref in the given Page. @@ -561,13 +571,17 @@ func (s *SiteInfo) RelRef(ref string, page *Page, options ...string) (string, er outputFormat = options[0] } - return s.refLink(ref, page, true, outputFormat) + return s.s.refLink(ref, page, true, outputFormat) } func (s *Site) running() bool { return s.owner != nil && s.owner.running } +func (s *Site) multilingual() *Multilingual { + return s.owner.multilingual +} + func init() { defaultTimer = nitro.Initalize() } @@ -1102,11 +1116,6 @@ func (s *Site) initializeSiteInfo() error { languagePrefix = "/" + lang.Lang } - var multilingual *Multilingual - if s.owner != nil { - multilingual = s.owner.multilingual - } - var uglyURLs = func(p *Page) bool { return false } @@ -1132,18 +1141,12 @@ func (s *Site) initializeSiteInfo() error { } } - siteConfig, err := loadSiteConfig(lang) - if err != nil { - return err - } - s.Info = SiteInfo{ Title: lang.GetString("title"), Author: lang.GetStringMap("author"), Social: lang.GetStringMapString("social"), LanguageCode: lang.GetString("languageCode"), Copyright: lang.GetString("copyright"), - multilingual: multilingual, Language: lang, LanguagePrefix: languagePrefix, Languages: languages, @@ -1161,7 +1164,6 @@ func (s *Site) initializeSiteInfo() error { Data: &s.Data, owner: s.owner, s: s, - Config: siteConfig, // TODO(bep) make this Menu and similar into delegate methods on SiteInfo Taxonomies: s.Taxonomies, } diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 202c01986..7787ea2a4 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -925,7 +925,7 @@ func TestRefLinking(t *testing.T) { {"level2/common.md", "", true, "/level2/common/"}, {"3-root.md", "", true, "/level2/level3/3-root/"}, } { - if out, err := site.Info.refLink(test.link, currentPage, test.relative, test.outputFormat); err != nil || out != test.expected { + if out, err := site.refLink(test.link, currentPage, test.relative, test.outputFormat); err != nil || out != test.expected { t.Errorf("[%d] Expected %s to resolve to (%s), got (%s) - error: %s", i, test.link, test.expected, out, err) } } diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index 9fe60c434..9a72bcbb8 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -566,7 +566,7 @@ func newTestSite(t testing.TB, configKeyValues ...interface{}) *Site { cfg.Set(configKeyValues[i].(string), configKeyValues[i+1]) } - d := deps.DepsCfg{Language: langs.NewLanguage("en", cfg), Fs: fs, Cfg: cfg} + d := deps.DepsCfg{Fs: fs, Cfg: cfg} s, err := NewSiteForCfg(d) -- cgit v1.2.3