From c6d650c8c8b22fdc7ddedc1e42a3ca698e1390d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 15 Jan 2020 15:59:56 +0100 Subject: tpl/tplimpl: Rework template management to get rid of concurrency issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This more or less completes the simplification of the template handling code in Hugo started in v0.62. The main motivation was to fix a long lasting issue about a crash in HTML content files without front matter. But this commit also comes with a big functional improvement. As we now have moved the base template evaluation to the build stage we now use the same lookup rules for `baseof` as for `list` etc. type of templates. This means that in this simple example you can have a `baseof` template for the `blog` section without having to duplicate the others: ``` layouts ├── _default │   ├── baseof.html │   ├── list.html │   └── single.html └── blog └── baseof.html ``` Also, when simplifying code, you often get rid of some double work, as shown in the "site building" benchmarks below. These benchmarks looks suspiciously good, but I have repeated the below with ca. the same result. Compared to master: ``` name old time/op new time/op delta SiteNew/Bundle_with_image-16 13.1ms ± 1% 10.5ms ± 1% -19.34% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 13.0ms ± 0% 10.7ms ± 1% -18.05% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 46.4ms ± 2% 43.1ms ± 1% -7.15% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 52.2ms ± 2% 47.8ms ± 1% -8.30% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 77.9ms ± 1% 70.9ms ± 1% -9.01% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 43.0ms ± 0% 37.2ms ± 1% -13.54% (p=0.029 n=4+4) SiteNew/Page_collections-16 58.2ms ± 1% 52.4ms ± 1% -9.95% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Bundle_with_image-16 3.81MB ± 0% 2.22MB ± 0% -41.70% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 3.60MB ± 0% 2.01MB ± 0% -44.20% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 19.3MB ± 1% 14.1MB ± 0% -26.91% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 70.7MB ± 0% 69.0MB ± 0% -2.40% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 37.1MB ± 0% 31.2MB ± 0% -15.94% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 17.6MB ± 0% 10.6MB ± 0% -39.92% (p=0.029 n=4+4) SiteNew/Page_collections-16 25.9MB ± 0% 21.2MB ± 0% -17.99% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Bundle_with_image-16 52.3k ± 0% 26.1k ± 0% -50.18% (p=0.029 n=4+4) SiteNew/Bundle_with_JSON_file-16 52.3k ± 0% 26.1k ± 0% -50.16% (p=0.029 n=4+4) SiteNew/Tags_and_categories-16 336k ± 1% 269k ± 0% -19.90% (p=0.029 n=4+4) SiteNew/Canonify_URLs-16 422k ± 0% 395k ± 0% -6.43% (p=0.029 n=4+4) SiteNew/Deep_content_tree-16 401k ± 0% 313k ± 0% -21.79% (p=0.029 n=4+4) SiteNew/Many_HTML_templates-16 247k ± 0% 143k ± 0% -42.17% (p=0.029 n=4+4) SiteNew/Page_collections-16 282k ± 0% 207k ± 0% -26.55% (p=0.029 n=4+4) ``` Fixes #6716 Fixes #6760 Fixes #6768 Fixes #6778 --- hugolib/alias.go | 2 +- hugolib/cascade_test.go | 6 +- hugolib/content_render_hooks_test.go | 4 +- hugolib/hugo_modules_test.go | 4 + hugolib/hugo_sites.go | 26 +++-- hugolib/hugo_sites_build.go | 9 +- hugolib/hugo_sites_build_errors_test.go | 14 ++- hugolib/hugo_sites_rebuild_test.go | 140 +++++++++++++++++++++++--- hugolib/page.go | 68 ++++++------- hugolib/page__meta.go | 6 +- hugolib/page__per_output.go | 7 +- hugolib/page_test.go | 13 --- hugolib/shortcode.go | 10 +- hugolib/site.go | 77 +++++--------- hugolib/site_render.go | 64 ++++++++---- hugolib/template_test.go | 172 ++++++++++++++++++++++++++++++++ hugolib/testhelpers_test.go | 5 +- 17 files changed, 456 insertions(+), 171 deletions(-) (limited to 'hugolib') diff --git a/hugolib/alias.go b/hugolib/alias.go index c80e7d0d2..9eba8b335 100644 --- a/hugolib/alias.go +++ b/hugolib/alias.go @@ -81,7 +81,7 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format } func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) { - handler := newAliasHandler(s.Tmpl, s.Log, allowRoot) + handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot) s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink) diff --git a/hugolib/cascade_test.go b/hugolib/cascade_test.go index 6b176ad64..be243e39b 100644 --- a/hugolib/cascade_test.go +++ b/hugolib/cascade_test.go @@ -68,12 +68,12 @@ func TestCascade(t *testing.T) { 42|taxonomy|tags/blue|blue|home.png|tags|HTML-| 42|section|sect3|Cascade Home|home.png|sect3|HTML-| 42|taxonomyTerm|tags|Cascade Home|home.png|tags|HTML-| - 42|page|p2.md|Cascade Home|home.png|page|HTML-| + 42|page|p2.md|Cascade Home|home.png||HTML-| 42|page|sect2/p2.md|Cascade Home|home.png|sect2|HTML-| 42|page|sect3/p1.md|Cascade Home|home.png|sect3|HTML-| 42|taxonomy|tags/green|green|home.png|tags|HTML-| - 42|home|_index.md|Home|home.png|page|HTML-| - 42|page|p1.md|p1|home.png|page|HTML-| + 42|home|_index.md|Home|home.png||HTML-| + 42|page|p1.md|p1|home.png||HTML-| 42|section|sect1/_index.md|Sect1|sect1.png|stype|HTML-| 42|section|sect1/s1_2/_index.md|Sect1_2|sect1.png|stype|HTML-| 42|page|sect1/s1_2/p1.md|Sect1_2_p1|sect1.png|stype|HTML-| diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go index ee7a02074..8aba1dd8c 100644 --- a/hugolib/content_render_hooks_test.go +++ b/hugolib/content_render_hooks_test.go @@ -13,7 +13,9 @@ package hugolib -import "testing" +import ( + "testing" +) func TestRenderHooks(t *testing.T) { config := ` diff --git a/hugolib/hugo_modules_test.go b/hugolib/hugo_modules_test.go index 14085e2c0..5c2b46b30 100644 --- a/hugolib/hugo_modules_test.go +++ b/hugolib/hugo_modules_test.go @@ -340,6 +340,8 @@ b = "B param" } func TestModulesIncompatible(t *testing.T) { + t.Parallel() + b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", ` baseURL="https://example.org" @@ -518,6 +520,7 @@ weight = 2 } func TestMountsProject(t *testing.T) { + t.Parallel() config := ` @@ -547,6 +550,7 @@ title: "My Page" // https://github.com/gohugoio/hugo/issues/6684 func TestMountsContentFile(t *testing.T) { + t.Parallel() c := qt.New(t) workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-content-file") c.Assert(err, qt.IsNil) diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 4e1623b2e..a0c62f01e 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -121,6 +121,9 @@ type hugoSitesInit struct { // Loads the data from all of the /data folders. data *lazy.Init + // Performs late initialization (before render) of the templates. + layouts *lazy.Init + // Loads the Git info for all the pages if enabled. gitInfo *lazy.Init @@ -130,6 +133,7 @@ type hugoSitesInit struct { func (h *hugoSitesInit) Reset() { h.data.Reset() + h.layouts.Reset() h.gitInfo.Reset() h.translations.Reset() } @@ -271,6 +275,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { Sites: sites, init: &hugoSitesInit{ data: lazy.New(), + layouts: lazy.New(), gitInfo: lazy.New(), translations: lazy.New(), }, @@ -289,6 +294,15 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { return nil, nil }) + h.init.layouts.Add(func() (interface{}, error) { + for _, s := range h.Sites { + if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil { + return nil, err + } + } + return nil, nil + }) + h.init.translations.Add(func() (interface{}, error) { if len(h.Sites) > 1 { allTranslations := pagesToTranslationsMap(h.Sites) @@ -429,10 +443,6 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateManager) error) func(templ tpl.TemplateManager) error { return func(templ tpl.TemplateManager) error { - if err := templ.LoadTemplates(""); err != nil { - return err - } - for _, wt := range withTemplates { if wt == nil { continue @@ -619,10 +629,10 @@ func (h *HugoSites) renderCrossSitesArtifacts() error { s := h.Sites[0] - smLayouts := []string{"sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml"} + templ := s.lookupLayouts("sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml") return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex", - s.siteCfg.sitemap.Filename, h.toSiteInfos(), smLayouts...) + s.siteCfg.sitemap.Filename, h.toSiteInfos(), templ) } func (h *HugoSites) removePageByFilename(filename string) { @@ -832,7 +842,7 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) { if po.cp == nil { continue } - for id, _ := range idset { + for id := range idset { if po.cp.dependencyTracker.Search(id) != nil { po.cp.Reset() continue OUTPUTS @@ -841,7 +851,7 @@ func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) { } for _, s := range p.shortcodeState.shortcodes { - for id, _ := range idset { + for id := range idset { if idm, ok := s.info.(identity.Manager); ok && idm.Search(id) != nil { for _, po := range p.pageOutputs { if po.cp != nil { diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index d749ff581..901941bda 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -291,6 +291,10 @@ func (h *HugoSites) assemble(bcfg *BuildCfg) error { } func (h *HugoSites) render(config *BuildCfg) error { + if _, err := h.init.layouts.Do(); err != nil { + return err + } + siteRenderContext := &siteRenderContext{cfg: config, multihost: h.multihost} if !config.PartialReRender { @@ -312,11 +316,6 @@ func (h *HugoSites) render(config *BuildCfg) error { case <-h.Done(): return nil default: - // For the non-renderable pages, we use the content iself as - // template and we may have to re-parse and execute it for - // each output format. - h.TemplateHandler().RebuildClone() - for _, s2 := range h.Sites { // We render site by site, but since the content is lazily rendered // and a site can "borrow" content from other sites, every site diff --git a/hugolib/hugo_sites_build_errors_test.go b/hugolib/hugo_sites_build_errors_test.go index 21b745ccd..d90a8b364 100644 --- a/hugolib/hugo_sites_build_errors_test.go +++ b/hugolib/hugo_sites_build_errors_test.go @@ -27,7 +27,7 @@ func (t testSiteBuildErrorAsserter) getFileError(err error) *herrors.ErrorWithFi func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) { fe := t.getFileError(err) - t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber) + t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber, qt.Commentf(err.Error())) } func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) { @@ -65,7 +65,8 @@ func TestSiteBuildErrors(t *testing.T) { fileFixer: func(content string) string { return strings.Replace(content, ".Title }}", ".Title }", 1) }, - assertCreateError: func(a testSiteBuildErrorAsserter, err error) { + // Base templates gets parsed at build time. + assertBuildError: func(a testSiteBuildErrorAsserter, err error) { a.assertLineNumber(4, err) }, }, @@ -90,7 +91,7 @@ func TestSiteBuildErrors(t *testing.T) { a.c.Assert(fe.Position().LineNumber, qt.Equals, 5) a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1) a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template") - a.assertErrorMessage("\"layouts/_default/single.html:5:1\": parse failed: template: _default/single.html:5: unexpected \"}\" in operand", fe.Error()) + a.assertErrorMessage("\"layouts/foo/single.html:5:1\": parse failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error()) }, }, @@ -256,6 +257,13 @@ SINGLE L3: SINGLE L4: SINGLE L5: {{ .Title }} {{ .Content }} {{ end }} +`)) + + b.WithTemplatesAdded("layouts/foo/single.html", f(single, ` +SINGLE L2: +SINGLE L3: +SINGLE L4: +SINGLE L5: {{ .Title }} {{ .Content }} `)) b.WithContent("myyaml.md", f(yamlcontent, `--- diff --git a/hugolib/hugo_sites_rebuild_test.go b/hugolib/hugo_sites_rebuild_test.go index e36c1a1d4..eca6c730b 100644 --- a/hugolib/hugo_sites_rebuild_test.go +++ b/hugolib/hugo_sites_rebuild_test.go @@ -23,23 +23,28 @@ func TestSitesRebuild(t *testing.T) { baseURL = "https://example.com" title = "Rebuild this" contentDir = "content" +enableInlineShortcodes = true ` - contentFilename := "content/blog/page1.md" + var ( + contentFilename = "content/blog/page1.md" + dataFilename = "data/mydata.toml" + ) - b := newTestSitesBuilder(t).WithConfigFile("toml", configFile) + createSiteBuilder := func(t testing.TB) *sitesBuilder { + b := newTestSitesBuilder(t).WithConfigFile("toml", configFile).Running() - // To simulate https://github.com/gohugoio/hugo/issues/5838, the home page - // needs a content page. - b.WithContent("content/_index.md", `--- + b.WithSourceFile(dataFilename, `hugo = "Rocks!"`) + + b.WithContent("content/_index.md", `--- title: Home, Sweet Home! --- `) - b.WithContent(contentFilename, ` + b.WithContent(contentFilename, ` --- title: "Page 1" summary: "Initial summary" @@ -48,22 +53,58 @@ paginate: 3 Content. +{{< badge.inline >}} +Data Inline: {{ site.Data.mydata.hugo }} +{{< /badge.inline >}} `) - b.WithTemplatesAdded("index.html", ` + // For .Page.Render tests + b.WithContent("prender.md", `--- +title: Page 1 +--- + +Content for Page 1. + +{{< dorender >}} + +`) + + b.WithTemplatesAdded( + "layouts/shortcodes/dorender.html", ` +{{ $p := .Page }} +Render {{ $p.RelPermalink }}: {{ $p.Render "single" }} + +`) + + b.WithTemplatesAdded("index.html", ` {{ range (.Paginate .Site.RegularPages).Pages }} * Page Paginate: {{ .Title }}|Summary: {{ .Summary }}|Content: {{ .Content }} {{ end }} {{ range .Site.RegularPages }} * Page Pages: {{ .Title }}|Summary: {{ .Summary }}|Content: {{ .Content }} {{ end }} +Content: {{ .Content }} +Data: {{ site.Data.mydata.hugo }} `) - b.Running().Build(BuildCfg{}) + b.WithTemplatesAdded("layouts/partials/mypartial1.html", `Mypartial1`) + b.WithTemplatesAdded("layouts/partials/mypartial2.html", `Mypartial2`) + b.WithTemplatesAdded("layouts/partials/mypartial3.html", `Mypartial3`) + b.WithTemplatesAdded("_default/single.html", `{{ define "main" }}Single Main: {{ .Title }}|Mypartial1: {{ partial "mypartial1.html" }}{{ end }}`) + b.WithTemplatesAdded("_default/list.html", `{{ define "main" }}List Main: {{ .Title }}{{ end }}`) + b.WithTemplatesAdded("_default/baseof.html", `Baseof:{{ block "main" . }}Baseof Main{{ end }}|Mypartial3: {{ partial "mypartial3.html" }}:END`) + + return b + } - b.AssertFileContent("public/index.html", "* Page Paginate: Page 1|Summary: Initial summary|Content:

