diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go index 1e8f16c79..df5b970c9 100644 --- a/hugolib/handler_test.go +++ b/hugolib/handler_test.go @@ -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", "")) - must(s.addTemplate("head_abs", "")) + s.prepTemplates( + "_default/single.html", "{{.Content}}", + "head", "", + "head_abs", "") // From site_test.go createAndRenderPages(t, s) diff --git a/hugolib/robotstxt_test.go b/hugolib/robotstxt_test.go index c964c0231..dfbaefea1 100644 --- a/hugolib/robotstxt_test.go +++ b/hugolib/robotstxt_test.go @@ -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) diff --git a/hugolib/rss_test.go b/hugolib/rss_test.go index 3a9a0cf07..a6a4f82e5 100644 --- a/hugolib/rss_test.go +++ b/hugolib/rss_test.go @@ -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) diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 306b53e84..07af6b3a6 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -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) diff --git a/hugolib/site.go b/hugolib/site.go index 02a310f32..cee614d86 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -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 { diff --git a/hugolib/site_test.go b/hugolib/site_test.go index f84ae5902..dec6cb29c 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -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", "RSS")) - must(s.addTemplate("sitemap.xml", "SITEMAP")) + 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", "RSS", + "sitemap.xml", "SITEMAP") 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", "")) - must(s.addTemplate("head_abs", "")) + s.prepTemplates( + "_default/single.html", "{{.Content}}", + "head", "", + "head_abs", "") 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) diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index f25b5dfaf..33843ebcf 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -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) diff --git a/hugolib/siteinfo_test.go b/hugolib/siteinfo_test.go index 4d075dc4d..4e4c924e9 100644 --- a/hugolib/siteinfo_test.go +++ b/hugolib/siteinfo_test.go @@ -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) diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go index bb3eaccd2..34f2d3477 100644 --- a/hugolib/sitemap_test.go +++ b/hugolib/sitemap_test.go @@ -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) diff --git a/tpl/template.go b/tpl/template.go index 2793e4dd5..b8405d580 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -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 {