doLiveReload bool
fastRenderMode bool
showErrorInBrowser bool
+ wasError bool
configured bool
paused bool
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()
}
}
- 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) {
// 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 {
}
+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)
// 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
}
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 {
for id, _ := range idset {
if po.cp.dependencyTracker.Search(id) != nil {
po.cp.Reset()
- p.forceRender = true
continue OUTPUTS
}
}
po.cp.Reset()
}
}
- p.forceRender = true
continue PAGES
}
}
}
-// 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()
contentFilesChanged []string
tmplChanged bool
+ tmplAdded bool
dataChanged bool
i18nChanged bool
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
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
// Used to test partial rebuilds.
changedFiles []string
+ removedFiles []string
// Aka the Hugo server mode.
running bool
}
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
}
}
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