Content.

") + t.Run("Refresh paginator on edit", func(t *testing.T) { + b := createSiteBuilder(t) - b.EditFiles(contentFilename, ` + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", "* Page Paginate: Page 1|Summary: Initial summary|Content:

Content.

") + + b.EditFiles(contentFilename, ` --- title: "Page 1 edit" summary: "Edited summary" @@ -73,11 +114,82 @@ Edited content. `) - b.Build(BuildCfg{}) + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", "* Page Paginate: Page 1 edit|Summary: Edited summary|Content:

Edited content.

") + // https://github.com/gohugoio/hugo/issues/5833 + b.AssertFileContent("public/index.html", "* Page Pages: Page 1 edit|Summary: Edited summary|Content:

Edited content.

") + }) + + // https://github.com/gohugoio/hugo/issues/6768 + t.Run("Edit data", func(t *testing.T) { + b := createSiteBuilder(t) + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", ` +Data: Rocks! +Data Inline: Rocks! +`) + + b.EditFiles(dataFilename, `hugo = "Rules!"`) + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", ` +Data: Rules! +Data Inline: Rules!`) + + }) + + t.Run("Page.Render, edit baseof", func(t *testing.T) { + b := createSiteBuilder(t) + + b.WithTemplatesAdded("index.html", ` +{{ $p := site.GetPage "prender.md" }} +prender: {{ $p.Title }}|{{ $p.Content }} + +`) + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", ` + Render /prender/: Baseof:Single Main: Page 1|Mypartial1: Mypartial1|Mypartial3: Mypartial3:END +`) + + b.EditFiles("layouts/_default/baseof.html", `Baseof Edited:{{ block "main" . }}Baseof Main{{ end }}:END`) + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", ` +Render /prender/: Baseof Edited:Single Main: Page 1|Mypartial1: Mypartial1:END +`) + + }) + + t.Run("Page.Render, edit partial in baseof", func(t *testing.T) { + b := createSiteBuilder(t) + + b.WithTemplatesAdded("index.html", ` +{{ $p := site.GetPage "prender.md" }} +prender: {{ $p.Title }}|{{ $p.Content }} + +`) + + b.Build(BuildCfg{}) - b.AssertFileContent("public/index.html", "* Page Paginate: Page 1 edit|Summary: Edited summary|Content:

