Create template clone for late template execution
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 9 Mar 2016 13:05:31 +0000 (14:05 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 9 Mar 2016 13:37:58 +0000 (14:37 +0100)
Fixing some breaking blogs on Go 1.6

Fixes #1879

hugolib/handler_test.go
hugolib/robotstxt_test.go
hugolib/rss_test.go
hugolib/shortcode_test.go
hugolib/site.go
hugolib/site_test.go
hugolib/site_url_test.go
hugolib/siteinfo_test.go
hugolib/sitemap_test.go
tpl/template.go

index 1e8f16c797beb4a12c459e62340eb202f18b0d6c..df5b970c9782e3cb85aa906cb1b9275b3e4ecca7 100644 (file)
@@ -51,12 +51,11 @@ func TestDefaultHandler(t *testing.T) {
        }
 
        s.initializeSiteInfo()
-       // From site_test.go
-       templatePrep(s)
 
-       must(s.addTemplate("_default/single.html", "{{.Content}}"))
-       must(s.addTemplate("head", "<head><script src=\"script.js\"></script></head>"))
-       must(s.addTemplate("head_abs", "<head><script src=\"/script.js\"></script></head>"))
+       s.prepTemplates(
+               "_default/single.html", "{{.Content}}",
+               "head", "<head><script src=\"script.js\"></script></head>",
+               "head_abs", "<head><script src=\"/script.js\"></script></head>")
 
        // From site_test.go
        createAndRenderPages(t, s)
index c964c02314fd5ffb74504b9ec2c8b6a05981a35a..dfbaefea19d1a7675303d2d531ba5b73243e199b 100644 (file)
@@ -31,8 +31,7 @@ func TestRobotsTXTOutput(t *testing.T) {
 
        s.initializeSiteInfo()
 
-       s.prepTemplates()
-       s.addTemplate("robots.txt", ROBOTSTXT_TEMPLATE)
+       s.prepTemplates("robots.txt", ROBOTSTXT_TEMPLATE)
 
        if err := s.CreatePages(); err != nil {
                t.Fatalf("Unable to create pages: %s", err)
index 3a9a0cf079d43d7627b9ac4f27cef0588e03c599..a6a4f82e56ae8a8dd1464ebd023f165175aac6a3 100644 (file)
@@ -58,10 +58,7 @@ func TestRSSOutput(t *testing.T) {
                Source: &source.InMemorySource{ByteSource: WEIGHTED_SOURCES},
        }
        s.initializeSiteInfo()
-       s.prepTemplates()
-
-       //  Add an rss.xml template to invoke the rss build.
-       s.addTemplate("rss.xml", RSS_TEMPLATE)
+       s.prepTemplates("rss.xml", RSS_TEMPLATE)
 
        if err := s.CreatePages(); err != nil {
                t.Fatalf("Unable to create pages: %s", err)
index 306b53e8430ca062553665095d949c93ddb0e5ac..07af6b3a662e56b4325238f61c577a0d8c4753cc 100644 (file)
@@ -458,12 +458,16 @@ e`,
        }
 
        s.initializeSiteInfo()
-       templatePrep(s)
+
+       s.loadTemplates()
+
+       s.Tmpl.AddTemplate("_default/single.html", "{{.Content}}")
+
        s.Tmpl.AddInternalShortcode("b.html", `b`)
        s.Tmpl.AddInternalShortcode("c.html", `c`)
        s.Tmpl.AddInternalShortcode("d.html", `d`)
 
-       must(s.addTemplate("_default/single.html", "{{.Content}}"))
+       s.Tmpl.MarkReady()
 
        createAndRenderPages(t, s)
 
index 02a310f3264b1e0346ef296838014b6d5d2cf919..cee614d86a025df549cef53194ed9461d479f0b4 100644 (file)
@@ -50,6 +50,9 @@ import (
 
 var _ = transform.AbsURL
 
+// used to indicate if run as a test.
+var testMode bool
+
 var DefaultTimer *nitro.B
 
 var distinctErrorLogger = helpers.NewDistinctErrorLogger()
@@ -594,7 +597,7 @@ func (s *Site) Analyze() error {
        return s.ShowPlan(os.Stdout)
 }
 
-func (s *Site) prepTemplates() {
+func (s *Site) loadTemplates() {
        s.Tmpl = tpl.InitializeT()
        s.Tmpl.LoadTemplates(s.absLayoutDir())
        if s.hasTheme() {
@@ -602,8 +605,18 @@ func (s *Site) prepTemplates() {
        }
 }
 
-func (s *Site) addTemplate(name, data string) error {
-       return s.Tmpl.AddTemplate(name, data)
+func (s *Site) prepTemplates(additionalNameValues ...string) error {
+       s.loadTemplates()
+
+       for i := 0; i < len(additionalNameValues); i += 2 {
+               err := s.Tmpl.AddTemplate(additionalNameValues[i], additionalNameValues[i+1])
+               if err != nil {
+                       return err
+               }
+       }
+       s.Tmpl.MarkReady()
+
+       return nil
 }
 
 func (s *Site) loadData(sources []source.Input) (err error) {
@@ -1386,7 +1399,7 @@ func (s *Site) RenderPages() error {
                var layouts []string
                if !p.IsRenderable() {
                        self := "__" + p.TargetPath()
-                       _, err := s.Tmpl.New(self).Parse(string(p.Content))
+                       _, err := s.Tmpl.GetClone().New(self).Parse(string(p.Content))
                        if err != nil {
                                results <- err
                                continue
@@ -2024,8 +2037,11 @@ func (s *Site) render(name string, d interface{}, w io.Writer, layouts ...string
        if err := s.renderThing(d, layout, w); err != nil {
                // Behavior here should be dependent on if running in server or watch mode.
                distinctErrorLogger.Printf("Error while rendering %s: %v", name, err)
-               if !s.Running() {
+               if !s.Running() && !testMode {
+                       // TODO(bep) check if this can be propagated
                        os.Exit(-1)
+               } else if testMode {
+                       return err
                }
        }
 
@@ -2043,10 +2059,11 @@ func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
 
 func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {
        // If the template doesn't exist, then return, but leave the Writer open
-       if s.Tmpl.Lookup(layout) == nil {
-               return fmt.Errorf("Layout not found: %s", layout)
+       if templ := s.Tmpl.Lookup(layout); templ != nil {
+               return templ.Execute(w, d)
        }
-       return s.Tmpl.ExecuteTemplate(w, layout, d)
+       return fmt.Errorf("Layout not found: %s", layout)
+
 }
 
 func (s *Site) NewXMLBuffer() *bytes.Buffer {
index f84ae59023c6f703dd7e39655478148b66755e72..dec6cb29c10bd8dfec83c26927202ed342970a94 100644 (file)
@@ -30,7 +30,6 @@ import (
        "github.com/spf13/hugo/hugofs"
        "github.com/spf13/hugo/source"
        "github.com/spf13/hugo/target"
-       "github.com/spf13/hugo/tpl"
        "github.com/spf13/viper"
        "github.com/stretchr/testify/assert"
 )
@@ -65,6 +64,10 @@ more text
 `
 )
 
+func init() {
+       testMode = true
+}
+
 // Issue #1797
 func TestReadPagesFromSourceWithEmptySource(t *testing.T) {
        viper.Reset()
@@ -110,14 +113,6 @@ func createAndRenderPages(t *testing.T, s *Site) {
        }
 }
 
-func templatePrep(s *Site) {
-       s.Tmpl = tpl.New()
-       s.Tmpl.LoadTemplates(s.absLayoutDir())
-       if s.hasTheme() {
-               s.Tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
-       }
-}
-
 func pageMust(p *Page, err error) *Page {
        if err != nil {
                panic(err)
@@ -129,7 +124,7 @@ func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
        p, _ := NewPageFrom(strings.NewReader(PAGE_SIMPLE_TITLE), "content/a/file.md")
        p.Convert()
        s := new(Site)
-       templatePrep(s)
+       s.prepTemplates()
        err := s.renderThing(p, "foobar", nil)
        if err == nil {
                t.Errorf("Expected err to be returned when missing the template.")
@@ -138,8 +133,7 @@ func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
 
 func TestAddInvalidTemplate(t *testing.T) {
        s := new(Site)
-       templatePrep(s)
-       err := s.addTemplate("missing", TEMPLATE_MISSING_FUNC)
+       err := s.prepTemplates("missing", TEMPLATE_MISSING_FUNC)
        if err == nil {
                t.Fatalf("Expecting the template to return an error")
        }
@@ -182,7 +176,6 @@ func TestRenderThing(t *testing.T) {
        for i, test := range tests {
 
                s := new(Site)
-               templatePrep(s)
 
                p, err := NewPageFrom(strings.NewReader(test.content), "content/a/file.md")
                p.Convert()
@@ -190,7 +183,9 @@ func TestRenderThing(t *testing.T) {
                        t.Fatalf("Error parsing buffer: %s", err)
                }
                templateName := fmt.Sprintf("foobar%d", i)
-               err = s.addTemplate(templateName, test.template)
+
+               s.prepTemplates(templateName, test.template)
+
                if err != nil {
                        t.Fatalf("Unable to add template: %s", err)
                }
@@ -230,17 +225,14 @@ func TestRenderThingOrDefault(t *testing.T) {
        for i, test := range tests {
 
                s := &Site{}
-               templatePrep(s)
 
                p, err := NewPageFrom(strings.NewReader(PAGE_SIMPLE_TITLE), "content/a/file.md")
                if err != nil {
                        t.Fatalf("Error parsing buffer: %s", err)
                }
                templateName := fmt.Sprintf("default%d", i)
-               err = s.addTemplate(templateName, test.template)
-               if err != nil {
-                       t.Fatalf("Unable to add template: %s", err)
-               }
+
+               s.prepTemplates(templateName, test.template)
 
                var err2 error
 
@@ -387,9 +379,8 @@ THE END.`, refShortcode))},
        }
 
        s.initializeSiteInfo()
-       templatePrep(s)
 
-       must(s.addTemplate("_default/single.html", "{{.Content}}"))
+       s.prepTemplates("_default/single.html", "{{.Content}}")
 
        createAndRenderPages(t, s)
 
@@ -454,15 +445,13 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
        }
 
        s.initializeSiteInfo()
-       templatePrep(s)
 
-       must(s.addTemplate("index.html", "Home Sweet {{ if.IsHome  }}Home{{ end }}."))
-       must(s.addTemplate("_default/single.html", "{{.Content}}{{ if.IsHome  }}This is not home!{{ end }}"))
-       must(s.addTemplate("404.html", "Page Not Found.{{ if.IsHome  }}This is not home!{{ end }}"))
-
-       // make sure the XML files also end up with ugly urls
-       must(s.addTemplate("rss.xml", "<root>RSS</root>"))
-       must(s.addTemplate("sitemap.xml", "<root>SITEMAP</root>"))
+       s.prepTemplates(
+               "index.html", "Home Sweet {{ if.IsHome  }}Home{{ end }}.",
+               "_default/single.html", "{{.Content}}{{ if.IsHome  }}This is not home!{{ end }}",
+               "404.html", "Page Not Found.{{ if.IsHome  }}This is not home!{{ end }}",
+               "rss.xml", "<root>RSS</root>",
+               "sitemap.xml", "<root>SITEMAP</root>")
 
        createAndRenderPages(t, s)
        s.RenderHomePage()
@@ -549,10 +538,9 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
        }
 
        s.initializeSiteInfo()
-       templatePrep(s)
-
-       must(s.addTemplate("_default/single.html", "{{.Content}}"))
-       must(s.addTemplate("_default/list.html", "{{ .Title }}"))
+       s.prepTemplates(
+               "_default/single.html", "{{.Content}}",
+               "_default/list.html", "{{ .Title }}")
 
        createAndRenderPages(t, s)
        s.RenderSectionLists()
@@ -614,11 +602,11 @@ func TestSkipRender(t *testing.T) {
        }
 
        s.initializeSiteInfo()
-       templatePrep(s)
 
-       must(s.addTemplate("_default/single.html", "{{.Content}}"))
-       must(s.addTemplate("head", "<head><script src=\"script.js\"></script></head>"))
-       must(s.addTemplate("head_abs", "<head><script src=\"/script.js\"></script></head>"))
+       s.prepTemplates(
+               "_default/single.html", "{{.Content}}",
+               "head", "<head><script src=\"script.js\"></script></head>",
+               "head_abs", "<head><script src=\"/script.js\"></script></head>")
 
        createAndRenderPages(t, s)
 
@@ -670,8 +658,8 @@ func TestAbsURLify(t *testing.T) {
                }
                t.Logf("Rendering with BaseURL %q and CanonifyURLs set %v", viper.GetString("baseURL"), canonify)
                s.initializeSiteInfo()
-               templatePrep(s)
-               must(s.addTemplate("blue/single.html", TEMPLATE_WITH_URL_ABS))
+
+               s.prepTemplates("blue/single.html", TEMPLATE_WITH_URL_ABS)
 
                if err := s.CreatePages(); err != nil {
                        t.Fatalf("Unable to create pages: %s", err)
index f25b5dfafbda921917480e2e8ce421ad8d54f0e4..33843ebcfbc3de37edf0e9a67f3f00e69feaeafb 100644 (file)
@@ -103,8 +103,7 @@ func TestPageCount(t *testing.T) {
                Source: &source.InMemorySource{ByteSource: urlFakeSource},
        }
        s.initializeSiteInfo()
-       s.prepTemplates()
-       must(s.addTemplate("indexes/blue.html", INDEX_TEMPLATE))
+       s.prepTemplates("indexes/blue.html", INDEX_TEMPLATE)
 
        if err := s.CreatePages(); err != nil {
                t.Errorf("Unable to create pages: %s", err)
index 4d075dc4d4799199c37e770c20a9458f18b65a75..4e4c924e9acf55d24881b1b8067261d968be3e9f 100644 (file)
@@ -33,8 +33,9 @@ func TestSiteInfoParams(t *testing.T) {
        if s.Info.Params["MyGlobalParam"] != "FOOBAR_PARAM" {
                t.Errorf("Unable to set site.Info.Param")
        }
-       s.prepTemplates()
-       s.addTemplate("template", SITE_INFO_PARAM_TEMPLATE)
+
+       s.prepTemplates("template", SITE_INFO_PARAM_TEMPLATE)
+
        buf := new(bytes.Buffer)
 
        err := s.renderThing(s.NewNode(), "template", buf)
index bb3eaccd2a81cec26672c8e61b7042c05d1c4315..34f2d3477a8fa5562077d25ab6bf131b230b030d 100644 (file)
@@ -50,8 +50,7 @@ func TestSitemapOutput(t *testing.T) {
 
        s.initializeSiteInfo()
 
-       s.prepTemplates()
-       s.addTemplate("sitemap.xml", SITEMAP_TEMPLATE)
+       s.prepTemplates("sitemap.xml", SITEMAP_TEMPLATE)
 
        if err := s.CreatePages(); err != nil {
                t.Fatalf("Unable to create pages: %s", err)
index 2793e4dd5fef1b56047431fe2740f5d1d8321be4..b8405d580f45a418f3dde65c75688af81e42fbc8 100644 (file)
@@ -37,8 +37,10 @@ type Template interface {
        Lookup(name string) *template.Template
        Templates() []*template.Template
        New(name string) *template.Template
+       GetClone() *template.Template
        LoadTemplates(absPath string)
        LoadTemplatesWithPrefix(absPath, prefix string)
+       MarkReady()
        AddTemplate(name, tpl string) error
        AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error
        AddInternalTemplate(prefix, name, tpl string) error
@@ -53,6 +55,7 @@ type templateErr struct {
 
 type GoHTMLTemplate struct {
        template.Template
+       clone  *template.Template
        errors []*templateErr
 }
 
@@ -109,12 +112,12 @@ func executeTemplate(context interface{}, w io.Writer, layouts ...string) {
 
                name := layout
 
-               if localTemplates.Lookup(name) == nil {
+               if Lookup(name) == nil {
                        name = layout + ".html"
                }
 
-               if localTemplates.Lookup(name) != nil {
-                       err := localTemplates.ExecuteTemplate(w, name, context)
+               if templ := Lookup(name); templ != nil {
+                       err := templ.Execute(w, context)
                        if err != nil {
                                jww.ERROR.Println(err, "in", name)
                        }
@@ -135,11 +138,49 @@ func ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML
        return template.HTML(b.String())
 }
 
+func Lookup(name string) *template.Template {
+       return (tmpl.(*GoHTMLTemplate)).Lookup(name)
+}
+
+func (t *GoHTMLTemplate) Lookup(name string) *template.Template {
+
+       templ := localTemplates.Lookup(name)
+
+       if templ != nil {
+               return templ
+       }
+
+       if t.clone != nil {
+               return t.clone.Lookup(name)
+       }
+
+       return nil
+
+}
+
+func (t *GoHTMLTemplate) GetClone() *template.Template {
+       return t.clone
+}
+
 func (t *GoHTMLTemplate) LoadEmbedded() {
        t.EmbedShortcodes()
        t.EmbedTemplates()
 }
 
+// MarkReady marks the template as "ready for execution". No changes allowed
+// after this is set.
+func (t *GoHTMLTemplate) MarkReady() {
+       if t.clone == nil {
+               t.clone = template.Must(t.Template.Clone())
+       }
+}
+
+func (t *GoHTMLTemplate) checkState() {
+       if t.clone != nil {
+               panic("template is cloned and cannot be modfified")
+       }
+}
+
 func (t *GoHTMLTemplate) AddInternalTemplate(prefix, name, tpl string) error {
        if prefix != "" {
                return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
@@ -153,6 +194,7 @@ func (t *GoHTMLTemplate) AddInternalShortcode(name, content string) error {
 }
 
 func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error {
+       t.checkState()
        _, err := t.New(name).Parse(tpl)
        if err != nil {
                t.errors = append(t.errors, &templateErr{name: name, err: err})
@@ -161,6 +203,7 @@ func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error {
 }
 
 func (t *GoHTMLTemplate) AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error {
+       t.checkState()
        var base, inner *ace.File
        name = name[:len(name)-len(filepath.Ext(innerPath))] + ".html"
 
@@ -188,6 +231,7 @@ func (t *GoHTMLTemplate) AddAceTemplate(name, basePath, innerPath string, baseCo
 }
 
 func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) error {
+       t.checkState()
        // get the suffix and switch on that
        ext := filepath.Ext(path)
        switch ext {