diff --git a/commands/commandeer.go b/commands/commandeer.go
index c55806980e4..41cb651180e 100644
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -14,6 +14,11 @@
package commands
import (
+ "errors"
+ "io/ioutil"
+
+ jww "github.com/spf13/jwalterweatherman"
+
"os"
"path/filepath"
"regexp"
@@ -21,13 +26,13 @@ import (
"sync"
"time"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/spf13/cobra"
- "github.com/spf13/afero"
-
"github.com/gohugoio/hugo/hugolib"
+ "github.com/spf13/afero"
"github.com/bep/debounce"
"github.com/gohugoio/hugo/common/types"
@@ -46,6 +51,8 @@ type commandeerHugoState struct {
type commandeer struct {
*commandeerHugoState
+ logger *loggers.Logger
+
// Currently only set when in "fast render mode". But it seems to
// be fast enough that we could maybe just add it for all server modes.
changeDetector *fileChangeDetector
@@ -74,6 +81,25 @@ type commandeer struct {
paused bool
}
+func (c *commandeer) errCount() int {
+ return int(c.logger.ErrorCounter.Count())
+}
+
+func (c *commandeer) getErrorWithContext() interface{} {
+ errCount := c.errCount()
+
+ if errCount == 0 {
+ return nil
+ }
+
+ m := make(map[string]interface{})
+
+ m["Error"] = errors.New(removeErrorPrefixFromLog(c.logger.Errors.String()))
+ m["Version"] = hugoVersionString()
+
+ return m
+}
+
func (c *commandeer) Set(key string, value interface{}) {
if c.configured {
panic("commandeer cannot be changed")
@@ -105,6 +131,8 @@ func newCommandeer(mustHaveConfigFile, running bool, h *hugoBuilderCommon, f fla
doWithCommandeer: doWithCommandeer,
visitedURLs: types.NewEvictingStringQueue(10),
debounce: rebuildDebouncer,
+ // This will be replaced later, but we need something to log to before the configuration is read.
+ logger: loggers.NewLogger(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, running),
}
return c, c.loadConfig(mustHaveConfigFile, running)
@@ -244,12 +272,13 @@ func (c *commandeer) loadConfig(mustHaveConfigFile, running bool) error {
}
}
- logger, err := c.createLogger(config)
+ logger, err := c.createLogger(config, running)
if err != nil {
return err
}
cfg.Logger = logger
+ c.logger = logger
createMemFs := config.GetBool("renderToMemory")
diff --git a/commands/commands.go b/commands/commands.go
index 54eb03b5b9a..8670d498303 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -14,12 +14,10 @@
package commands
import (
- "os"
-
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/nitro"
)
@@ -242,7 +240,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
}
-func checkErr(logger *jww.Notepad, err error, s ...string) {
+func checkErr(logger *loggers.Logger, err error, s ...string) {
if err == nil {
return
}
@@ -255,25 +253,3 @@ func checkErr(logger *jww.Notepad, err error, s ...string) {
}
logger.ERROR.Println(err)
}
-
-func stopOnErr(logger *jww.Notepad, err error, s ...string) {
- if err == nil {
- return
- }
-
- defer os.Exit(-1)
-
- if len(s) == 0 {
- newMessage := err.Error()
- // Printing an empty string results in a error with
- // no message, no bueno.
- if newMessage != "" {
- logger.CRITICAL.Println(newMessage)
- }
- }
- for _, message := range s {
- if message != "" {
- logger.CRITICAL.Println(message)
- }
- }
-}
diff --git a/commands/hugo.go b/commands/hugo.go
index 2e7353d5152..f20f78dadc5 100644
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -21,13 +21,15 @@ import (
"os/signal"
"sort"
"sync/atomic"
+
+ "github.com/gohugoio/hugo/common/loggers"
+
"syscall"
"github.com/gohugoio/hugo/hugolib/filesystems"
"golang.org/x/sync/errgroup"
- "log"
"os"
"path/filepath"
"runtime"
@@ -85,7 +87,7 @@ func Execute(args []string) Response {
}
if err == nil {
- errCount := int(jww.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError))
+ errCount := int(loggers.GlobalErrorCounter.Count())
if errCount > 0 {
err = fmt.Errorf("logged %d errors", errCount)
} else if resp.Result != nil {
@@ -118,7 +120,7 @@ func initializeConfig(mustHaveConfigFile, running bool,
}
-func (c *commandeer) createLogger(cfg config.Provider) (*jww.Notepad, error) {
+func (c *commandeer) createLogger(cfg config.Provider, running bool) (*loggers.Logger, error) {
var (
logHandle = ioutil.Discard
logThreshold = jww.LevelWarn
@@ -161,7 +163,7 @@ func (c *commandeer) createLogger(cfg config.Provider) (*jww.Notepad, error) {
jww.SetStdoutThreshold(stdoutThreshold)
helpers.InitLoggers()
- return jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime), nil
+ return loggers.NewLogger(stdoutThreshold, logThreshold, outHandle, logHandle, running), nil
}
func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
@@ -277,7 +279,7 @@ func (c *commandeer) fullBuild() error {
if !os.IsNotExist(err) {
return fmt.Errorf("Error copying static files: %s", err)
}
- c.Logger.WARN.Println("No Static directory found")
+ c.logger.WARN.Println("No Static directory found")
}
langCount = cnt
langCount = cnt
@@ -345,8 +347,8 @@ func (c *commandeer) build() error {
if err != nil {
return err
}
- c.Logger.FEEDBACK.Println("Watching for changes in", c.hugo.PathSpec.AbsPathify(c.Cfg.GetString("contentDir")))
- c.Logger.FEEDBACK.Println("Press Ctrl+C to stop")
+ c.logger.FEEDBACK.Println("Watching for changes in", c.hugo.PathSpec.AbsPathify(c.Cfg.GetString("contentDir")))
+ c.logger.FEEDBACK.Println("Press Ctrl+C to stop")
watcher, err := c.newWatcher(watchDirs...)
checkErr(c.Logger, err)
defer watcher.Close()
@@ -388,7 +390,7 @@ func (c *commandeer) doWithPublishDirs(f func(sourceFs *filesystems.SourceFilesy
staticFilesystems := c.hugo.BaseFs.SourceFilesystems.Static
if len(staticFilesystems) == 0 {
- c.Logger.WARN.Println("No static directories found to sync")
+ c.logger.WARN.Println("No static directories found to sync")
return langCount, nil
}
@@ -448,13 +450,13 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6
syncer.Delete = c.Cfg.GetBool("cleanDestinationDir")
if syncer.Delete {
- c.Logger.INFO.Println("removing all files from destination that don't exist in static dirs")
+ c.logger.INFO.Println("removing all files from destination that don't exist in static dirs")
syncer.DeleteFilter = func(f os.FileInfo) bool {
return f.IsDir() && strings.HasPrefix(f.Name(), ".")
}
}
- c.Logger.INFO.Println("syncing static files to", publishDir)
+ c.logger.INFO.Println("syncing static files to", publishDir)
var err error
@@ -480,7 +482,7 @@ func (c *commandeer) timeTrack(start time.Time, name string) {
return
}
elapsed := time.Since(start)
- c.Logger.FEEDBACK.Printf("%s in %v ms", name, int(1000*elapsed.Seconds()))
+ c.logger.FEEDBACK.Printf("%s in %v ms", name, int(1000*elapsed.Seconds()))
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
@@ -498,7 +500,7 @@ func (c *commandeer) getDirList() ([]string, error) {
return nil
}
- c.Logger.ERROR.Println("Walker: ", err)
+ c.logger.ERROR.Println("Walker: ", err)
return nil
}
@@ -511,16 +513,16 @@ func (c *commandeer) getDirList() ([]string, error) {
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
link, err := filepath.EvalSymlinks(path)
if err != nil {
- c.Logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
+ c.logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err)
return nil
}
linkfi, err := helpers.LstatIfPossible(c.Fs.Source, link)
if err != nil {
- c.Logger.ERROR.Printf("Cannot stat %q: %s", link, err)
+ c.logger.ERROR.Printf("Cannot stat %q: %s", link, err)
return nil
}
if !allowSymbolicDirs && !linkfi.Mode().IsRegular() {
- c.Logger.ERROR.Printf("Symbolic links for directories not supported, skipping %q", path)
+ c.logger.ERROR.Printf("Symbolic links for directories not supported, skipping %q", path)
return nil
}
@@ -603,7 +605,7 @@ func (c *commandeer) getDirList() ([]string, error) {
func (c *commandeer) resetAndBuildSites() (err error) {
if !c.h.quiet {
- c.Logger.FEEDBACK.Println("Started building sites ...")
+ c.logger.FEEDBACK.Println("Started building sites ...")
}
return c.hugo.Build(hugolib.BuildCfg{ResetState: true})
}
@@ -637,7 +639,7 @@ func (c *commandeer) fullRebuild() {
c.commandeerHugoState = &commandeerHugoState{}
err := c.loadConfig(true, true)
if err != nil {
- jww.ERROR.Println("Failed to reload config:", err)
+ c.logger.ERROR.Println("Failed to reload config:", err)
// Set the processing on pause until the state is recovered.
c.paused = true
} else {
@@ -645,8 +647,9 @@ func (c *commandeer) fullRebuild() {
}
if !c.paused {
- if err := c.buildSites(); err != nil {
- jww.ERROR.Println(err)
+ err := c.buildSites()
+ if err != nil {
+ c.logger.ERROR.Println(err)
} else if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
livereload.ForceRefresh()
}
@@ -680,7 +683,7 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
configSet := make(map[string]bool)
for _, configFile := range c.configFiles {
- c.Logger.FEEDBACK.Println("Watching for config changes in", configFile)
+ c.logger.FEEDBACK.Println("Watching for config changes in", configFile)
watcher.Add(configFile)
configSet[configFile] = true
}
@@ -689,241 +692,256 @@ func (c *commandeer) newWatcher(dirList ...string) (*watcher.Batcher, error) {
for {
select {
case evs := <-watcher.Events:
- for _, ev := range evs {
- if configSet[ev.Name] {
- if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
- continue
- }
- if ev.Op&fsnotify.Remove == fsnotify.Remove {
- for _, configFile := range c.configFiles {
- counter := 0
- for watcher.Add(configFile) != nil {
- counter++
- if counter >= 100 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- }
- }
- // Config file changed. Need full rebuild.
- c.fullRebuild()
- break
- }
- }
-
- if c.paused {
- // Wait for the server to get into a consistent state before
- // we continue with processing.
- continue
+ c.handleEvents(watcher, staticSyncer, evs, configSet)
+ if c.errCount() > 0 {
+ // TODO(bep) check if livereload enabled + other flag
+ // Need to reload browser to show the error
+ livereload.ForceRefresh()
}
-
- if len(evs) > 50 {
- // This is probably a mass edit of the content dir.
- // Schedule a full rebuild for when it slows down.
- c.debounce(c.fullRebuild)
- continue
+ case err := <-watcher.Errors:
+ if err != nil {
+ c.logger.ERROR.Println("Error while watching:", err)
}
+ }
+ }
+ }()
- c.Logger.INFO.Println("Received System Events:", evs)
+ return watcher, nil
+}
- staticEvents := []fsnotify.Event{}
- dynamicEvents := []fsnotify.Event{}
+func (c *commandeer) handleEvents(watcher *watcher.Batcher,
+ staticSyncer *staticSyncer,
+ evs []fsnotify.Event,
+ configSet map[string]bool) {
- // Special handling for symbolic links inside /content.
- filtered := []fsnotify.Event{}
- for _, ev := range evs {
- // Check the most specific first, i.e. files.
- contentMapped := c.hugo.ContentChanges.GetSymbolicLinkMappings(ev.Name)
- if len(contentMapped) > 0 {
- for _, mapped := range contentMapped {
- filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
+ for _, ev := range evs {
+ if configSet[ev.Name] {
+ if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
+ continue
+ }
+ if ev.Op&fsnotify.Remove == fsnotify.Remove {
+ for _, configFile := range c.configFiles {
+ counter := 0
+ for watcher.Add(configFile) != nil {
+ counter++
+ if counter >= 100 {
+ break
}
- continue
+ time.Sleep(100 * time.Millisecond)
}
+ }
+ }
+ // Config file changed. Need full rebuild.
+ c.fullRebuild()
+ break
+ }
+ }
- // Check for any symbolic directory mapping.
+ if c.paused {
+ // Wait for the server to get into a consistent state before
+ // we continue with processing.
+ return
+ }
- dir, name := filepath.Split(ev.Name)
+ if len(evs) > 50 {
+ // This is probably a mass edit of the content dir.
+ // Schedule a full rebuild for when it slows down.
+ c.debounce(c.fullRebuild)
+ return
+ }
- contentMapped = c.hugo.ContentChanges.GetSymbolicLinkMappings(dir)
+ c.logger.INFO.Println("Received System Events:", evs)
- if len(contentMapped) == 0 {
- filtered = append(filtered, ev)
- continue
- }
+ staticEvents := []fsnotify.Event{}
+ dynamicEvents := []fsnotify.Event{}
- for _, mapped := range contentMapped {
- mappedFilename := filepath.Join(mapped, name)
- filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
- }
- }
+ // Special handling for symbolic links inside /content.
+ filtered := []fsnotify.Event{}
+ for _, ev := range evs {
+ // Check the most specific first, i.e. files.
+ contentMapped := c.hugo.ContentChanges.GetSymbolicLinkMappings(ev.Name)
+ if len(contentMapped) > 0 {
+ for _, mapped := range contentMapped {
+ filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
+ }
+ continue
+ }
- evs = filtered
-
- for _, ev := range evs {
- ext := filepath.Ext(ev.Name)
- baseName := filepath.Base(ev.Name)
- istemp := strings.HasSuffix(ext, "~") ||
- (ext == ".swp") || // vim
- (ext == ".swx") || // vim
- (ext == ".tmp") || // generic temp file
- (ext == ".DS_Store") || // OSX Thumbnail
- baseName == "4913" || // vim
- strings.HasPrefix(ext, ".goutputstream") || // gnome
- strings.HasSuffix(ext, "jb_old___") || // intelliJ
- strings.HasSuffix(ext, "jb_tmp___") || // intelliJ
- strings.HasSuffix(ext, "jb_bak___") || // intelliJ
- strings.HasPrefix(ext, ".sb-") || // byword
- strings.HasPrefix(baseName, ".#") || // emacs
- strings.HasPrefix(baseName, "#") // emacs
- if istemp {
- continue
- }
- // Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these
- if ev.Name == "" {
- continue
- }
+ // Check for any symbolic directory mapping.
- // Write and rename operations are often followed by CHMOD.
- // There may be valid use cases for rebuilding the site on CHMOD,
- // but that will require more complex logic than this simple conditional.
- // On OS X this seems to be related to Spotlight, see:
- // https://github.com/go-fsnotify/fsnotify/issues/15
- // A workaround is to put your site(s) on the Spotlight exception list,
- // but that may be a little mysterious for most end users.
- // So, for now, we skip reload on CHMOD.
- // We do have to check for WRITE though. On slower laptops a Chmod
- // could be aggregated with other important events, and we still want
- // to rebuild on those
- if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
- continue
- }
+ dir, name := filepath.Split(ev.Name)
- walkAdder := func(path string, f os.FileInfo, err error) error {
- if f.IsDir() {
- c.Logger.FEEDBACK.Println("adding created directory to watchlist", path)
- if err := watcher.Add(path); err != nil {
- return err
- }
- } else if !staticSyncer.isStatic(path) {
- // Hugo's rebuilding logic is entirely file based. When you drop a new folder into
- // /content on OSX, the above logic will handle future watching of those files,
- // but the initial CREATE is lost.
- dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create})
- }
- return nil
- }
+ contentMapped = c.hugo.ContentChanges.GetSymbolicLinkMappings(dir)
- // recursively add new directories to watch list
- // When mkdir -p is used, only the top directory triggers an event (at least on OSX)
- if ev.Op&fsnotify.Create == fsnotify.Create {
- if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
- _ = helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder)
- }
- }
+ if len(contentMapped) == 0 {
+ filtered = append(filtered, ev)
+ continue
+ }
- if staticSyncer.isStatic(ev.Name) {
- staticEvents = append(staticEvents, ev)
- } else {
- dynamicEvents = append(dynamicEvents, ev)
- }
- }
+ for _, mapped := range contentMapped {
+ mappedFilename := filepath.Join(mapped, name)
+ filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
+ }
+ }
- if len(staticEvents) > 0 {
- c.Logger.FEEDBACK.Println("\nStatic file changes detected")
- const layout = "2006-01-02 15:04:05.000 -0700"
- c.Logger.FEEDBACK.Println(time.Now().Format(layout))
+ evs = filtered
+
+ for _, ev := range evs {
+ ext := filepath.Ext(ev.Name)
+ baseName := filepath.Base(ev.Name)
+ istemp := strings.HasSuffix(ext, "~") ||
+ (ext == ".swp") || // vim
+ (ext == ".swx") || // vim
+ (ext == ".tmp") || // generic temp file
+ (ext == ".DS_Store") || // OSX Thumbnail
+ baseName == "4913" || // vim
+ strings.HasPrefix(ext, ".goutputstream") || // gnome
+ strings.HasSuffix(ext, "jb_old___") || // intelliJ
+ strings.HasSuffix(ext, "jb_tmp___") || // intelliJ
+ strings.HasSuffix(ext, "jb_bak___") || // intelliJ
+ strings.HasPrefix(ext, ".sb-") || // byword
+ strings.HasPrefix(baseName, ".#") || // emacs
+ strings.HasPrefix(baseName, "#") // emacs
+ if istemp {
+ continue
+ }
+ // Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these
+ if ev.Name == "" {
+ continue
+ }
- if c.Cfg.GetBool("forceSyncStatic") {
- c.Logger.FEEDBACK.Printf("Syncing all static files\n")
- _, err := c.copyStatic()
- if err != nil {
- stopOnErr(c.Logger, err, "Error copying static files to publish dir")
- }
- } else {
- if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
- c.Logger.ERROR.Println(err)
- continue
- }
- }
+ // Write and rename operations are often followed by CHMOD.
+ // There may be valid use cases for rebuilding the site on CHMOD,
+ // but that will require more complex logic than this simple conditional.
+ // On OS X this seems to be related to Spotlight, see:
+ // https://github.com/go-fsnotify/fsnotify/issues/15
+ // A workaround is to put your site(s) on the Spotlight exception list,
+ // but that may be a little mysterious for most end users.
+ // So, for now, we skip reload on CHMOD.
+ // We do have to check for WRITE though. On slower laptops a Chmod
+ // could be aggregated with other important events, and we still want
+ // to rebuild on those
+ if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
+ continue
+ }
- if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
- // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
-
- // force refresh when more than one file
- if len(staticEvents) == 1 {
- ev := staticEvents[0]
- path := c.hugo.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
- path = c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(path), false)
- livereload.RefreshPath(path)
- } else {
- livereload.ForceRefresh()
- }
- }
+ walkAdder := func(path string, f os.FileInfo, err error) error {
+ if f.IsDir() {
+ c.logger.FEEDBACK.Println("adding created directory to watchlist", path)
+ if err := watcher.Add(path); err != nil {
+ return err
}
+ } else if !staticSyncer.isStatic(path) {
+ // Hugo's rebuilding logic is entirely file based. When you drop a new folder into
+ // /content on OSX, the above logic will handle future watching of those files,
+ // but the initial CREATE is lost.
+ dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create})
+ }
+ return nil
+ }
- if len(dynamicEvents) > 0 {
- partitionedEvents := partitionDynamicEvents(
- c.firstPathSpec().BaseFs.SourceFilesystems,
- dynamicEvents)
+ // recursively add new directories to watch list
+ // When mkdir -p is used, only the top directory triggers an event (at least on OSX)
+ if ev.Op&fsnotify.Create == fsnotify.Create {
+ if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
+ _ = helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder)
+ }
+ }
- doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
- onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
+ if staticSyncer.isStatic(ev.Name) {
+ staticEvents = append(staticEvents, ev)
+ } else {
+ dynamicEvents = append(dynamicEvents, ev)
+ }
+ }
- c.Logger.FEEDBACK.Println("\nChange detected, rebuilding site")
- const layout = "2006-01-02 15:04:05.000 -0700"
- c.Logger.FEEDBACK.Println(time.Now().Format(layout))
+ if len(staticEvents) > 0 {
+ c.logger.FEEDBACK.Println("\nStatic file changes detected")
+ const layout = "2006-01-02 15:04:05.000 -0700"
+ c.logger.FEEDBACK.Println(time.Now().Format(layout))
- c.changeDetector.PrepareNew()
- if err := c.rebuildSites(dynamicEvents); err != nil {
- c.Logger.ERROR.Println("Failed to rebuild site:", err)
- }
+ if c.Cfg.GetBool("forceSyncStatic") {
+ c.logger.FEEDBACK.Printf("Syncing all static files\n")
+ _, err := c.copyStatic()
+ if err != nil {
+ c.logger.ERROR.Println("Error copying static files to publish dir:", err)
+ return
+ }
+ } else {
+ if err := staticSyncer.syncsStaticEvents(staticEvents); err != nil {
+ c.logger.ERROR.Println("Error syncing static files to publish dir:", err)
+ return
+ }
+ }
- if doLiveReload {
- if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
- changed := c.changeDetector.changed()
- if c.changeDetector != nil && len(changed) == 0 {
- // Nothing has changed.
- continue
- } else if len(changed) == 1 {
- pathToRefresh := c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
- livereload.RefreshPath(pathToRefresh)
- } else {
- livereload.ForceRefresh()
- }
- }
+ if !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload") {
+ // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
+
+ // force refresh when more than one file
+ if len(staticEvents) == 1 {
+ ev := staticEvents[0]
+ path := c.hugo.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
+ path = c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(path), false)
+ livereload.RefreshPath(path)
+ } else {
+ livereload.ForceRefresh()
+ }
+ }
+ }
- if len(partitionedEvents.ContentEvents) > 0 {
+ if len(dynamicEvents) > 0 {
+ partitionedEvents := partitionDynamicEvents(
+ c.firstPathSpec().BaseFs.SourceFilesystems,
+ dynamicEvents)
- navigate := c.Cfg.GetBool("navigateToChanged")
- // We have fetched the same page above, but it may have
- // changed.
- var p *hugolib.Page
+ doLiveReload := !c.h.buildWatch && !c.Cfg.GetBool("disableLiveReload")
+ onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
- if navigate {
- if onePageName != "" {
- p = c.hugo.GetContentPage(onePageName)
- }
- }
+ c.logger.FEEDBACK.Println("\nChange detected, rebuilding site")
+ const layout = "2006-01-02 15:04:05.000 -0700"
+ c.logger.FEEDBACK.Println(time.Now().Format(layout))
- if p != nil {
- livereload.NavigateToPathForPort(p.RelPermalink(), p.Site.ServerPort())
- } else {
- livereload.ForceRefresh()
- }
- }
+ c.changeDetector.PrepareNew()
+ if err := c.rebuildSites(dynamicEvents); err != nil {
+ c.logger.ERROR.Println("Rebuild failed:", err)
+ }
+
+ if doLiveReload {
+
+ if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
+ changed := c.changeDetector.changed()
+ if c.changeDetector != nil && len(changed) == 0 {
+ // Nothing has changed.
+ return
+ } else if len(changed) == 1 {
+ pathToRefresh := c.firstPathSpec().RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
+ livereload.RefreshPath(pathToRefresh)
+ } else {
+ livereload.ForceRefresh()
+ }
+ }
+
+ if len(partitionedEvents.ContentEvents) > 0 {
+
+ navigate := c.Cfg.GetBool("navigateToChanged")
+ // We have fetched the same page above, but it may have
+ // changed.
+ var p *hugolib.Page
+
+ if navigate {
+ if onePageName != "" {
+ p = c.hugo.GetContentPage(onePageName)
}
}
- case err := <-watcher.Errors:
- if err != nil {
- c.Logger.ERROR.Println(err)
+
+ if p != nil {
+ livereload.NavigateToPathForPort(p.RelPermalink(), p.Site.ServerPort())
+ } else {
+ livereload.ForceRefresh()
}
}
}
- }()
-
- return watcher, nil
+ }
}
// dynamicEvents contains events that is considered dynamic, as in "not static".
diff --git a/commands/server.go b/commands/server.go
index 27999fa6c2a..8387c58cfef 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -14,6 +14,7 @@
package commands
import (
+ "bytes"
"fmt"
"net"
"net/http"
@@ -21,6 +22,7 @@ import (
"os"
"os/signal"
"path/filepath"
+ "regexp"
"runtime"
"strconv"
"strings"
@@ -29,6 +31,7 @@ import (
"time"
"github.com/gohugoio/hugo/livereload"
+ "github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/config"
@@ -176,7 +179,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
// port set explicitly by user -- he/she probably meant it!
err = newSystemErrorF("Server startup failed: %s", err)
}
- jww.ERROR.Println("port", sc.serverPort, "already in use, attempting to use an available port")
+ c.logger.FEEDBACK.Println("port", sc.serverPort, "already in use, attempting to use an available port")
sp, err := helpers.FindAvailablePort()
if err != nil {
err = newSystemError("Unable to find alternative port to use:", err)
@@ -223,7 +226,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
}
if err := memStats(); err != nil {
- jww.ERROR.Println("memstats error:", err)
+ jww.WARN.Println("memstats error:", err)
}
c, err := initializeConfig(true, true, &sc.hugoBuilderCommon, sc, cfgInit)
@@ -271,10 +274,11 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
}
type fileServer struct {
- baseURLs []string
- roots []string
- c *commandeer
- s *serverCmd
+ baseURLs []string
+ roots []string
+ errorTemplate tpl.Template
+ c *commandeer
+ s *serverCmd
}
func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, error) {
@@ -316,6 +320,18 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
decorate := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ // First check the error state
+ err := f.c.getErrorWithContext()
+ if err != nil {
+ w.WriteHeader(500)
+ var b bytes.Buffer
+ f.errorTemplate.Execute(&b, err)
+ fmt.Fprint(w, injectLiveReloadScript(&b))
+
+ return
+ }
+
if f.s.noHTTPCache {
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
w.Header().Set("Pragma", "no-cache")
@@ -345,6 +361,11 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
return mu, u.String(), endpoint, nil
}
+var logErrorRe = regexp.MustCompile("(?s)ERROR \\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2} ")
+
+func removeErrorPrefixFromLog(content string) string {
+ return logErrorRe.ReplaceAllLiteralString(content, "")
+}
func (c *commandeer) serve(s *serverCmd) error {
isMultiHost := c.hugo.IsMultihost()
@@ -365,11 +386,17 @@ func (c *commandeer) serve(s *serverCmd) error {
roots = []string{""}
}
+ templ, err := c.hugo.TextTmpl.Parse("__default_server_error", defaultBuildErrorTemplate)
+ if err != nil {
+ return err
+ }
+
srv := &fileServer{
- baseURLs: baseURLs,
- roots: roots,
- c: c,
- s: s,
+ baseURLs: baseURLs,
+ roots: roots,
+ c: c,
+ s: s,
+ errorTemplate: templ,
}
doLiveReload := !c.Cfg.GetBool("disableLiveReload")
@@ -392,7 +419,7 @@ func (c *commandeer) serve(s *serverCmd) error {
go func() {
err = http.ListenAndServe(endpoint, mu)
if err != nil {
- jww.ERROR.Printf("Error: %s\n", err.Error())
+ c.logger.ERROR.Printf("Error: %s\n", err.Error())
os.Exit(1)
}
}()
diff --git a/commands/server_errors.go b/commands/server_errors.go
new file mode 100644
index 00000000000..ae8d2116d92
--- /dev/null
+++ b/commands/server_errors.go
@@ -0,0 +1,86 @@
+// Copyright 2018 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
+// 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
+// limitations under the License.
+
+package commands
+
+import (
+ "bytes"
+ "io"
+
+ "github.com/gohugoio/hugo/transform"
+ "github.com/gohugoio/hugo/transform/livereloadinject"
+)
+
+var defaultBuildErrorTemplate = `
+
+
+
+ Hugo Server: Error
+
+
+
+
+ {{ highlight .Error "apl" "noclasses=true,style=monokai" }}
+ {{ .Version }}
+ Reload Page
+
+
+
+`
+
+func injectLiveReloadScript(src io.Reader) string {
+ // TODO(bep) errors port
+ var b bytes.Buffer
+ chain := transform.Chain{livereloadinject.New(1313)}
+ chain.Apply(&b, src)
+
+ return b.String()
+}
+
+//s.Cfg.GetInt("liveReloadPort")
diff --git a/commands/server_test.go b/commands/server_test.go
index 72d81d70db4..438837a90a8 100644
--- a/commands/server_test.go
+++ b/commands/server_test.go
@@ -18,6 +18,7 @@ import (
"net/http"
"os"
"runtime"
+ "strings"
"testing"
"time"
@@ -113,6 +114,18 @@ func TestFixURL(t *testing.T) {
}
}
+func TestRemoveErrorPrefixFromLog(t *testing.T) {
+ assert := require.New(t)
+ content := `ERROR 2018/10/07 13:11:12 Error while rendering "home": template: _default/baseof.html:4:3: executing "main" at : error calling partial: template: partials/logo.html:5:84: executing "partials/logo.html" at <$resized.AHeight>: can't evaluate field AHeight in type *resource.Image
+ERROR 2018/10/07 13:11:12 Rebuild failed: logged 1 error(s)
+`
+
+ withoutError := removeErrorPrefixFromLog(content)
+
+ assert.False(strings.Contains(withoutError, "ERROR"), withoutError)
+
+}
+
func isWindowsCI() bool {
return runtime.GOOS == "windows" && os.Getenv("CI") != ""
}
diff --git a/commands/static_syncer.go b/commands/static_syncer.go
index 1e73e7fc259..2374538683f 100644
--- a/commands/static_syncer.go
+++ b/commands/static_syncer.go
@@ -105,10 +105,10 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
- c.Logger.ERROR.Println(err)
+ c.logger.ERROR.Println(err)
}
} else {
- c.Logger.ERROR.Println(err)
+ c.logger.ERROR.Println(err)
}
continue
@@ -117,7 +117,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
// For all other event operations Hugo will sync static.
logger.Println("Syncing", relPath, "to", publishDir)
if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil {
- c.Logger.ERROR.Println(err)
+ c.logger.ERROR.Println(err)
}
}
diff --git a/commands/version.go b/commands/version.go
index ea4e4c926c0..b85f53725fe 100644
--- a/commands/version.go
+++ b/commands/version.go
@@ -14,14 +14,16 @@
package commands
import (
+ "fmt"
"runtime"
"strings"
+ jww "github.com/spf13/jwalterweatherman"
+
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resource/tocss/scss"
"github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
)
var _ cmder = (*versionCmd)(nil)
@@ -45,6 +47,10 @@ func newVersionCmd() *versionCmd {
}
func printHugoVersion() {
+ jww.FEEDBACK.Println(hugoVersionString())
+}
+
+func hugoVersionString() string {
program := "Hugo Static Site Generator"
version := "v" + helpers.CurrentHugoVersion.String()
@@ -64,5 +70,6 @@ func printHugoVersion() {
buildDate = "unknown"
}
- jww.FEEDBACK.Println(program, version, osArch, "BuildDate:", buildDate)
+ return fmt.Sprintf("%s %s %s BuildDate: %s", program, version, osArch, buildDate)
+
}
diff --git a/common/loggers/loggers.go b/common/loggers/loggers.go
index 2f7f36b3440..a26cbd8ca9c 100644
--- a/common/loggers/loggers.go
+++ b/common/loggers/loggers.go
@@ -14,6 +14,8 @@
package loggers
import (
+ "bytes"
+ "io"
"io/ioutil"
"log"
"os"
@@ -21,17 +23,78 @@ import (
jww "github.com/spf13/jwalterweatherman"
)
+var (
+ // Counts ERROR logs to the global jww logger.
+ GlobalErrorCounter *jww.Counter
+)
+
+func init() {
+ GlobalErrorCounter = &jww.Counter{}
+ jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError))
+}
+
+// Logger wraps a *loggers.Logger and some other related logging state.
+type Logger struct {
+ *jww.Notepad
+ ErrorCounter *jww.Counter
+
+ // This is only set in server mode.
+ Errors *bytes.Buffer
+}
+
+// Reset resets the logger's internal state.
+func (l *Logger) Reset() {
+ l.ErrorCounter.Reset()
+ if l.Errors != nil {
+ l.Errors.Reset()
+ }
+}
+
+// NewLogger creates a new Logger for the given thresholds
+func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *Logger {
+ return newLogger(stdoutThreshold, logThreshold, outHandle, logHandle, saveErrors)
+}
+
// NewDebugLogger is a convenience function to create a debug logger.
-func NewDebugLogger() *jww.Notepad {
- return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+func NewDebugLogger() *Logger {
+ return newBasicLogger(jww.LevelDebug)
}
// NewWarningLogger is a convenience function to create a warning logger.
-func NewWarningLogger() *jww.Notepad {
- return jww.NewNotepad(jww.LevelWarn, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+func NewWarningLogger() *Logger {
+ return newBasicLogger(jww.LevelWarn)
}
// NewErrorLogger is a convenience function to create an error logger.
-func NewErrorLogger() *jww.Notepad {
- return jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+func NewErrorLogger() *Logger {
+ return newBasicLogger(jww.LevelError)
+}
+
+func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *Logger {
+ errorCounter := &jww.Counter{}
+ listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError)}
+ var errorBuff *bytes.Buffer
+ if saveErrors {
+ errorBuff = new(bytes.Buffer)
+ errorCapture := func(t jww.Threshold) io.Writer {
+ if t != jww.LevelError {
+ // Only interested in ERROR
+ return nil
+ }
+
+ return errorBuff
+ }
+
+ listeners = append(listeners, errorCapture)
+ }
+
+ return &Logger{
+ Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
+ ErrorCounter: errorCounter,
+ Errors: errorBuff,
+ }
+}
+
+func newBasicLogger(t jww.Threshold) *Logger {
+ return newLogger(t, jww.LevelError, os.Stdout, ioutil.Discard, false)
}
diff --git a/deps/deps.go b/deps/deps.go
index 2b66a153f4b..1e2686421dd 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -16,7 +16,6 @@ import (
"github.com/gohugoio/hugo/resource"
"github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/tpl"
- jww "github.com/spf13/jwalterweatherman"
)
// Deps holds dependencies used by many.
@@ -25,7 +24,7 @@ import (
type Deps struct {
// The logger to use.
- Log *jww.Notepad `json:"-"`
+ Log *loggers.Logger `json:"-"`
// Used to log errors that may repeat itself many times.
DistinctErrorLog *helpers.DistinctLogger
@@ -122,10 +121,6 @@ func (d *Deps) LoadResources() error {
return err
}
- if th, ok := d.Tmpl.(tpl.TemplateHandler); ok {
- th.PrintErrors()
- }
-
return nil
}
@@ -256,7 +251,7 @@ func (d Deps) ForLanguage(cfg DepsCfg) (*Deps, error) {
type DepsCfg struct {
// The Logger to use.
- Logger *jww.Notepad
+ Logger *loggers.Logger
// The file systems to use
Fs *hugofs.Fs
diff --git a/go.mod b/go.mod
index 36fdf260b9a..5040282f5f5 100644
--- a/go.mod
+++ b/go.mod
@@ -38,7 +38,6 @@ require (
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nicksnyder/go-i18n v1.10.0
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84
- github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday v0.0.0-20180804101149-46c73eb196ba
github.com/sanity-io/litter v1.1.0
github.com/sergi/go-diff v1.0.0 // indirect
@@ -47,7 +46,7 @@ require (
github.com/spf13/cast v1.2.0
github.com/spf13/cobra v0.0.3
github.com/spf13/fsync v0.0.0-20170320142552-12a01e648f05
- github.com/spf13/jwalterweatherman v1.0.0
+ github.com/spf13/jwalterweatherman v1.0.1-0.20181005085228-103a6da826d0
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d
github.com/spf13/pflag v1.0.2
github.com/spf13/viper v1.2.0
@@ -60,6 +59,7 @@ require (
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
+ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect
golang.org/x/text v0.3.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.1
diff --git a/go.sum b/go.sum
index 5a71e5d7690..9361ff1688e 100644
--- a/go.sum
+++ b/go.sum
@@ -107,6 +107,8 @@ github.com/spf13/fsync v0.0.0-20170320142552-12a01e648f05 h1:pQHm7pxjSgC54M1rtLS
github.com/spf13/fsync v0.0.0-20170320142552-12a01e648f05/go.mod h1:jdsEoy1w+v0NpuwXZEaRAH6ADTDmzfRnE2eVwshwFrM=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/jwalterweatherman v1.0.1-0.20181005085228-103a6da826d0 h1:kPJPXmEs6V1YyXfHFbp1NCpdqhvFVssh2FGx7+OoJLM=
+github.com/spf13/jwalterweatherman v1.0.1-0.20181005085228-103a6da826d0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d h1:ihvj2nmx8eqWjlgNgdW6h0DyGJuq5GiwHadJkG0wXtQ=
github.com/spf13/nitro v0.0.0-20131003134307-24d7ef30a12d/go.mod h1:jU8A+8xL+6n1OX4XaZtCj4B3mIa64tULUsD6YegdpFo=
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
@@ -133,6 +135,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6Zh
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/hugolib/alias.go b/hugolib/alias.go
index 73d8acafce7..bcf8f1963ec 100644
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -22,12 +22,12 @@ import (
"runtime"
"strings"
+ "github.com/gohugoio/hugo/common/loggers"
+
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/publisher"
"github.com/gohugoio/hugo/tpl"
- jww "github.com/spf13/jwalterweatherman"
-
"github.com/gohugoio/hugo/helpers"
)
@@ -47,11 +47,11 @@ func init() {
type aliasHandler struct {
t tpl.TemplateFinder
- log *jww.Notepad
+ log *loggers.Logger
allowRoot bool
}
-func newAliasHandler(t tpl.TemplateFinder, l *jww.Notepad, allowRoot bool) aliasHandler {
+func newAliasHandler(t tpl.TemplateFinder, l *loggers.Logger, allowRoot bool) aliasHandler {
return aliasHandler{t, l, allowRoot}
}
diff --git a/hugolib/datafiles_test.go b/hugolib/datafiles_test.go
index 8b2dc8c0fef..6685de4cc61 100644
--- a/hugolib/datafiles_test.go
+++ b/hugolib/datafiles_test.go
@@ -347,7 +347,7 @@ func doTestDataDirImpl(t *testing.T, dd dataDir, expected interface{}, configKey
}
}()
- s := buildSingleSiteExpected(t, expectBuildError, depsCfg, BuildCfg{SkipRender: true})
+ s := buildSingleSiteExpected(t, false, expectBuildError, depsCfg, BuildCfg{SkipRender: true})
if !expectBuildError && !reflect.DeepEqual(expected, s.Data) {
// This disabled code detects the situation described in the WARNING message below.
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 3ff31ece36a..d9eb9f57d1a 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -21,6 +21,7 @@ import (
"strings"
"sync"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/langs"
@@ -29,7 +30,6 @@ import (
"github.com/gohugoio/hugo/i18n"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/tpl/tplimpl"
- jww "github.com/spf13/jwalterweatherman"
)
// HugoSites represents the sites to build. Each site represents a language.
@@ -69,7 +69,7 @@ func (h *HugoSites) NumLogErrors() int {
if h == nil {
return 0
}
- return int(h.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError))
+ return int(h.Log.ErrorCounter.Count())
}
func (h *HugoSites) PrintProcessingStats(w io.Writer) {
@@ -250,7 +250,9 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateHandler) error) func(templ tpl.TemplateHandler) error {
return func(templ tpl.TemplateHandler) error {
- templ.LoadTemplates("")
+ if err := templ.LoadTemplates(""); err != nil {
+ return err
+ }
for _, wt := range withTemplates {
if wt == nil {
@@ -301,7 +303,8 @@ func (h *HugoSites) reset() {
// resetLogs resets the log counters etc. Used to do a new build on the same sites.
func (h *HugoSites) resetLogs() {
- h.Log.ResetLogCounters()
+ h.Log.Reset()
+ loggers.GlobalErrorCounter.Reset()
for _, s := range h.Sites {
s.Deps.DistinctErrorLog = helpers.NewDistinctLogger(h.Log.ERROR)
}
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index 8ca2128a166..5bb328aa286 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -19,8 +19,6 @@ import (
"errors"
- jww "github.com/spf13/jwalterweatherman"
-
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/helpers"
)
@@ -79,7 +77,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
h.Log.FEEDBACK.Println()
}
- errorCount := h.Log.LogCountForLevel(jww.LevelError)
+ errorCount := h.Log.ErrorCounter.Count()
if errorCount > 0 {
return fmt.Errorf("logged %d error(s)", errorCount)
}
diff --git a/hugolib/page.go b/hugolib/page.go
index 1fefd945a2e..f48d5f18dbf 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -989,11 +989,15 @@ func (s *Site) NewPage(name string) (*Page, error) {
return p, nil
}
+func (p *Page) errorf(format string, a ...interface{}) error {
+ args := append([]interface{}{p.Lang(), p.pathOrTitle()}, a...)
+ return fmt.Errorf("[%s] Page %q: "+format, args...)
+}
+
func (p *Page) ReadFrom(buf io.Reader) (int64, error) {
// Parse for metadata & body
if err := p.parse(buf); err != nil {
- p.s.Log.ERROR.Printf("%s for %s", err, p.File.Path())
- return 0, err
+ return 0, p.errorf("parse failed: %s", err)
}
return int64(len(p.rawContent)), nil
@@ -1751,6 +1755,7 @@ func (p *Page) shouldRenderTo(f output.Format) bool {
func (p *Page) parse(reader io.Reader) error {
psr, err := parser.ReadFrom(reader)
+
if err != nil {
return err
}
@@ -1762,7 +1767,7 @@ func (p *Page) parse(reader io.Reader) error {
meta, err := psr.Metadata()
if err != nil {
- return fmt.Errorf("failed to parse page metadata for %q: %s", p.File.Path(), err)
+ return fmt.Errorf("error in front matter: %s", err)
}
if meta == nil {
// missing frontmatter equivalent to empty frontmatter
@@ -2303,8 +2308,13 @@ func (p *Page) setValuesForKind(s *Site) {
// Used in error logs.
func (p *Page) pathOrTitle() string {
- if p.Path() != "" {
- return p.Path()
+ if p.Filename() != "" {
+ // Make a path relative to the working dir if possible.
+ filename := strings.TrimPrefix(p.Filename(), p.s.WorkingDir)
+ if filename != p.Filename() {
+ filename = strings.TrimPrefix(filename, helpers.FilePathSeparator)
+ }
+ return filename
}
return p.title
}
diff --git a/hugolib/page_bundler_capture.go b/hugolib/page_bundler_capture.go
index fbfad0103a2..ca41df1fe71 100644
--- a/hugolib/page_bundler_capture.go
+++ b/hugolib/page_bundler_capture.go
@@ -20,6 +20,9 @@ import (
"path"
"path/filepath"
"runtime"
+
+ "github.com/gohugoio/hugo/common/loggers"
+
"sort"
"strings"
"sync"
@@ -33,7 +36,6 @@ import (
"golang.org/x/sync/errgroup"
"github.com/gohugoio/hugo/source"
- jww "github.com/spf13/jwalterweatherman"
)
var errSkipCyclicDir = errors.New("skip potential cyclic dir")
@@ -47,7 +49,7 @@ type capturer struct {
sourceSpec *source.SourceSpec
fs afero.Fs
- logger *jww.Notepad
+ logger *loggers.Logger
// Filenames limits the content to process to a list of filenames/directories.
// This is used for partial building in server mode.
@@ -61,7 +63,7 @@ type capturer struct {
}
func newCapturer(
- logger *jww.Notepad,
+ logger *loggers.Logger,
sourceSpec *source.SourceSpec,
handler captureResultHandler,
contentChanges *contentChangeMap,
diff --git a/hugolib/page_bundler_capture_test.go b/hugolib/page_bundler_capture_test.go
index ace96b633ad..d6128352c0a 100644
--- a/hugolib/page_bundler_capture_test.go
+++ b/hugolib/page_bundler_capture_test.go
@@ -22,8 +22,6 @@ import (
"github.com/gohugoio/hugo/common/loggers"
- jww "github.com/spf13/jwalterweatherman"
-
"runtime"
"strings"
"sync"
@@ -100,9 +98,6 @@ func TestPageBundlerCaptureSymlinks(t *testing.T) {
assert.NoError(c.capture())
- // Symlink back to content skipped to prevent infinite recursion.
- assert.Equal(uint64(3), logger.LogCountForLevelsGreaterThanorEqualTo(jww.LevelWarn))
-
expected := `
F:
/base/a/page_s.md
diff --git a/hugolib/pagemeta/page_frontmatter.go b/hugolib/pagemeta/page_frontmatter.go
index c1139bd907c..88f6f3a11e4 100644
--- a/hugolib/pagemeta/page_frontmatter.go
+++ b/hugolib/pagemeta/page_frontmatter.go
@@ -14,17 +14,14 @@
package pagemeta
import (
- "io/ioutil"
- "log"
- "os"
"strings"
"time"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/config"
"github.com/spf13/cast"
- jww "github.com/spf13/jwalterweatherman"
)
// FrontMatterHandler maps front matter into Page fields and .Params.
@@ -40,7 +37,7 @@ type FrontMatterHandler struct {
// A map of all date keys configured, including any custom.
allDateKeys map[string]bool
- logger *jww.Notepad
+ logger *loggers.Logger
}
// FrontMatterDescriptor describes how to handle front matter for a given Page.
@@ -263,10 +260,10 @@ func toLowerSlice(in interface{}) []string {
// NewFrontmatterHandler creates a new FrontMatterHandler with the given logger and configuration.
// If no logger is provided, one will be created.
-func NewFrontmatterHandler(logger *jww.Notepad, cfg config.Provider) (FrontMatterHandler, error) {
+func NewFrontmatterHandler(logger *loggers.Logger, cfg config.Provider) (FrontMatterHandler, error) {
if logger == nil {
- logger = jww.NewNotepad(jww.LevelWarn, jww.LevelWarn, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+ logger = loggers.NewWarningLogger()
}
frontMatterConfig, err := newFrontmatterConfig(cfg)
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index c2fcf1b8d0f..9619b3e3b0e 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -524,7 +524,7 @@ Loop:
// return that error, more specific
continue
}
- return sc, fmt.Errorf("Shortcode '%s' in page '%s' has no .Inner, yet a closing tag was provided", next.val, p.FullFilePath())
+ return sc, p.errorf("shortcode %q has no .Inner, yet a closing tag was provided", next.val)
}
if next.typ == tRightDelimScWithMarkup || next.typ == tRightDelimScNoMarkup {
// self-closing
@@ -542,13 +542,13 @@ Loop:
// if more than one. It is "all inner or no inner".
tmpl := getShortcodeTemplateForTemplateKey(scKey{}, sc.name, p.s.Tmpl)
if tmpl == nil {
- return sc, fmt.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
+ return sc, p.errorf("unable to locate template for shortcode %q", sc.name)
}
var err error
isInner, err = isInnerShortcode(tmpl.(tpl.TemplateExecutor))
if err != nil {
- return sc, fmt.Errorf("Failed to handle template for shortcode %q for page %q: %s", sc.name, p.Path(), err)
+ return sc, p.errorf("failed to handle template for shortcode %q: %s", sc.name, err)
}
case tScParam:
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index df7b7103f98..0f60f8e5ab7 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -24,8 +24,6 @@ import (
"github.com/spf13/viper"
- jww "github.com/spf13/jwalterweatherman"
-
"github.com/spf13/afero"
"github.com/gohugoio/hugo/output"
@@ -777,7 +775,7 @@ NotFound: {{< thisDoesNotExist >}}
"thisDoesNotExist",
)
- require.Equal(t, uint64(1), s.Log.LogCountForLevel(jww.LevelError))
+ require.Equal(t, uint64(1), s.Log.ErrorCounter.Count())
}
diff --git a/hugolib/site.go b/hugolib/site.go
index 1196496d303..fe539825d62 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -754,8 +754,6 @@ func (s *Site) processPartial(events []fsnotify.Event) (whatChanged, error) {
return whatChanged{}, err
}
- s.TemplateHandler().PrintErrors()
-
for i := 1; i < len(sites); i++ {
site := sites[i]
var err error
@@ -1759,7 +1757,7 @@ func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts
if err = templ.Execute(w, d); err != nil {
// Behavior here should be dependent on if running in server or watch mode.
if p, ok := d.(*PageOutput); ok {
- if p.File != nil {
+ if p.File != nil && p.File.Dir() != "" {
s.DistinctErrorLog.Printf("Error while rendering %q in %q: %s", name, p.File.Dir(), err)
} else {
s.DistinctErrorLog.Printf("Error while rendering %q: %s", name, err)
diff --git a/hugolib/site_test.go b/hugolib/site_test.go
index f775b0e7971..a5688c78ef4 100644
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -54,7 +54,7 @@ func TestRenderWithInvalidTemplate(t *testing.T) {
withTemplate := createWithTemplateFromNameValues("missing", templateMissingFunc)
- buildSingleSiteExpected(t, true, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
+ buildSingleSiteExpected(t, true, false, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
}
diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
index 27edf3fdd6b..1740deefdc0 100644
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -14,7 +14,6 @@ import (
"github.com/gohugoio/hugo/langs"
"github.com/sanity-io/litter"
- jww "github.com/spf13/jwalterweatherman"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
@@ -26,6 +25,7 @@ import (
"os"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugofs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -38,7 +38,7 @@ type sitesBuilder struct {
Fs *hugofs.Fs
T testing.TB
- logger *jww.Notepad
+ logger *loggers.Logger
dumper litter.Options
@@ -103,7 +103,7 @@ func (s *sitesBuilder) Running() *sitesBuilder {
return s
}
-func (s *sitesBuilder) WithLogger(logger *jww.Notepad) *sitesBuilder {
+func (s *sitesBuilder) WithLogger(logger *loggers.Logger) *sitesBuilder {
s.logger = logger
return s
}
@@ -639,13 +639,19 @@ func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ
}
func buildSingleSite(t testing.TB, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
- return buildSingleSiteExpected(t, false, depsCfg, buildCfg)
+ return buildSingleSiteExpected(t, false, false, depsCfg, buildCfg)
}
-func buildSingleSiteExpected(t testing.TB, expectBuildError bool, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
+func buildSingleSiteExpected(t testing.TB, expectSiteInitEror, expectBuildError bool, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
h, err := NewHugoSites(depsCfg)
- require.NoError(t, err)
+ if expectSiteInitEror {
+ require.Error(t, err)
+ return nil
+ } else {
+ require.NoError(t, err)
+ }
+
require.Len(t, h.Sites, 1)
if expectBuildError {
diff --git a/i18n/i18n.go b/i18n/i18n.go
index 73417fb3240..d436ea58d37 100644
--- a/i18n/i18n.go
+++ b/i18n/i18n.go
@@ -14,6 +14,7 @@
package i18n
import (
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/nicksnyder/go-i18n/i18n/bundle"
@@ -28,11 +29,11 @@ var (
type Translator struct {
translateFuncs map[string]bundle.TranslateFunc
cfg config.Provider
- logger *jww.Notepad
+ logger *loggers.Logger
}
// NewTranslator creates a new Translator for the given language bundle and configuration.
-func NewTranslator(b *bundle.Bundle, cfg config.Provider, logger *jww.Notepad) Translator {
+func NewTranslator(b *bundle.Bundle, cfg config.Provider, logger *loggers.Logger) Translator {
t := Translator{cfg: cfg, logger: logger, translateFuncs: make(map[string]bundle.TranslateFunc)}
t.initFuncs(b)
return t
diff --git a/i18n/i18n_test.go b/i18n/i18n_test.go
index 5075839ff2f..84b7384d075 100644
--- a/i18n/i18n_test.go
+++ b/i18n/i18n_test.go
@@ -19,24 +19,19 @@ import (
"github.com/gohugoio/hugo/tpl/tplimpl"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/langs"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/deps"
- "io/ioutil"
- "os"
-
- "log"
-
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
- jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
-var logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+var logger = loggers.NewErrorLogger()
type i18nTest struct {
data map[string][]byte
diff --git a/resource/resource.go b/resource/resource.go
index dd9cbbd4179..a18d03aabf3 100644
--- a/resource/resource.go
+++ b/resource/resource.go
@@ -32,8 +32,6 @@ import (
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/loggers"
- jww "github.com/spf13/jwalterweatherman"
-
"github.com/spf13/afero"
"github.com/gobwas/glob"
@@ -273,7 +271,7 @@ type Spec struct {
MediaTypes media.Types
OutputFormats output.Formats
- Logger *jww.Notepad
+ Logger *loggers.Logger
TextTemplates tpl.TemplateParseFinder
@@ -287,7 +285,7 @@ type Spec struct {
GenAssetsPath string
}
-func NewSpec(s *helpers.PathSpec, logger *jww.Notepad, outputFormats output.Formats, mimeTypes media.Types) (*Spec, error) {
+func NewSpec(s *helpers.PathSpec, logger *loggers.Logger, outputFormats output.Formats, mimeTypes media.Types) (*Spec, error) {
imaging, err := decodeImaging(s.Cfg.GetStringMap("imaging"))
if err != nil {
@@ -542,7 +540,7 @@ type resourceHash struct {
type publishOnce struct {
publisherInit sync.Once
publisherErr error
- logger *jww.Notepad
+ logger *loggers.Logger
}
func (l *publishOnce) publish(s Source) error {
diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go
index c8a7207ea4b..9a26100e090 100644
--- a/tpl/collections/collections_test.go
+++ b/tpl/collections/collections_test.go
@@ -17,20 +17,18 @@ import (
"errors"
"fmt"
"html/template"
- "io/ioutil"
- "log"
"math/rand"
- "os"
"reflect"
"testing"
"time"
+ "github.com/gohugoio/hugo/common/collections"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/langs"
- jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -856,7 +854,7 @@ func newDeps(cfg config.Provider) *deps.Deps {
Cfg: cfg,
Fs: hugofs.NewMem(l),
ContentSpec: cs,
- Log: jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime),
+ Log: loggers.NewErrorLogger(),
}
}
diff --git a/tpl/data/data_test.go b/tpl/data/data_test.go
index 6bee0d52481..9ef969244a9 100644
--- a/tpl/data/data_test.go
+++ b/tpl/data/data_test.go
@@ -113,11 +113,11 @@ func TestGetCSV(t *testing.T) {
require.NoError(t, err, msg)
if _, ok := test.expect.(bool); ok {
- require.Equal(t, 1, int(ns.deps.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)))
+ require.Equal(t, 1, int(ns.deps.Log.ErrorCounter.Count()))
require.Nil(t, got)
continue
}
- require.Equal(t, 0, int(ns.deps.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)))
+ require.Equal(t, 0, int(ns.deps.Log.ErrorCounter.Count()))
require.NotNil(t, got, msg)
assert.EqualValues(t, test.expect, got, msg)
@@ -198,14 +198,14 @@ func TestGetJSON(t *testing.T) {
continue
}
- if errLevel, ok := test.expect.(jww.Threshold); ok {
- logCount := ns.deps.Log.LogCountForLevelsGreaterThanorEqualTo(errLevel)
+ if errLevel, ok := test.expect.(jww.Threshold); ok && errLevel >= jww.LevelError {
+ logCount := ns.deps.Log.ErrorCounter.Count()
require.True(t, logCount >= 1, fmt.Sprintf("got log count %d", logCount))
continue
}
require.NoError(t, err, msg)
- require.Equal(t, 0, int(ns.deps.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError)), msg)
+ require.Equal(t, 0, int(ns.deps.Log.ErrorCounter.Count()), msg)
require.NotNil(t, got, msg)
assert.EqualValues(t, test.expect, got, msg)
diff --git a/tpl/fmt/fmt.go b/tpl/fmt/fmt.go
index 0f4f906c289..09e4f5a405a 100644
--- a/tpl/fmt/fmt.go
+++ b/tpl/fmt/fmt.go
@@ -16,12 +16,13 @@ package fmt
import (
_fmt "fmt"
+ "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
)
// New returns a new instance of the fmt-namespaced template functions.
-func New() *Namespace {
- return &Namespace{helpers.NewDistinctErrorLogger()}
+func New(d *deps.Deps) *Namespace {
+ return &Namespace{helpers.NewDistinctLogger(d.Log.ERROR)}
}
// Namespace provides template functions for the "fmt" namespace.
diff --git a/tpl/fmt/init.go b/tpl/fmt/init.go
index 76c68957aaa..1170558010b 100644
--- a/tpl/fmt/init.go
+++ b/tpl/fmt/init.go
@@ -22,7 +22,7 @@ const name = "fmt"
func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
- ctx := New()
+ ctx := New(d)
ns := &internal.TemplateFuncsNamespace{
Name: name,
diff --git a/tpl/partials/init_test.go b/tpl/partials/init_test.go
index 4832e6b66bc..0513f1572a3 100644
--- a/tpl/partials/init_test.go
+++ b/tpl/partials/init_test.go
@@ -16,6 +16,7 @@ package partials
import (
"testing"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/internal"
"github.com/stretchr/testify/require"
@@ -28,6 +29,7 @@ func TestInit(t *testing.T) {
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
ns = nsf(&deps.Deps{
BuildStartListeners: &deps.Listeners{},
+ Log: loggers.NewErrorLogger(),
})
if ns.Name == name {
found = true
diff --git a/tpl/template.go b/tpl/template.go
index 2cef92bb225..461d06ce0d9 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -35,8 +35,7 @@ type TemplateHandler interface {
TemplateFinder
AddTemplate(name, tpl string) error
AddLateTemplate(name, tpl string) error
- LoadTemplates(prefix string)
- PrintErrors()
+ LoadTemplates(prefix string) error
NewTextTemplate() TemplateParseFinder
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index f19c312ec92..4888a384182 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -14,12 +14,14 @@
package tplimpl
import (
+ "errors"
"fmt"
"html/template"
"path"
"strings"
texttemplate "text/template"
+ "github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/tpl/tplimpl/embedded"
"github.com/eknkc/amber"
@@ -64,7 +66,7 @@ type templateErr struct {
}
type templateLoader interface {
- handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error
+ handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error
addTemplate(name, tpl string) error
addLateTemplate(name, tpl string) error
}
@@ -114,22 +116,11 @@ func (t *templateHandler) NewTextTemplate() tpl.TemplateParseFinder {
}
-func (t *templateHandler) addError(name string, err error) {
- t.errors = append(t.errors, &templateErr{name, err})
-}
-
func (t *templateHandler) Debug() {
fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
}
-// PrintErrors prints the accumulated errors as ERROR to the log.
-func (t *templateHandler) PrintErrors() {
- for _, e := range t.errors {
- t.Log.ERROR.Println(e.name, ":", e.err)
- }
-}
-
// Lookup tries to find a template with the given name in both template
// collections: First HTML, then the plain text template collection.
func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
@@ -321,8 +312,8 @@ func (t *textTemplates) setFuncs(funcMap map[string]interface{}) {
// LoadTemplates loads the templates from the layouts filesystem.
// A prefix can be given to indicate a template namespace to load the templates
// into, i.e. "_internal" etc.
-func (t *templateHandler) LoadTemplates(prefix string) {
- t.loadTemplates(prefix)
+func (t *templateHandler) LoadTemplates(prefix string) error {
+ return t.loadTemplates(prefix)
}
@@ -423,7 +414,6 @@ func (t *templateHandler) addLateTemplate(name, tpl string) error {
func (t *templateHandler) AddLateTemplate(name, tpl string) error {
h := t.getTemplateHandler(name)
if err := h.addLateTemplate(name, tpl); err != nil {
- t.addError(name, err)
return err
}
return nil
@@ -435,7 +425,6 @@ func (t *templateHandler) AddLateTemplate(name, tpl string) error {
func (t *templateHandler) AddTemplate(name, tpl string) error {
h := t.getTemplateHandler(name)
if err := h.addTemplate(name, tpl); err != nil {
- t.addError(name, err)
return err
}
return nil
@@ -458,14 +447,21 @@ func (t *templateHandler) MarkReady() {
// RebuildClone rebuilds the cloned templates. Used for live-reloads.
func (t *templateHandler) RebuildClone() {
- t.html.clone = template.Must(t.html.cloneClone.Clone())
- t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
+ if t.html != nil && t.html.cloneClone != nil {
+ t.html.clone = template.Must(t.html.cloneClone.Clone())
+ }
+ if t.text != nil && t.text.cloneClone != nil {
+ t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
+ }
}
-func (t *templateHandler) loadTemplates(prefix string) {
+func (t *templateHandler) loadTemplates(prefix string) error {
+
+ var failed bool
+
walker := func(path string, fi os.FileInfo, err error) error {
if err != nil || fi.IsDir() {
- return nil
+ return err
}
if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
@@ -490,21 +486,38 @@ func (t *templateHandler) loadTemplates(prefix string) {
tplID, err := output.CreateTemplateNames(descriptor)
if err != nil {
t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
-
- return nil
+ failed = true
}
if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
- t.Log.ERROR.Printf("Failed to add template %q in path %q: %s", tplID.Name, path, err)
+
+ filename := path
+ // This layout can be overridden, so we need to give the user the full path in the error message,
+ // if possible
+ if realFi, ok := fi.(hugofs.RealFilenameInfo); ok {
+ filename = realFi.RealFilename()
+ }
+
+ t.Log.ERROR.Printf("Failed to add %q: %s", filename, err)
+ failed = true
}
return nil
}
if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil {
- t.Log.ERROR.Printf("Failed to load templates: %s", err)
+ if !os.IsNotExist(err) {
+ return err
+ }
+ return nil
}
+ if failed {
+ return errors.New("Failed to load templates")
+ }
+
+ return nil
+
}
func (t *templateHandler) initFuncs() {
@@ -553,12 +566,18 @@ func (t *templateHandler) getTemplateHandler(name string) templateLoader {
return t.html
}
-func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
+type templateInfo struct {
+ template string
+ // The real filename (if possible). Used for logging.
+ filename string
+}
+
+func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
h := t.getTemplateHandler(name)
return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
}
-func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
+func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
masterTpl := t.lookup(masterFilename)
@@ -570,7 +589,7 @@ func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename strin
masterTpl, err = t.t.New(overlayFilename).Parse(templ)
if err != nil {
- return err
+ return fmt.Errorf("failed to parse %q: %s", templ.filename, err)
}
}
@@ -579,9 +598,9 @@ func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename strin
return err
}
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ)
+ overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template)
if err != nil {
- return err
+ return fmt.Errorf("failed to parse %q: %s", templ.filename, err)
}
// The extra lookup is a workaround, see
@@ -598,7 +617,7 @@ func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename strin
}
-func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
+func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
name = strings.TrimPrefix(name, textTmplNamePrefix)
masterTpl := t.lookup(masterFilename)
@@ -609,9 +628,9 @@ func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename strin
return err
}
- masterTpl, err = t.t.New(overlayFilename).Parse(templ)
+ masterTpl, err = t.t.New(masterFilename).Parse(templ.template)
if err != nil {
- return err
+ return fmt.Errorf("failed to parse %q: %s", templ.filename, err)
}
}
@@ -620,9 +639,9 @@ func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename strin
return err
}
- overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ)
+ overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ.template)
if err != nil {
- return err
+ return fmt.Errorf("failed to parse %q: %s", templ.filename, err)
}
overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
@@ -640,14 +659,20 @@ func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) e
t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path)
- getTemplate := func(filename string) (string, error) {
+ getTemplate := func(filename string) (templateInfo, error) {
b, err := afero.ReadFile(t.Layouts.Fs, filename)
if err != nil {
- return "", err
+ return templateInfo{filename: filename}, err
}
s := string(b)
- return s, nil
+ if fi, err := t.Layouts.Fs.Stat(filename); err == nil {
+ if fir, ok := fi.(hugofs.RealFilenameInfo); ok {
+ filename = fir.RealFilename()
+ }
+ }
+
+ return templateInfo{template: s, filename: filename}, nil
}
// get the suffix and switch on that
@@ -712,7 +737,7 @@ func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) e
return err
}
- return t.AddTemplate(name, templ)
+ return t.AddTemplate(name, templ.template)
}
}
@@ -720,19 +745,24 @@ var embeddedTemplatesAliases = map[string][]string{
"shortcodes/twitter.html": []string{"shortcodes/tweet.html"},
}
-func (t *templateHandler) loadEmbedded() {
+func (t *templateHandler) loadEmbedded() error {
for _, kv := range embedded.EmbeddedTemplates {
- // TODO(bep) error handling
name, templ := kv[0], kv[1]
- t.addInternalTemplate(name, templ)
+ if err := t.addInternalTemplate(name, templ); err != nil {
+ return err
+ }
if aliases, found := embeddedTemplatesAliases[name]; found {
for _, alias := range aliases {
- t.addInternalTemplate(alias, templ)
+ if err := t.addInternalTemplate(alias, templ); err != nil {
+ return err
+ }
}
}
}
+ return nil
+
}
func (t *templateHandler) addInternalTemplate(name, tpl string) error {
diff --git a/tpl/tplimpl/templateProvider.go b/tpl/tplimpl/templateProvider.go
index df44e81a6e0..3a803f2da9c 100644
--- a/tpl/tplimpl/templateProvider.go
+++ b/tpl/tplimpl/templateProvider.go
@@ -33,12 +33,15 @@ func (*TemplateProvider) Update(deps *deps.Deps) error {
deps.TextTmpl = newTmpl.NewTextTemplate()
newTmpl.initFuncs()
- newTmpl.loadEmbedded()
+
+ if err := newTmpl.loadEmbedded(); err != nil {
+ return err
+ }
if deps.WithTemplate != nil {
err := deps.WithTemplate(newTmpl)
if err != nil {
- newTmpl.addError("init", err)
+ return err
}
}
diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
index 8594c67a455..04bb4941a7e 100644
--- a/tpl/tplimpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -21,10 +21,7 @@ import (
"testing"
"time"
- "io/ioutil"
- "log"
- "os"
-
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
@@ -35,13 +32,12 @@ import (
"github.com/gohugoio/hugo/tpl/internal"
"github.com/gohugoio/hugo/tpl/partials"
"github.com/spf13/afero"
- jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
var (
- logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
+ logger = loggers.NewErrorLogger()
)
func newTestConfig() config.Provider {