Edited content.

") + b.AssertFileContent("public/index.html", ` + Render /prender/: Baseof:Single Main: Page 1|Mypartial1: Mypartial1|Mypartial3: Mypartial3:END +`) + + b.EditFiles("layouts/partials/mypartial3.html", `Mypartial3 Edited`) + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", ` +Render /prender/: Baseof:Single Main: Page 1|Mypartial1: Mypartial1|Mypartial3: Mypartial3 Edited:END +`) - // https://github.com/gohugoio/hugo/issues/5833 - b.AssertFileContent("public/index.html", "* Page Pages: Page 1 edit|Summary: Edited summary|Content:

Edited content.

") + }) } diff --git a/hugolib/page.go b/hugolib/page.go index 8aad8dcc7..bb5107747 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -337,37 +337,33 @@ func (p *pageState) createRenderHooks(f output.Format) (*hooks.Render, error) { layoutDescriptor.Layout = "" layoutDescriptor.Kind = "render-link" - linkLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f) + linkTempl, linkTemplFound, err := p.s.Tmpl().LookupLayout(layoutDescriptor, f) if err != nil { return nil, err } layoutDescriptor.Kind = "render-image" - imageLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f) + imgTempl, imgTemplFound, err := p.s.Tmpl().LookupLayout(layoutDescriptor, f) if err != nil { return nil, err } - if linkLayouts == nil && imageLayouts == nil { - return nil, nil - } - var linkRenderer hooks.LinkRenderer var imageRenderer hooks.LinkRenderer - if templ, found := p.s.lookupTemplate(linkLayouts...); found { + if linkTemplFound { linkRenderer = contentLinkRenderer{ - templateHandler: p.s.Tmpl, - Provider: templ.(tpl.Info), - templ: templ, + templateHandler: p.s.Tmpl(), + Provider: linkTempl.(tpl.Info), + templ: linkTempl, } } - if templ, found := p.s.lookupTemplate(imageLayouts...); found { + if imgTemplFound { imageRenderer = contentLinkRenderer{ - templateHandler: p.s.Tmpl, - Provider: templ.(tpl.Info), - templ: templ, + templateHandler: p.s.Tmpl(), + Provider: imgTempl.(tpl.Info), + templ: imgTempl, } } @@ -406,24 +402,25 @@ func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor { } -func (p *pageState) getLayouts(layouts ...string) ([]string, error) { +func (p *pageState) resolveTemplate(layouts ...string) (tpl.Template, bool, error) { f := p.outputFormat() if len(layouts) == 0 { selfLayout := p.selfLayoutForOutput(f) if selfLayout != "" { - return []string{selfLayout}, nil + templ, found := p.s.Tmpl().Lookup(selfLayout) + return templ, found, nil } } - layoutDescriptor := p.getLayoutDescriptor() + d := p.getLayoutDescriptor() if len(layouts) > 0 { - layoutDescriptor.Layout = layouts[0] - layoutDescriptor.LayoutOverride = true + d.Layout = layouts[0] + d.LayoutOverride = true } - return p.s.layoutHandler.For(layoutDescriptor, f) + return p.s.Tmpl().LookupLayout(d, f) } // This is serialized @@ -601,31 +598,21 @@ func (p *pageState) RenderWithTemplateInfo(info tpl.Info, layout ...string) (tem } func (p *pageState) Render(layout ...string) (template.HTML, error) { - l, err := p.getLayouts(layout...) + templ, found, err := p.resolveTemplate(layout...) if err != nil { - return "", p.wrapError(errors.Errorf("failed to resolve layout %v", layout)) + return "", p.wrapError(err) } - for _, layout := range l { - templ, found := p.s.Tmpl.Lookup(layout) - if !found { - // This is legacy from when we had only one output format and - // HTML templates only. Some have references to layouts without suffix. - // We default to good old HTML. - templ, _ = p.s.Tmpl.Lookup(layout + ".html") - } - - if templ != nil { - p.addDependency(templ.(tpl.Info)) - res, err := executeToString(p.s.Tmpl, templ, p) - if err != nil { - return "", p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout)) - } - return template.HTML(res), nil - } + if !found { + return "", nil } - return "", nil + p.addDependency(templ.(tpl.Info)) + res, err := executeToString(p.s.Tmpl(), templ, p) + if err != nil { + return "", p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout)) + } + return template.HTML(res), nil } @@ -689,6 +676,7 @@ Loop: // This is HTML without front matter. It can still have shortcodes. p.selfLayout = "__" + p.File().Filename() p.renderable = false + p.s.BuildFlags.HasLateTemplate.CAS(false, true) rn.AddBytes(it) case it.IsFrontMatter(): f := metadecoders.FormatFromFrontMatterType(it.Type) diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go index 9f3e1687a..caffbe736 100644 --- a/hugolib/page__meta.go +++ b/hugolib/page__meta.go @@ -296,11 +296,7 @@ func (p *pageMeta) Type() string { return p.contentType } - if x := p.Section(); x != "" { - return x - } - - return "page" + return p.Section() } func (p *pageMeta) Weight() int { diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index 03448ba80..59440c7cb 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -189,9 +189,10 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err html := cp.p.s.ContentSpec.TrimShortHTML(b.Bytes()) cp.summary = helpers.BytesToHTML(html) } - } - cp.content = helpers.BytesToHTML(cp.workContent) + cp.content = helpers.BytesToHTML(cp.workContent) + + } if !p.renderable { err := cp.addSelfTemplate() @@ -427,7 +428,7 @@ func (p *pageContentOutput) setWordCounts(isCJKLanguage bool) { func (p *pageContentOutput) addSelfTemplate() error { self := p.p.selfLayoutForOutput(p.f) - err := p.p.s.TemplateHandler().AddLateTemplate(self, string(p.content)) + err := p.p.s.Tmpl().(tpl.TemplateManager).AddLateTemplate(self, string(p.workContent)) if err != nil { return err } diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 9bcfc1fc8..d7cbc0fca 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -333,12 +333,6 @@ func checkPageSummary(t *testing.T, page page.Page, summary string, msg ...inter } } -func checkPageType(t *testing.T, page page.Page, pageType string) { - if page.Type() != pageType { - t.Fatalf("Page type is: %s. Expected: %s", page.Type(), pageType) - } -} - func checkPageDate(t *testing.T, page page.Page, time time.Time) { if page.Date() != time { t.Fatalf("Page date is: %s. Expected: %s", page.Date(), time) @@ -542,7 +536,6 @@ func TestCreateNewPage(t *testing.T) { checkPageTitle(t, p, "Simple") checkPageContent(t, p, normalizeExpected(ext, "

