ref: c6d650c8c8b22fdc7ddedc1e42a3ca698e1390d6
parent: 8585b388d27abde1ab6b6c63ad6addf4066ec8dd
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Wed Jan 15 10:59:56 EST 2020
tpl/tplimpl: Rework template management to get rid of concurrency issues 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
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -133,7 +133,7 @@
if c.h.verbose {
var b bytes.Buffer
- herrors.FprintStackTrace(&b, c.buildErr)
+ herrors.FprintStackTraceFromErr(&b, c.buildErr)
m["StackTrace"] = b.String()
}
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -716,7 +716,7 @@
c.logger.ERROR.Print(msg + ":\n\n")
c.logger.ERROR.Println(helpers.FirstUpper(err.Error()))
if !c.h.quiet && c.h.verbose {
- herrors.PrintStackTrace(err)
+ herrors.PrintStackTraceFromErr(err)
}
}
--- a/commands/server.go
+++ b/commands/server.go
@@ -416,7 +416,7 @@
roots = []string{""}
}
- templ, err := c.hugo().TextTmpl.Parse("__default_server_error", buildErrorTemplate)
+ templ, err := c.hugo().TextTmpl().Parse("__default_server_error", buildErrorTemplate)
if err != nil {
return err
}
@@ -428,7 +428,7 @@
s: s,
errorTemplate: func(ctx interface{}) (io.Reader, error) {
b := &bytes.Buffer{}
- err := c.hugo().Tmpl.Execute(templ, b, ctx)
+ err := c.hugo().Tmpl().Execute(templ, b, ctx)
return b, err
},
}
--- a/common/herrors/errors.go
+++ b/common/herrors/errors.go
@@ -15,11 +15,14 @@
package herrors
import (
+ "bytes"
"errors"
"fmt"
"io"
"os"
+ "runtime"
"runtime/debug"
+ "strconv"
_errors "github.com/pkg/errors"
)
@@ -33,13 +36,13 @@
StackTrace() _errors.StackTrace
}
-// PrintStackTrace prints the error's stack trace to stdoud.
-func PrintStackTrace(err error) {
- FprintStackTrace(os.Stdout, err)
+// PrintStackTraceFromErr prints the error's stack trace to stdoud.
+func PrintStackTraceFromErr(err error) {
+ FprintStackTraceFromErr(os.Stdout, err)
}
-// FprintStackTrace prints the error's stack trace to w.
-func FprintStackTrace(w io.Writer, err error) {
+// FprintStackTraceFromErr prints the error's stack trace to w.
+func FprintStackTraceFromErr(w io.Writer, err error) {
if err, ok := err.(stackTracer); ok {
for _, f := range err.StackTrace() {
fmt.Fprintf(w, "%+s:%d\n", f, f)
@@ -47,6 +50,13 @@
}
}
+// PrintStackTrace prints the current stacktrace to w.
+func PrintStackTrace(w io.Writer) {
+ buf := make([]byte, 1<<16)
+ runtime.Stack(buf, true)
+ fmt.Fprintf(w, "%s", buf)
+}
+
// Recover is a helper function that can be used to capture panics.
// Put this at the top of a method/function that crashes in a template:
// defer herrors.Recover()
@@ -56,7 +66,16 @@
args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
fmt.Println(args...)
}
+}
+// Get the current goroutine id. Used only for debugging.
+func GetGID() uint64 {
+ b := make([]byte, 64)
+ b = b[:runtime.Stack(b, false)]
+ b = bytes.TrimPrefix(b, []byte("goroutine "))
+ b = b[:bytes.IndexByte(b, ' ')]
+ n, _ := strconv.ParseUint(string(b), 10, 64)
+ return n
}
// ErrFeatureNotAvailable denotes that a feature is unavailable.
--- a/common/types/types.go
+++ b/common/types/types.go
@@ -21,6 +21,12 @@
"github.com/spf13/cast"
)
+// RLocker represents the read locks in sync.RWMutex.
+type RLocker interface {
+ RLock()
+ RUnlock()
+}
+
// KeyValueStr is a string tuple.
type KeyValueStr struct {
Key string
--- a/create/content_template_handler.go
+++ b/create/content_template_handler.go
@@ -129,9 +129,9 @@
archetypeTemplate = []byte(archetypeShortcodeReplacementsPre.Replace(string(archetypeTemplate)))
// Reuse the Hugo template setup to get the template funcs properly set up.
- templateHandler := s.Deps.Tmpl.(tpl.TemplateManager)
- templateName := "_text/" + helpers.Filename(archetypeFilename)
- if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil {
+ templateHandler := s.Deps.Tmpl().(tpl.TemplateManager)
+ templateName := helpers.Filename(archetypeFilename)
+ if err := templateHandler.AddTemplate("_text/"+templateName, string(archetypeTemplate)); err != nil {
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
}
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -5,6 +5,7 @@
"time"
"github.com/pkg/errors"
+ "go.uber.org/atomic"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/loggers"
@@ -38,10 +39,10 @@
DistinctWarningLog *helpers.DistinctLogger
// The templates to use. This will usually implement the full tpl.TemplateManager.
- Tmpl tpl.TemplateHandler `json:"-"`
+ tmpl tpl.TemplateHandler
// We use this to parse and execute ad-hoc text templates.
- TextTmpl tpl.TemplateParseFinder `json:"-"`
+ textTmpl tpl.TemplateParseFinder
// The file systems to use.
Fs *hugofs.Fs `json:"-"`
@@ -92,6 +93,9 @@
// BuildStartListeners will be notified before a build starts.
BuildStartListeners *Listeners
+ // Atomic flags set during a build.
+ BuildFlags BuildFlags
+
*globalErrHandler
}
@@ -153,11 +157,22 @@
Clone(deps *Deps) error
}
-// TemplateHandler returns the used tpl.TemplateFinder as tpl.TemplateHandler.
-func (d *Deps) TemplateHandler() tpl.TemplateManager {
- return d.Tmpl.(tpl.TemplateManager)
+func (d *Deps) Tmpl() tpl.TemplateHandler {
+ return d.tmpl
}
+func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
+ return d.textTmpl
+}
+
+func (d *Deps) SetTmpl(tmpl tpl.TemplateHandler) {
+ d.tmpl = tmpl
+}
+
+func (d *Deps) SetTextTmpl(tmpl tpl.TemplateParseFinder) {
+ d.textTmpl = tmpl
+}
+
// LoadResources loads translations and templates.
func (d *Deps) LoadResources() error {
// Note that the translations need to be loaded before the templates.
@@ -315,6 +330,7 @@
}
d.BuildStartListeners = &Listeners{}
+ d.BuildFlags = BuildFlags{}
return &d, nil
@@ -357,4 +373,15 @@
// Whether we are in running (server) mode
Running bool
+}
+
+// BuildFlags are flags that may be turned on during a build.
+type BuildFlags struct {
+ HasLateTemplate atomic.Bool
+}
+
+func NewBuildFlags() BuildFlags {
+ return BuildFlags{
+ //HasLateTemplate: atomic.NewBool(false),
+ }
}
--- /dev/null
+++ b/deps/deps_test.go
@@ -1,0 +1,28 @@
+// 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 deps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestBuildFlags(t *testing.T) {
+ c := qt.New(t)
+ var bf BuildFlags
+ c.Assert(bf.HasLateTemplate.Load(), qt.Equals, false)
+ bf.HasLateTemplate.Store(true)
+ c.Assert(bf.HasLateTemplate.Load(), qt.Equals, true)
+}
--- a/go.mod
+++ b/go.mod
@@ -55,6 +55,7 @@
github.com/yuin/goldmark v1.1.21
github.com/yuin/goldmark-highlighting v0.0.0-20191202084645-78f32c8dd6d5
go.opencensus.io v0.22.0 // indirect
+ go.uber.org/atomic v1.4.0
gocloud.dev v0.15.0
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
--- a/go.sum
+++ b/go.sum
@@ -421,6 +421,7 @@
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -81,7 +81,7 @@
}
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)
--- a/hugolib/cascade_test.go
+++ b/hugolib/cascade_test.go
@@ -68,12 +68,12 @@
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-|
--- 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 := `
--- a/hugolib/hugo_modules_test.go
+++ b/hugolib/hugo_modules_test.go
@@ -340,6 +340,8 @@
}
func TestModulesIncompatible(t *testing.T) {
+ t.Parallel()
+
b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", `
baseURL="https://example.org"
@@ -518,6 +520,7 @@
}
func TestMountsProject(t *testing.T) {
+ t.Parallel()
config := `
@@ -547,6 +550,7 @@
// 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)
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -121,6 +121,9 @@
// 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 @@
func (h *hugoSitesInit) Reset() {
h.data.Reset()
+ h.layouts.Reset()
h.gitInfo.Reset()
h.translations.Reset()
}
@@ -271,6 +275,7 @@
Sites: sites,
init: &hugoSitesInit{
data: lazy.New(),
+ layouts: lazy.New(),
gitInfo: lazy.New(),
translations: lazy.New(),
},
@@ -289,6 +294,15 @@
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 (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 @@
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 @@
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 @@
}
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 {
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -291,6 +291,10 @@
}
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 @@
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
--- a/hugolib/hugo_sites_build_errors_test.go
+++ b/hugolib/hugo_sites_build_errors_test.go
@@ -27,7 +27,7 @@
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 @@
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 @@
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 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, `---
--- a/hugolib/hugo_sites_rebuild_test.go
+++ b/hugolib/hugo_sites_rebuild_test.go
@@ -23,23 +23,28 @@
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,9 +53,30 @@
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 }}
@@ -57,13 +83,28 @@
{{ 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`)
- b.AssertFileContent("public/index.html", "* Page Paginate: Page 1|Summary: Initial summary|Content: <p>Content.</p>")
+ return b
+ }
- b.EditFiles(contentFilename, `
+ t.Run("Refresh paginator on edit", func(t *testing.T) {
+ b := createSiteBuilder(t)
+
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/index.html", "* Page Paginate: Page 1|Summary: Initial summary|Content: <p>Content.</p>")
+
+ b.EditFiles(contentFilename, `
---
title: "Page 1 edit"
summary: "Edited summary"
@@ -73,11 +114,82 @@
`)
- b.Build(BuildCfg{})
+ b.Build(BuildCfg{})
- b.AssertFileContent("public/index.html", "* Page Paginate: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
+ b.AssertFileContent("public/index.html", "* Page Paginate: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
+ // https://github.com/gohugoio/hugo/issues/5833
+ b.AssertFileContent("public/index.html", "* Page Pages: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
+ })
- // https://github.com/gohugoio/hugo/issues/5833
- b.AssertFileContent("public/index.html", "* Page Pages: Page 1 edit|Summary: Edited summary|Content: <p>Edited content.</p>")
+ // 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", `
+ 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
+`)
+
+ })
}
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -337,37 +337,33 @@
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) 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,32 +598,22 @@
}
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 !found {
+ return "", nil
+ }
- 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
- }
+ 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
- return "", nil
-
}
// wrapError adds some more context to the given error if possible/needed
@@ -689,6 +676,7 @@
// 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)
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -296,11 +296,7 @@
return p.contentType
}
- if x := p.Section(); x != "" {
- return x
- }
-
- return "page"
+ return p.Section()
}
func (p *pageMeta) Weight() int {
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -189,10 +189,11 @@
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()
return err
@@ -427,7 +428,7 @@
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
}
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -333,12 +333,6 @@
}
}
-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 @@
checkPageTitle(t, p, "Simple")
checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
checkPageSummary(t, p, "Simple Page")
- checkPageType(t, p, "page")
}
settings := map[string]interface{}{
@@ -562,7 +555,6 @@
checkPageContent(t, p, normalizeExpected(ext, "<p><a href=\"https://lipsum.com/\">Lorem ipsum</a> 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.</p>\n\n<p>Additional text.</p>\n\n<p>Further text.</p>\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 @@
checkPageTitle(t, p, "Simple")
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n"), ext)
checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>"), ext)
- checkPageType(t, p, "page")
}
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter)
@@ -591,7 +582,6 @@
if ext != "ad" && ext != "rst" {
checkPageSummary(t, p, normalizeExpected(ext, "Page with summary parameter and <a href=\"http://www.example.com/\">a link</a>"), ext)
}
- checkPageType(t, p, "page")
}
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryParameter)
@@ -663,7 +653,6 @@
checkPageTitle(t, p, "Simple")
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure> <img src=\"/not/real\"/> </figure> . More text here.</p><p>Some more text</p>"))
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 @@
checkPageTitle(t, p, "Simple")
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n"))
checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>"))
- checkPageType(t, p, "page")
-
}
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine)
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -303,7 +303,7 @@
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,7 +314,7 @@
} 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)
}
@@ -321,7 +321,7 @@
}
} 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 @@
}
- 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 @@
// 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)
}
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -105,8 +105,6 @@
Sections Taxonomy
Info SiteInfo
- layoutHandler *output.LayoutHandler
-
language *langs.Language
siteCfg siteConfigHolder
@@ -324,7 +322,6 @@
// 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 @@
s := &Site{
PageCollections: c,
- layoutHandler: output.NewLayoutHandler(),
language: cfg.Language,
disabledKinds: disabledKinds,
titleFunc: titleFunc,
@@ -936,7 +932,7 @@
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 @@
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) 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) 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) 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 @@
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) {
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -19,6 +19,8 @@
"strings"
"sync"
+ "github.com/gohugoio/hugo/tpl"
+
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/output"
@@ -136,25 +138,25 @@
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
}
}
@@ -161,8 +163,37 @@
}
}
+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 @@
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,8 +251,7 @@
return err
}
- nfLayouts := []string{"404.html"}
-
+ templ := s.lookupLayouts("404.html")
targetPath := p.targetPaths().TargetFilename
if targetPath == "" {
@@ -228,7 +258,7 @@
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 @@
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 @@
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)
}
--- a/hugolib/template_test.go
+++ b/hugolib/template_test.go
@@ -244,6 +244,178 @@
}
+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 := `<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>{{ .RelPermalink }}</title>
+ <meta name="description" content="The HTML5 Herald">
+ <meta name="author" content="SitePoint">
+ <link rel="stylesheet" href="css/styles.css?v=1.0">
+</head>
+<body>
+ <h1>{{ .RelPermalink }}</h1>
+ <p>Shortcode: {{< shortcode >}}</p>
+ <p>Partial: {{ partial "mypartial.html" . }}</p>
+ <script src="js/scripts.js"></script>
+</body>
+</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(`<title>/page%d/</title>`, i+1),
+ `<p>Shortcode: Shortcode: Hello</p>`,
+ "<p>Partial: this my partial</p>",
+ )
+ }
+
+ 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(`<title>/page%d/</title>`, i+1),
+ `<p>Shortcode: Shortcode: Hello</p>`,
+ "<p>Partial: this my changed partial</p>",
+ "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()
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -429,7 +429,7 @@
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 @@
}
}
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 @@
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)
}
--- a/output/layout.go
+++ b/output/layout.go
@@ -39,6 +39,7 @@
LayoutOverride bool
RenderingHook bool
+ Baseof bool
}
func (d LayoutDescriptor) isList() bool {
@@ -76,7 +77,6 @@
layouts := resolvePageTemplate(d, f)
- layouts = prependTextPrefixIfNeeded(f, layouts...)
layouts = helpers.UniqueStringsReuse(layouts)
l.mu.Lock()
@@ -95,9 +95,13 @@
func (l *layoutBuilder) addLayoutVariations(vars ...string) {
for _, layoutVar := range vars {
- if !l.d.RenderingHook && l.d.LayoutOverride && layoutVar != l.d.Layout {
+ if l.d.Baseof && layoutVar != "baseof" {
+ l.layoutVariations = append(l.layoutVariations, layoutVar+"-baseof")
continue
}
+ if !l.d.RenderingHook && !l.d.Baseof && l.d.LayoutOverride && layoutVar != l.d.Layout {
+ continue
+ }
l.layoutVariations = append(l.layoutVariations, layoutVar)
}
}
@@ -173,7 +177,7 @@
}
isRSS := f.Name == RSSFormat.Name
- if !d.RenderingHook && isRSS {
+ if !d.RenderingHook && !d.Baseof && isRSS {
// The historic and common rss.xml case
b.addLayoutVariations("")
}
@@ -186,9 +190,13 @@
b.addLayoutVariations("list")
}
+ if d.Baseof {
+ b.addLayoutVariations("baseof")
+ }
+
layouts := b.resolveVariations()
- if !d.RenderingHook && isRSS {
+ if !d.RenderingHook && !d.Baseof && isRSS {
layouts = append(layouts, "_internal/_default/rss.xml")
}
@@ -264,20 +272,6 @@
}
return filteredLayouts
-}
-
-func prependTextPrefixIfNeeded(f Format, layouts ...string) []string {
- if !f.IsPlainText {
- return layouts
- }
-
- newLayouts := make([]string, len(layouts))
-
- for i, l := range layouts {
- newLayouts[i] = "_text/" + l
- }
-
- return newLayouts
}
func replaceKeyValues(s string, oldNew ...string) string {
--- a/output/layout_base.go
+++ /dev/null
@@ -1,182 +1,0 @@
-// Copyright 2017-present 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 output
-
-import (
- "fmt"
- "path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/helpers"
-)
-
-const (
- baseFileBase = "baseof"
-)
-
-var (
- goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define"), []byte("{{- define"), []byte("{{-define")}
-)
-
-// TemplateNames represents a template naming scheme.
-type TemplateNames struct {
- // The name used as key in the template map. Note that this will be
- // prefixed with "_text/" if it should be parsed with text/template.
- Name string
-
- OverlayFilename string
- MasterFilename string
-}
-
-// TemplateLookupDescriptor describes the template lookup configuration.
-type TemplateLookupDescriptor struct {
- // The full path to the site root.
- WorkingDir string
-
- // The path to the template relative the the base.
- // I.e. shortcodes/youtube.html
- RelPath string
-
- // The template name prefix to look for.
- Prefix string
-
- // All the output formats in play. This is used to decide if text/template or
- // html/template.
- OutputFormats Formats
-
- FileExists func(filename string) (bool, error)
- ContainsAny func(filename string, subslices [][]byte) (bool, error)
-}
-
-func isShorthCodeOrPartial(name string) bool {
- return strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/")
-}
-
-// CreateTemplateNames returns a TemplateNames object for a given template.
-func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
-
- name := filepath.ToSlash(d.RelPath)
- name = strings.TrimPrefix(name, "/")
-
- if d.Prefix != "" {
- name = strings.Trim(d.Prefix, "/") + "/" + name
- }
-
- var (
- id TemplateNames
- )
-
- // The filename will have a suffix with an optional type indicator.
- // Examples:
- // index.html
- // index.amp.html
- // index.json
- filename := filepath.Base(d.RelPath)
- isPlainText := false
- outputFormat, found := d.OutputFormats.FromFilename(filename)
-
- if found && outputFormat.IsPlainText {
- isPlainText = true
- }
-
- var ext, outFormat string
-
- parts := strings.Split(filename, ".")
- if len(parts) > 2 {
- outFormat = parts[1]
- ext = parts[2]
- } else if len(parts) > 1 {
- ext = parts[1]
- }
-
- filenameNoSuffix := parts[0]
-
- id.OverlayFilename = d.RelPath
- id.Name = name
-
- if isPlainText {
- id.Name = "_text/" + id.Name
- }
-
- // Go templates may have both a base and inner template.
- if isShorthCodeOrPartial(name) {
- // No base template support
- return id, nil
- }
-
- pathDir := filepath.Dir(d.RelPath)
-
- innerMarkers := goTemplateInnerMarkers
-
- var baseFilename string
-
- if outFormat != "" {
- baseFilename = fmt.Sprintf("%s.%s.%s", baseFileBase, outFormat, ext)
- } else {
- baseFilename = fmt.Sprintf("%s.%s", baseFileBase, ext)
- }
-
- // This may be a view that shouldn't have base template
- // Have to look inside it to make sure
- needsBase, err := d.ContainsAny(d.RelPath, innerMarkers)
- if err != nil {
- return id, err
- }
-
- if needsBase {
- currBaseFilename := fmt.Sprintf("%s-%s", filenameNoSuffix, baseFilename)
-
- // Look for base template in the follwing order:
- // 1. <current-path>/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
- // 2. <current-path>/baseof.<outputFormat>(optional).<suffix>
- // 3. _default/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
- // 4. _default/baseof.<outputFormat>(optional).<suffix>
- //
- // The filesystem it looks in a a composite of the project and potential theme(s).
- pathsToCheck := createPathsToCheck(pathDir, baseFilename, currBaseFilename)
-
- // We may have language code and/or "terms" in the template name. We want the most specific,
- // but need to fall back to the baseof.html if needed.
- // E.g. list-baseof.en.html and list-baseof.terms.en.html
- // See #3893, #3856.
- baseBaseFilename, currBaseBaseFilename := helpers.Filename(baseFilename), helpers.Filename(currBaseFilename)
- p1, p2 := strings.Split(baseBaseFilename, "."), strings.Split(currBaseBaseFilename, ".")
- if len(p1) > 0 && len(p1) == len(p2) {
- for i := len(p1); i > 0; i-- {
- v1, v2 := strings.Join(p1[:i], ".")+"."+ext, strings.Join(p2[:i], ".")+"."+ext
- pathsToCheck = append(pathsToCheck, createPathsToCheck(pathDir, v1, v2)...)
-
- }
- }
-
- for _, p := range pathsToCheck {
- if ok, err := d.FileExists(p); err == nil && ok {
- id.MasterFilename = p
- break
- }
- }
- }
-
- return id, nil
-
-}
-
-func createPathsToCheck(baseTemplatedDir, baseFilename, currBaseFilename string) []string {
- return []string{
- filepath.Join(baseTemplatedDir, currBaseFilename),
- filepath.Join(baseTemplatedDir, baseFilename),
- filepath.Join("_default", currBaseFilename),
- filepath.Join("_default", baseFilename),
- }
-}
--- a/output/layout_base_test.go
+++ /dev/null
@@ -1,163 +1,0 @@
-// Copyright 2017-present 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 output
-
-import (
- "path/filepath"
- "strings"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestLayoutBase(t *testing.T) {
- c := qt.New(t)
-
- var (
- workingDir = "/sites/mysite/"
- layoutPath1 = "_default/single.html"
- layoutPathAmp = "_default/single.amp.html"
- layoutPathJSON = "_default/single.json"
- )
-
- for _, this := range []struct {
- name string
- d TemplateLookupDescriptor
- needsBase bool
- basePathMatchStrings string
- expect TemplateNames
- }{
- {"No base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPath1}, false, "",
- TemplateNames{
- Name: "_default/single.html",
- OverlayFilename: "_default/single.html",
- }},
- {"Base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPath1}, true, "",
- TemplateNames{
- Name: "_default/single.html",
- OverlayFilename: "_default/single.html",
- MasterFilename: "_default/single-baseof.html",
- }},
- // Issue #3893
- {"Base Lang, Default Base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "_default/list.en.html"}, true, "_default/baseof.html",
- TemplateNames{
- Name: "_default/list.en.html",
- OverlayFilename: "_default/list.en.html",
- MasterFilename: "_default/baseof.html",
- }},
- {"Base Lang, Lang Base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "_default/list.en.html"}, true, "_default/baseof.html|_default/baseof.en.html",
- TemplateNames{
- Name: "_default/list.en.html",
- OverlayFilename: "_default/list.en.html",
- MasterFilename: "_default/baseof.en.html",
- }},
- // Issue #3856
- {"Base Taxonomy Term", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "taxonomy/tag.terms.html"}, true, "_default/baseof.html",
- TemplateNames{
- Name: "taxonomy/tag.terms.html",
- OverlayFilename: "taxonomy/tag.terms.html",
- MasterFilename: "_default/baseof.html",
- }},
-
- {"Partial", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "partials/menu.html"}, true,
- "mytheme/layouts/_default/baseof.html",
- TemplateNames{
- Name: "partials/menu.html",
- OverlayFilename: "partials/menu.html",
- }},
- {"Partial in subfolder", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "/partials/sub/menu.html"}, true,
- "_default/baseof.html",
- TemplateNames{
- Name: "partials/sub/menu.html",
- OverlayFilename: "/partials/sub/menu.html",
- }},
- {"Shortcode in subfolder", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: "shortcodes/sub/menu.html"}, true,
- "_default/baseof.html",
- TemplateNames{
- Name: "shortcodes/sub/menu.html",
- OverlayFilename: "shortcodes/sub/menu.html",
- }},
- {"AMP, no base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathAmp}, false, "",
- TemplateNames{
- Name: "_default/single.amp.html",
- OverlayFilename: "_default/single.amp.html",
- }},
- {"JSON, no base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathJSON}, false, "",
- TemplateNames{
- Name: "_default/single.json",
- OverlayFilename: "_default/single.json",
- }},
- {"AMP with base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathAmp}, true, "single-baseof.html|single-baseof.amp.html",
- TemplateNames{
- Name: "_default/single.amp.html",
- OverlayFilename: "_default/single.amp.html",
- MasterFilename: "_default/single-baseof.amp.html",
- }},
- {"AMP with no AMP base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathAmp}, true, "single-baseof.html",
- TemplateNames{
- Name: "_default/single.amp.html",
- OverlayFilename: "_default/single.amp.html",
- MasterFilename: "_default/single-baseof.html",
- }},
-
- {"JSON with base", TemplateLookupDescriptor{WorkingDir: workingDir, RelPath: layoutPathJSON}, true, "single-baseof.json",
- TemplateNames{
- Name: "_default/single.json",
- OverlayFilename: "_default/single.json",
- MasterFilename: "_default/single-baseof.json",
- }},
- } {
- c.Run(this.name, func(c *qt.C) {
-
- this.basePathMatchStrings = filepath.FromSlash(this.basePathMatchStrings)
-
- fileExists := func(filename string) (bool, error) {
- stringsToMatch := strings.Split(this.basePathMatchStrings, "|")
- for _, s := range stringsToMatch {
- if strings.Contains(filename, s) {
- return true, nil
- }
-
- }
- return false, nil
- }
-
- needsBase := func(filename string, subslices [][]byte) (bool, error) {
- return this.needsBase, nil
- }
-
- this.d.OutputFormats = Formats{AMPFormat, HTMLFormat, RSSFormat, JSONFormat}
- this.d.WorkingDir = filepath.FromSlash(this.d.WorkingDir)
- this.d.RelPath = filepath.FromSlash(this.d.RelPath)
- this.d.ContainsAny = needsBase
- this.d.FileExists = fileExists
-
- this.expect.MasterFilename = filepath.FromSlash(this.expect.MasterFilename)
- this.expect.OverlayFilename = filepath.FromSlash(this.expect.OverlayFilename)
-
- if strings.Contains(this.d.RelPath, "json") {
- // currently the only plain text templates in this test.
- this.expect.Name = "_text/" + this.expect.Name
- }
-
- id, err := CreateTemplateNames(this.d)
-
- c.Assert(err, qt.IsNil)
- msg := qt.Commentf(this.name)
- c.Assert(id, qt.Equals, this.expect, msg)
-
- })
- }
-
-}
--- a/output/layout_test.go
+++ b/output/layout_test.go
@@ -66,9 +66,13 @@
}{
{"Home", LayoutDescriptor{Kind: "home"}, "", ampType,
[]string{"index.amp.html", "home.amp.html", "list.amp.html", "index.html", "home.html", "list.html", "_default/index.amp.html"}, 12},
+ {"Home baseof", LayoutDescriptor{Kind: "home", Baseof: true}, "", ampType,
+ []string{"index-baseof.amp.html", "home-baseof.amp.html", "list-baseof.amp.html", "baseof.amp.html", "index-baseof.html"}, 16},
{"Home, HTML", LayoutDescriptor{Kind: "home"}, "", htmlFormat,
// We will eventually get to index.html. This looks stuttery, but makes the lookup logic easy to understand.
[]string{"index.html.html", "home.html.html"}, 12},
+ {"Home, HTML, baseof", LayoutDescriptor{Kind: "home", Baseof: true}, "", htmlFormat,
+ []string{"index-baseof.html.html", "home-baseof.html.html", "list-baseof.html.html", "baseof.html.html"}, 16},
{"Home, french language", LayoutDescriptor{Kind: "home", Lang: "fr"}, "", ampType,
[]string{"index.fr.amp.html"},
24},
@@ -80,6 +84,8 @@
[]string{"_default/single.nem"}, 1},
{"Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, "", ampType,
[]string{"sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/sect1.html", "sect1/section.html", "sect1/list.html", "section/sect1.amp.html", "section/section.amp.html"}, 18},
+ {"Section, baseof", LayoutDescriptor{Kind: "section", Section: "sect1", Baseof: true}, "", ampType,
+ []string{"sect1/sect1-baseof.amp.html", "sect1/section-baseof.amp.html", "sect1/list-baseof.amp.html", "sect1/baseof.amp.html", "sect1/sect1-baseof.html", "sect1/section-baseof.html", "sect1/list-baseof.html", "sect1/baseof.html"}, 24},
{"Section with layout", LayoutDescriptor{Kind: "section", Section: "sect1", Layout: "mylayout"}, "", ampType,
[]string{"sect1/mylayout.amp.html", "sect1/sect1.amp.html", "sect1/section.amp.html", "sect1/list.amp.html", "sect1/mylayout.html", "sect1/sect1.html"}, 24},
{"Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, "", ampType,
@@ -88,8 +94,12 @@
[]string{"taxonomy/categories.terms.amp.html", "taxonomy/terms.amp.html", "taxonomy/list.amp.html", "taxonomy/categories.terms.html", "taxonomy/terms.html"}, 18},
{"Page", LayoutDescriptor{Kind: "page"}, "", ampType,
[]string{"_default/single.amp.html", "_default/single.html"}, 2},
+ {"Page, baseof", LayoutDescriptor{Kind: "page", Baseof: true}, "", ampType,
+ []string{"_default/single-baseof.amp.html", "_default/baseof.amp.html", "_default/single-baseof.html", "_default/baseof.html"}, 4},
{"Page with layout", LayoutDescriptor{Kind: "page", Layout: "mylayout"}, "", ampType,
[]string{"_default/mylayout.amp.html", "_default/single.amp.html", "_default/mylayout.html", "_default/single.html"}, 4},
+ {"Page with layout, baseof", LayoutDescriptor{Kind: "page", Layout: "mylayout", Baseof: true}, "", ampType,
+ []string{"_default/mylayout-baseof.amp.html", "_default/single-baseof.amp.html", "_default/baseof.amp.html", "_default/mylayout-baseof.html", "_default/single-baseof.html", "_default/baseof.html"}, 6},
{"Page with layout and type", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, "", ampType,
[]string{"myttype/mylayout.amp.html", "myttype/single.amp.html", "myttype/mylayout.html"}, 8},
{"Page with layout and type with subtype", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype"}, "", ampType,
@@ -97,6 +107,8 @@
// RSS
{"RSS Home", LayoutDescriptor{Kind: "home"}, "", RSSFormat,
[]string{"index.rss.xml", "home.rss.xml", "rss.xml"}, 15},
+ {"RSS Home, baseof", LayoutDescriptor{Kind: "home", Baseof: true}, "", RSSFormat,
+ []string{"index-baseof.rss.xml", "home-baseof.rss.xml", "list-baseof.rss.xml", "baseof.rss.xml"}, 16},
{"RSS Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, "", RSSFormat,
[]string{"sect1/sect1.rss.xml", "sect1/section.rss.xml", "sect1/rss.xml", "sect1/list.rss.xml", "sect1/sect1.xml", "sect1/section.xml"}, 22},
{"RSS Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, "", RSSFormat,
@@ -104,13 +116,14 @@
{"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, "", RSSFormat,
[]string{"taxonomy/tag.terms.rss.xml", "taxonomy/terms.rss.xml", "taxonomy/rss.xml", "taxonomy/list.rss.xml", "taxonomy/tag.terms.xml"}, 22},
{"Home plain text", LayoutDescriptor{Kind: "home"}, "", JSONFormat,
- []string{"_text/index.json.json", "_text/home.json.json"}, 12},
+ []string{"index.json.json", "home.json.json"}, 12},
{"Page plain text", LayoutDescriptor{Kind: "page"}, "", JSONFormat,
- []string{"_text/_default/single.json.json", "_text/_default/single.json"}, 2},
+ []string{"_default/single.json.json", "_default/single.json"}, 2},
{"Reserved section, shortcodes", LayoutDescriptor{Kind: "section", Section: "shortcodes", Type: "shortcodes"}, "", ampType,
[]string{"section/shortcodes.amp.html"}, 12},
{"Reserved section, partials", LayoutDescriptor{Kind: "section", Section: "partials", Type: "partials"}, "", ampType,
[]string{"section/partials.amp.html"}, 12},
+
// We may add type support ... later.
{"Content hook", LayoutDescriptor{Kind: "render-link", RenderingHook: true, Layout: "mylayout", Section: "blog"}, "", ampType,
[]string{"_default/_markup/render-link.amp.html", "_default/_markup/render-link.html"}, 2},
@@ -122,7 +135,7 @@
c.Assert(err, qt.IsNil)
c.Assert(layouts, qt.Not(qt.IsNil))
- c.Assert(len(layouts) >= len(this.expect), qt.Equals, true)
+ c.Assert(len(layouts) >= len(this.expect), qt.Equals, true, qt.Commentf("%d vs %d", len(layouts), len(this.expect)))
// Not checking the complete list for now ...
got := layouts[:len(this.expect)]
if len(layouts) != this.expectCount || !reflect.DeepEqual(got, this.expect) {
@@ -130,7 +143,7 @@
formatted = strings.Replace(formatted, "]", "\"", 1)
formatted = strings.Replace(formatted, " ", "\", \"", -1)
- t.Fatalf("Got %d/%d:\n%v\nExpected:\n%v\nAll:\n%v\nFormatted:\n%s", len(layouts), this.expectCount, got, this.expect, layouts, formatted)
+ c.Fatalf("Got %d/%d:\n%v\nExpected:\n%v\nAll:\n%v\nFormatted:\n%s", len(layouts), this.expectCount, got, this.expect, layouts, formatted)
}
--- a/resources/resource_transformers/templates/execute_as_template.go
+++ b/resources/resource_transformers/templates/execute_as_template.go
@@ -26,28 +26,25 @@
// Client contains methods to perform template processing of Resource objects.
type Client struct {
rs *resources.Spec
-
- templateHandler tpl.TemplateHandler
- textTemplate tpl.TemplateParseFinder
+ t tpl.TemplatesProvider
}
// New creates a new Client with the given specification.
-func New(rs *resources.Spec, h tpl.TemplateHandler, textTemplate tpl.TemplateParseFinder) *Client {
+func New(rs *resources.Spec, t tpl.TemplatesProvider) *Client {
if rs == nil {
panic("must provice a resource Spec")
}
- if textTemplate == nil {
- panic("must provide a textTemplate")
+ if t == nil {
+ panic("must provide a template provider")
}
- return &Client{rs: rs, templateHandler: h, textTemplate: textTemplate}
+ return &Client{rs: rs, t: t}
}
type executeAsTemplateTransform struct {
- rs *resources.Spec
- textTemplate tpl.TemplateParseFinder
- templateHandler tpl.TemplateHandler
- targetPath string
- data interface{}
+ rs *resources.Spec
+ t tpl.TemplatesProvider
+ targetPath string
+ data interface{}
}
func (t *executeAsTemplateTransform) Key() internal.ResourceTransformationKey {
@@ -56,7 +53,7 @@
func (t *executeAsTemplateTransform) Transform(ctx *resources.ResourceTransformationCtx) error {
tplStr := helpers.ReaderToString(ctx.From)
- templ, err := t.textTemplate.Parse(ctx.InPath, tplStr)
+ templ, err := t.t.TextTmpl().Parse(ctx.InPath, tplStr)
if err != nil {
return errors.Wrapf(err, "failed to parse Resource %q as Template:", ctx.InPath)
}
@@ -63,15 +60,14 @@
ctx.OutPath = t.targetPath
- return t.templateHandler.Execute(templ, ctx.To, t.data)
+ return t.t.Tmpl().Execute(templ, ctx.To, t.data)
}
func (c *Client) ExecuteAsTemplate(res resources.ResourceTransformer, targetPath string, data interface{}) (resource.Resource, error) {
return res.Transform(&executeAsTemplateTransform{
- rs: c.rs,
- targetPath: helpers.ToSlashTrimLeading(targetPath),
- templateHandler: c.templateHandler,
- textTemplate: c.textTemplate,
- data: data,
+ rs: c.rs,
+ targetPath: helpers.ToSlashTrimLeading(targetPath),
+ t: c.t,
+ data: data,
})
}
--- a/tpl/collections/apply.go
+++ b/tpl/collections/apply.go
@@ -106,7 +106,7 @@
func (ns *Namespace) lookupFunc(fname string) (reflect.Value, bool) {
if !strings.ContainsRune(fname, '.') {
- templ := ns.deps.Tmpl.(tpl.TemplateFuncGetter)
+ templ := ns.deps.Tmpl().(tpl.TemplateFuncGetter)
return templ.GetFunc(fname)
}
--- a/tpl/collections/apply_test.go
+++ b/tpl/collections/apply_test.go
@@ -22,6 +22,7 @@
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/deps"
+ "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/tpl"
)
@@ -31,10 +32,18 @@
return nil, false
}
+func (templateFinder) HasTemplate(name string) bool {
+ return false
+}
+
func (templateFinder) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
return nil, false, false
}
+func (templateFinder) LookupLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
+ return nil, false, nil
+}
+
func (templateFinder) Execute(t tpl.Template, wr io.Writer, data interface{}) error {
return nil
}
@@ -51,8 +60,9 @@
func TestApply(t *testing.T) {
t.Parallel()
c := qt.New(t)
-
- ns := New(&deps.Deps{Tmpl: new(templateFinder)})
+ d := &deps.Deps{}
+ d.SetTmpl(new(templateFinder))
+ ns := New(d)
strings := []interface{}{"a\n", "b\n"}
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -105,11 +105,11 @@
}
n := "partials/" + name
- templ, found := ns.deps.Tmpl.Lookup(n)
+ templ, found := ns.deps.Tmpl().Lookup(n)
if !found {
// For legacy reasons.
- templ, found = ns.deps.Tmpl.Lookup(n + ".html")
+ templ, found = ns.deps.Tmpl().Lookup(n + ".html")
}
if !found {
@@ -139,7 +139,7 @@
w = b
}
- if err := ns.deps.Tmpl.Execute(templ, w, context); err != nil {
+ if err := ns.deps.Tmpl().Execute(templ, w, context); err != nil {
return "", err
}
--- a/tpl/resources/resources.go
+++ b/tpl/resources/resources.go
@@ -45,6 +45,7 @@
if err != nil {
return nil, err
}
+
return &Namespace{
deps: deps,
scssClient: scssClient,
@@ -53,7 +54,7 @@
integrityClient: integrity.New(deps.ResourceSpec),
minifyClient: minifier.New(deps.ResourceSpec),
postcssClient: postcss.New(deps.ResourceSpec),
- templatesClient: templates.New(deps.ResourceSpec, deps.Tmpl, deps.TextTmpl),
+ templatesClient: templates.New(deps.ResourceSpec, deps),
}, nil
}
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -30,9 +30,7 @@
TemplateFuncGetter
AddTemplate(name, tpl string) error
AddLateTemplate(name, tpl string) error
- LoadTemplates(prefix string) error
-
- RebuildClone()
+ MarkReady() error
}
// TemplateVariants describes the possible variants of a template.
@@ -52,6 +50,8 @@
type TemplateHandler interface {
TemplateFinder
Execute(t Template, wr io.Writer, data interface{}) error
+ LookupLayout(d output.LayoutDescriptor, f output.Format) (Template, bool, error)
+ HasTemplate(name string) bool
}
type TemplateLookup interface {
@@ -103,6 +103,12 @@
type templateInfoManager struct {
Template
InfoManager
+}
+
+// TemplatesProvider as implemented by deps.Deps.
+type TemplatesProvider interface {
+ Tmpl() TemplateHandler
+ TextTmpl() TemplateParseFinder
}
// WithInfo wraps the info in a template.
--- a/tpl/templates/templates.go
+++ b/tpl/templates/templates.go
@@ -34,7 +34,7 @@
// Note that this is the Unix-styled relative path including filename suffix,
// e.g. partials/header.html
func (ns *Namespace) Exists(name string) bool {
- _, found := ns.deps.Tmpl.Lookup(name)
+ _, found := ns.deps.Tmpl().Lookup(name)
return found
}
--- a/tpl/tplimpl/shortcodes.go
+++ b/tpl/tplimpl/shortcodes.go
@@ -32,8 +32,7 @@
// A slice of length numTemplateVariants.
variants []string
- info tpl.Info
- templ tpl.Template
+ ts *templateState
}
type shortcodeTemplates struct {
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -14,59 +14,44 @@
package tplimpl
import (
- "fmt"
"io"
+ "os"
+ "path/filepath"
"reflect"
"regexp"
+ "strings"
+ "sync"
"time"
- "github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/common/types"
- "github.com/gohugoio/hugo/identity"
+ "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/output"
- "strings"
+ "github.com/gohugoio/hugo/deps"
+ "github.com/spf13/afero"
- template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
-
- texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
-
+ "github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/tpl/tplimpl/embedded"
+ "github.com/gohugoio/hugo/hugofs/files"
"github.com/pkg/errors"
- "os"
+ "github.com/gohugoio/hugo/tpl/tplimpl/embedded"
- "github.com/gohugoio/hugo/output"
+ htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
- "path/filepath"
- "sync"
-
- "github.com/gohugoio/hugo/deps"
- "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl"
- "github.com/spf13/afero"
)
const (
textTmplNamePrefix = "_text/"
-)
-var (
- _ tpl.TemplateManager = (*templateHandler)(nil)
- _ tpl.TemplateHandler = (*templateHandler)(nil)
- _ tpl.TemplateDebugger = (*templateHandler)(nil)
- _ tpl.TemplateFuncGetter = (*templateHandler)(nil)
- _ tpl.TemplateFinder = (*htmlTemplates)(nil)
- _ tpl.TemplateFinder = (*textTemplates)(nil)
- _ templateLoader = (*htmlTemplates)(nil)
- _ templateLoader = (*textTemplates)(nil)
-)
-
-const (
shortcodesPathPrefix = "shortcodes/"
internalPathPrefix = "_internal/"
+ baseFileBase = "baseof"
)
// The identifiers may be truncated in the log, e.g.
@@ -77,286 +62,273 @@
"shortcodes/twitter.html": {"shortcodes/tweet.html"},
}
-const baseFileBase = "baseof"
+var (
+ _ tpl.TemplateManager = (*templateExec)(nil)
+ _ tpl.TemplateHandler = (*templateExec)(nil)
+ _ tpl.TemplateFuncGetter = (*templateExec)(nil)
+ _ tpl.TemplateFinder = (*templateExec)(nil)
-func newTemplateAdapter(deps *deps.Deps) *templateHandler {
+ _ tpl.Template = (*templateState)(nil)
+ _ tpl.Info = (*templateState)(nil)
+)
- common := &templatesCommon{
- nameBaseTemplateName: make(map[string]string),
- transformNotFound: make(map[string]bool),
- identityNotFound: make(map[string][]identity.Manager),
- }
+var defineRe = regexp.MustCompile(`{{-?\s?define`)
- htmlT := &htmlTemplates{
- t: template.New(""),
- overlays: make(map[string]*template.Template),
- templatesCommon: common,
+func newIdentity(name string) identity.Manager {
+ return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name))
+}
+
+func newStandaloneTextTemplate(funcs map[string]interface{}) tpl.TemplateParseFinder {
+ return &textTemplateWrapperWithLock{
+ RWMutex: &sync.RWMutex{},
+ Template: texttemplate.New("").Funcs(funcs),
}
+}
- textT := &textTemplates{
- textTemplate: &textTemplate{t: texttemplate.New("")},
- standalone: &textTemplate{t: texttemplate.New("")},
- overlays: make(map[string]*texttemplate.Template),
- templatesCommon: common,
+func newTemplateExec(d *deps.Deps) (*templateExec, error) {
+ exec, funcs := newTemplateExecuter(d)
+ funcMap := make(map[string]interface{})
+ for k, v := range funcs {
+ funcMap[k] = v.Interface()
}
h := &templateHandler{
- Deps: deps,
- layoutsFs: deps.BaseFs.Layouts.Fs,
- templateHandlerCommon: &templateHandlerCommon{
- shortcodes: make(map[string]*shortcodeTemplates),
- templateInfo: make(map[string]tpl.Info),
- templateInfoTree: make(map[string]*templateInfoTree),
- html: htmlT,
- text: textT,
- },
- }
+ nameBaseTemplateName: make(map[string]string),
+ transformNotFound: make(map[string]*templateState),
+ identityNotFound: make(map[string][]identity.Manager),
- textT.textTemplate.templates = textT
- textT.standalone.templates = textT
- common.handler = h
+ shortcodes: make(map[string]*shortcodeTemplates),
+ templateInfo: make(map[string]tpl.Info),
+ baseof: make(map[string]templateInfo),
+ needsBaseof: make(map[string]templateInfo),
- return h
+ main: newTemplateNamespace(funcMap, false),
-}
+ Deps: d,
+ layoutHandler: output.NewLayoutHandler(),
+ layoutsFs: d.BaseFs.Layouts.Fs,
+ layoutTemplateCache: make(map[layoutCacheKey]tpl.Template),
+ }
-type htmlTemplates struct {
- *templatesCommon
+ if err := h.loadEmbedded(); err != nil {
+ return nil, err
+ }
- t *template.Template
+ if err := h.loadTemplates(); err != nil {
+ return nil, err
+ }
- // This looks, and is, strange.
- // The clone is used by non-renderable content pages, and these need to be
- // re-parsed on content change, and to avoid the
- // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
- clone *template.Template
- cloneClone *template.Template
-
- // a separate storage for the overlays created from cloned master templates.
- // note: No mutex protection, so we add these in one Go routine, then just read.
- overlays map[string]*template.Template
-}
-
-func (t *htmlTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+ e := &templateExec{
+ d: d,
+ executor: exec,
+ funcs: funcs,
+ templateHandler: h,
}
- return templ, true
-}
+ d.SetTmpl(e)
+ d.SetTextTmpl(newStandaloneTextTemplate(funcMap))
-func (t *htmlTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
+ if d.WithTemplate != nil {
+ if err := d.WithTemplate(e); err != nil {
+ return nil, err
-func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
-}
+ }
+ }
-func (t *htmlTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
+ return e, nil
}
-func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, templstr string) (*templateContext, error) {
- templ, err := tt.New(name).Parse(templstr)
- if err != nil {
- return nil, err
+func newTemplateNamespace(funcs map[string]interface{}, lock bool) *templateNamespace {
+ var mu *sync.RWMutex
+ if lock {
+ mu = &sync.RWMutex{}
}
- typ := resolveTemplateType(name)
-
- c, err := t.handler.applyTemplateTransformersToHMLTTemplate(typ, templ)
- if err != nil {
- return nil, err
+ return &templateNamespace{
+ prototypeHTML: htmltemplate.New("").Funcs(funcs),
+ prototypeText: texttemplate.New("").Funcs(funcs),
+ templateStateMap: &templateStateMap{
+ mu: mu,
+ templates: make(map[string]*templateState),
+ },
}
+}
- for k := range c.templateNotFound {
- t.transformNotFound[k] = true
- t.identityNotFound[k] = append(t.identityNotFound[k], c.id)
+func newTemplateState(templ tpl.Template, info templateInfo) *templateState {
+ return &templateState{
+ info: info,
+ typ: info.resolveType(),
+ Template: templ,
+ Manager: newIdentity(info.name),
+ parseInfo: tpl.DefaultParseInfo,
}
+}
- for k := range c.identityNotFound {
- t.identityNotFound[k] = append(t.identityNotFound[k], c.id)
- }
-
- return c, nil
+type layoutCacheKey struct {
+ d output.LayoutDescriptor
+ f string
}
-func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+type templateExec struct {
+ d *deps.Deps
+ executor texttemplate.Executer
+ funcs map[string]reflect.Value
- masterTpl := t.lookup(masterFilename)
+ *templateHandler
+}
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- return err
- }
+func (t templateExec) Clone(d *deps.Deps) *templateExec {
+ exec, funcs := newTemplateExecuter(d)
+ t.executor = exec
+ t.funcs = funcs
+ t.d = d
+ return &t
+}
- masterTpl, err = t.t.New(overlayFilename).Parse(templ.template)
- if err != nil {
- return templ.errWithFileContext("parse master failed", err)
- }
+func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data interface{}) error {
+ if rlocker, ok := templ.(types.RLocker); ok {
+ rlocker.RLock()
+ defer rlocker.RUnlock()
}
-
- templ, err := onMissing(overlayFilename)
- if err != nil {
- return err
+ if t.Metrics != nil {
+ defer t.Metrics.MeasureSince(templ.Name(), time.Now())
}
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template)
- if err != nil {
- return templ.errWithFileContext("parse failed", err)
+ execErr := t.executor.Execute(templ, wr, data)
+ if execErr != nil {
+ execErr = t.addFileContext(templ, execErr)
}
+ return execErr
+}
- // The extra lookup is a workaround, see
- // * https://github.com/golang/go/issues/16101
- // * https://github.com/gohugoio/hugo/issues/2549
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- if _, err := t.handler.applyTemplateTransformersToHMLTTemplate(templateUndefined, overlayTpl); err != nil {
- return err
- }
-
- t.overlays[name] = overlayTpl
- t.nameBaseTemplateName[name] = masterFilename
-
- return err
-
+func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
+ v, found := t.funcs[name]
+ return v, found
}
-func (t *htmlTemplates) lookup(name string) *template.Template {
- // Need to check in the overlay registry first as it will also be found below.
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
+func (t *templateExec) MarkReady() error {
+ var err error
+ t.readyInit.Do(func() {
+ // We only need the clones if base templates are in use.
+ if len(t.needsBaseof) > 0 {
+ err = t.main.createPrototypes()
}
- }
+ })
- if templ := t.t.Lookup(name); templ != nil {
- return templ
+ if err != nil {
+ return err
}
- if t.clone != nil {
- return t.clone.Lookup(name)
+ if t.Deps.BuildFlags.HasLateTemplate.Load() {
+ // This costs memory, so try to avoid it if we don't have to.
+ // The late templates are used to handle HTML in files in /content
+ // without front matter.
+ t.readyLateInit.Do(func() {
+ t.late = t.main.Clone(true)
+ t.late.createPrototypes()
+ })
}
return nil
}
-func (t htmlTemplates) withNewHandler(h *templateHandler) *htmlTemplates {
- t.templatesCommon = t.templatesCommon.withNewHandler(h)
- return &t
-}
-
-type nopLookupVariant int
-
-func (l nopLookupVariant) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return nil, false, false
-}
-
-// templateHandler holds the templates in play.
-// It implements the templateLoader and tpl.TemplateHandler interfaces.
-// There is one templateHandler created per Site.
type templateHandler struct {
- ready bool
+ main *templateNamespace
+ needsBaseof map[string]templateInfo
+ baseof map[string]templateInfo
- executor texttemplate.Executer
- funcs map[string]reflect.Value
+ late *templateNamespace // Templates added after main has started executing.
+ readyInit sync.Once
+ readyLateInit sync.Once
+
// This is the filesystem to load the templates from. All the templates are
// stored in the root of this filesystem.
layoutsFs afero.Fs
+ layoutHandler *output.LayoutHandler
+
+ layoutTemplateCache map[layoutCacheKey]tpl.Template
+ layoutTemplateCacheMu sync.RWMutex
+
*deps.Deps
- *templateHandlerCommon
+ // Used to get proper filenames in errors
+ nameBaseTemplateName map[string]string
+
+ // Holds name and source of template definitions not found during the first
+ // AST transformation pass.
+ transformNotFound map[string]*templateState
+
+ // Holds identities of templates not found during first pass.
+ identityNotFound map[string][]identity.Manager
+
+ // shortcodes maps shortcode name to template variants
+ // (language, output format etc.) of that shortcode.
+ shortcodes map[string]*shortcodeTemplates
+
+ // templateInfo maps template name to some additional information about that template.
+ // Note that for shortcodes that same information is embedded in the
+ // shortcodeTemplates type.
+ templateInfo map[string]tpl.Info
}
-// AddLateTemplate is used to add a template late, i.e. after the
+// AddLateTemplate is used to add a template after the
// regular templates have started its execution.
+// These are currently "pure HTML content files".
func (t *templateHandler) AddLateTemplate(name, tpl string) error {
- h := t.getTemplateHandler(name)
- if err := h.addLateTemplate(name, tpl); err != nil {
- return err
- }
- return nil
+ _, err := t.late.parse(t.newTemplateInfo(name, tpl))
+ return err
}
// AddTemplate parses and adds a template to the collection.
// Templates with name prefixed with "_text" will be handled as plain
// text templates.
-// TODO(bep) clean up these addTemplate variants
func (t *templateHandler) AddTemplate(name, tpl string) error {
- h := t.getTemplateHandler(name)
- _, err := h.addTemplate(name, tpl)
- if err != nil {
- return err
+ templ, err := t.addTemplateTo(t.newTemplateInfo(name, tpl), t.main)
+ if err == nil {
+ t.applyTemplateTransformers(t.main, templ)
}
- return nil
+ return err
}
-func (t *templateHandler) Debug() {
- fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
- fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
-}
-
-func (t *templateHandler) Execute(templ tpl.Template, wr io.Writer, data interface{}) error {
- if t.Metrics != nil {
- defer t.Metrics.MeasureSince(templ.Name(), time.Now())
+func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
+ templ, found := t.main.Lookup(name)
+ if found {
+ return templ, true
}
- execErr := t.executor.Execute(templ, wr, data)
- if execErr != nil {
- execErr = t.addFileContext(templ.Name(), execErr)
+ if t.late != nil {
+ return t.late.Lookup(name)
}
- return execErr
-
+ return nil, false
}
-func (t *templateHandler) GetFunc(name string) (reflect.Value, bool) {
- v, found := t.funcs[name]
- return v, found
+func (t *templateHandler) LookupLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
+ key := layoutCacheKey{d, f.Name}
+ t.layoutTemplateCacheMu.RLock()
+ if cacheVal, found := t.layoutTemplateCache[key]; found {
+ t.layoutTemplateCacheMu.RUnlock()
+ return cacheVal, true, nil
+ }
+ t.layoutTemplateCacheMu.RUnlock()
-}
+ t.layoutTemplateCacheMu.Lock()
+ defer t.layoutTemplateCacheMu.Unlock()
-// 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) error {
- return t.loadTemplates(prefix)
-
-}
-
-// 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) {
-
- if strings.HasPrefix(name, textTmplNamePrefix) {
- // The caller has explicitly asked for a text template, so only look
- // in the text template collection.
- // The templates are stored without the prefix identificator.
- name = strings.TrimPrefix(name, textTmplNamePrefix)
-
- return t.applyTemplateInfo(t.text.Lookup(name))
+ templ, found, err := t.findLayout(d, f)
+ if err == nil && found {
+ t.layoutTemplateCache[key] = templ
+ return templ, true, nil
}
- // Look in both
- if te, found := t.html.Lookup(name); found {
- return t.applyTemplateInfo(te, true)
- }
-
- return t.applyTemplateInfo(t.text.Lookup(name))
-
+ return nil, false, err
}
// This currently only applies to shortcodes and what we get here is the
// shortcode name.
func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- if !t.ready {
- panic("handler not ready")
- }
name = templateBaseName(templateShortcode, name)
s, found := t.shortcodes[name]
if !found {
@@ -370,131 +342,142 @@
more := len(s.variants) > 1
- return tpl.WithInfo(sv.templ, sv.info), true, more
+ return sv.ts, true, more
}
-// markReady marks the templates as "ready for execution". No changes allowed
-// after this is set.
-func (t *templateHandler) markReady() error {
- defer func() {
- t.ready = true
- }()
-
- if err := t.postTransform(); err != nil {
- return err
+func (t *templateHandler) HasTemplate(name string) bool {
+ if _, found := t.baseof[name]; found {
+ return true
}
+ _, found := t.Lookup(name)
+ return found
+}
- if t.html.clone == nil {
- t.html.clone = template.Must(t.html.t.Clone())
- t.html.cloneClone = template.Must(t.html.clone.Clone())
- }
- if t.text.clone == nil {
- t.text.clone = texttemplate.Must(t.text.t.Clone())
- t.text.cloneClone = texttemplate.Must(t.text.clone.Clone())
- }
+func (t *templateHandler) findLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
+ layouts, _ := t.layoutHandler.For(d, f)
+ for _, name := range layouts {
+ templ, found := t.main.Lookup(name)
+ if found {
+ return templ, true, nil
+ }
- return nil
-}
+ overlay, found := t.needsBaseof[name]
-// RebuildClone rebuilds the cloned templates. Used for live-reloads.
-func (t *templateHandler) RebuildClone() {
- if t.html != nil && t.html.cloneClone != nil {
- t.html.clone = template.Must(t.html.cloneClone.Clone())
+ if !found {
+ continue
+ }
+
+ d.Baseof = true
+ baseLayouts, _ := t.layoutHandler.For(d, f)
+ var base templateInfo
+ found = false
+ for _, l := range baseLayouts {
+ base, found = t.baseof[l]
+ if found {
+ break
+ }
+ }
+
+ if !found {
+ return nil, false, errors.Errorf("no baseof layout found for %q:", name)
+ }
+
+ templ, err := t.applyBaseTemplate(overlay, base)
+ if err != nil {
+ return nil, false, err
+ }
+
+ ts := newTemplateState(templ, overlay)
+ ts.baseInfo = base
+
+ // Add the base identity to detect changes
+ ts.Add(identity.NewPathIdentity(files.ComponentFolderLayouts, base.name))
+
+ t.applyTemplateTransformers(t.main, ts)
+
+ return ts, true, nil
+
}
- if t.text != nil && t.text.cloneClone != nil {
- t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
- }
+
+ return nil, false, nil
}
-func (h *templateHandler) initTemplateExecuter() {
- exec, funcs := newTemplateExecuter(h.Deps)
- h.executor = exec
- h.funcs = funcs
- funcMap := make(map[string]interface{})
- for k, v := range funcs {
- funcMap[k] = v.Interface()
+func (t *templateHandler) findTemplate(name string) *templateState {
+ if templ, found := t.Lookup(name); found {
+ return templ.(*templateState)
}
-
- // Note that these funcs are not the ones getting called
- // on execution, but they are needed at parse time.
- h.text.textTemplate.t.Funcs(funcMap)
- h.text.standalone.t.Funcs(funcMap)
- h.html.t.Funcs(funcMap)
+ return nil
}
-func (t *templateHandler) getTemplateHandler(name string) templateLoader {
- if strings.HasPrefix(name, textTmplNamePrefix) {
- return t.text
+func (t *templateHandler) newTemplateInfo(name, tpl string) templateInfo {
+ var isText bool
+ name, isText = t.nameIsText(name)
+ return templateInfo{
+ name: name,
+ isText: isText,
+ template: tpl,
}
- return t.html
}
-func (t *templateHandler) addFileContext(name string, inerr error) error {
- if strings.HasPrefix(name, "_internal") {
+func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error {
+ if strings.HasPrefix(templ.Name(), "_internal") {
return inerr
}
- f, realFilename, err := t.fileAndFilename(name)
- if err != nil {
+ ts, ok := templ.(*templateState)
+ if !ok {
return inerr
-
}
- defer f.Close()
- master, hasMaster := t.html.nameBaseTemplateName[name]
+ //lint:ignore ST1008 the error is the main result
+ checkFilename := func(info templateInfo, inErr error) (error, bool) {
+ if info.filename == "" {
+ return inErr, false
+ }
- ferr := errors.Wrap(inerr, "execute of template failed")
+ lineMatcher := func(m herrors.LineMatcher) bool {
+ if m.Position.LineNumber != m.LineNumber {
+ return false
+ }
- // Since this can be a composite of multiple template files (single.html + baseof.html etc.)
- // we potentially need to look in both -- and cannot rely on line number alone.
- lineMatcher := func(m herrors.LineMatcher) bool {
- if m.Position.LineNumber != m.LineNumber {
+ identifiers := t.extractIdentifiers(m.Error.Error())
+
+ for _, id := range identifiers {
+ if strings.Contains(m.Line, id) {
+ return true
+ }
+ }
return false
}
- if !hasMaster {
- return true
+
+ f, err := t.layoutsFs.Open(info.filename)
+ if err != nil {
+ return inErr, false
}
+ defer f.Close()
- identifiers := t.extractIdentifiers(m.Error.Error())
-
- for _, id := range identifiers {
- if strings.Contains(m.Line, id) {
- return true
- }
+ fe, ok := herrors.WithFileContext(inErr, info.realFilename, f, lineMatcher)
+ if ok {
+ return fe, true
}
- return false
+ return inErr, false
}
- fe, ok := herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
- if ok || !hasMaster {
- return fe
- }
+ inerr = errors.Wrap(inerr, "execute of template failed")
- // Try the base template if relevant
- f, realFilename, err = t.fileAndFilename(master)
- if err != nil {
+ if err, ok := checkFilename(ts.info, inerr); ok {
return err
}
- defer f.Close()
- fe, ok = herrors.WithFileContext(ferr, realFilename, f, lineMatcher)
+ err, _ := checkFilename(ts.baseInfo, inerr)
- if !ok {
- // Return the most specific.
- return ferr
+ return err
- }
- return fe
-
}
-func (t *templateHandler) addInternalTemplate(name, tpl string) error {
- return t.AddTemplate("_internal/"+name, tpl)
-}
-
-func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ tpl.Template) {
-
+func (t *templateHandler) addShortcodeVariant(ts *templateState) {
+ name := ts.Name()
base := templateBaseName(templateShortcode, name)
shortcodename, variants := templateNameAndVariants(base)
@@ -505,7 +488,7 @@
t.shortcodes[shortcodename] = templs
}
- sv := shortcodeVariant{variants: variants, info: info, templ: templ}
+ sv := shortcodeVariant{variants: variants, ts: ts}
i := templs.indexOf(variants)
@@ -519,11 +502,7 @@
}
}
-func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
- t.checkState()
-
- t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path)
-
+func (t *templateHandler) addTemplateFile(name, path string) error {
getTemplate := func(filename string) (templateInfo, error) {
fs := t.Layouts.Fs
b, err := afero.ReadFile(fs, filename)
@@ -540,71 +519,97 @@
}
}
- return templateInfo{template: s, filename: filename, realFilename: realFilename, fs: fs}, nil
+ var isText bool
+ name, isText = t.nameIsText(name)
+
+ return templateInfo{
+ name: name,
+ isText: isText,
+ template: s,
+ filename: filename,
+ realFilename: realFilename,
+ fs: fs,
+ }, nil
}
- // get the suffix and switch on that
- ext := filepath.Ext(path)
- switch ext {
- case ".amber":
- helpers.Deprecated("Amber templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ tinfo, err := getTemplate(path)
+ if err != nil {
+ return err
+ }
+
+ if isBaseTemplate(name) {
+ // Store it for later.
+ t.baseof[name] = tinfo
return nil
- case ".ace":
- helpers.Deprecated("ACE templates are no longer supported.", "Use Go templates or a Hugo version <= 0.60.", true)
+ }
+
+ needsBaseof := !t.noBaseNeeded(name) && defineRe.MatchString(tinfo.template)
+ if needsBaseof {
+ t.needsBaseof[name] = tinfo
return nil
- default:
+ }
- if baseTemplatePath != "" {
- return t.handleMaster(name, path, baseTemplatePath, getTemplate)
- }
+ templ, err := t.addTemplateTo(tinfo, t.main)
+ if err != nil {
+ return tinfo.errWithFileContext("parse failed", err)
+ }
+ t.applyTemplateTransformers(t.main, templ)
- templ, err := getTemplate(path)
+ return nil
+}
+
+func (t *templateHandler) addTemplateTo(info templateInfo, to *templateNamespace) (*templateState, error) {
+ return to.parse(info)
+}
+
+func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) {
+ if overlay.isText {
+ templ, err := t.main.prototypeTextClone.New(overlay.name).Parse(base.template)
if err != nil {
- return err
+ return nil, base.errWithFileContext("parse failed", err)
}
-
- err = t.AddTemplate(name, templ.template)
+ templ, err = templ.Parse(overlay.template)
if err != nil {
- return templ.errWithFileContext("parse failed", err)
+ return nil, overlay.errWithFileContext("parse failed", err)
}
- return nil
+ return templ, nil
}
-}
-func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) {
- if templ != nil {
- if info, found := t.templateInfo[templ.Name()]; found {
- return tpl.WithInfo(templ, info), true
- }
+ templ, err := t.main.prototypeHTMLClone.New(overlay.name).Parse(base.template)
+ if err != nil {
+ return nil, base.errWithFileContext("parse failed", err)
}
- return templ, found
+ templ, err = htmltemplate.Must(templ.Clone()).Parse(overlay.template)
+ if err != nil {
+ return nil, overlay.errWithFileContext("parse failed", err)
+ }
+
+ // The extra lookup is a workaround, see
+ // * https://github.com/golang/go/issues/16101
+ // * https://github.com/gohugoio/hugo/issues/2549
+ templ = templ.Lookup(templ.Name())
+
+ return templ, err
}
-func (t *templateHandler) checkState() {
- if t.html.clone != nil || t.text.clone != nil {
- panic("template is cloned and cannot be modfified")
+func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *templateState) (*templateContext, error) {
+ c, err := applyTemplateTransformers(ts, ns.newTemplateLookup(ts))
+ if err != nil {
+ return nil, err
}
-}
-func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
- if !t.ready {
- panic("invalid state")
+ for k := range c.templateNotFound {
+ t.transformNotFound[k] = ts
+ t.identityNotFound[k] = append(t.identityNotFound[k], c.t)
}
- c := &templateHandler{
- ready: true,
- Deps: d,
- layoutsFs: d.BaseFs.Layouts.Fs,
+
+ for k := range c.identityNotFound {
+ t.identityNotFound[k] = append(t.identityNotFound[k], c.t)
}
- c.templateHandlerCommon = t.templateHandlerCommon.withNewHandler(c)
- d.Tmpl = c
- d.TextTmpl = c.wrapTextTemplate(c.text.standalone)
- c.executor, c.funcs = newTemplateExecuter(d)
-
- return c
-
+ return c, err
}
func (t *templateHandler) extractIdentifiers(line string) []string {
@@ -616,83 +621,44 @@
return identifiers
}
-func (t *templateHandler) fileAndFilename(name string) (afero.File, string, error) {
- fs := t.layoutsFs
- filename := filepath.FromSlash(name)
-
- fi, err := fs.Stat(filename)
- if err != nil {
- return nil, "", err
- }
- fim := fi.(hugofs.FileMetaInfo)
- meta := fim.Meta()
-
- f, err := meta.Open()
- if err != nil {
- return nil, "", errors.Wrapf(err, "failed to open template file %q:", filename)
- }
-
- return f, meta.Filename(), nil
-}
-
-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 *templateHandler) loadEmbedded() error {
for _, kv := range embedded.EmbeddedTemplates {
name, templ := kv[0], kv[1]
- if err := t.addInternalTemplate(name, templ); err != nil {
+ if err := t.AddTemplate(internalPathPrefix+name, templ); err != nil {
return err
}
if aliases, found := embeddedTemplatesAliases[name]; found {
+ // TODO(bep) avoid reparsing these aliases
for _, alias := range aliases {
- if err := t.addInternalTemplate(alias, templ); err != nil {
+ alias = internalPathPrefix + alias
+ if err := t.AddTemplate(alias, templ); err != nil {
return err
}
}
-
}
}
-
return nil
-
}
-func (t *templateHandler) loadTemplates(prefix string) error {
-
+func (t *templateHandler) loadTemplates() error {
walker := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil || fi.IsDir() {
return err
}
- if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
+ if isDotFile(path) || isBackupFile(path) {
return nil
}
- workingDir := t.PathSpec.WorkingDir
+ name := strings.TrimPrefix(filepath.ToSlash(path), "/")
+ filename := filepath.Base(path)
+ outputFormat, found := t.OutputFormatsConfig.FromFilename(filename)
- descriptor := output.TemplateLookupDescriptor{
- WorkingDir: workingDir,
- RelPath: path,
- Prefix: prefix,
- OutputFormats: t.OutputFormatsConfig,
- FileExists: func(filename string) (bool, error) {
- return helpers.Exists(filename, t.Layouts.Fs)
- },
- ContainsAny: func(filename string, subslices [][]byte) (bool, error) {
- return helpers.FileContainsAny(filename, subslices, t.Layouts.Fs)
- },
+ if found && outputFormat.IsPlainText {
+ name = textTmplNamePrefix + name
}
- tplID, err := output.CreateTemplateNames(descriptor)
- if err != nil {
- t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
- return nil
- }
-
- if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
+ if err := t.addTemplateFile(name, path); err != nil {
return err
}
@@ -710,86 +676,44 @@
}
-func (t *templateHandler) getOrCreateTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) {
- info, found := t.templateInfo[name]
- if found {
- return info.(identity.Manager), info.ParseInfo()
+func (t *templateHandler) nameIsText(name string) (string, bool) {
+ isText := strings.HasPrefix(name, textTmplNamePrefix)
+ if isText {
+ name = strings.TrimPrefix(name, textTmplNamePrefix)
}
- return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo
+ return name, isText
}
-func (t *templateHandler) createTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) {
- _, found := t.templateInfo[name]
- if found {
- panic("already created: " + name)
+func (t *templateHandler) noBaseNeeded(name string) bool {
+ if strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/") {
+ return true
}
-
- return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo
+ return strings.Contains(name, "_markup/")
}
func (t *templateHandler) postTransform() error {
- for k, v := range t.templateInfoTree {
- if v.id != nil {
- info := tpl.NewInfo(
- v.id,
- v.info,
- )
- t.templateInfo[k] = info
-
- if v.typ == templateShortcode {
- t.addShortcodeVariant(k, info, v.templ)
- }
+ for _, v := range t.main.templates {
+ if v.typ == templateShortcode {
+ t.addShortcodeVariant(v)
}
}
- for _, s := range []struct {
- lookup func(name string) *templateInfoTree
- transformNotFound map[string]bool
- identityNotFound map[string][]identity.Manager
- }{
- // html templates
- {func(name string) *templateInfoTree {
- templ := t.html.lookup(name)
- if templ == nil {
- return nil
+ for name, source := range t.transformNotFound {
+ lookup := t.main.newTemplateLookup(source)
+ templ := lookup(name)
+ if templ != nil {
+ _, err := applyTemplateTransformers(templ, lookup)
+ if err != nil {
+ return err
}
- id, info := t.getOrCreateTemplateInfo(name)
- return &templateInfoTree{
- id: id,
- info: info,
- tree: templ.Tree,
- }
- }, t.html.transformNotFound, t.html.identityNotFound},
- // text templates
- {func(name string) *templateInfoTree {
- templT := t.text.lookup(name)
- if templT == nil {
- return nil
- }
- id, info := t.getOrCreateTemplateInfo(name)
- return &templateInfoTree{
- id: id,
- info: info,
- tree: templT.Tree,
- }
- }, t.text.transformNotFound, t.text.identityNotFound},
- } {
- for name := range s.transformNotFound {
- templ := s.lookup(name)
- if templ != nil {
- _, err := applyTemplateTransformers(templateUndefined, templ, s.lookup)
- if err != nil {
- return err
- }
- }
}
+ }
- for k, v := range s.identityNotFound {
- tmpl := s.lookup(k)
- if tmpl != nil {
- for _, im := range v {
- im.Add(tmpl.id)
- }
+ for k, v := range t.identityNotFound {
+ ts := t.findTemplate(k)
+ if ts != nil {
+ for _, im := range v {
+ im.Add(ts)
}
}
}
@@ -797,215 +721,169 @@
return nil
}
-func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder {
- return struct {
- tpl.TemplateParser
- tpl.TemplateLookup
- tpl.TemplateLookupVariant
- }{
- tt,
- tt,
- new(nopLookupVariant),
- }
+type templateNamespace struct {
+ prototypeText *texttemplate.Template
+ prototypeHTML *htmltemplate.Template
+ prototypeTextClone *texttemplate.Template
+ prototypeHTMLClone *htmltemplate.Template
+
+ *templateStateMap
}
-type templateHandlerCommon struct {
- // shortcodes maps shortcode name to template variants
- // (language, output format etc.) of that shortcode.
- shortcodes map[string]*shortcodeTemplates
+func (t templateNamespace) Clone(lock bool) *templateNamespace {
+ if t.mu != nil {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ }
- // templateInfo maps template name to some additional information about that template.
- // Note that for shortcodes that same information is embedded in the
- // shortcodeTemplates type.
- templateInfo map[string]tpl.Info
+ var mu *sync.RWMutex
+ if lock {
+ mu = &sync.RWMutex{}
+ }
- // Used to track templates during the AST transformations.
- templateInfoTree map[string]*templateInfoTree
+ t.templateStateMap = &templateStateMap{
+ templates: make(map[string]*templateState),
+ mu: mu,
+ }
- // text holds all the pure text templates.
- text *textTemplates
- html *htmlTemplates
-}
+ t.prototypeText = texttemplate.Must(t.prototypeText.Clone())
+ t.prototypeHTML = htmltemplate.Must(t.prototypeHTML.Clone())
-func (t templateHandlerCommon) withNewHandler(h *templateHandler) *templateHandlerCommon {
- t.text = t.text.withNewHandler(h)
- t.html = t.html.withNewHandler(h)
return &t
}
-type templateLoader interface {
- addLateTemplate(name, tpl string) error
- addTemplate(name, tpl string) (*templateContext, error)
- handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error
-}
+func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
+ if t.mu != nil {
+ t.mu.RLock()
+ defer t.mu.RLock()
+ }
-// Shared by both HTML and text templates.
-type templatesCommon struct {
- handler *templateHandler
+ templ, found := t.templates[name]
+ if !found {
+ return nil, false
+ }
- // Used to get proper filenames in errors
- nameBaseTemplateName map[string]string
+ if t.mu != nil {
+ return &templateWrapperWithLock{RWMutex: t.mu, Template: templ}, true
+ }
- // Holds names of the template definitions not found during the first AST transformation
- // pass.
- transformNotFound map[string]bool
-
- // Holds identities of templates not found during first pass.
- identityNotFound map[string][]identity.Manager
+ return templ, found
}
-func (t templatesCommon) withNewHandler(h *templateHandler) *templatesCommon {
- t.handler = h
- return &t
-}
+func (t *templateNamespace) createPrototypes() error {
+ t.prototypeTextClone = texttemplate.Must(t.prototypeText.Clone())
+ t.prototypeHTMLClone = htmltemplate.Must(t.prototypeHTML.Clone())
-type textTemplate struct {
- mu sync.RWMutex
- t *texttemplate.Template
- templates *textTemplates
+ return nil
}
-func (t *textTemplate) Lookup(name string) (tpl.Template, bool) {
- t.mu.RLock()
- defer t.mu.RUnlock()
+func (t *templateNamespace) newTemplateLookup(in *templateState) func(name string) *templateState {
+ return func(name string) *templateState {
+ if templ, found := t.templates[name]; found {
+ if templ.isText() != in.isText() {
+ return nil
+ }
+ return templ
+ }
+ if templ, found := findTemplateIn(name, in); found {
+ return newTemplateState(templ, templateInfo{name: templ.Name()})
+ }
+ return nil
- tpl := t.t.Lookup(name)
- return tpl, tpl != nil
-}
-
-func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) {
- return t.parseIn(t.t, name, tpl)
-}
-
-func (t *textTemplate) parseIn(tt *texttemplate.Template, name, tpl string) (*texttemplate.Template, error) {
- t.mu.Lock()
- defer t.mu.Unlock()
-
- templ, err := tt.New(name).Parse(tpl)
- if err != nil {
- return nil, err
}
-
- if _, err := t.templates.handler.applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil {
- return nil, err
- }
- return templ, nil
}
-type textTemplates struct {
- *templatesCommon
- *textTemplate
- standalone *textTemplate
- clone *texttemplate.Template
- cloneClone *texttemplate.Template
-
- overlays map[string]*texttemplate.Template
-}
-
-func (t *textTemplates) Lookup(name string) (tpl.Template, bool) {
- templ := t.lookup(name)
- if templ == nil {
- return nil, false
+func (t *templateNamespace) parse(info templateInfo) (*templateState, error) {
+ if t.mu != nil {
+ t.mu.Lock()
+ defer t.mu.Unlock()
}
- return templ, true
-}
-func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
- return t.handler.LookupVariant(name, variants)
-}
+ if info.isText {
+ prototype := t.prototypeText
-func (t *textTemplates) addLateTemplate(name, tpl string) error {
- _, err := t.addTemplateIn(t.clone, name, tpl)
- return err
-}
+ templ, err := prototype.New(info.name).Parse(info.template)
+ if err != nil {
+ return nil, err
+ }
-func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) {
- return t.addTemplateIn(t.t, name, tpl)
-}
+ ts := newTemplateState(templ, info)
-func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tplstr string) (*templateContext, error) {
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- templ, err := t.parseIn(tt, name, tplstr)
- if err != nil {
- return nil, err
+ t.templates[info.name] = ts
+
+ return ts, nil
}
- typ := resolveTemplateType(name)
+ prototype := t.prototypeHTML
- c, err := t.handler.applyTemplateTransformersToTextTemplate(typ, templ)
+ templ, err := prototype.New(info.name).Parse(info.template)
if err != nil {
return nil, err
}
- for k := range c.templateNotFound {
- t.transformNotFound[k] = true
- }
+ ts := newTemplateState(templ, info)
- return c, nil
+ t.templates[info.name] = ts
+
+ return ts, nil
}
-func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error {
+type templateState struct {
+ tpl.Template
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- masterTpl := t.lookup(masterFilename)
+ typ templateType
+ parseInfo tpl.ParseInfo
+ identity.Manager
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- return err
- }
+ info templateInfo
+ baseInfo templateInfo // Set when a base template is used.
+}
- masterTpl, err = t.t.New(masterFilename).Parse(templ.template)
- if err != nil {
- return errors.Wrapf(err, "failed to parse %q:", templ.filename)
- }
- t.nameBaseTemplateName[masterFilename] = templ.filename
- }
+func (t *templateState) ParseInfo() tpl.ParseInfo {
+ return t.parseInfo
+}
- templ, err := onMissing(overlayFilename)
- if err != nil {
- return err
- }
+func (t *templateState) isText() bool {
+ _, isText := t.Template.(*texttemplate.Template)
+ return isText
+}
- overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ.template)
- if err != nil {
- return errors.Wrapf(err, "failed to parse %q:", templ.filename)
- }
+type templateStateMap struct {
+ mu *sync.RWMutex // May be nil
+ templates map[string]*templateState
+}
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- if _, err := t.handler.applyTemplateTransformersToTextTemplate(templateUndefined, overlayTpl); err != nil {
- return err
- }
- t.overlays[name] = overlayTpl
- t.nameBaseTemplateName[name] = templ.filename
+type templateWrapperWithLock struct {
+ *sync.RWMutex
+ tpl.Template
+}
- return err
-
+type textTemplateWrapperWithLock struct {
+ *sync.RWMutex
+ *texttemplate.Template
}
-func (t *textTemplates) lookup(name string) *texttemplate.Template {
-
- // Need to check in the overlay registry first as it will also be found below.
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
- }
+func (t *textTemplateWrapperWithLock) Lookup(name string) (tpl.Template, bool) {
+ t.RLock()
+ templ := t.Template.Lookup(name)
+ t.RUnlock()
+ if templ == nil {
+ return nil, false
}
+ return &textTemplateWrapperWithLock{
+ RWMutex: t.RWMutex,
+ Template: templ,
+ }, true
+}
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
-
- if t.clone != nil {
- return t.clone.Lookup(name)
- }
-
- return nil
+func (t *textTemplateWrapperWithLock) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
+ panic("not supported")
}
-func (t textTemplates) withNewHandler(h *templateHandler) *textTemplates {
- t.templatesCommon = t.templatesCommon.withNewHandler(h)
- return &t
+func (t *textTemplateWrapperWithLock) Parse(name, tpl string) (tpl.Template, error) {
+ t.Lock()
+ defer t.Unlock()
+ return t.Template.New(name).Parse(tpl)
}
func isBackupFile(path string) bool {
@@ -1046,4 +924,11 @@
panic("not implemented")
}
+}
+
+func unwrap(templ tpl.Template) tpl.Template {
+ if ts, ok := templ.(*templateState); ok {
+ return ts.Template
+ }
+ return templ
}
--- a/tpl/tplimpl/templateProvider.go
+++ b/tpl/tplimpl/templateProvider.go
@@ -25,32 +25,17 @@
// Update updates the Hugo Template System in the provided Deps
// with all the additional features, templates & functions.
-func (*TemplateProvider) Update(deps *deps.Deps) error {
- newTmpl := newTemplateAdapter(deps)
- deps.Tmpl = newTmpl
- deps.TextTmpl = newTmpl.wrapTextTemplate(newTmpl.text.standalone)
- // These needs to be there at parse time.
- newTmpl.initTemplateExecuter()
-
- if err := newTmpl.loadEmbedded(); err != nil {
+func (*TemplateProvider) Update(d *deps.Deps) error {
+ tmpl, err := newTemplateExec(d)
+ if err != nil {
return err
}
-
- if deps.WithTemplate != nil {
- err := deps.WithTemplate(newTmpl)
- if err != nil {
- return err
- }
-
- }
-
- return newTmpl.markReady()
-
+ return tmpl.postTransform()
}
// Clone clones.
func (*TemplateProvider) Clone(d *deps.Deps) error {
- t := d.Tmpl.(*templateHandler)
- t.clone(d)
+ t := d.Tmpl().(*templateExec)
+ d.SetTmpl(t.Clone(d))
return nil
}
--- a/tpl/tplimpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -17,10 +17,9 @@
"regexp"
"strings"
- "github.com/gohugoio/hugo/identity"
-
- template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
+ htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
"github.com/gohugoio/hugo/common/maps"
@@ -41,25 +40,21 @@
visited map[string]bool
templateNotFound map[string]bool
identityNotFound map[string]bool
- lookupFn func(name string) *templateInfoTree
+ lookupFn func(name string) *templateState
// The last error encountered.
err error
- typ templateType
-
// Set when we're done checking for config header.
configChecked bool
- // Contains some info about the template
- parseInfo *tpl.ParseInfo
- id identity.Manager
+ t *templateState
// Store away the return node in partials.
returnNode *parse.CommandNode
}
-func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
+func (c templateContext) getIfNotVisited(name string) *templateState {
if c.visited[name] {
return nil
}
@@ -76,13 +71,11 @@
}
func newTemplateContext(
- id identity.Manager,
- info *tpl.ParseInfo,
- lookupFn func(name string) *templateInfoTree) *templateContext {
+ t *templateState,
+ lookupFn func(name string) *templateState) *templateContext {
return &templateContext{
- id: id,
- parseInfo: info,
+ t: t,
lookupFn: lookupFn,
visited: make(map[string]bool),
templateNotFound: make(map[string]bool),
@@ -90,79 +83,36 @@
}
}
-func createGetTemplateInfoTreeFor(getID func(name string) *templateInfoTree) func(nn string) *templateInfoTree {
- return func(nn string) *templateInfoTree {
- return getID(nn)
- }
-}
-
-func (t *templateHandler) applyTemplateTransformersToHMLTTemplate(typ templateType, templ *template.Template) (*templateContext, error) {
- id, info := t.createTemplateInfo(templ.Name())
- ti := &templateInfoTree{
- tree: templ.Tree,
- templ: templ,
- typ: typ,
- id: id,
- info: info,
- }
- t.templateInfoTree[templ.Name()] = ti
- getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
- return t.templateInfoTree[name]
- })
-
- return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
-}
-
-func (t *templateHandler) applyTemplateTransformersToTextTemplate(typ templateType, templ *texttemplate.Template) (*templateContext, error) {
- id, info := t.createTemplateInfo(templ.Name())
- ti := &templateInfoTree{
- tree: templ.Tree,
- templ: templ,
- typ: typ,
- id: id,
- info: info,
- }
-
- t.templateInfoTree[templ.Name()] = ti
- getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
- return t.templateInfoTree[name]
- })
-
- return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
-
-}
-
-type templateInfoTree struct {
- info tpl.ParseInfo
- typ templateType
- id identity.Manager
- templ tpl.Template
- tree *parse.Tree
-}
-
func applyTemplateTransformers(
- typ templateType,
- templ *templateInfoTree,
- lookupFn func(name string) *templateInfoTree) (*templateContext, error) {
+ t *templateState,
+ lookupFn func(name string) *templateState) (*templateContext, error) {
- if templ == nil {
+ if t == nil {
return nil, errors.New("expected template, but none provided")
}
- c := newTemplateContext(templ.id, &templ.info, lookupFn)
- c.typ = typ
+ c := newTemplateContext(t, lookupFn)
+ tree := getParseTree(t.Template)
- _, err := c.applyTransformations(templ.tree.Root)
+ _, err := c.applyTransformations(tree.Root)
if err == nil && c.returnNode != nil {
// This is a partial with a return statement.
- c.parseInfo.HasReturn = true
- templ.tree.Root = c.wrapInPartialReturnWrapper(templ.tree.Root)
+ c.t.parseInfo.HasReturn = true
+ tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
}
return c, err
}
+func getParseTree(templ tpl.Template) *parse.Tree {
+ templ = unwrap(templ)
+ if text, ok := templ.(*texttemplate.Template); ok {
+ return text.Tree
+ }
+ return templ.(*htmltemplate.Template).Tree
+}
+
const (
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
)
@@ -215,7 +165,7 @@
case *parse.TemplateNode:
subTempl := c.getIfNotVisited(x.Name)
if subTempl != nil {
- c.applyTransformationsToNodes(subTempl.tree.Root)
+ c.applyTransformationsToNodes(getParseTree(subTempl.Template).Root)
}
case *parse.PipeNode:
c.collectConfig(x)
@@ -263,7 +213,7 @@
// on the form:
// {{ $_hugo_config:= `{ "version": 1 }` }}
func (c *templateContext) collectConfig(n *parse.PipeNode) {
- if c.typ != templateShortcode {
+ if c.t.typ != templateShortcode {
return
}
if c.configChecked {
@@ -295,7 +245,7 @@
c.err = errors.Wrap(err, errMsg)
return
}
- if err := mapstructure.WeakDecode(m, &c.parseInfo.Config); err != nil {
+ if err := mapstructure.WeakDecode(m, &c.t.parseInfo.Config); err != nil {
c.err = errors.Wrap(err, errMsg)
}
}
@@ -304,10 +254,10 @@
// collectInner determines if the given CommandNode represents a
// shortcode call to its .Inner.
func (c *templateContext) collectInner(n *parse.CommandNode) {
- if c.typ != templateShortcode {
+ if c.t.typ != templateShortcode {
return
}
- if c.parseInfo.IsInner || len(n.Args) == 0 {
+ if c.t.parseInfo.IsInner || len(n.Args) == 0 {
return
}
@@ -321,7 +271,7 @@
}
if c.hasIdent(idents, "Inner") {
- c.parseInfo.IsInner = true
+ c.t.parseInfo.IsInner = true
break
}
}
@@ -351,8 +301,9 @@
}
partialName = "partials/" + partialName
info := c.lookupFn(partialName)
+
if info != nil {
- c.id.Add(info.id)
+ c.t.Add(info)
} else {
// Delay for later
c.identityNotFound[partialName] = true
@@ -361,7 +312,7 @@
}
func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
- if c.typ != templatePartial || c.returnNode != nil {
+ if c.t.typ != templatePartial || c.returnNode != nil {
return true
}
@@ -379,5 +330,20 @@
c.returnNode.Args = c.returnNode.Args[1:]
return false
+
+}
+
+func findTemplateIn(name string, in tpl.Template) (tpl.Template, bool) {
+ in = unwrap(in)
+ if text, ok := in.(*texttemplate.Template); ok {
+ if templ := text.Lookup(name); templ != nil {
+ return templ, true
+ }
+ return nil, false
+ }
+ if templ := in.(*htmltemplate.Template).Lookup(name); templ != nil {
+ return templ, true
+ }
+ return nil, false
}
--- a/tpl/tplimpl/template_ast_transformers_test.go
+++ b/tpl/tplimpl/template_ast_transformers_test.go
@@ -13,15 +13,11 @@
package tplimpl
import (
- "github.com/gohugoio/hugo/hugofs/files"
-
"testing"
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
- "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl"
)
@@ -33,7 +29,7 @@
{{ define "menu-nodes" }}
{{ template "menu-node" }}
{{ end }}
-{{ define "menu-nßode" }}
+{{ define "menu-node" }}
{{ template "menu-node" }}
{{ end }}
{{ template "menu-nodes" }}
@@ -41,34 +37,44 @@
templ, err := template.New("foo").Parse(recursive)
c.Assert(err, qt.IsNil)
- parseInfo := tpl.DefaultParseInfo
+ ts := newTestTemplate(templ)
ctx := newTemplateContext(
- newTemplateInfo("test").(identity.Manager),
- &parseInfo,
- createGetTemplateInfoTree(templ.Tree),
+ ts,
+ newTestTemplateLookup(ts),
)
ctx.applyTransformations(templ.Tree.Root)
}
-func createGetTemplateInfoTree(tree *parse.Tree) func(name string) *templateInfoTree {
- return func(name string) *templateInfoTree {
- return &templateInfoTree{
- tree: tree,
- }
- }
+func newTestTemplate(templ tpl.Template) *templateState {
+ return newTemplateState(
+ templ,
+ templateInfo{
+ name: templ.Name(),
+ },
+ )
}
-type I interface {
- Method0()
-}
+func newTestTemplateLookup(in *templateState) func(name string) *templateState {
+ m := make(map[string]*templateState)
+ return func(name string) *templateState {
+ if in.Name() == name {
+ return in
+ }
-type T struct {
- NonEmptyInterfaceTypedNil I
-}
+ if ts, found := m[name]; found {
+ return ts
+ }
-func (T) Method0() {
+ if templ, found := findTemplateIn(name, in); found {
+ ts := newTestTemplate(templ)
+ m[name] = ts
+ return ts
+ }
+
+ return nil
+ }
}
func TestCollectInfo(t *testing.T) {
@@ -98,13 +104,14 @@
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
c.Assert(err, qt.IsNil)
- parseInfo := tpl.DefaultParseInfo
-
+ ts := newTestTemplate(templ)
+ ts.typ = templateShortcode
ctx := newTemplateContext(
- newTemplateInfo("test").(identity.Manager), &parseInfo, createGetTemplateInfoTree(templ.Tree))
- ctx.typ = templateShortcode
+ ts,
+ newTestTemplateLookup(ts),
+ )
ctx.applyTransformations(templ.Tree.Root)
- c.Assert(ctx.parseInfo, qt.DeepEquals, &test.expected)
+ c.Assert(ctx.t.parseInfo, qt.DeepEquals, test.expected)
})
}
@@ -141,11 +148,13 @@
templ, err := template.New("foo").Funcs(funcs).Parse(test.tplString)
c.Assert(err, qt.IsNil)
+ ts := newTestTemplate(templ)
+ ctx := newTemplateContext(
+ ts,
+ newTestTemplateLookup(ts),
+ )
- _, err = applyTemplateTransformers(
- templatePartial,
- &templateInfoTree{tree: templ.Tree, info: tpl.DefaultParseInfo},
- createGetTemplateInfoTree(templ.Tree))
+ _, err = ctx.applyTransformations(templ.Tree.Root)
// Just check that it doesn't fail in this test. We have functional tests
// in hugoblib.
@@ -154,11 +163,4 @@
})
}
-}
-
-func newTemplateInfo(name string) tpl.Info {
- return tpl.NewInfo(
- identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)),
- tpl.DefaultParseInfo,
- )
}
--- a/tpl/tplimpl/template_errors.go
+++ b/tpl/tplimpl/template_errors.go
@@ -20,7 +20,9 @@
)
type templateInfo struct {
+ name string
template string
+ isText bool // HTML or plain text template.
// Used to create some error context in error situations
fs afero.Fs
@@ -30,6 +32,10 @@
// The real filename (if possible). Used for logging.
realFilename string
+}
+
+func (t templateInfo) resolveType() templateType {
+ return resolveTemplateType(t.name)
}
func (info templateInfo) errWithFileContext(what string, err error) error {
--- a/tpl/tplimpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -127,8 +127,8 @@
c.Assert(d.LoadResources(), qt.IsNil)
var b bytes.Buffer
- templ, _ := d.Tmpl.Lookup("test")
- c.Assert(d.Tmpl.Execute(templ, &b, &data), qt.IsNil)
+ templ, _ := d.Tmpl().Lookup("test")
+ c.Assert(d.Tmpl().Execute(templ, &b, &data), qt.IsNil)
if b.String() != expected {
t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected)
}
--- a/tpl/tplimpl/template_info_test.go
+++ b/tpl/tplimpl/template_info_test.go
@@ -24,14 +24,15 @@
func TestTemplateInfoShortcode(t *testing.T) {
c := qt.New(t)
d := newD(c)
- h := d.Tmpl.(*templateHandler)
+ h := d.Tmpl().(*templateExec)
c.Assert(h.AddTemplate("shortcodes/mytemplate.html", `
{{ .Inner }}
`), qt.IsNil)
- c.Assert(h.markReady(), qt.IsNil)
- tt, found, _ := d.Tmpl.LookupVariant("mytemplate", tpl.TemplateVariants{})
+ c.Assert(h.postTransform(), qt.IsNil)
+
+ tt, found, _ := d.Tmpl().LookupVariant("mytemplate", tpl.TemplateVariants{})
c.Assert(found, qt.Equals, true)
tti, ok := tt.(tpl.Info)