hugo

Unnamed repository; edit this file 'description' to name the repository.

git clone git://git.shimmy1996.com/hugo.git
commit f55d2f43769053b80b419a690554e747dc5dcede
parent 282f1aa3db9f6420fdd360e46db1ffadd5b083a1
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date:   Mon,  7 Jun 2021 16:36:48 +0200

tpl/fmt: Add erroridf template func

Fixes #8613

Diffstat:
Mcommands/static_syncer.go | 2+-
Mcommon/loggers/ignorableLogger.go | 8++++++++
Mcommon/loggers/loggers.go | 10++++++++++
Mdeps/deps.go | 11+++--------
Mdocs/content/en/functions/errorf.md | 19+++++++++++++++++++
Mhelpers/general.go | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mhugolib/alias.go | 2+-
Mhugolib/collections.go | 1-
Mhugolib/hugo_sites.go | 3++-
Mhugolib/page__new.go | 2+-
Mhugolib/site.go | 10+++++-----
Mhugolib/site_render.go | 4++--
Mlangs/i18n/i18n.go | 2+-
Mresources/page/zero_file.autogen.go | 6+++---
Mtpl/collections/collections.go | 2+-
Mtpl/data/resources_test.go | 12++++++------
Mtpl/fmt/fmt.go | 25+++++++++++++++++--------
Mtpl/fmt/init.go | 7+++++++
Mtpl/fmt/init_test.go | 2+-
19 files changed, 170 insertions(+), 71 deletions(-)
diff --git a/commands/static_syncer.go b/commands/static_syncer.go
@@ -58,7 +58,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
 		syncer.DestFs = c.Fs.Destination
 
 		// prevent spamming the log on changes