Simple Page

\n")) checkPageSummary(t, p, "Simple Page") - checkPageType(t, p, "page") } settings := map[string]interface{}{ @@ -562,7 +555,6 @@ func TestPageSummary(t *testing.T) { checkPageContent(t, p, normalizeExpected(ext, "

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

\n\n

Additional text.

\n\n

Further text.

\n"), ext) checkPageSummary(t, p, normalizeExpected(ext, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Additional text."), ext) } - checkPageType(t, p, "page") } testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithoutSummaryDelimiter) @@ -575,7 +567,6 @@ func TestPageWithDelimiter(t *testing.T) { checkPageTitle(t, p, "Simple") checkPageContent(t, p, normalizeExpected(ext, "

Summary Next Line

\n\n

Some more text

\n"), ext) checkPageSummary(t, p, normalizeExpected(ext, "

Summary Next Line

"), ext) - checkPageType(t, p, "page") } testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter) @@ -591,7 +582,6 @@ func TestPageWithSummaryParameter(t *testing.T) { if ext != "ad" && ext != "rst" { checkPageSummary(t, p, normalizeExpected(ext, "Page with summary parameter and a link"), ext) } - checkPageType(t, p, "page") } testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryParameter) @@ -663,7 +653,6 @@ func TestPageWithShortCodeInSummary(t *testing.T) { checkPageTitle(t, p, "Simple") checkPageContent(t, p, normalizeExpected(ext, "

Summary Next Line.

. More text here.

Some more text

")) checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text") - checkPageType(t, p, "page") } testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary) @@ -713,8 +702,6 @@ func TestPageWithMoreTag(t *testing.T) { checkPageTitle(t, p, "Simple") checkPageContent(t, p, normalizeExpected(ext, "

Summary Same Line

\n\n

Some more text

\n")) checkPageSummary(t, p, normalizeExpected(ext, "

Summary Same Line

")) - checkPageType(t, p, "page") - } testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine) diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 7d7e8b68c..b1041707b 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -303,7 +303,7 @@ func renderShortcode( templStr := sc.innerString() var err error - tmpl, err = s.TextTmpl.Parse(templName, templStr) + tmpl, err = s.TextTmpl().Parse(templName, templStr) if err != nil { fe := herrors.ToFileError("html", err) l1, l2 := p.posOffset(sc.pos).LineNumber, fe.Position().LineNumber @@ -314,14 +314,14 @@ func renderShortcode( } else { // Re-use of shortcode defined earlier in the same page. var found bool - tmpl, found = s.TextTmpl.Lookup(templName) + tmpl, found = s.TextTmpl().Lookup(templName) if !found { return "", false, _errors.Errorf("no earlier definition of shortcode %q found", sc.name) } } } else { var found, more bool - tmpl, found, more = s.Tmpl.LookupVariant(sc.name, tplVariants) + tmpl, found, more = s.Tmpl().LookupVariant(sc.name, tplVariants) if !found { s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path()) return "", false, nil @@ -395,7 +395,7 @@ func renderShortcode( } - result, err := renderShortcodeWithPage(s.Tmpl, tmpl, data) + result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data) if err != nil && sc.isInline { fe := herrors.ToFileError("html", err) @@ -537,7 +537,7 @@ Loop: // Check if the template expects inner content. // We pick the first template for an arbitrary output format // if more than one. It is "all inner or no inner". - tmpl, found, _ := s.s.Tmpl.LookupVariant(sc.name, tpl.TemplateVariants{}) + tmpl, found, _ := s.s.Tmpl().LookupVariant(sc.name, tpl.TemplateVariants{}) if !found { return nil, _errors.Errorf("template for shortcode %q not found", sc.name) } diff --git a/hugolib/site.go b/hugolib/site.go index eb232c629..bbcbcd27a 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -105,8 +105,6 @@ type Site struct { Sections Taxonomy Info SiteInfo - layoutHandler *output.LayoutHandler - language *langs.Language siteCfg siteConfigHolder @@ -324,7 +322,6 @@ func (s *Site) isEnabled(kind string) bool { // reset returns a new Site prepared for rebuild. func (s *Site) reset() *Site { return &Site{Deps: s.Deps, - layoutHandler: output.NewLayoutHandler(), disabledKinds: s.disabledKinds, titleFunc: s.titleFunc, relatedDocsHandler: s.relatedDocsHandler.Clone(), @@ -439,7 +436,6 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { s := &Site{ PageCollections: c, - layoutHandler: output.NewLayoutHandler(), language: cfg.Language, disabledKinds: disabledKinds, titleFunc: titleFunc, @@ -936,7 +932,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro sourceChanged = append(sourceChanged, ev) case files.ComponentFolderLayouts: tmplChanged = true - if _, found := s.Tmpl.Lookup(id.Path); !found { + if !s.Tmpl().HasTemplate(id.Path) { tmplAdded = true } if tmplAdded { @@ -1030,7 +1026,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro sourceFilesChanged[ev.Name] = true } - if config.ErrRecovery || tmplAdded { + if config.ErrRecovery || tmplAdded || dataChanged { h.resetPageState() } else { h.resetPageStateFromEvents(changeIdentities) @@ -1226,10 +1222,9 @@ func (s *Site) initializeSiteInfo() error { func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) { for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() { if p := fs.Path(e.Name); p != "" { - return identity.NewPathIdentity(fs.Name, p), true + return identity.NewPathIdentity(fs.Name, filepath.ToSlash(p)), true } } - return identity.PathIdentity{}, false } @@ -1464,12 +1459,22 @@ func (s *Site) permalink(link string) string { } -func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, layouts ...string) error { +func (s *Site) lookupLayouts(layouts ...string) tpl.Template { + for _, l := range layouts { + if templ, found := s.Tmpl().Lookup(l); found { + return templ + } + } + + return nil +} + +func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, templ tpl.Template) error { s.Log.DEBUG.Printf("Render XML for %q to %q", name, targetPath) renderBuffer := bp.GetBuffer() defer bp.PutBuffer(renderBuffer) - if err := s.renderForLayouts(name, "", d, renderBuffer, layouts...); err != nil { + if err := s.renderForTemplate(name, "", d, renderBuffer, templ); err != nil { return err } @@ -1498,13 +1503,13 @@ func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath st } -func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, layouts ...string) error { +func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error { renderBuffer := bp.GetBuffer() defer bp.PutBuffer(renderBuffer) of := p.outputFormat() - if err := s.renderForLayouts(p.Kind(), of.Name, p, renderBuffer, layouts...); err != nil { + if err := s.renderForTemplate(p.Kind(), of.Name, p, renderBuffer, templ); err != nil { return err } @@ -1571,56 +1576,26 @@ func (r contentLinkRenderer) Render(w io.Writer, ctx hooks.LinkContext) error { return r.templateHandler.Execute(r.templ, w, ctx) } -func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) { - for _, l := range layouts { - if templ, found := s.Tmpl.Lookup(l); found { - return templ, true - } - } - - return nil, false -} - -func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.Writer, layouts ...string) (err error) { - templ := s.findFirstTemplate(layouts...) +func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) { if templ == nil { - log := s.Log.WARN - if infoOnMissingLayout[name] { - log = s.Log.INFO - } - - errMsg := "You should create a template file which matches Hugo Layouts Lookup Rules for this combination." - var args []interface{} - msg := "found no layout file for" - if outputFormat != "" { - msg += " %q" - args = append(args, outputFormat) - } - if name != "" { - msg += " for %q" - args = append(args, name) - } - - msg += ": " + errMsg - - log.Printf(msg, args...) - + s.logMissingLayout(name, "", outputFormat) return nil } - if err = s.Tmpl.Execute(templ, w, d); err != nil { + if err = s.Tmpl().Execute(templ, w, d); err != nil { return _errors.Wrapf(err, "render of %q failed", name) } return } -func (s *Site) findFirstTemplate(layouts ...string) tpl.Template { - for _, layout := range layouts { - if templ, found := s.Tmpl.Lookup(layout); found { - return templ +func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) { + for _, l := range layouts { + if templ, found := s.Tmpl().Lookup(l); found { + return templ, true } } - return nil + + return nil, false } func (s *Site) publish(statCounter *uint64, path string, r io.Reader) (err error) { diff --git a/hugolib/site_render.go b/hugolib/site_render.go index 34f288da2..a55cbb402 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -19,6 +19,8 @@ import ( "strings" "sync" + "github.com/gohugoio/hugo/tpl" + "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/output" @@ -136,33 +138,62 @@ func pageRenderer( continue } - layouts, err := p.getLayouts() + templ, found, err := p.resolveTemplate() if err != nil { - s.Log.ERROR.Printf("Failed to resolve layout for output %q for page %q: %s", f.Name, p, err) + s.SendError(p.errorf(err, "failed to resolve template")) continue } - targetPath := p.targetPaths().TargetFilename - - if targetPath == "" { - s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", f.Name, p, err) + if !found { + s.logMissingLayout("", p.Kind(), f.Name) continue } - if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+p.Title(), targetPath, p, layouts...); err != nil { + targetPath := p.targetPaths().TargetFilename + + if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+p.Title(), targetPath, p, templ); err != nil { results <- err } if p.paginator != nil && p.paginator.current != nil { - if err := s.renderPaginator(p, layouts); err != nil { + if err := s.renderPaginator(p, templ); err != nil { results <- err } } } } +func (s *Site) logMissingLayout(name, kind, outputFormat string) { + log := s.Log.WARN + if name != "" && infoOnMissingLayout[name] { + log = s.Log.INFO + } + + errMsg := "You should create a template file which matches Hugo Layouts Lookup Rules for this combination." + var args []interface{} + msg := "found no layout file for" + if outputFormat != "" { + msg += " %q" + args = append(args, outputFormat) + } + + if kind != "" { + msg += " for kind %q" + args = append(args, kind) + } + + if name != "" { + msg += " for %q" + args = append(args, name) + } + + msg += ": " + errMsg + + log.Printf(msg, args...) +} + // renderPaginator must be run after the owning Page has been rendered. -func (s *Site) renderPaginator(p *pageState, layouts []string) error { +func (s *Site) renderPaginator(p *pageState, templ tpl.Template) error { paginatePath := s.Cfg.GetString("paginatePath") @@ -192,7 +223,7 @@ func (s *Site) renderPaginator(p *pageState, layouts []string) error { if err := s.renderAndWritePage( &s.PathSpec.ProcessingStats.PaginatorPages, p.Title(), - targetPaths.TargetFilename, p, layouts...); err != nil { + targetPaths.TargetFilename, p, templ); err != nil { return err } @@ -220,15 +251,14 @@ func (s *Site) render404() error { return err } - nfLayouts := []string{"404.html"} - + templ := s.lookupLayouts("404.html") targetPath := p.targetPaths().TargetFilename if targetPath == "" { return errors.New("failed to create targetPath for 404 page") } - return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, nfLayouts...) + return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, templ) } func (s *Site) renderSitemap() error { @@ -255,9 +285,9 @@ func (s *Site) renderSitemap() error { return errors.New("failed to create targetPath for sitemap") } - smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"} + templ := s.lookupLayouts("sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml") - return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, smLayouts...) + return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, templ) } func (s *Site) renderRobotsTXT() error { @@ -282,9 +312,9 @@ func (s *Site) renderRobotsTXT() error { return err } - rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"} + templ := s.lookupLayouts("robots.txt", "_default/robots.txt", "_internal/_default/robots.txt") - return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, rLayouts...) + return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, templ) } diff --git a/hugolib/template_test.go b/hugolib/template_test.go index 80a703801..e75bda790 100644 --- a/hugolib/template_test.go +++ b/hugolib/template_test.go @@ -244,6 +244,178 @@ Page Content } +func TestTemplateLateTemplates(t *testing.T) { + t.Parallel() + b := newTestSitesBuilder(t).WithSimpleConfigFile().Running() + + numPages := 500 // To get some parallelism + homeTempl := ` +Len RegularPages: {{ len site.RegularPages }} +{{ range site.RegularPages }} +Link: {{ .RelPermalink }} Len Content: {{ len .Content }} +{{ end }} +` + pageTemplate := ` + + + + {{ .RelPermalink }} + + + + + +

