diff --git a/commands/server.go b/commands/server.go index 64409ee1854..72884749277 100644 --- a/commands/server.go +++ b/commands/server.go @@ -416,7 +416,7 @@ func (c *commandeer) serve(s *serverCmd) error { 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 @@ func (c *commandeer) serve(s *serverCmd) error { 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 }, } diff --git a/common/types/types.go b/common/types/types.go index f03031439e6..04a27766e94 100644 --- a/common/types/types.go +++ b/common/types/types.go @@ -21,6 +21,12 @@ import ( "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 diff --git a/create/content_template_handler.go b/create/content_template_handler.go index b70cf02ebf6..e4cddedf53b 100644 --- a/create/content_template_handler.go +++ b/create/content_template_handler.go @@ -129,9 +129,9 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety 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) } diff --git a/deps/deps.go b/deps/deps.go index ecbba2e5619..ed2835a1283 100644 --- a/deps/deps.go +++ b/deps/deps.go @@ -5,6 +5,7 @@ import ( "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 @@ type Deps struct { 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:"-"` @@ -77,7 +78,7 @@ type Deps struct { OutputFormatsConfig output.Formats templateProvider ResourceProvider - WithTemplate func(templ tpl.TemplateManager) error `json:"-"` + WithTemplate func(templ tpl.TemplateManager) error `json:"-"` // TODO1 // Used in tests OverloadedTemplateFuncs map[string]interface{} @@ -92,6 +93,9 @@ type Deps struct { // BuildStartListeners will be notified before a build starts. BuildStartListeners *Listeners + // Atomic flags set during a build. + BuildFlags BuildFlags + *globalErrHandler } @@ -153,9 +157,20 @@ type ResourceProvider interface { 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. @@ -315,6 +330,7 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er } d.BuildStartListeners = &Listeners{} + d.BuildFlags = BuildFlags{} return &d, nil @@ -358,3 +374,14 @@ type DepsCfg struct { // 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), + } +} diff --git a/deps/deps_test.go b/deps/deps_test.go new file mode 100644 index 00000000000..e2dca0eccb4 --- /dev/null +++ b/deps/deps_test.go @@ -0,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) +} diff --git a/go.mod b/go.mod index d99cb010b3b..6f227f9cc14 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( 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 diff --git a/go.sum b/go.sum index beb3423c46c..855e49c9064 100644 --- a/go.sum +++ b/go.sum @@ -419,6 +419,7 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 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= diff --git a/hugolib/alias.go b/hugolib/alias.go index c80e7d0d23e..9eba8b3353c 100644 --- a/hugolib/alias.go +++ b/hugolib/alias.go @@ -81,7 +81,7 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format } func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) { - handler := newAliasHandler(s.Tmpl, s.Log, allowRoot) + handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot) s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink) diff --git a/hugolib/cascade_test.go b/hugolib/cascade_test.go index 6b176ad64d7..be243e39b2f 100644 --- a/hugolib/cascade_test.go +++ b/hugolib/cascade_test.go @@ -68,12 +68,12 @@ func TestCascade(t *testing.T) { 42|taxonomy|tags/blue|blue|home.png|tags|HTML-| 42|section|sect3|Cascade Home|home.png|sect3|HTML-| 42|taxonomyTerm|tags|Cascade Home|home.png|tags|HTML-| - 42|page|p2.md|Cascade Home|home.png|page|HTML-| + 42|page|p2.md|Cascade Home|home.png||HTML-| 42|page|sect2/p2.md|Cascade Home|home.png|sect2|HTML-| 42|page|sect3/p1.md|Cascade Home|home.png|sect3|HTML-| 42|taxonomy|tags/green|green|home.png|tags|HTML-| - 42|home|_index.md|Home|home.png|page|HTML-| - 42|page|p1.md|p1|home.png|page|HTML-| + 42|home|_index.md|Home|home.png||HTML-| + 42|page|p1.md|p1|home.png||HTML-| 42|section|sect1/_index.md|Sect1|sect1.png|stype|HTML-| 42|section|sect1/s1_2/_index.md|Sect1_2|sect1.png|stype|HTML-| 42|page|sect1/s1_2/p1.md|Sect1_2_p1|sect1.png|stype|HTML-| diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go index ee7a02074b9..8aba1dd8cc8 100644 --- a/hugolib/content_render_hooks_test.go +++ b/hugolib/content_render_hooks_test.go @@ -13,7 +13,9 @@ package hugolib -import "testing" +import ( + "testing" +) func TestRenderHooks(t *testing.T) { config := ` diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go index cf9ff3c38b2..cd6636aca0f 100644 --- a/hugolib/filesystems/basefs.go +++ b/hugolib/filesystems/basefs.go @@ -309,7 +309,7 @@ func (d *SourceFilesystem) Path(filename string) string { if strings.HasPrefix(filename, meta.Filename()) { p := strings.TrimPrefix(strings.TrimPrefix(filename, meta.Filename()), filePathSeparator) - p = path.Join(meta.PathFile(), p) + // TODO1 p = path.Join(meta.PathFile(), p) return p } } diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 4e1623b2ea9..ee1612c0686 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -121,6 +121,9 @@ type hugoSitesInit struct { // Loads the data from all of the /data folders. data *lazy.Init + // Performs late initialization (before render) of the templates. + layouts *lazy.Init + // Loads the Git info for all the pages if enabled. gitInfo *lazy.Init @@ -130,6 +133,7 @@ type hugoSitesInit struct { func (h *hugoSitesInit) Reset() { h.data.Reset() + h.layouts.Reset() h.gitInfo.Reset() h.translations.Reset() } @@ -271,6 +275,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { Sites: sites, init: &hugoSitesInit{ data: lazy.New(), + layouts: lazy.New(), gitInfo: lazy.New(), translations: lazy.New(), }, @@ -289,6 +294,15 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { return nil, nil }) + h.init.layouts.Add(func() (interface{}, error) { + for _, s := range h.Sites { + if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil { + return nil, err + } + } + return nil, nil + }) + h.init.translations.Add(func() (interface{}, error) { if len(h.Sites) > 1 { allTranslations := pagesToTranslationsMap(h.Sites) @@ -429,9 +443,10 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateManager) error) func(templ tpl.TemplateManager) error { return func(templ tpl.TemplateManager) error { - if err := templ.LoadTemplates(""); err != nil { - return err - } + // TODO1 + /* if err := templ.LoadTemplates(""); err != nil { + return err + }*/ for _, wt := range withTemplates { if wt == nil { @@ -619,10 +634,10 @@ func (h *HugoSites) renderCrossSitesArtifacts() error { s := h.Sites[0] - smLayouts := []string{"sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml"} + templ := s.lookupLayouts("sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml") return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemapindex", - s.siteCfg.sitemap.Filename, h.toSiteInfos(), smLayouts...) + s.siteCfg.sitemap.Filename, h.toSiteInfos(), templ) } func (h *HugoSites) removePageByFilename(filename string) { diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index d749ff581d5..901941bda61 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -291,6 +291,10 @@ func (h *HugoSites) assemble(bcfg *BuildCfg) error { } func (h *HugoSites) render(config *BuildCfg) error { + if _, err := h.init.layouts.Do(); err != nil { + return err + } + siteRenderContext := &siteRenderContext{cfg: config, multihost: h.multihost} if !config.PartialReRender { @@ -312,11 +316,6 @@ func (h *HugoSites) render(config *BuildCfg) error { case <-h.Done(): return nil default: - // For the non-renderable pages, we use the content iself as - // template and we may have to re-parse and execute it for - // each output format. - h.TemplateHandler().RebuildClone() - for _, s2 := range h.Sites { // We render site by site, but since the content is lazily rendered // and a site can "borrow" content from other sites, every site diff --git a/hugolib/hugo_sites_build_errors_test.go b/hugolib/hugo_sites_build_errors_test.go index 21b745ccd84..c63b05ffef4 100644 --- a/hugolib/hugo_sites_build_errors_test.go +++ b/hugolib/hugo_sites_build_errors_test.go @@ -37,6 +37,8 @@ func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) { } +// TODO1 add a change test with change in base template only + func TestSiteBuildErrors(t *testing.T) { const ( @@ -65,7 +67,8 @@ func TestSiteBuildErrors(t *testing.T) { fileFixer: func(content string) string { return strings.Replace(content, ".Title }}", ".Title }", 1) }, - assertCreateError: func(a testSiteBuildErrorAsserter, err error) { + // Base templates gets parsed at build time. + assertBuildError: func(a testSiteBuildErrorAsserter, err error) { a.assertLineNumber(4, err) }, }, @@ -90,7 +93,7 @@ func TestSiteBuildErrors(t *testing.T) { a.c.Assert(fe.Position().LineNumber, qt.Equals, 5) a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1) a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template") - a.assertErrorMessage("\"layouts/_default/single.html:5:1\": parse failed: template: _default/single.html:5: unexpected \"}\" in operand", fe.Error()) + a.assertErrorMessage("\"layouts/foo/single.html:5:1\": parse failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error()) }, }, @@ -256,6 +259,13 @@ SINGLE L3: SINGLE L4: SINGLE L5: {{ .Title }} {{ .Content }} {{ end }} +`)) + + b.WithTemplatesAdded("layouts/foo/single.html", f(single, ` +SINGLE L2: +SINGLE L3: +SINGLE L4: +SINGLE L5: {{ .Title }} {{ .Content }} `)) b.WithContent("myyaml.md", f(yamlcontent, `--- diff --git a/hugolib/page.go b/hugolib/page.go index 8aad8dcc7e0..6951da6c2b2 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -337,37 +337,33 @@ func (p *pageState) createRenderHooks(f output.Format) (*hooks.Render, error) { layoutDescriptor.Layout = "" layoutDescriptor.Kind = "render-link" - linkLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f) + linkTempl, linkTemplFound, err := p.s.Tmpl().LookupLayout(layoutDescriptor, f) if err != nil { return nil, err } layoutDescriptor.Kind = "render-image" - imageLayouts, err := p.s.layoutHandler.For(layoutDescriptor, f) + imgTempl, imgTemplFound, err := p.s.Tmpl().LookupLayout(layoutDescriptor, f) if err != nil { return nil, err } - if linkLayouts == nil && imageLayouts == nil { - return nil, nil - } - var linkRenderer hooks.LinkRenderer var imageRenderer hooks.LinkRenderer - if templ, found := p.s.lookupTemplate(linkLayouts...); found { + if linkTemplFound { linkRenderer = contentLinkRenderer{ - templateHandler: p.s.Tmpl, - Provider: templ.(tpl.Info), - templ: templ, + templateHandler: p.s.Tmpl(), + Provider: linkTempl.(tpl.Info), + templ: linkTempl, } } - if templ, found := p.s.lookupTemplate(imageLayouts...); found { + if imgTemplFound { imageRenderer = contentLinkRenderer{ - templateHandler: p.s.Tmpl, - Provider: templ.(tpl.Info), - templ: templ, + templateHandler: p.s.Tmpl(), + Provider: imgTempl.(tpl.Info), + templ: imgTempl, } } @@ -406,6 +402,29 @@ func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor { } +func (p *pageState) resolveTemplate(layouts ...string) (tpl.Template, bool, error) { + f := p.outputFormat() + + if len(layouts) == 0 { + selfLayout := p.selfLayoutForOutput(f) + if selfLayout != "" { + templ, found := p.s.Tmpl().Lookup(selfLayout) + return templ, found, nil + } + } + + d := p.getLayoutDescriptor() + + if len(layouts) > 0 { + // TODO1 check .Render without suffix. + d.Layout = layouts[0] + d.LayoutOverride = true + } + + return p.s.Tmpl().LookupLayout(d, f) +} + +// TODO1 remove func (p *pageState) getLayouts(layouts ...string) ([]string, error) { f := p.outputFormat() @@ -607,17 +626,17 @@ func (p *pageState) Render(layout ...string) (template.HTML, error) { } for _, layout := range l { - templ, found := p.s.Tmpl.Lookup(layout) - if !found { + templ, found := p.s.Tmpl().Lookup(layout) + if !found && !strings.HasSuffix(layout, ".html") { // 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") + templ, _ = p.s.Tmpl().Lookup(layout + ".html") } if templ != nil { p.addDependency(templ.(tpl.Info)) - res, err := executeToString(p.s.Tmpl, templ, p) + res, err := executeToString(p.s.Tmpl(), templ, p) if err != nil { return "", p.wrapError(errors.Wrapf(err, "failed to execute template %q v", layout)) } @@ -689,6 +708,7 @@ Loop: // This is HTML without front matter. It can still have shortcodes. p.selfLayout = "__" + p.File().Filename() p.renderable = false + p.s.BuildFlags.HasLateTemplate.CAS(false, true) rn.AddBytes(it) case it.IsFrontMatter(): f := metadecoders.FormatFromFrontMatterType(it.Type) diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go index 9f3e1687ad8..caffbe73665 100644 --- a/hugolib/page__meta.go +++ b/hugolib/page__meta.go @@ -296,11 +296,7 @@ func (p *pageMeta) Type() string { return p.contentType } - if x := p.Section(); x != "" { - return x - } - - return "page" + return p.Section() } func (p *pageMeta) Weight() int { diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index 03448ba80af..6ba7232949c 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -427,7 +427,7 @@ func (p *pageContentOutput) setWordCounts(isCJKLanguage bool) { func (p *pageContentOutput) addSelfTemplate() error { self := p.p.selfLayoutForOutput(p.f) - err := p.p.s.TemplateHandler().AddLateTemplate(self, string(p.content)) + err := p.p.s.Tmpl().(tpl.TemplateManager).AddLateTemplate(self, string(p.content)) if err != nil { return err } diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 9bcfc1fc88e..d7cbc0fcaa0 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -333,12 +333,6 @@ func checkPageSummary(t *testing.T, page page.Page, summary string, msg ...inter } } -func checkPageType(t *testing.T, page page.Page, pageType string) { - if page.Type() != pageType { - t.Fatalf("Page type is: %s. Expected: %s", page.Type(), pageType) - } -} - func checkPageDate(t *testing.T, page page.Page, time time.Time) { if page.Date() != time { t.Fatalf("Page date is: %s. Expected: %s", page.Date(), time) @@ -542,7 +536,6 @@ func TestCreateNewPage(t *testing.T) { checkPageTitle(t, p, "Simple") checkPageContent(t, p, normalizeExpected(ext, "

Simple Page

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

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

\n\n

Additional text.

\n\n

Further text.

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

Summary Next Line

\n\n

Some more text

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

Summary Next Line

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

Summary Next Line.

. More text here.

Some more text

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

Summary Same Line

\n\n

Some more text

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

Summary Same Line

")) - checkPageType(t, p, "page") - } testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine) diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 7d7e8b68c39..b1041707b14 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -303,7 +303,7 @@ func renderShortcode( templStr := sc.innerString() var err error - tmpl, err = s.TextTmpl.Parse(templName, templStr) + tmpl, err = s.TextTmpl().Parse(templName, templStr) if err != nil { fe := herrors.ToFileError("html", err) l1, l2 := p.posOffset(sc.pos).LineNumber, fe.Position().LineNumber @@ -314,14 +314,14 @@ func renderShortcode( } else { // Re-use of shortcode defined earlier in the same page. var found bool - tmpl, found = s.TextTmpl.Lookup(templName) + tmpl, found = s.TextTmpl().Lookup(templName) if !found { return "", false, _errors.Errorf("no earlier definition of shortcode %q found", sc.name) } } } else { var found, more bool - tmpl, found, more = s.Tmpl.LookupVariant(sc.name, tplVariants) + tmpl, found, more = s.Tmpl().LookupVariant(sc.name, tplVariants) if !found { s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path()) return "", false, nil @@ -395,7 +395,7 @@ func renderShortcode( } - result, err := renderShortcodeWithPage(s.Tmpl, tmpl, data) + result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data) if err != nil && sc.isInline { fe := herrors.ToFileError("html", err) @@ -537,7 +537,7 @@ Loop: // Check if the template expects inner content. // We pick the first template for an arbitrary output format // if more than one. It is "all inner or no inner". - tmpl, found, _ := s.s.Tmpl.LookupVariant(sc.name, tpl.TemplateVariants{}) + tmpl, found, _ := s.s.Tmpl().LookupVariant(sc.name, tpl.TemplateVariants{}) if !found { return nil, _errors.Errorf("template for shortcode %q not found", sc.name) } diff --git a/hugolib/site.go b/hugolib/site.go index eb232c629ea..83c13237977 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -105,6 +105,7 @@ type Site struct { Sections Taxonomy Info SiteInfo + // TODO1 remove layoutHandler *output.LayoutHandler language *langs.Language @@ -936,7 +937,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro sourceChanged = append(sourceChanged, ev) case files.ComponentFolderLayouts: tmplChanged = true - if _, found := s.Tmpl.Lookup(id.Path); !found { + if _, found := s.Tmpl().Lookup(id.Path); !found { tmplAdded = true } if tmplAdded { @@ -1226,10 +1227,9 @@ func (s *Site) initializeSiteInfo() error { func (s *Site) eventToIdentity(e fsnotify.Event) (identity.PathIdentity, bool) { for _, fs := range s.BaseFs.SourceFilesystems.FileSystems() { if p := fs.Path(e.Name); p != "" { - return identity.NewPathIdentity(fs.Name, p), true + return identity.NewPathIdentity(fs.Name, filepath.ToSlash(p)), true } } - return identity.PathIdentity{}, false } @@ -1464,12 +1464,22 @@ func (s *Site) permalink(link string) string { } -func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, layouts ...string) error { +func (s *Site) lookupLayouts(layouts ...string) tpl.Template { + for _, l := range layouts { + if templ, found := s.Tmpl().Lookup(l); found { + return templ + } + } + + return nil +} + +func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, templ tpl.Template) error { s.Log.DEBUG.Printf("Render XML for %q to %q", name, targetPath) renderBuffer := bp.GetBuffer() defer bp.PutBuffer(renderBuffer) - if err := s.renderForLayouts(name, "", d, renderBuffer, layouts...); err != nil { + if err := s.renderForTemplate(name, "", d, renderBuffer, templ); err != nil { return err } @@ -1498,13 +1508,13 @@ func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath st } -func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, layouts ...string) error { +func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error { renderBuffer := bp.GetBuffer() defer bp.PutBuffer(renderBuffer) of := p.outputFormat() - if err := s.renderForLayouts(p.Kind(), of.Name, p, renderBuffer, layouts...); err != nil { + if err := s.renderForTemplate(p.Kind(), of.Name, p, renderBuffer, templ); err != nil { return err } @@ -1571,18 +1581,7 @@ func (r contentLinkRenderer) Render(w io.Writer, ctx hooks.LinkContext) error { return r.templateHandler.Execute(r.templ, w, ctx) } -func (s *Site) lookupTemplate(layouts ...string) (tpl.Template, bool) { - for _, l := range layouts { - if templ, found := s.Tmpl.Lookup(l); found { - return templ, true - } - } - - return nil, false -} - -func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.Writer, layouts ...string) (err error) { - templ := s.findFirstTemplate(layouts...) +func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) { if templ == nil { log := s.Log.WARN if infoOnMissingLayout[name] { @@ -1608,7 +1607,7 @@ func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.W 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 @@ -1616,13 +1615,23 @@ func (s *Site) renderForLayouts(name, outputFormat string, d interface{}, w io.W func (s *Site) findFirstTemplate(layouts ...string) tpl.Template { for _, layout := range layouts { - if templ, found := s.Tmpl.Lookup(layout); found { + if templ, found := s.Tmpl().Lookup(layout); found { return templ } } return nil } +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) publish(statCounter *uint64, path string, r io.Reader) (err error) { s.PathSpec.ProcessingStats.Incr(statCounter) diff --git a/hugolib/site_render.go b/hugolib/site_render.go index 34f288da231..fa4619b97b2 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -19,6 +19,8 @@ import ( "strings" "sync" + "github.com/gohugoio/hugo/tpl" + "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/output" @@ -136,25 +138,24 @@ func pageRenderer( continue } - layouts, err := p.getLayouts() + templ, found, err := p.resolveTemplate() if err != nil { - s.Log.ERROR.Printf("Failed to resolve layout for output %q for page %q: %s", f.Name, p, err) + s.SendError(p.errorf(err, "failed to resolve template")) continue } - - targetPath := p.targetPaths().TargetFilename - - if targetPath == "" { - s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", f.Name, p, err) + if !found { + // TODO1 consolidate not found 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 } } @@ -162,7 +163,7 @@ func pageRenderer( } // 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 +193,7 @@ func (s *Site) renderPaginator(p *pageState, layouts []string) error { if err := s.renderAndWritePage( &s.PathSpec.ProcessingStats.PaginatorPages, p.Title(), - targetPaths.TargetFilename, p, layouts...); err != nil { + targetPaths.TargetFilename, p, templ); err != nil { return err } @@ -220,15 +221,14 @@ func (s *Site) render404() error { return err } - nfLayouts := []string{"404.html"} - + templ := s.lookupLayouts("404.html") targetPath := p.targetPaths().TargetFilename if targetPath == "" { return errors.New("failed to create targetPath for 404 page") } - return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, nfLayouts...) + return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, templ) } func (s *Site) renderSitemap() error { @@ -255,9 +255,9 @@ func (s *Site) renderSitemap() error { return errors.New("failed to create targetPath for sitemap") } - smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"} + templ := s.lookupLayouts("sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml") - return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, smLayouts...) + return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, templ) } func (s *Site) renderRobotsTXT() error { @@ -282,9 +282,9 @@ func (s *Site) renderRobotsTXT() error { return err } - rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"} + templ := s.lookupLayouts("robots.txt", "_default/robots.txt", "_internal/_default/robots.txt") - return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, rLayouts...) + return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, templ) } diff --git a/hugolib/template_test.go b/hugolib/template_test.go index 80a703801db..0ea4aed49cf 100644 --- a/hugolib/template_test.go +++ b/hugolib/template_test.go @@ -244,6 +244,61 @@ Page Content } +func TestTemplateLookupSite(t *testing.T) { + t.Run("basic", func(t *testing.T) { + t.Parallel() + b := newTestSitesBuilder(t).WithSimpleConfigFile() + b.WithTemplates( + "_default/single.html", `Single: {{ .Title }}`, + "_default/list.html", `List: {{ .Title }}`, + ) + + createContent := func(title string) string { + return fmt.Sprintf(`--- +title: %s +---`, title) + } + + b.WithContent( + "_index.md", createContent("Home Sweet Home"), + "p1.md", createContent("P1")) + + b.CreateSites().Build(BuildCfg{}) + b.AssertFileContent("public/index.html", `List: Home Sweet Home`) + b.AssertFileContent("public/p1/index.html", `Single: P1`) + }) + + t.Run("baseof", func(t *testing.T) { + t.Parallel() + b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() + + b.WithTemplatesAdded( + "index.html", `{{ define "main" }}Main Home En{{ end }}`, + "index.fr.html", `{{ define "main" }}Main Home Fr{{ end }}`, + "baseof.html", `Baseof en: {{ block "main" . }}main block{{ end }}`, + "baseof.fr.html", `Baseof fr: {{ block "main" . }}main block{{ end }}`, + "mysection/baseof.html", `Baseof mysection: {{ block "main" . }}mysection block{{ end }}`, + "_default/single.html", `{{ define "main" }}Main Default Single{{ end }}`, + "_default/list.html", `{{ define "main" }}Main Default List{{ end }}`, + ) + + b.WithContent("mysection/p1.md", `--- +title: My Page +--- + +`) + + b.CreateSites().Build(BuildCfg{}) + + b.AssertFileContent("public/en/index.html", `Baseof en: Main Home En`) + b.AssertFileContent("public/fr/index.html", `Baseof fr: Main Home Fr`) + b.AssertFileContent("public/en/mysection/index.html", `Baseof mysection: Main Default List`) + b.AssertFileContent("public/en/mysection/p1/index.html", `Baseof mysection: Main Default Single`) + + }) + +} + func TestTemplateFuncs(t *testing.T) { b := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() diff --git a/output/layout.go b/output/layout.go index 091684bee08..460d07e0287 100644 --- a/output/layout.go +++ b/output/layout.go @@ -39,6 +39,7 @@ type LayoutDescriptor struct { LayoutOverride bool RenderingHook bool + Baseof bool } func (d LayoutDescriptor) isList() bool { @@ -76,7 +77,6 @@ func (l *LayoutHandler) For(d LayoutDescriptor, f Format) ([]string, error) { layouts := resolvePageTemplate(d, f) - layouts = prependTextPrefixIfNeeded(f, layouts...) layouts = helpers.UniqueStringsReuse(layouts) l.mu.Lock() @@ -95,6 +95,10 @@ type layoutBuilder struct { func (l *layoutBuilder) addLayoutVariations(vars ...string) { for _, layoutVar := range vars { + if l.d.Baseof && layoutVar != "baseof" { + l.layoutVariations = append(l.layoutVariations, layoutVar+"-baseof") + continue + } if !l.d.RenderingHook && l.d.LayoutOverride && layoutVar != l.d.Layout { continue } @@ -173,7 +177,7 @@ func resolvePageTemplate(d LayoutDescriptor, f Format) []string { } 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 @@ func resolvePageTemplate(d LayoutDescriptor, f Format) []string { 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") } @@ -266,20 +274,6 @@ func filterDotLess(layouts []string) []string { 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 { replacer := strings.NewReplacer(oldNew...) return replacer.Replace(s) diff --git a/output/layout_base.go b/output/layout_base.go deleted file mode 100644 index 772002e6814..00000000000 --- a/output/layout_base.go +++ /dev/null @@ -1,182 +0,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. /-baseof.(optional)., e.g. list-baseof.(optional).. - // 2. /baseof.(optional). - // 3. _default/-baseof.(optional)., e.g. list-baseof.(optional).. - // 4. _default/baseof.(optional). - // - // 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), - } -} diff --git a/output/layout_base_test.go b/output/layout_base_test.go deleted file mode 100644 index 8eea9e61e2c..00000000000 --- a/output/layout_base_test.go +++ /dev/null @@ -1,163 +0,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) - - }) - } - -} diff --git a/output/layout_test.go b/output/layout_test.go index cff27592906..7efa5675fa0 100644 --- a/output/layout_test.go +++ b/output/layout_test.go @@ -66,9 +66,13 @@ func TestLayout(t *testing.T) { }{ {"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 @@ func TestLayout(t *testing.T) { []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 @@ func TestLayout(t *testing.T) { []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 @@ func TestLayout(t *testing.T) { // 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 @@ func TestLayout(t *testing.T) { {"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 @@ func TestLayout(t *testing.T) { 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 @@ func TestLayout(t *testing.T) { 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) } diff --git a/resources/resource_transformers/templates/execute_as_template.go b/resources/resource_transformers/templates/execute_as_template.go index 953cccc04ef..115b3d047c2 100644 --- a/resources/resource_transformers/templates/execute_as_template.go +++ b/resources/resource_transformers/templates/execute_as_template.go @@ -26,28 +26,25 @@ import ( // 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,22 +53,21 @@ func (t *executeAsTemplateTransform) Key() internal.ResourceTransformationKey { 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) } 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, }) } diff --git a/tpl/collections/apply.go b/tpl/collections/apply.go index d41a3b1dac7..55d29d3a93b 100644 --- a/tpl/collections/apply.go +++ b/tpl/collections/apply.go @@ -106,7 +106,7 @@ func applyFnToThis(fn, this reflect.Value, args ...interface{}) (reflect.Value, 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) } diff --git a/tpl/collections/apply_test.go b/tpl/collections/apply_test.go index 5b21d5a9740..1e2c5126e09 100644 --- a/tpl/collections/apply_test.go +++ b/tpl/collections/apply_test.go @@ -22,6 +22,7 @@ import ( qt "github.com/frankban/quicktest" "github.com/gohugoio/hugo/deps" + "github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/tpl" ) @@ -35,6 +36,10 @@ func (templateFinder) LookupVariant(name string, variants tpl.TemplateVariants) 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 +56,9 @@ func (templateFinder) GetFunc(name string) (reflect.Value, bool) { 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"} diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go index 6f3ba2d1358..e03bf471fa8 100644 --- a/tpl/partials/partials.go +++ b/tpl/partials/partials.go @@ -105,11 +105,11 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface } 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 @@ func (ns *Namespace) Include(name string, contextList ...interface{}) (interface 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 } diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index 9a7b2969677..fd0ffc5ece0 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -45,6 +45,7 @@ func New(deps *deps.Deps) (*Namespace, error) { if err != nil { return nil, err } + return &Namespace{ deps: deps, scssClient: scssClient, @@ -53,7 +54,7 @@ func New(deps *deps.Deps) (*Namespace, error) { 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 } diff --git a/tpl/template.go b/tpl/template.go index 0841236deed..e61386d5f35 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -30,9 +30,10 @@ type TemplateManager interface { TemplateFuncGetter AddTemplate(name, tpl string) error AddLateTemplate(name, tpl string) error - LoadTemplates(prefix string) error + MarkReady() error + // TODO1 remove LoadTemplates(prefix string) error - RebuildClone() + // TODO1 remove RebuildClone() } // TemplateVariants describes the possible variants of a template. @@ -52,6 +53,7 @@ type TemplateFinder interface { type TemplateHandler interface { TemplateFinder Execute(t Template, wr io.Writer, data interface{}) error + LookupLayout(d output.LayoutDescriptor, f output.Format) (Template, bool, error) } type TemplateLookup interface { @@ -105,6 +107,12 @@ type templateInfoManager struct { InfoManager } +// TemplatesProvider as implemented by deps.Deps. +type TemplatesProvider interface { + Tmpl() TemplateHandler + TextTmpl() TemplateParseFinder +} + // WithInfo wraps the info in a template. func WithInfo(templ Template, info Info) Template { if manager, ok := info.(InfoManager); ok { diff --git a/tpl/templates/templates.go b/tpl/templates/templates.go index 44d397e68ce..80eb2d378b5 100644 --- a/tpl/templates/templates.go +++ b/tpl/templates/templates.go @@ -34,7 +34,7 @@ type Namespace struct { // 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 } diff --git a/tpl/tplimpl/shortcodes.go b/tpl/tplimpl/shortcodes.go index abef11e1e92..cc4d99491d9 100644 --- a/tpl/tplimpl/shortcodes.go +++ b/tpl/tplimpl/shortcodes.go @@ -32,8 +32,7 @@ type shortcodeVariant struct { // A slice of length numTemplateVariants. variants []string - info tpl.Info - templ tpl.Template + ts *templateState } type shortcodeTemplates struct { diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go index 2d2a63cf967..a7f7bfe2032 100644 --- a/tpl/tplimpl/template.go +++ b/tpl/tplimpl/template.go @@ -14,56 +14,42 @@ package tplimpl import ( - "fmt" "io" + "os" + "path/filepath" "reflect" "regexp" + "strings" + "sync" "time" - "github.com/gohugoio/hugo/hugofs/files" - - "github.com/gohugoio/hugo/identity" - - "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/types" - "strings" + "github.com/gohugoio/hugo/helpers" - template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" + "github.com/gohugoio/hugo/output" - texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" + "github.com/gohugoio/hugo/deps" + "github.com/spf13/afero" + "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/output" + "github.com/gohugoio/hugo/tpl/tplimpl/embedded" - "path/filepath" - "sync" + htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" + texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" - "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/" @@ -79,284 +65,280 @@ var embeddedTemplatesAliases = map[string][]string{ const baseFileBase = "baseof" -func newTemplateAdapter(deps *deps.Deps) *templateHandler { +var ( + _ tpl.TemplateManager = (*templateExec)(nil) + _ tpl.TemplateHandler = (*templateExec)(nil) + //_ tpl.TemplateDebugger = (*newTemplateHandler)(nil) + //_ tpl.TemplateFuncGetter = (*newTemplateHandler)(nil) + _ tpl.TemplateFinder = (*templateHandler)(nil) + //_ templateLoader = (*newTemplateHandler)(nil) + //_ templateLoader = (*newTemplateHandler)(nil) +) - common := &templatesCommon{ - nameBaseTemplateName: make(map[string]string), - transformNotFound: make(map[string]bool), - identityNotFound: make(map[string][]identity.Manager), - } +var ( + _ tpl.Template = (*templateState)(nil) + _ tpl.Info = (*templateState)(nil) +) - htmlT := &htmlTemplates{ - t: template.New(""), - overlays: make(map[string]*template.Template), - templatesCommon: common, - } +var defineRe = regexp.MustCompile(`{{-?\s?define`) - 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, - }, - } - - textT.textTemplate.templates = textT - textT.standalone.templates = textT - common.handler = h - - return h + nameBaseTemplateName: make(map[string]string), + transformNotFound: make(map[string]*templateState), + identityNotFound: make(map[string][]identity.Manager), -} + shortcodes: make(map[string]*shortcodeTemplates), + templateInfo: make(map[string]tpl.Info), + templateInfoTree: make(map[string]*templateInfoTree), + baseof: make(map[string]templateInfo), + needsBaseof: make(map[string]templateInfo), -type htmlTemplates struct { - *templatesCommon + main: newTemplateNamespace(false, funcMap), - t *template.Template + Deps: d, + layoutHandler: output.NewLayoutHandler(), + layoutsFs: d.BaseFs.Layouts.Fs, + layoutTemplateCache: make(map[layoutCacheKey]tpl.Template), + } - // 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 + if err := h.loadEmbedded(); err != nil { + return nil, err + } - // 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 -} + if err := h.loadTemplates(); err != nil { + return nil, err + } -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 newStandaloneTextTemplate(funcs map[string]interface{}) tpl.TemplateParseFinder { + return &textTemplateWrapperWithLock{ + t: texttemplate.New("").Funcs(funcs), } +} - typ := resolveTemplateType(name) - - c, err := t.handler.applyTemplateTransformersToHMLTTemplate(typ, templ) - if err != nil { - return nil, err +func newTemplateNamespace(mutable bool, funcs map[string]interface{}) *templateNamespace { + return &templateNamespace{ + mutable: mutable, + prototypeHTML: htmltemplate.New("").Funcs(funcs), + prototypeText: texttemplate.New("").Funcs(funcs), + templateStateMap: &templateStateMap{ + templates: make(map[string]*templateState), + }, } +} - for k := range c.templateNotFound { - t.transformNotFound[k] = true - t.identityNotFound[k] = append(t.identityNotFound[k], c.id) - } +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 - for k := range c.identityNotFound { - t.identityNotFound[k] = append(t.identityNotFound[k], c.id) } - - return c, nil } -func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error { +type layoutCacheKey struct { + d output.LayoutDescriptor + f string +} - masterTpl := t.lookup(masterFilename) +type templateExec struct { + d *deps.Deps + executor texttemplate.Executer + funcs map[string]reflect.Value - if masterTpl == nil { - templ, err := onMissing(masterFilename) - if err != nil { - return err - } + *templateHandler +} - masterTpl, err = t.t.New(overlayFilename).Parse(templ.template) - if err != nil { - return templ.errWithFileContext("parse master failed", err) - } - } +func (t templateExec) Clone(d *deps.Deps) *templateExec { + exec, funcs := newTemplateExecuter(d) + t.executor = exec + t.funcs = funcs + t.d = d + return &t +} - templ, err := onMissing(overlayFilename) - if err != nil { - return 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() } - - overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ.template) - if err != nil { - return templ.errWithFileContext("parse failed", err) + if t.Metrics != nil { + defer t.Metrics.MeasureSince(templ.Name(), time.Now()) } - // 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 + execErr := t.executor.Execute(templ, wr, data) + if execErr != nil { + execErr = t.addFileContext(templ.Name(), execErr) } + return execErr +} - 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() + t.late.mutable = 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 + + // Used to track templates during the AST transformations. + templateInfoTree map[string]*templateInfoTree } -// 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. func (t *templateHandler) AddLateTemplate(name, tpl string) error { - h := t.getTemplateHandler(name) - if err := h.addLateTemplate(name, tpl); err != nil { - return err - } - return nil + // TODO1 apply + _, 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 +// TODO1 check if needed 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 -} - -func (t *templateHandler) Debug() { - fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates()) - fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates()) + return err } -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 - -} - -// 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)) +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() - // Look in both - if te, found := t.html.Lookup(name); found { - return t.applyTemplateInfo(te, true) - } + t.layoutTemplateCacheMu.Lock() + defer t.layoutTemplateCacheMu.Unlock() - 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 + } + 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,64 +352,76 @@ func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVarian 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 - }() +func (t *templateHandler) findLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) { + layouts, _ := t.layoutHandler.For(d, f) - if err := t.postTransform(); err != nil { - return err - } + for _, name := range layouts { + templ, found := t.main.Lookup(name) + if found { + return templ, true, nil + } + + overlay, found := t.needsBaseof[name] + 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) + } + + // TODO1 apply transforms + templ, err := t.applyBaseTemplate(overlay, base) + if err != nil { + return nil, false, base.errWithFileContext("parse failed", err) + } + return templ, true, nil - 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()) } - return nil + return nil, false, nil } -// 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 t.text != nil && t.text.cloneClone != nil { - t.text.clone = texttemplate.Must(t.text.cloneClone.Clone()) +func (t *templateHandler) getOrCreateTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) { + info, found := t.templateInfo[name] + if found { + return info.(identity.Manager), info.ParseInfo() } + return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo } -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) newTemplateInfo(name, tpl string) templateInfo { + var isText bool + name, isText = t.nameIsText(name) + return templateInfo{ + name: name, + isText: isText, + template: tpl, } - - // 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) } -func (t *templateHandler) getTemplateHandler(name string) templateLoader { - if strings.HasPrefix(name, textTmplNamePrefix) { - return t.text +func (t *templateHandler) createTemplateInfo(name string) (identity.Manager, tpl.ParseInfo) { + _, found := t.templateInfo[name] + if found { + panic("already created: " + name) } - return t.html + + return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo } func (t *templateHandler) addFileContext(name string, inerr error) error { @@ -442,7 +436,8 @@ func (t *templateHandler) addFileContext(name string, inerr error) error { } defer f.Close() - master, hasMaster := t.html.nameBaseTemplateName[name] + hasMaster := false + // TODO1 master, hasMaster := t.html.nameBaseTemplateName[name] ferr := errors.Wrap(inerr, "execute of template failed") @@ -472,7 +467,7 @@ func (t *templateHandler) addFileContext(name string, inerr error) error { } // Try the base template if relevant - f, realFilename, err = t.fileAndFilename(master) + f, realFilename, err = t.fileAndFilename("TODO1") if err != nil { return err } @@ -489,12 +484,8 @@ func (t *templateHandler) addFileContext(name string, inerr error) error { } -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 +496,7 @@ func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ t.shortcodes[shortcodename] = templs } - sv := shortcodeVariant{variants: variants, info: info, templ: templ} + sv := shortcodeVariant{variants: variants, ts: ts} i := templs.indexOf(variants) @@ -519,11 +510,7 @@ func (t *templateHandler) addShortcodeVariant(name string, info tpl.Info, templ } } -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 +527,100 @@ func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) e } } - 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 + } + + tinfo, err := getTemplate(path) + if err != nil { + return err } - // 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) + 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 - if err != nil { - return err - } +} - err = t.AddTemplate(name, templ.template) +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 templ.errWithFileContext("parse failed", err) + return nil, err } - return nil + return texttemplate.Must(templ.Parse(overlay.template)), 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, err } - return templ, found + templ, err = htmltemplate.Must(templ.Clone()).Parse(overlay.template) + if err != nil { + return nil, 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) applyTemplateInfo(templ tpl.Template) tpl.Template { + if info, found := t.templateInfo[templ.Name()]; found { + return tpl.WithInfo(templ, info) } + return templ } -func (t *templateHandler) clone(d *deps.Deps) *templateHandler { - if !t.ready { - panic("invalid state") - } - c := &templateHandler{ - ready: true, - Deps: d, - layoutsFs: d.BaseFs.Layouts.Fs, +func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *templateState) (*templateContext, error) { + c, err := applyTemplateTransformers(ts, ns.newTemplateLookup(ts)) + if err != nil { + return nil, err } - c.templateHandlerCommon = t.templateHandlerCommon.withNewHandler(c) - d.Tmpl = c - d.TextTmpl = c.wrapTextTemplate(c.text.standalone) - c.executor, c.funcs = newTemplateExecuter(d) + for k := range c.templateNotFound { + t.transformNotFound[k] = ts + t.identityNotFound[k] = append(t.identityNotFound[k], c.t) + } - return c + for k := range c.identityNotFound { + t.identityNotFound[k] = append(t.identityNotFound[k], c.t) + } + return c, err } func (t *templateHandler) extractIdentifiers(line string) []string { @@ -635,20 +651,17 @@ func (t *templateHandler) fileAndFilename(name string) (afero.File, string, erro 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 } } @@ -660,39 +673,25 @@ func (t *templateHandler) loadEmbedded() error { } -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 - - 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) - }, - } + name := strings.TrimPrefix(filepath.ToSlash(path), "/") + filename := filepath.Base(path) + outputFormat, found := t.OutputFormatsConfig.FromFilename(filename) - 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 found && outputFormat.IsPlainText { + name = textTmplNamePrefix + name } - if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil { + if err := t.addTemplateFile(name, path); err != nil { return err } @@ -710,86 +709,51 @@ func (t *templateHandler) loadTemplates(prefix string) error { } -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 strings.Contains(name, "_markup/") +} - return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)), tpl.DefaultParseInfo +func (t *templateHandler) findTemplate(name string) *templateState { + if templ, found := t.Lookup(name); found { + return templ.(*templateState) + } + return nil } 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 - } - 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 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 } } + } - 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 +761,141 @@ func (t *templateHandler) postTransform() error { return nil } -func (t *templateHandler) wrapTextTemplate(tt *textTemplate) tpl.TemplateParseFinder { - return struct { - tpl.TemplateParser - tpl.TemplateLookup - tpl.TemplateLookupVariant - }{ - tt, - tt, - new(nopLookupVariant), - } -} - -type templateHandlerCommon struct { - // 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 - - // Used to track templates during the AST transformations. - templateInfoTree map[string]*templateInfoTree +type nopLookupVariant int - // text holds all the pure text templates. - text *textTemplates - html *htmlTemplates +func (l nopLookupVariant) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) { + return nil, false, false } -func (t templateHandlerCommon) withNewHandler(h *templateHandler) *templateHandlerCommon { - t.text = t.text.withNewHandler(h) - t.html = t.html.withNewHandler(h) - return &t +type templateStateMap struct { + sync.RWMutex + templates map[string]*templateState } -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 -} +type templateNamespace struct { + mutable bool // whether templates will be added after execution starts. + done bool // TODO1 + // TODO1 lookup consider mutable vs lock -// Shared by both HTML and text templates. -type templatesCommon struct { - handler *templateHandler + prototypeText *texttemplate.Template + prototypeHTML *htmltemplate.Template + prototypeTextClone *texttemplate.Template + prototypeHTMLClone *htmltemplate.Template - // Used to get proper filenames in errors - nameBaseTemplateName map[string]string - - // 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 + *templateStateMap } -func (t templatesCommon) withNewHandler(h *templateHandler) *templatesCommon { - t.handler = h +func (t templateNamespace) Clone() *templateNamespace { + t.prototypeText = texttemplate.Must(t.prototypeText.Clone()) + t.prototypeHTML = htmltemplate.Must(t.prototypeHTML.Clone()) + return &t } -type textTemplate struct { - mu sync.RWMutex - t *texttemplate.Template - templates *textTemplates +func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) { + templ, found := t.templates[name] + return templ, found } -func (t *textTemplate) Lookup(name string) (tpl.Template, bool) { - t.mu.RLock() - defer t.mu.RUnlock() - - tpl := t.t.Lookup(name) - return tpl, tpl != nil -} +func (t *templateNamespace) createPrototypes() error { + t.prototypeTextClone = texttemplate.Must(t.prototypeText.Clone()) + t.prototypeHTMLClone = htmltemplate.Must(t.prototypeHTML.Clone()) -func (t *textTemplate) Parse(name, tpl string) (tpl.Template, error) { - return t.parseIn(t.t, name, tpl) + return nil } -func (t *textTemplate) parseIn(tt *texttemplate.Template, name, tpl string) (*texttemplate.Template, error) { - t.mu.Lock() - defer t.mu.Unlock() +func (t *templateNamespace) parse(info templateInfo) (*templateState, error) { + t.Lock() + defer t.Unlock() //TODO1 - templ, err := tt.New(name).Parse(tpl) - if err != nil { - return nil, err - } + if info.isText { + var prototype *texttemplate.Template + if t.mutable { + prototype = texttemplate.Must(t.prototypeText.Clone()) + } else { + prototype = t.prototypeText + } - if _, err := t.templates.handler.applyTemplateTransformersToTextTemplate(templateUndefined, templ); err != nil { - return nil, err - } - return templ, nil -} + templ, err := prototype.New(info.name).Parse(info.template) + if err != nil { + return nil, err + } -type textTemplates struct { - *templatesCommon - *textTemplate - standalone *textTemplate - clone *texttemplate.Template - cloneClone *texttemplate.Template + ts := newTemplateState(templ, info) - overlays map[string]*texttemplate.Template -} + t.templates[info.name] = ts -func (t *textTemplates) Lookup(name string) (tpl.Template, bool) { - templ := t.lookup(name) - if templ == nil { - return nil, false + return ts, nil } - return templ, true -} - -func (t *textTemplates) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) { - return t.handler.LookupVariant(name, variants) -} -func (t *textTemplates) addLateTemplate(name, tpl string) error { - _, err := t.addTemplateIn(t.clone, name, tpl) - return err -} - -func (t *textTemplates) addTemplate(name, tpl string) (*templateContext, error) { - return t.addTemplateIn(t.t, name, tpl) -} - -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 + var prototype *htmltemplate.Template + if t.mutable { + prototype = htmltemplate.Must(t.prototypeHTML.Clone()) + } else { + prototype = t.prototypeHTML } - typ := resolveTemplateType(name) - - 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 - } - - return c, nil -} - -func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (templateInfo, error)) error { - - name = strings.TrimPrefix(name, textTmplNamePrefix) - masterTpl := t.lookup(masterFilename) - - if masterTpl == nil { - templ, err := onMissing(masterFilename) - if err != nil { - return err - } + ts := newTemplateState(templ, info) - 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 - } + t.templates[info.name] = ts - templ, err := onMissing(overlayFilename) - if err != nil { - return err - } + return ts, nil +} - overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ.template) - if err != nil { - return errors.Wrapf(err, "failed to parse %q:", templ.filename) +func newTemplateState(templ tpl.Template, info templateInfo) *templateState { + return &templateState{ + tinfo: info, + typ: info.resolveType(), + Template: templ, + Manager: newIdentity(info.name), + parseInfo: tpl.DefaultParseInfo, } +} - 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 templateState struct { + tpl.Template - return err + typ templateType + parseInfo tpl.ParseInfo + identity.Manager + tinfo templateInfo } -func (t *textTemplates) lookup(name string) *texttemplate.Template { +func (t *templateState) ParseInfo() tpl.ParseInfo { + return t.parseInfo +} - // 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 *templateState) isText() bool { + _, isText := t.Template.(*texttemplate.Template) + return isText +} - if templ := t.t.Lookup(name); templ != nil { - return templ - } +type textTemplateWrapperWithLock struct { + sync.RWMutex + t *texttemplate.Template +} - if t.clone != nil { - return t.clone.Lookup(name) - } +func (t *textTemplateWrapperWithLock) Lookup(name string) (tpl.Template, bool) { + t.RLock() + templ := t.t.Lookup(name) + t.RUnlock() + return templ, templ != nil +} - 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.t.New(name).Parse(tpl) } func isBackupFile(path string) bool { @@ -1047,3 +937,14 @@ func templateBaseName(typ templateType, name string) string { } } + +func unwrap(templ tpl.Template) tpl.Template { + if ts, ok := templ.(*templateState); ok { + return ts.Template + } + return templ +} + +func newIdentity(name string) identity.Manager { + return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name)) +} diff --git a/tpl/tplimpl/templateProvider.go b/tpl/tplimpl/templateProvider.go index 68de00561fa..933ee7dc374 100644 --- a/tpl/tplimpl/templateProvider.go +++ b/tpl/tplimpl/templateProvider.go @@ -25,32 +25,17 @@ var DefaultTemplateProvider *TemplateProvider // 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 } diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go index d3614981970..8a9646f19ee 100644 --- a/tpl/tplimpl/template_ast_transformers.go +++ b/tpl/tplimpl/template_ast_transformers.go @@ -19,8 +19,9 @@ import ( "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 +42,21 @@ type templateContext struct { 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 +73,11 @@ func (c templateContext) getIfNotVisited(name string) *templateInfoTree { } 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), @@ -96,42 +91,6 @@ func createGetTemplateInfoTreeFor(getID func(name string) *templateInfoTree) fun } } -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 @@ -141,28 +100,35 @@ type templateInfoTree struct { } 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 +181,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) { 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 +229,7 @@ func (c *templateContext) hasIdent(idents []string, ident string) bool { // 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 +261,7 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) { 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 +270,10 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) { // 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 +287,7 @@ func (c *templateContext) collectInner(n *parse.CommandNode) { } if c.hasIdent(idents, "Inner") { - c.parseInfo.IsInner = true + c.t.parseInfo.IsInner = true break } } @@ -351,8 +317,9 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) { } 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 +328,7 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) { } 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 } @@ -381,3 +348,18 @@ func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool { 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 + +} diff --git a/tpl/tplimpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go index 5efa6a14d80..443ae6a6746 100644 --- a/tpl/tplimpl/template_ast_transformers_test.go +++ b/tpl/tplimpl/template_ast_transformers_test.go @@ -33,7 +33,7 @@ func TestTransformRecursiveTemplate(t *testing.T) { {{ define "menu-nodes" }} {{ template "menu-node" }} {{ end }} -{{ define "menu-nßode" }} +{{ define "menu-node" }} {{ template "menu-node" }} {{ end }} {{ template "menu-nodes" }} @@ -41,17 +41,46 @@ func TestTransformRecursiveTemplate(t *testing.T) { 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 newTestTemplate(templ tpl.Template) *templateState { + return newTemplateState( + templ, + templateInfo{ + name: templ.Name(), + }, + ) +} + +func newTestTemplateLookup(in *templateState) func(name string) *templateState { + m := make(map[string]*templateState) + return func(name string) *templateState { + if in.Name() == name { + return in + } + + if ts, found := m[name]; found { + return ts + } + + if templ, found := findTemplateIn(name, in); found { + ts := newTestTemplate(templ) + m[name] = ts + return ts + } + + return nil + } +} + func createGetTemplateInfoTree(tree *parse.Tree) func(name string) *templateInfoTree { return func(name string) *templateInfoTree { return &templateInfoTree{ @@ -98,13 +127,14 @@ func TestCollectInfo(t *testing.T) { 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 +171,13 @@ func TestPartialReturn(t *testing.T) { 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. diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go index 63695c5f66f..48818cb60bf 100644 --- a/tpl/tplimpl/template_errors.go +++ b/tpl/tplimpl/template_errors.go @@ -20,7 +20,9 @@ import ( ) 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 @@ -32,6 +34,10 @@ type templateInfo struct { realFilename string } +func (t templateInfo) resolveType() templateType { + return resolveTemplateType(t.name) +} + func (info templateInfo) errWithFileContext(what string, err error) error { err = errors.Wrapf(err, what) diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go index 6ca9de4daf6..852b63930ba 100644 --- a/tpl/tplimpl/template_funcs_test.go +++ b/tpl/tplimpl/template_funcs_test.go @@ -127,8 +127,8 @@ func TestTemplateFuncsExamples(t *testing.T) { 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) } diff --git a/tpl/tplimpl/template_info_test.go b/tpl/tplimpl/template_info_test.go index e72e859ed00..1324b458e35 100644 --- a/tpl/tplimpl/template_info_test.go +++ b/tpl/tplimpl/template_info_test.go @@ -24,14 +24,15 @@ import ( 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)