-		logger := helpers.NewDistinctFeedbackLogger()
+		logger := helpers.NewDistinctErrorLogger()
 
 		for _, ev := range staticEvents {
 			// Due to our approach of layering both directories and the content's rendered output
diff --git a/common/loggers/ignorableLogger.go b/common/loggers/ignorableLogger.go
@@ -22,6 +22,7 @@ import (
 type IgnorableLogger interface {
 	Logger
 	Errorsf(statementID, format string, v ...interface{})
+	Apply(logger Logger) IgnorableLogger
 }
 
 type ignorableLogger struct {
@@ -55,3 +56,10 @@ ignoreErrors = [%q]`, statementID)
 
 	l.Errorf(format, v...)
 }
+
+func (l ignorableLogger) Apply(logger Logger) IgnorableLogger {
+	return ignorableLogger{
+		Logger:     logger,
+		statements: l.statements,
+	}
+}
diff --git a/common/loggers/loggers.go b/common/loggers/loggers.go
@@ -59,6 +59,8 @@ type Logger interface {
 	Println(v ...interface{})
 	PrintTimerIfDelayed(start time.Time, name string)
 	Debug() *log.Logger
+	Debugf(format string, v ...interface{})
+	Debugln(v ...interface{})
 	Info() *log.Logger
 	Infof(format string, v ...interface{})
 	Infoln(v ...interface{})
@@ -108,6 +110,14 @@ func (l *logger) Debug() *log.Logger {
 	return l.DEBUG
 }
 
+func (l *logger) Debugf(format string, v ...interface{}) {
+	l.DEBUG.Printf(format, v...)
+}
+
+func (l *logger) Debugln(v ...interface{}) {
+	l.DEBUG.Println(v...)
+}
+
 func (l *logger) Infof(format string, v ...interface{}) {
 	l.INFO.Printf(format, v...)
 }
diff --git a/deps/deps.go b/deps/deps.go
@@ -34,10 +34,7 @@ type Deps struct {
 	Log loggers.Logger `json:"-"`
 
 	// Used to log errors that may repeat itself many times.
-	DistinctErrorLog *helpers.DistinctLogger
-
-	// Used to log warnings that may repeat itself many times.
-	DistinctWarningLog *helpers.DistinctLogger
+	LogDistinct loggers.Logger
 
 	// The templates to use. This will usually implement the full tpl.TemplateManager.
 	tmpl tpl.TemplateHandler
@@ -266,14 +263,12 @@ func New(cfg DepsCfg) (*Deps, error) {
 	ignoreErrors := cast.ToStringSlice(cfg.Cfg.Get("ignoreErrors"))
 	ignorableLogger := loggers.NewIgnorableLogger(logger, ignoreErrors...)
 
-	distinctErrorLogger := helpers.NewDistinctLogger(logger.Error())
-	distinctWarnLogger := helpers.NewDistinctLogger(logger.Warn())
+	logDistinct := helpers.NewDistinctLogger(logger)
 
 	d := &Deps{
 		Fs:                      fs,
 		Log:                     ignorableLogger,
-		DistinctErrorLog:        distinctErrorLogger,
-		DistinctWarningLog:      distinctWarnLogger,
+		LogDistinct:             logDistinct,
 		templateProvider:        cfg.TemplateProvider,
 		translationProvider:     cfg.TranslationProvider,
 		WithTemplate:            cfg.WithTemplate,
diff --git a/docs/content/en/functions/errorf.md b/docs/content/en/functions/errorf.md
@@ -31,3 +31,22 @@ Both functions return an empty string, so the messages are only printed to the c
 ```
 
 Note that `errorf` and `warnf` support all the formatting verbs of the [fmt](https://golang.org/pkg/fmt/) package.
+
+## Suppress errors
+
+Some times it may make sense to let the user suppress an ERROR and make the build succeed.
+
+You can do this by using the `erroridf` function. This functions takes an error ID as the first arument.
+
+
+``
+{{ erroridf "my-custom-error" "You should consider fixing this."}}
+```  
+
+This will produce:
+
+```
+ERROR 2021/06/07 17:47:38 You should consider fixing this.
+If you feel that this should not be logged as an ERROR, you can ignore it by adding this to your site config:
+ignoreErrors = ["my-custom-error"]
+```
diff --git a/helpers/general.go b/helpers/general.go
@@ -29,6 +29,8 @@ import (
 	"unicode"
 	"unicode/utf8"
 
+	"github.com/gohugoio/hugo/common/loggers"
+
 	"github.com/mitchellh/hashstructure"
 
 	"github.com/gohugoio/hugo/hugofs"
@@ -40,7 +42,6 @@ import (
 	"github.com/jdkato/prose/transform"
 
 	bp "github.com/gohugoio/hugo/bufferpool"
-	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/pflag"
 )
 
@@ -253,9 +254,9 @@ type LogPrinter interface {
 
 // DistinctLogger ignores duplicate log statements.
 type DistinctLogger struct {
+	loggers.Logger
 	sync.RWMutex
-	getLogger func() LogPrinter
-	m         map[string]bool
+	m map[string]bool
 }
 
 func (l *DistinctLogger) Reset() {
@@ -270,52 +271,105 @@ func (l *DistinctLogger) Reset() {
 func (l *DistinctLogger) Println(v ...interface{}) {
 	// fmt.Sprint doesn't add space between string arguments
 	logStatement := strings.TrimSpace(fmt.Sprintln(v...))
-	l.print(logStatement)
+	l.printIfNotPrinted("println", logStatement, func() {
+		l.Logger.Println(logStatement)
+	})
 }
 
 // Printf will log the string returned from fmt.Sprintf given the arguments,
 // but not if it has been logged before.
-// Note: A newline is appended.
 func (l *DistinctLogger) Printf(format string, v ...interface{}) {
 	logStatement := fmt.Sprintf(format, v...)
-	l.print(logStatement)
+	l.printIfNotPrinted("printf", logStatement, func() {
+		l.Logger.Printf(format, v...)
+	})
+}
+
+func (l *DistinctLogger) Debugf(format string, v ...interface{}) {
+	logStatement := fmt.Sprintf(format, v...)
+	l.printIfNotPrinted("debugf", logStatement, func() {
+		l.Logger.Debugf(format, v...)
+	})
+}
+
+func (l *DistinctLogger) Debugln(v ...interface{}) {
+	logStatement := fmt.Sprint(v...)
+	l.printIfNotPrinted("debugln", logStatement, func() {
+		l.Logger.Debugln(v...)
+	})
+}
+
+func (l *DistinctLogger) Infof(format string, v ...interface{}) {
+	logStatement := fmt.Sprintf(format, v...)
+	l.printIfNotPrinted("info", logStatement, func() {
+		l.Logger.Infof(format, v...)
+	})
+}
+
+func (l *DistinctLogger) Infoln(v ...interface{}) {
+	logStatement := fmt.Sprint(v...)
+	l.printIfNotPrinted("infoln", logStatement, func() {
+		l.Logger.Infoln(v...)
+	})
+}
+
+func (l *DistinctLogger) Warnf(format string, v ...interface{}) {
+	logStatement := fmt.Sprintf(format, v...)
+	l.printIfNotPrinted("warnf", logStatement, func() {
+		l.Logger.Warnf(format, v...)
+	})
+}
+func (l *DistinctLogger) Warnln(v ...interface{}) {
+	logStatement := fmt.Sprint(v...)
+	l.printIfNotPrinted("warnln", logStatement, func() {
+		l.Logger.Warnln(v...)
+	})
+}
+func (l *DistinctLogger) Errorf(format string, v ...interface{}) {
+	logStatement := fmt.Sprint(v...)
+	l.printIfNotPrinted("errorf", logStatement, func() {
+		l.Logger.Errorf(format, v...)
+	})
+}
+
+func (l *DistinctLogger) Errorln(v ...interface{}) {
+	logStatement := fmt.Sprint(v...)
+	l.printIfNotPrinted("errorln", logStatement, func() {
+		l.Logger.Errorln(v...)
+	})
 }
 
-func (l *DistinctLogger) print(logStatement string) {
+func (l *DistinctLogger) hasPrinted(key string) bool {
 	l.RLock()
-	if l.m[logStatement] {
-		l.RUnlock()
+	defer l.RUnlock()
+	_, found := l.m[key]
+	return found
+}
+
+func (l *DistinctLogger) printIfNotPrinted(level, logStatement string, print func()) {
+	key := level + logStatement
+	if l.hasPrinted(key) {
 		return
 	}
-	l.RUnlock()
-
 	l.Lock()
-	if !l.m[logStatement] {
-		l.getLogger().Println(logStatement)
-		l.m[logStatement] = true
-	}
+	print()
+	l.m[key] = true
 	l.Unlock()
 }
 
 // NewDistinctErrorLogger creates a new DistinctLogger that logs ERRORs
-func NewDistinctErrorLogger() *DistinctLogger {
-	return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return jww.ERROR }}
+func NewDistinctErrorLogger() loggers.Logger {
+	return &DistinctLogger{m: make(map[string]bool), Logger: loggers.NewErrorLogger()}
 }
 
 // NewDistinctLogger creates a new DistinctLogger that logs to the provided logger.
-func NewDistinctLogger(logger LogPrinter) *DistinctLogger {
-	return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return logger }}
+func NewDistinctLogger(logger loggers.Logger) loggers.Logger {
+	return &DistinctLogger{m: make(map[string]bool), Logger: logger}
 }
 
 // NewDistinctWarnLogger creates a new DistinctLogger that logs WARNs
-func NewDistinctWarnLogger() *DistinctLogger {
-	return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return jww.WARN }}
-}
-
-// NewDistinctFeedbackLogger creates a new DistinctLogger that can be used
-// to give feedback to the user while not spamming with duplicates.
-func NewDistinctFeedbackLogger() *DistinctLogger {
-	return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return jww.FEEDBACK }}
+func NewDistinctWarnLogger() loggers.Logger {
+	return &DistinctLogger{m: make(map[string]bool), Logger: loggers.NewWarningLogger()}
 }
 
 var (
@@ -324,16 +378,13 @@ var (
 
 	// DistinctWarnLog can be used to avoid spamming the logs with warnings.
 	DistinctWarnLog = NewDistinctWarnLogger()
-
-	// DistinctFeedbackLog can be used to avoid spamming the logs with info messages.
-	DistinctFeedbackLog = NewDistinctFeedbackLogger()
 )
 
 // InitLoggers resets the global distinct loggers.
 func InitLoggers() {
 	DistinctErrorLog.Reset()
 	DistinctWarnLog.Reset()
-	DistinctFeedbackLog.Reset()
+
 }
 
 // Deprecated informs about a deprecation, but only once for a given set of arguments' values.
diff --git a/hugolib/alias.go b/hugolib/alias.go
@@ -79,7 +79,7 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format
 func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
 	handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot)
 
-	s.Log.Debug().Println("creating alias:", path, "redirecting to", permalink)
+	s.Log.Debugln("creating alias:", path, "redirecting to", permalink)
 
 	targetPath, err := handler.targetPathAlias(path)
 	if err != nil {
diff --git a/hugolib/collections.go b/hugolib/collections.go
@@ -28,7 +28,6 @@ var (
 // implementations have no value on their own.
 
 // Slice is not meant to be used externally. It's a bridge function
-// for the template functions. See collections.Slice.
 func (p *pageState) Slice(items interface{}) (interface{}, error) {
 	return page.ToPages(items)
 }
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
@@ -579,7 +579,8 @@ func (h *HugoSites) resetLogs() {
 	h.Log.Reset()
 	loggers.GlobalErrorCounter.Reset()
 	for _, s := range h.Sites {
-		s.Deps.DistinctErrorLog = helpers.NewDistinctLogger(h.Log.Error())
+		s.Deps.Log.Reset()
+		s.Deps.LogDistinct.Reset()
 	}
 }
 
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
@@ -102,7 +102,7 @@ func newPageFromMeta(
 	meta map[string]interface{},
 	metaProvider *pageMeta) (*pageState, error) {
 	if metaProvider.f == nil {
-		metaProvider.f = page.NewZeroFile(metaProvider.s.DistinctWarningLog)
+		metaProvider.f = page.NewZeroFile(metaProvider.s.LogDistinct)
 	}
 
 	ps, err := newPageBase(metaProvider)
diff --git a/hugolib/site.go b/hugolib/site.go
@@ -1007,7 +1007,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
 
 	changeIdentities := make(identity.Identities)
 
-	s.Log.Debug().Printf("Rebuild for events %q", events)
+	s.Log.Debugf("Rebuild for events %q", events)
 
 	h := s.h
 
@@ -1026,7 +1026,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
 		sourceFilesChanged = make(map[string]bool)
 
 		// prevent spamming the log on changes
-		logger = helpers.NewDistinctFeedbackLogger()
+		logger = helpers.NewDistinctErrorLogger()
 	)
 
 	var cachePartitions []string
@@ -1385,7 +1385,7 @@ func (s *Site) getMenusFromConfig() navigation.Menus {
 				s.Log.Errorln(err)
 			} else {
 				for _, entry := range m {
-					s.Log.Debug().Printf("found menu: %q, in site config\n", name)
+					s.Log.Debugf("found menu: %q, in site config\n", name)
 
 					menuEntry := navigation.MenuEntry{Menu: name}
 					ime, err := maps.ToStringMapE(entry)
@@ -1646,7 +1646,7 @@ func (s *Site) lookupLayouts(layouts ...string) tpl.Template {
 }
 
 func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, templ tpl.Template) error {
-	s.Log.Debug().Printf("Render XML for %q to %q", name, targetPath)
+	s.Log.Debugf("Render XML for %q to %q", name, targetPath)
 	renderBuffer := bp.GetBuffer()
 	defer bp.PutBuffer(renderBuffer)
 
@@ -1668,7 +1668,7 @@ func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath st
 }
 
 func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error {
-	s.Log.Debug().Printf("Render %s to %q", name, targetPath)
+	s.Log.Debugf("Render %s to %q", name, targetPath)
 	renderBuffer := bp.GetBuffer()
 	defer bp.PutBuffer(renderBuffer)
 
diff --git a/hugolib/site_render.go b/hugolib/site_render.go
@@ -389,13 +389,13 @@ func (s *Site) renderMainLanguageRedirect() error {
 		mainLang := s.h.multilingual.DefaultLang
 		if s.Info.defaultContentLanguageInSubdir {
 			mainLangURL := s.PathSpec.AbsURL(mainLang.Lang+"/", false)
-			s.Log.Debug().Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
+			s.Log.Debugf("Write redirect to main language %s: %s", mainLang, mainLangURL)
 			if err := s.publishDestAlias(true, "/", mainLangURL, html, nil); err != nil {
 				return err
 			}
 		} else {
 			mainLangURL := s.PathSpec.AbsURL("", false)
-			s.Log.Debug().Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
+			s.Log.Debugf("Write redirect to main language %s: %s", mainLang, mainLangURL)
 			if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, html, nil); err != nil {
 				return err
 			}
diff --git a/langs/i18n/i18n.go b/langs/i18n/i18n.go
@@ -30,7 +30,7 @@ import (
 
 type translateFunc func(translationID string, templateData interface{}) string
 
-var i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
+var i18nWarningLogger = helpers.NewDistinctErrorLogger()
 
 // Translator handles i18n translations.
 type Translator struct {
diff --git a/resources/page/zero_file.autogen.go b/resources/page/zero_file.autogen.go
@@ -16,17 +16,17 @@
 package page
 
 import (
-	"github.com/gohugoio/hugo/helpers"
+	"github.com/gohugoio/hugo/common/loggers"
 	"github.com/gohugoio/hugo/hugofs"
 	"github.com/gohugoio/hugo/source"
 )
 
 // ZeroFile represents a zero value of source.File with warnings if invoked.
 type zeroFile struct {
-	log *helpers.DistinctLogger
+	log loggers.Logger
 }
 
-func NewZeroFile(log *helpers.DistinctLogger) source.File {
+func NewZeroFile(log loggers.Logger) source.File {
 	return zeroFile{log: log}
 }
 
diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go
@@ -380,7 +380,7 @@ func (ns *Namespace) IsSet(a interface{}, key interface{}) (bool, error) {
 			return av.MapIndex(kv).IsValid(), nil
 		}
 	default:
-		helpers.DistinctFeedbackLog.Printf("WARNING: calling IsSet with unsupported type %q (%T) will always return false.\n", av.Kind(), a)
+		helpers.DistinctErrorLog.Printf("WARNING: calling IsSet with unsupported type %q (%T) will always return false.\n", av.Kind(), a)
 	}
 
 	return false, nil
diff --git a/tpl/data/resources_test.go b/tpl/data/resources_test.go
@@ -213,12 +213,12 @@ func newDeps(cfg config.Provider) *deps.Deps {
 	}
 
 	return &deps.Deps{
-		Cfg:              cfg,
-		Fs:               fs,
-		FileCaches:       fileCaches,
-		ContentSpec:      cs,
-		Log:              logger,
-		DistinctErrorLog: helpers.NewDistinctLogger(logger.Error()),
+		Cfg:         cfg,
+		Fs:          fs,
+		FileCaches:  fileCaches,
+		ContentSpec: cs,
+		Log:         logger,
+		LogDistinct: helpers.NewDistinctLogger(logger),
 	}
 }
 
diff --git a/tpl/fmt/fmt.go b/tpl/fmt/fmt.go
@@ -17,20 +17,22 @@ package fmt
 import (
 	_fmt "fmt"
 
+	"github.com/gohugoio/hugo/common/loggers"
+
 	"github.com/gohugoio/hugo/deps"
 	"github.com/gohugoio/hugo/helpers"
 )
 
 // New returns a new instance of the fmt-namespaced template functions.
 func New(d *deps.Deps) *Namespace {
+	ignorableLogger := d.Log.(loggers.IgnorableLogger)
+	distinctLogger := helpers.NewDistinctLogger(d.Log)
 	ns := &Namespace{
-		errorLogger: helpers.NewDistinctLogger(d.Log.Error()),
-		warnLogger:  helpers.NewDistinctLogger(d.Log.Warn()),
+		distinctLogger: ignorableLogger.Apply(distinctLogger),
 	}
 
 	d.BuildStartListeners.Add(func() {
-		ns.errorLogger.Reset()
-		ns.warnLogger.Reset()
+		ns.distinctLogger.Reset()
 	})
 
 	return ns
@@ -38,8 +40,7 @@ func New(d *deps.Deps) *Namespace {
 
 // Namespace provides template functions for the "fmt" namespace.
 type Namespace struct {
-	errorLogger *helpers.DistinctLogger
-	warnLogger  *helpers.DistinctLogger
+	distinctLogger loggers.IgnorableLogger
 }
 
 // Print returns string representation of the passed arguments.
@@ -60,13 +61,21 @@ func (ns *Namespace) Println(a ...interface{}) string {
 // Errorf formats according to a format specifier and logs an ERROR.
 // It returns an empty string.
 func (ns *Namespace) Errorf(format string, a ...interface{}) string {
-	ns.errorLogger.Printf(format, a...)
+	ns.distinctLogger.Errorf(format, a...)
+	return ""
+}
+
+// Erroridf formats according to a format specifier and logs an ERROR and
+// an information text that the error with the given ID can be suppressed in config.
+// It returns an empty string.
+func (ns *Namespace) Erroridf(id, format string, a ...interface{}) string {
+	ns.distinctLogger.Errorsf(id, format, a...)
 	return ""
 }
 
 // Warnf formats according to a format specifier and logs a WARNING.
 // It returns an empty string.
 func (ns *Namespace) Warnf(format string, a ...interface{}) string {
-	ns.warnLogger.Printf(format, a...)
+	ns.distinctLogger.Warnf(format, a...)
 	return ""
 }
diff --git a/tpl/fmt/init.go b/tpl/fmt/init.go
@@ -57,6 +57,13 @@ func init() {
 			},
 		)
 
+		ns.AddMethodMapping(ctx.Erroridf,
+			[]string{"erroridf"},
+			[][2]string{
+				{`{{ erroridf "my-err-id" "%s." "failed" }}`, ``},
+			},
+		)
+
 		ns.AddMethodMapping(ctx.Warnf,
 			[]string{"warnf"},
 			[][2]string{
diff --git a/tpl/fmt/init_test.go b/tpl/fmt/init_test.go
@@ -30,7 +30,7 @@ func TestInit(t *testing.T) {
 	var ns *internal.TemplateFuncsNamespace
 
 	for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
-		ns = nsf(&deps.Deps{Log: loggers.NewErrorLogger()})
+		ns = nsf(&deps.Deps{Log: loggers.NewIgnorableLogger(loggers.NewErrorLogger())})
 		if ns.Name == name {
 			found = true
 			break