{{ .RelPermalink }}

+

Shortcode: {{< shortcode >}}

+

Partial: {{ partial "mypartial.html" . }}

+ + + +` + + b.WithTemplatesAdded( + "index.html", homeTempl, + "partials/mypartial.html", `this my partial`, + ) + + // Make sure we get some parallelism. + for i := 0; i < numPages; i++ { + b.WithContent(fmt.Sprintf("page%d.html", i+1), pageTemplate) + } + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", fmt.Sprintf(` +Len RegularPages: %d +Link: /page3/ Len Content: 0 +Link: /page22/ Len Content: 0 +`, numPages)) + + for i := 0; i < numPages; i++ { + b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i+1), + fmt.Sprintf(`/page%d/`, i+1), + `

Shortcode: Shortcode: Hello

`, + "

Partial: this my partial

", + ) + } + + b.EditFiles( + "layouts/partials/mypartial.html", `this my changed partial`, + "layouts/index.html", (homeTempl + "CHANGED"), + ) + for i := 0; i < 5; i++ { + b.EditFiles(fmt.Sprintf("content/page%d.html", i+1), pageTemplate+"CHANGED") + } + + b.Build(BuildCfg{}) + b.AssertFileContent("public/index.html", fmt.Sprintf(` +Len RegularPages: %d +Link: /page3/ Len Content: 0 +Link: /page2/ Len Content: 0 +CHANGED +`, numPages)) + for i := 0; i < 5; i++ { + b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i+1), + fmt.Sprintf(`/page%d/`, i+1), + `

