shithub: hugo

Download patch

ref: e25aa655f4227ac064be5fe770d517a80acd46b2
parent: 12679b408362a93a3c6159588d6291a3b7ed5548
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Jul 18 15:58:39 EDT 2018

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

--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -392,6 +392,6 @@
 	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)
 
 }
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -129,7 +129,7 @@
 		s.owner = h
 	}
 
-	if err := applyDepsIfNeeded(cfg, sites...); err != nil {
+	if err := applyDeps(cfg, sites...); err != nil {
 		return nil, err
 	}
 
@@ -161,7 +161,7 @@
 	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 @@
 			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 @@
 		s.owner = h
 	}
 
-	if err := applyDepsIfNeeded(depsCfg, sites...); err != nil {
+	if err := applyDeps(depsCfg, sites...); err != nil {
 		return err
 	}
 
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -2063,7 +2063,8 @@
 		}
 
 		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 @@
 		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 @@
 		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 @@
 		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 @@
 			return
 		}
 
-		ml := p.Site.multilingual
+		ml := p.s.multilingual()
 		if ml == nil {
 			panic("Multilanguage not set")
 		}
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -34,7 +34,6 @@
 
 	"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 @@
 
 // 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)
 }
 
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"html/template"
 	"io"
+	"log"
 	"mime"
 	"net/url"
 	"os"
@@ -127,6 +128,8 @@
 	outputFormatsConfig output.Formats
 	mediaTypesConfig    media.Types
 
+	siteConfig SiteConfig
+
 	// How to handle page front matter.
 	frontmatterHandler pagemeta.FrontMatterHandler
 
@@ -147,6 +150,7 @@
 	titleFunc func(s string) string
 
 	relatedDocsHandler *relatedDocsHandler
+	siteRefLinker
 }
 
 type siteRenderingContext struct {
@@ -183,6 +187,7 @@
 		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 @@
 		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 @@
 		frontmatterHandler:  frontMatterHandler,
 	}
 
-	s.Info = newSiteInfo(siteBuilderCfg{s: s, pageCollections: c, language: s.Language})
-
 	return s, nil
 
 }
@@ -291,7 +296,7 @@
 		return nil, err
 	}
 
-	if err = applyDepsIfNeeded(cfg, s); err != nil {
+	if err = applyDeps(cfg, s); err != nil {
 		return nil, err
 	}
 
@@ -333,7 +338,7 @@
 		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 @@
 // 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 @@
 	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 @@
 	sectionPagesMenu               string
 }
 
+func (s *SiteInfo) Config() SiteConfig {
+	return s.s.siteConfig
+}
+
 func (s *SiteInfo) String() string {
 	return fmt.Sprintf("Site(%q)", s.Title)
 }
@@ -422,36 +423,15 @@
 
 // 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
+	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
-		},
-	}
-}
-
 // SiteSocial is a place to put social details on a site level. These are the
 // standard keys that themes will expect to have available, but can be
 // expanded to any others on a per site basis
@@ -487,7 +467,35 @@
 	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,7 +504,7 @@
 	refURL, err = url.Parse(ref)
 
 	if err != nil {
-		return "", err
+		return s.notFoundURL, err
 	}
 
 	var target *Page
@@ -503,7 +511,7 @@
 	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
@@ -510,7 +518,8 @@
 		}
 
 		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 @@
 			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 @@
 		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,7 +571,7 @@
 		outputFormat = options[0]
 	}
 
-	return s.refLink(ref, page, true, outputFormat)
+	return s.s.refLink(ref, page, true, outputFormat)
 }
 
 func (s *Site) running() bool {
@@ -568,6 +578,10 @@
 	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 @@
 		languagePrefix = "/" + lang.Lang
 	}
 
-	var multilingual *Multilingual
-	if s.owner != nil {
-		multilingual = s.owner.multilingual
-	}
-
 	var uglyURLs = func(p *Page) bool {
 		return false
 	}
@@ -1132,11 +1141,6 @@
 		}
 	}
 
-	siteConfig, err := loadSiteConfig(lang)
-	if err != nil {
-		return err
-	}
-
 	s.Info = SiteInfo{
 		Title:                          lang.GetString("title"),
 		Author:                         lang.GetStringMap("author"),
@@ -1143,7 +1147,6 @@
 		Social:                         lang.GetStringMapString("social"),
 		LanguageCode:                   lang.GetString("languageCode"),
 		Copyright:                      lang.GetString("copyright"),
-		multilingual:                   multilingual,
 		Language:                       lang,
 		LanguagePrefix:                 languagePrefix,
 		Languages:                      languages,
@@ -1161,7 +1164,6 @@
 		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,
 	}
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -925,7 +925,7 @@
 		{"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)
 		}
 	}
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -566,7 +566,7 @@
 		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)