Fix paginator refresh on server change
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 10 Apr 2019 08:11:51 +0000 (10:11 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 12 Apr 2019 07:18:59 +0000 (09:18 +0200)
Fixes #5838

hugolib/hugo_sites.go
hugolib/hugo_sites_build.go
hugolib/hugo_sites_rebuild_test.go [new file with mode: 0644]
hugolib/page.go
hugolib/page__output.go
hugolib/page__paginator.go
hugolib/site_render.go
hugolib/testhelpers_test.go

index caa9a4bc2efd210c6fa11503b8df04bbdd795c5b..8c80e189c854f9e71c532ff752e30f8be19adca5 100644 (file)
@@ -761,16 +761,16 @@ func (h *HugoSites) createPageCollections() error {
        return nil
 }
 
-func (s *Site) preparePagesForRender(idx int) error {
+func (s *Site) preparePagesForRender(isRenderingSite bool, idx int) error {
 
        for _, p := range s.workAllPages {
-               if err := p.initOutputFormat(idx); err != nil {
+               if err := p.initOutputFormat(isRenderingSite, idx); err != nil {
                        return err
                }
        }
 
        for _, p := range s.headlessPages {
-               if err := p.initOutputFormat(idx); err != nil {
+               if err := p.initOutputFormat(isRenderingSite, idx); err != nil {
                        return err
                }
        }
index 91dfb9a37f5ed32efcddded56d45164dc70dbbde..d748a0169dd815ace68aed471317cd14971c80ba 100644 (file)
@@ -288,7 +288,7 @@ func (h *HugoSites) render(config *BuildCfg) error {
                                        // needs this set.
                                        s2.rc = &siteRenderingContext{Format: renderFormat}
 
-                                       if err := s2.preparePagesForRender(siteRenderContext.sitesOutIdx); err != nil {
+                                       if err := s2.preparePagesForRender(s == s2, siteRenderContext.sitesOutIdx); err != nil {
                                                return err
                                        }
                                }
diff --git a/hugolib/hugo_sites_rebuild_test.go b/hugolib/hugo_sites_rebuild_test.go
new file mode 100644 (file)
index 0000000..c2acbc2
--- /dev/null
@@ -0,0 +1,77 @@
+// Copyright 2019 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 hugolib
+
+import (
+       "testing"
+)
+
+func TestSitesRebuild(t *testing.T) {
+
+       configFile := `
+baseURL = "https://example.com"
+title = "Rebuild this"
+contentDir = "content"
+
+
+`
+
+       contentFilename := "content/blog/page1.md"
+
+       b := newTestSitesBuilder(t).WithConfigFile("toml", configFile)
+
+       // To simulate https://github.com/gohugoio/hugo/issues/5838, the home page
+       // needs a content page.
+       b.WithContent("content/_index.md", `---
+title: Home, Sweet Home!
+---
+
+`)
+
+       b.WithContent(contentFilename, `
+---
+title: "Page 1"
+summary: "Initial summary"
+paginate: 3
+---
+
+Content.
+
+`)
+
+       b.WithTemplatesAdded("index.html", `
+{{ range (.Paginate .Site.RegularPages).Pages }}
+* Page: {{ .Title }}|Summary: {{ .Summary }}|Content: {{ .Content }}
+{{ end }}
+`)
+
+       b.Running().Build(BuildCfg{})
+
+       b.AssertFileContent("public/index.html", "* Page: Page 1|Summary: Initial summary|Content: <p>Content.</p>")
+
+       b.EditFiles(contentFilename, `
+---
+title: "Page 1 edit"
+summary: "Edited summary"
+---
+
+Edited content.
+
+`)
+
+       b.Build(BuildCfg{})
+
+       b.AssertFileContent("public/index.html", "* Page: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
+
+}
index 99b771eee302c8d8123ea87a75fe19b0cd8b2da0..2ed828df6bd00b2242a305926d09ea2d8e35aa0e 100644 (file)
@@ -332,8 +332,8 @@ func (p *pageState) getLayouts(layouts ...string) ([]string, error) {
 }
 
 // This is serialized
-func (p *pageState) initOutputFormat(idx int) error {
-       if err := p.shiftToOutputFormat(idx); err != nil {
+func (p *pageState) initOutputFormat(isRenderingSite bool, idx int) error {
+       if err := p.shiftToOutputFormat(isRenderingSite, idx); err != nil {
                return err
        }
 
@@ -700,7 +700,7 @@ func (p *pageState) posOffset(offset int) text.Position {
 
 // shiftToOutputFormat is serialized. The output format idx refers to the
 // full set of output formats for all sites.
-func (p *pageState) shiftToOutputFormat(idx int) error {
+func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
        if err := p.initPage(); err != nil {
                return err
        }
@@ -715,6 +715,12 @@ func (p *pageState) shiftToOutputFormat(idx int) error {
                panic(fmt.Sprintf("pageOutput is nil for output idx %d", idx))
        }
 
+       // Reset any built paginator. This will trigger when re-rendering pages in
+       // server mode.
+       if isRenderingSite && p.pageOutput.paginator != nil && p.pageOutput.paginator.current != nil {
+               p.pageOutput.paginator.reset()
+       }
+
        if idx > 0 {
                // Check if we can reuse content from one of the previous formats.
                for i := idx - 1; i >= 0; i-- {
@@ -728,7 +734,7 @@ func (p *pageState) shiftToOutputFormat(idx int) error {
 
        for _, r := range p.Resources().ByType(pageResourceType) {
                rp := r.(*pageState)
-               if err := rp.shiftToOutputFormat(idx); err != nil {
+               if err := rp.shiftToOutputFormat(isRenderingSite, idx); err != nil {
                        return errors.Wrap(err, "failed to shift outputformat in Page resource")
                }
        }
index d38d7c852dd034b5d391d3b094e9af27d49104dc..619ac0d77884cd219615dd7bc24326406324d065 100644 (file)
@@ -41,7 +41,7 @@ func newPageOutput(
        var pag *pagePaginator
 
        if render && ps.IsNode() {
-               pag = &pagePaginator{source: ps}
+               pag = newPagePaginator(ps)
                paginatorProvider = pag
        }
 
index 020d0808971bac579a6839cc696dcfb49cf796d6..026546742c7065fa8b963224f6ab4591df32d658 100644 (file)
@@ -19,16 +19,31 @@ import (
        "github.com/gohugoio/hugo/resources/page"
 )
 
-type pagePaginator struct {
-       paginatorInit sync.Once
-       current       *page.Pager
+func newPagePaginator(source *pageState) *pagePaginator {
+       return &pagePaginator{
+               source:            source,
+               pagePaginatorInit: &pagePaginatorInit{},
+       }
+}
 
+type pagePaginator struct {
+       *pagePaginatorInit
        source *pageState
 }
 
+type pagePaginatorInit struct {
+       init    sync.Once
+       current *page.Pager
+}
+
+// reset resets the paginator to allow for a rebuild.
+func (p *pagePaginator) reset() {
+       p.pagePaginatorInit = &pagePaginatorInit{}
+}
+
 func (p *pagePaginator) Paginate(seq interface{}, options ...interface{}) (*page.Pager, error) {
        var initErr error
-       p.paginatorInit.Do(func() {
+       p.init.Do(func() {
                pagerSize, err := page.ResolvePagerSize(p.source.s.Cfg, options...)
                if err != nil {
                        initErr = err
@@ -56,7 +71,7 @@ func (p *pagePaginator) Paginate(seq interface{}, options ...interface{}) (*page
 
 func (p *pagePaginator) Paginator(options ...interface{}) (*page.Pager, error) {
        var initErr error
-       p.paginatorInit.Do(func() {
+       p.init.Do(func() {
                pagerSize, err := page.ResolvePagerSize(p.source.s.Cfg, options...)
                if err != nil {
                        initErr = err
@@ -81,7 +96,3 @@ func (p *pagePaginator) Paginator(options ...interface{}) (*page.Pager, error) {
 
        return p.current, nil
 }
-
-func (p *pagePaginator) rewind() {
-       p.current = p.current.First()
-}
index 7607040537ed2ef5217eb05fc0745a0aef377d64..407061b677874cfefd36eb46b656bc1bfb1bbb58 100644 (file)
@@ -171,12 +171,9 @@ func (s *Site) renderPaginator(p *pageState, layouts []string) error {
        f := p.s.rc.Format
        d.Type = f
 
-       // Rewind
-       p.paginator.rewind()
-       defer func() {
-               // Prepare for any re-rendering in server mode.
-               p.paginator.rewind()
-       }()
+       if p.paginator.current == nil || p.paginator.current != p.paginator.current.First() {
+               panic(fmt.Sprintf("invalid paginator state for %q", p.pathOrTitle()))
+       }
 
        // Write alias for page 1
        d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1)
index 0a8fbe7f5a355f11ad0633060541ac77a94288f6..d7c9724432327d405c8e435ede9aef7492ba5758 100644 (file)
@@ -14,6 +14,7 @@ import (
        "strings"
        "text/template"
 
+       "github.com/fsnotify/fsnotify"
        "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/deps"
@@ -45,6 +46,9 @@ type sitesBuilder struct {
 
        dumper litter.Options
 
+       // Used to test partial rebuilds.
+       changedFiles []string
+
        // Aka the Hugo server mode.
        running bool
 
@@ -296,6 +300,19 @@ func (s *sitesBuilder) WithI18nAdded(filenameContent ...string) *sitesBuilder {
        return s
 }
 
+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]
+               changedFiles = append(changedFiles, filename)
+               writeSource(s.T, s.Fs, filename, content)
+
+       }
+       s.changedFiles = changedFiles
+
+       return s
+}
+
 func (s *sitesBuilder) writeFilePairs(folder string, filenameContent []string) *sitesBuilder {
        if len(filenameContent)%2 != 0 {
                s.Fatalf("expect filenameContent for %q in pairs (%d)", folder, len(filenameContent))
@@ -376,12 +393,33 @@ func (s *sitesBuilder) BuildFail(cfg BuildCfg) *sitesBuilder {
        return s.build(cfg, true)
 }
 
+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{
+                       Name: v,
+                       Op:   fsnotify.Write,
+               }
+       }
+
+       return events
+}
+
 func (s *sitesBuilder) build(cfg BuildCfg, shouldFail bool) *sitesBuilder {
+       defer func() {
+               s.changedFiles = nil
+       }()
+
        if s.H == nil {
                s.CreateSites()
        }
 
-       err := s.H.Build(cfg)
+       err := s.H.Build(cfg, s.changeEvents()...)
 
        if err == nil {
                logErrorCount := s.H.NumLogErrors()