URI: 
       hugolib: Improve error and reload handling  of hook templates in server mode - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit 8a58ebb311fd079f65068e7e37725e4d43f17ab5
   DIR parent 0453683816cfbc94e1e19c644f5f84213bb8cf35
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Fri, 20 Dec 2019 08:11:36 +0100
       
       hugolib: Improve error and reload handling  of hook templates in server mode
       
       Fixes #6635
       
       Diffstat:
         M commands/commandeer.go              |       1 +
         M commands/hugo.go                    |      10 ++++++++--
         M commands/server.go                  |       1 +
         M hugolib/content_render_hooks_test.… |      54 +++++++++++++++++++++++++++++++
         M hugolib/hugo_sites.go               |      19 ++++++++++++++++---
         M hugolib/page.go                     |       7 +++++--
         M hugolib/site.go                     |      17 +++++++++++++++--
         M hugolib/testhelpers_test.go         |      32 ++++++++++++++++++++-----------
       
       8 files changed, 121 insertions(+), 20 deletions(-)
       ---
   DIR diff --git a/commands/commandeer.go b/commands/commandeer.go
       @@ -88,6 +88,7 @@ type commandeer struct {
                doLiveReload        bool
                fastRenderMode      bool
                showErrorInBrowser  bool
       +        wasError            bool
        
                configured bool
                paused     bool
   DIR diff --git a/commands/hugo.go b/commands/hugo.go
       @@ -718,6 +718,9 @@ func (c *commandeer) handleBuildErr(err error, msg string) {
        
        func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
                defer c.timeTrack(time.Now(), "Total")
       +        defer func() {
       +                c.wasError = false
       +        }()
        
                c.buildErr = nil
                visited := c.visitedURLs.PeekAllSet()
       @@ -734,16 +737,19 @@ func (c *commandeer) rebuildSites(events []fsnotify.Event) error {
                        }
        
                }
       -        return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited}, events...)
       +        return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, ErrRecovery: c.wasError}, events...)
        }
        
        func (c *commandeer) partialReRender(urls ...string) error {
       +        defer func() {
       +                c.wasError = false
       +        }()
                c.buildErr = nil
                visited := make(map[string]bool)
                for _, url := range urls {
                        visited[url] = true
                }
       -        return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true})
       +        return c.hugo().Build(hugolib.BuildCfg{RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.wasError})
        }
        
        func (c *commandeer) fullRebuild(changeType string) {
   DIR diff --git a/commands/server.go b/commands/server.go
       @@ -334,6 +334,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
                                        // First check the error state
                                        err := f.c.getErrorWithContext()
                                        if err != nil {
       +                                        f.c.wasError = true
                                                w.WriteHeader(500)
                                                r, err := f.errorTemplate(err)
                                                if err != nil {
   DIR diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
       @@ -158,6 +158,60 @@ SHORT3|
        
        }
        
       +func TestRenderHooksDeleteTemplate(t *testing.T) {
       +        config := `
       +baseURL="https://example.org"
       +workingDir="/mywork"
       +`
       +        b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
       +        b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
       +        b.WithTemplatesAdded("_default/_markup/render-link.html", `html-render-link`)
       +
       +        b.WithContent("p1.md", `---
       +title: P1
       +---
       +[First Link](https://www.google.com "Google's Homepage")
       +
       +`)
       +        b.Build(BuildCfg{})
       +
       +        b.AssertFileContent("public/p1/index.html", `<p>html-render-link</p>`)
       +
       +        b.RemoveFiles(
       +                "layouts/_default/_markup/render-link.html",
       +        )
       +
       +        b.Build(BuildCfg{})
       +        b.AssertFileContent("public/p1/index.html", `<p><a href="https://www.google.com" title="Google's Homepage">First Link</a></p>`)
       +
       +}
       +
       +func TestRenderHookAddTemplate(t *testing.T) {
       +        config := `
       +baseURL="https://example.org"
       +workingDir="/mywork"
       +`
       +        b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
       +        b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
       +
       +        b.WithContent("p1.md", `---
       +title: P1
       +---
       +[First Link](https://www.google.com "Google's Homepage")
       +
       +`)
       +        b.Build(BuildCfg{})
       +
       +        b.AssertFileContent("public/p1/index.html", `<p><a href="https://www.google.com" title="Google's Homepage">First Link</a></p>`)
       +
       +        b.EditFiles("layouts/_default/_markup/render-link.html", `html-render-link`)
       +
       +        b.Build(BuildCfg{})
       +
       +        b.AssertFileContent("public/p1/index.html", `<p>html-render-link</p>`)
       +
       +}
       +
        func TestRenderHooksRSS(t *testing.T) {
        
                b := newTestSitesBuilder(t)
   DIR diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
       @@ -564,6 +564,9 @@ type BuildCfg struct {
                // we should skip most of the processing.
                PartialReRender bool
        
       +        // Set in server mode when the last build failed for some reason.
       +        ErrRecovery bool
       +
                // Recently visited URLs. This is used for partial re-rendering.
                RecentlyVisited map[string]bool
        }
       @@ -807,8 +810,20 @@ func (h *HugoSites) findPagesByKindIn(kind string, inPages page.Pages) page.Page
                return h.Sites[0].findPagesByKindIn(kind, inPages)
        }
        
       -func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
       +func (h *HugoSites) resetPageState() {
       +        for _, s := range h.Sites {
       +                for _, p := range s.rawAllPages {
       +                        for _, po := range p.pageOutputs {
       +                                if po.cp == nil {
       +                                        continue
       +                                }
       +                                po.cp.Reset()
       +                        }
       +                }
       +        }
       +}
        
       +func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
                for _, s := range h.Sites {
                PAGES:
                        for _, p := range s.rawAllPages {
       @@ -820,7 +835,6 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
                                        for id, _ := range idset {
                                                if po.cp.dependencyTracker.Search(id) != nil {
                                                        po.cp.Reset()
       -                                                p.forceRender = true
                                                        continue OUTPUTS
                                                }
                                        }
       @@ -834,7 +848,6 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) {
                                                                        po.cp.Reset()
                                                                }
                                                        }
       -                                                p.forceRender = true
                                                        continue PAGES
                                                }
                                        }
   DIR diff --git a/hugolib/page.go b/hugolib/page.go
       @@ -629,9 +629,12 @@ func (p *pageState) Render(layout ...string) (template.HTML, error) {
        
        }
        
       -// wrapError adds some more context to the given error if possible
       +// wrapError adds some more context to the given error if possible/needed
        func (p *pageState) wrapError(err error) error {
       -
       +        if _, ok := err.(*herrors.ErrorWithFileContext); ok {
       +                // Preserve the first file context.
       +                return err
       +        }
                var filename string
                if !p.File().IsZero() {
                        filename = p.File().Filename()
   DIR diff --git a/hugolib/site.go b/hugolib/site.go
       @@ -909,6 +909,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
                        contentFilesChanged []string
        
                        tmplChanged bool
       +                tmplAdded   bool
                        dataChanged bool
                        i18nChanged bool
        
       @@ -934,8 +935,16 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
                                        logger.Println("Source changed", ev)
                                        sourceChanged = append(sourceChanged, ev)
                                case files.ComponentFolderLayouts:
       -                                logger.Println("Template changed", ev)
                                        tmplChanged = true
       +                                if _, found := s.Tmpl.Lookup(id.Path); !found {
       +                                        tmplAdded = true
       +                                }
       +                                if tmplAdded {
       +                                        logger.Println("Template added", ev)
       +                                } else {
       +                                        logger.Println("Template changed", ev)
       +                                }
       +
                                case files.ComponentFolderData:
                                        logger.Println("Data changed", ev)
                                        dataChanged = true
       @@ -1021,7 +1030,11 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
                        sourceFilesChanged[ev.Name] = true
                }
        
       -        h.resetPageStateFromEvents(changeIdentities)
       +        if config.ErrRecovery || tmplAdded {
       +                h.resetPageState()
       +        } else {
       +                h.resetPageStateFromEvents(changeIdentities)
       +        }
        
                if len(sourceReallyChanged) > 0 || len(contentFilesChanged) > 0 {
                        var filenamesChanged []string
   DIR diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go
       @@ -68,6 +68,7 @@ type sitesBuilder struct {
        
                // Used to test partial rebuilds.
                changedFiles []string
       +        removedFiles []string
        
                // Aka the Hugo server mode.
                running bool
       @@ -386,16 +387,22 @@ func (s *sitesBuilder) WithI18nAdded(filenameContent ...string) *sitesBuilder {
        }
        
        func (s *sitesBuilder) EditFiles(filenameContent ...string) *sitesBuilder {
       -        var changedFiles []string
                for i := 0; i < len(filenameContent); i += 2 {
                        filename, content := filepath.FromSlash(filenameContent[i]), filenameContent[i+1]
                        absFilename := s.absFilename(filename)
       -                changedFiles = append(changedFiles, absFilename)
       +                s.changedFiles = append(s.changedFiles, absFilename)
                        writeSource(s.T, s.Fs, absFilename, content)
        
                }
       -        s.changedFiles = changedFiles
       +        return s
       +}
        
       +func (s *sitesBuilder) RemoveFiles(filenames ...string) *sitesBuilder {
       +        for _, filename := range filenames {
       +                absFilename := s.absFilename(filename)
       +                s.removedFiles = append(s.removedFiles, absFilename)
       +                s.Assert(s.Fs.Source.Remove(absFilename), qt.IsNil)
       +        }
                return s
        }
        
       @@ -523,17 +530,20 @@ func (s *sitesBuilder) BuildFail(cfg BuildCfg) *sitesBuilder {
        }
        
        func (s *sitesBuilder) changeEvents() []fsnotify.Event {
       -        if len(s.changedFiles) == 0 {
       -                return nil
       -        }
        
       -        events := make([]fsnotify.Event, len(s.changedFiles))
       -        // TODO(bep) remove?
       -        for i, v := range s.changedFiles {
       -                events[i] = fsnotify.Event{
       +        var events []fsnotify.Event
       +
       +        for _, v := range s.changedFiles {
       +                events = append(events, fsnotify.Event{
                                Name: v,
                                Op:   fsnotify.Write,
       -                }
       +                })
       +        }
       +        for _, v := range s.removedFiles {
       +                events = append(events, fsnotify.Event{
       +                        Name: v,
       +                        Op:   fsnotify.Remove,
       +                })
                }
        
                return events