Shortcode: Shortcode: Hello

`, + "

Partial: this my changed partial

", + "CHANGED", + ) + } + +} + +func TestTemplateManyBaseTemplates(t *testing.T) { + t.Parallel() + b := newTestSitesBuilder(t).WithSimpleConfigFile() + + numPages := 100 // To get some parallelism + + pageTemplate := `--- +title: "Page %d" +layout: "layout%d" +--- + +Content. +` + + singleTemplate := ` +{{ define "main" }}%d{{ end }} +` + baseTemplate := ` +Base %d: {{ block "main" . }}FOO{{ end }} +` + + for i := 0; i < numPages; i++ { + id := i + 1 + b.WithContent(fmt.Sprintf("page%d.md", id), fmt.Sprintf(pageTemplate, id, id)) + b.WithTemplates(fmt.Sprintf("_default/layout%d.html", id), fmt.Sprintf(singleTemplate, id)) + b.WithTemplates(fmt.Sprintf("_default/layout%d-baseof.html", id), fmt.Sprintf(baseTemplate, id)) + } + + b.Build(BuildCfg{}) + for i := 0; i < numPages; i++ { + id := i + 1 + b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", id), fmt.Sprintf(`Base %d: %d`, id, id)) + } + +} + +func TestTemplateLookupSite(t *testing.T) { + t.Run("basic", func(t *testing.T) { + t.Parallel() + b := newTestSitesBuilder(t).WithSimpleConfigFile() + b.WithTemplates( + "_default/single.html", `Single: {{ .Title }}`, + "_default/list.html", `List: {{ .Title }}`, + ) + + createContent := func(title string) string { + return fmt.Sprintf(`--- +title: %s +---`, title) + } + + b.WithContent( + "_index.md", createContent("Home Sweet Home"), + "p1.md", createContent("P1")) + + b.CreateSites().Build(BuildCfg{}) + b.AssertFileContent("public/index.html", `List: Home Sweet Home`) + b.AssertFileContent("public/p1/index.html", `Single: P1`) + }) + + t.Run("baseof", func(t *testing.T) { + t.Parallel() + b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() + + b.WithTemplatesAdded( + "index.html", `{{ define "main" }}Main Home En{{ end }}`, + "index.fr.html", `{{ define "main" }}Main Home Fr{{ end }}`, + "baseof.html", `Baseof en: {{ block "main" . }}main block{{ end }}`, + "baseof.fr.html", `Baseof fr: {{ block "main" . }}main block{{ end }}`, + "mysection/baseof.html", `Baseof mysection: {{ block "main" . }}mysection block{{ end }}`, + "_default/single.html", `{{ define "main" }}Main Default Single{{ end }}`, + "_default/list.html", `{{ define "main" }}Main Default List{{ end }}`, + ) + + b.WithContent("mysection/p1.md", `--- +title: My Page +--- + +`) + + b.CreateSites().Build(BuildCfg{}) + + b.AssertFileContent("public/en/index.html", `Baseof en: Main Home En`) + b.AssertFileContent("public/fr/index.html", `Baseof fr: Main Home Fr`) + b.AssertFileContent("public/en/mysection/index.html", `Baseof mysection: Main Default List`) + b.AssertFileContent("public/en/mysection/p1/index.html", `Baseof mysection: Main Default Single`) + + }) + +} + func TestTemplateFuncs(t *testing.T) { b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index 44185eadc..376c0899c 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -429,7 +429,7 @@ func (s *sitesBuilder) writeFilePairs(folder string, filenameContent []string) * func (s *sitesBuilder) CreateSites() *sitesBuilder { if err := s.CreateSitesE(); err != nil { - herrors.PrintStackTrace(err) + herrors.PrintStackTraceFromErr(err) s.Fatalf("Failed to create sites: %s", err) } @@ -569,7 +569,7 @@ func (s *sitesBuilder) build(cfg BuildCfg, shouldFail bool) *sitesBuilder { } } if err != nil && !shouldFail { - herrors.PrintStackTrace(err) + herrors.PrintStackTraceFromErr(err) s.Fatalf("Build failed: %s", err) } else if err == nil && shouldFail { s.Fatalf("Expected error") @@ -690,6 +690,7 @@ func (s *sitesBuilder) AssertImage(width, height int, filename string) { s.Assert(err, qt.IsNil) defer f.Close() cfg, err := jpeg.DecodeConfig(f) + s.Assert(err, qt.IsNil) s.Assert(cfg.Width, qt.Equals, width) s.Assert(cfg.Height, qt.Equals, height) } -- cgit v1.2.3