diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go index e2eb4706b..61c40cf01 100644 --- a/hugolib/embedded_shortcodes_test.go +++ b/hugolib/embedded_shortcodes_test.go @@ -19,16 +19,17 @@ import ( "html/template" "net/url" "os" - "path/filepath" "regexp" "strings" "testing" "io/ioutil" "log" + "path/filepath" + + "github.com/spf13/hugo/tpl" "github.com/spf13/hugo/helpers" - "github.com/spf13/hugo/tpl" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -106,9 +107,8 @@ void do(); "(?s)^\n
.*?void.*?do.*?().*?
\n$", }, } { - templ := tpl.New(logger) p, _ := pageFromString(simplePage, "simple.md") - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) if err != nil { t.Fatalf("[%d] Handle shortcode error", i) @@ -150,9 +150,8 @@ func TestShortcodeFigure(t *testing.T) { "(?s)^\n
.*?.*?
.*?

.*?.*?Hugo logo.*?.*?

.*?
.*?
\n$", }, } { - templ := tpl.New(logger) p, _ := pageFromString(simplePage, "simple.md") - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) matched, err := regexp.MatchString(this.expected, output) @@ -175,9 +174,8 @@ func TestShortcodeSpeakerdeck(t *testing.T) { "(?s)^$", }, } { - templ := tpl.New(logger) p, _ := pageFromString(simplePage, "simple.md") - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) matched, err := regexp.MatchString(this.expected, output) @@ -210,9 +208,8 @@ func TestShortcodeYoutube(t *testing.T) { "(?s)^\n
.*?.*?
$", }, } { - templ := tpl.New(logger) p, _ := pageFromString(simplePage, "simple.md") - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) matched, err := regexp.MatchString(this.expected, output) @@ -245,9 +242,8 @@ func TestShortcodeVimeo(t *testing.T) { "(?s)^
.*?.*?
$", }, } { - templ := tpl.New(logger) p, _ := pageFromString(simplePage, "simple.md") - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) matched, err := regexp.MatchString(this.expected, output) @@ -274,9 +270,8 @@ func TestShortcodeGist(t *testing.T) { "(?s)^$", }, } { - templ := tpl.New(logger) p, _ := pageFromString(simplePage, "simple.md") - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) matched, err := regexp.MatchString(this.expected, output) @@ -313,13 +308,14 @@ func TestShortcodeTweet(t *testing.T) { }, } - templ := tpl.New(logger) - templ.Lookup("").Funcs(tweetFuncMap) + p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error { + templ.Funcs(tweetFuncMap) + return nil + }) - p, _ := pageFromString(simplePage, "simple.md") cacheFileID := viper.GetString("cacheDir") + url.QueryEscape("https://api.twitter.com/1/statuses/oembed.json?id=666616452582129664") defer os.Remove(cacheFileID) - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) matched, err := regexp.MatchString(this.expected, output) @@ -353,7 +349,7 @@ func TestShortcodeInstagram(t *testing.T) { }, } { // overload getJSON to return mock API response from Instagram - tweetFuncMap := template.FuncMap{ + instagramFuncMap := template.FuncMap{ "getJSON": func(urlParts ...string) interface{} { var v interface{} err := json.Unmarshal([]byte(this.resp), &v) @@ -365,13 +361,14 @@ func TestShortcodeInstagram(t *testing.T) { }, } - templ := tpl.New(logger) - templ.Lookup("").Funcs(tweetFuncMap) + p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error { + templ.Funcs(instagramFuncMap) + return nil + }) - p, _ := pageFromString(simplePage, "simple.md") cacheFileID := viper.GetString("cacheDir") + url.QueryEscape("https://api.instagram.com/oembed/?url=https://instagram.com/p/BMokmydjG-M/&hidecaption="+this.hidecaption) defer os.Remove(cacheFileID) - output, err := HandleShortcodes(this.in, p, templ) + output, err := HandleShortcodes(this.in, p) if err != nil { t.Fatalf("[%d] Failed to render shortcodes", i) diff --git a/hugolib/handler_base.go b/hugolib/handler_base.go index 2029b7ac8..5b094fe16 100644 --- a/hugolib/handler_base.go +++ b/hugolib/handler_base.go @@ -15,12 +15,11 @@ package hugolib import ( "github.com/spf13/hugo/source" - "github.com/spf13/hugo/tpl" ) type Handler interface { FileConvert(*source.File, *Site) HandledResult - PageConvert(*Page, tpl.Template) HandledResult + PageConvert(*Page) HandledResult Read(*source.File, *Site) HandledResult Extensions() []string } diff --git a/hugolib/handler_file.go b/hugolib/handler_file.go index def0408a4..71b895603 100644 --- a/hugolib/handler_file.go +++ b/hugolib/handler_file.go @@ -18,7 +18,6 @@ import ( "github.com/dchest/cssmin" "github.com/spf13/hugo/source" - "github.com/spf13/hugo/tpl" ) func init() { @@ -32,7 +31,7 @@ func (h basicFileHandler) Read(f *source.File, s *Site) HandledResult { return HandledResult{file: f} } -func (h basicFileHandler) PageConvert(*Page, tpl.Template) HandledResult { +func (h basicFileHandler) PageConvert(*Page) HandledResult { return HandledResult{} } diff --git a/hugolib/handler_meta.go b/hugolib/handler_meta.go index 74a20e83a..0f92f0c6c 100644 --- a/hugolib/handler_meta.go +++ b/hugolib/handler_meta.go @@ -74,7 +74,7 @@ func (mh *MetaHandle) Convert(i interface{}, s *Site, results HandleResults) { return } - results <- h.PageConvert(p, s.owner.tmpl) + results <- h.PageConvert(p) } } diff --git a/hugolib/handler_page.go b/hugolib/handler_page.go index 83ced95b5..2026f2bbf 100644 --- a/hugolib/handler_page.go +++ b/hugolib/handler_page.go @@ -19,7 +19,6 @@ import ( "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/source" - "github.com/spf13/hugo/tpl" "github.com/spf13/viper" ) @@ -56,8 +55,8 @@ type markdownHandler struct { } func (h markdownHandler) Extensions() []string { return []string{"mdown", "markdown", "md"} } -func (h markdownHandler) PageConvert(p *Page, t tpl.Template) HandledResult { - return commonConvert(p, t) +func (h markdownHandler) PageConvert(p *Page) HandledResult { + return commonConvert(p) } type htmlHandler struct { @@ -65,7 +64,9 @@ type htmlHandler struct { } func (h htmlHandler) Extensions() []string { return []string{"html", "htm"} } -func (h htmlHandler) PageConvert(p *Page, t tpl.Template) HandledResult { + +// TODO(bep) globals use p.s.t +func (h htmlHandler) PageConvert(p *Page) HandledResult { if p.rendered { panic(fmt.Sprintf("Page %q already rendered, does not need conversion", p.BaseFileName())) } @@ -73,7 +74,7 @@ func (h htmlHandler) PageConvert(p *Page, t tpl.Template) HandledResult { // Work on a copy of the raw content from now on. p.createWorkContentCopy() - p.ProcessShortcodes(t) + p.ProcessShortcodes() return HandledResult{err: nil} } @@ -83,8 +84,8 @@ type asciidocHandler struct { } func (h asciidocHandler) Extensions() []string { return []string{"asciidoc", "adoc", "ad"} } -func (h asciidocHandler) PageConvert(p *Page, t tpl.Template) HandledResult { - return commonConvert(p, t) +func (h asciidocHandler) PageConvert(p *Page) HandledResult { + return commonConvert(p) } type rstHandler struct { @@ -92,8 +93,8 @@ type rstHandler struct { } func (h rstHandler) Extensions() []string { return []string{"rest", "rst"} } -func (h rstHandler) PageConvert(p *Page, t tpl.Template) HandledResult { - return commonConvert(p, t) +func (h rstHandler) PageConvert(p *Page) HandledResult { + return commonConvert(p) } type mmarkHandler struct { @@ -101,11 +102,11 @@ type mmarkHandler struct { } func (h mmarkHandler) Extensions() []string { return []string{"mmark"} } -func (h mmarkHandler) PageConvert(p *Page, t tpl.Template) HandledResult { - return commonConvert(p, t) +func (h mmarkHandler) PageConvert(p *Page) HandledResult { + return commonConvert(p) } -func commonConvert(p *Page, t tpl.Template) HandledResult { +func commonConvert(p *Page) HandledResult { if p.rendered { panic(fmt.Sprintf("Page %q already rendered, does not need conversion", p.BaseFileName())) } @@ -113,7 +114,7 @@ func commonConvert(p *Page, t tpl.Template) HandledResult { // Work on a copy of the raw content from now on. p.createWorkContentCopy() - p.ProcessShortcodes(t) + p.ProcessShortcodes() // TODO(bep) these page handlers need to be re-evaluated, as it is hard to // process a page in isolation. See the new preRender func. diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index d1eb493a5..0d9105ef6 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -34,7 +34,6 @@ import ( type HugoSites struct { Sites []*Site - tmpl tpl.Template runMode runmode multilingual *Multilingual @@ -50,7 +49,14 @@ type deps struct { // The logger to use. log *jww.Notepad - // TODO(bep) next in line: Viper, hugofs, template + tmpl *tpl.GoHTMLTemplate + + // TODO(bep) next in line: Viper, hugofs +} + +func (d *deps) refreshTemplates(withTemplate ...func(templ tpl.Template) error) { + d.tmpl = tpl.New(d.log, withTemplate...) + d.tmpl.PrintErrors() // TODO(bep) globals error handling } func newDeps(cfg DepsCfg) *deps { @@ -59,11 +65,12 @@ func newDeps(cfg DepsCfg) *deps { if logger == nil { // TODO(bep) globals default log level //logger = jww.NewNotepad(jww.LevelError, jww.LevelWarn, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) - logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) + logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) } return &deps{ - log: logger, + log: logger, + tmpl: tpl.New(logger, cfg.WithTemplate...), } } @@ -76,8 +83,16 @@ func newHugoSites(cfg DepsCfg, sites ...*Site) (*HugoSites, error) { return nil, err } + var d *deps + + if sites[0].deps != nil { + d = sites[0].deps + } else { + d = newDeps(cfg) + } + h := &HugoSites{ - deps: newDeps(cfg), + deps: d, multilingual: langConfig, Sites: sites} @@ -91,18 +106,24 @@ func newHugoSites(cfg DepsCfg, sites ...*Site) (*HugoSites, error) { // NewHugoSitesFromConfiguration creates HugoSites from the global Viper config. // TODO(bep) globals rename this when all the globals are gone. func NewHugoSitesFromConfiguration(cfg DepsCfg) (*HugoSites, error) { - sites, err := createSitesFromConfig() + sites, err := createSitesFromConfig(cfg) if err != nil { return nil, err } return newHugoSites(cfg, sites...) } -func createSitesFromConfig() ([]*Site, error) { +func createSitesFromConfig(cfg DepsCfg) ([]*Site, error) { + deps := newDeps(cfg) + return createSitesFromDeps(deps) +} + +func createSitesFromDeps(deps *deps) ([]*Site, error) { var sites []*Site multilingual := viper.GetStringMap("languages") + if len(multilingual) == 0 { - sites = append(sites, newSite(helpers.NewDefaultLanguage())) + sites = append(sites, newSite(helpers.NewDefaultLanguage(), deps)) } if len(multilingual) > 0 { @@ -115,7 +136,7 @@ func createSitesFromConfig() ([]*Site, error) { } for _, lang := range languages { - sites = append(sites, newSite(lang)) + sites = append(sites, newSite(lang, deps)) } } @@ -134,7 +155,7 @@ func (h *HugoSites) reset() { func (h *HugoSites) createSitesFromConfig() error { - sites, err := createSitesFromConfig() + sites, err := createSitesFromDeps(h.deps) if err != nil { return err @@ -192,6 +213,8 @@ type DepsCfg struct { // The Logger to use. Logger *jww.Notepad + + WithTemplate []func(templ tpl.Template) error } func (h *HugoSites) renderCrossSitesArtifacts() error { diff --git a/hugolib/page.go b/hugolib/page.go index 037f808fc..0f6973297 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -40,7 +40,6 @@ import ( bp "github.com/spf13/hugo/bufferpool" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" - "github.com/spf13/hugo/tpl" "github.com/spf13/viper" ) @@ -1284,7 +1283,7 @@ func (p *Page) Render(layout ...string) template.HTML { l = p.layouts() } - return tpl.ExecuteTemplateToHTML(p, l...) + return p.s.tmpl.ExecuteTemplateToHTML(p, l...) } func (p *Page) determineMarkupType() string { @@ -1399,8 +1398,8 @@ func (p *Page) SaveSource() error { return p.SaveSourceAs(p.FullFilePath()) } -func (p *Page) ProcessShortcodes(t tpl.Template) { - tmpContent, tmpContentShortCodes, _ := extractAndRenderShortcodes(string(p.workContent), p, t) +func (p *Page) ProcessShortcodes() { + tmpContent, tmpContentShortCodes, _ := extractAndRenderShortcodes(string(p.workContent), p) p.workContent = []byte(tmpContent) p.contentShortCodes = tmpContentShortCodes } diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 8e9dc756f..78610d638 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -151,8 +151,8 @@ func (sc shortcode) String() string { // HandleShortcodes does all in one go: extract, render and replace // only used for testing -func HandleShortcodes(stringToParse string, page *Page, t tpl.Template) (string, error) { - tmpContent, tmpShortcodes, err := extractAndRenderShortcodes(stringToParse, page, t) +func HandleShortcodes(stringToParse string, page *Page) (string, error) { + tmpContent, tmpShortcodes, err := extractAndRenderShortcodes(stringToParse, page) if err != nil { return "", err @@ -210,8 +210,8 @@ const innerNewlineRegexp = "\n" const innerCleanupRegexp = `\A

(.*)

\n\z` const innerCleanupExpand = "$1" -func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page, t tpl.Template) string { - tmpl := getShortcodeTemplate(sc.name, t) +func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page) string { + tmpl := getShortcodeTemplate(sc.name, p.s.tmpl) if tmpl == nil { p.s.log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName()) @@ -230,7 +230,7 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page, t tpl.Tem case string: inner += innerData.(string) case shortcode: - inner += renderShortcode(innerData.(shortcode), data, p, t) + inner += renderShortcode(innerData.(shortcode), data, p) default: p.s.log.ERROR.Printf("Illegal state on shortcode rendering of '%s' in page %s. Illegal type in inner data: %s ", sc.name, p.BaseFileName(), reflect.TypeOf(innerData)) @@ -280,9 +280,9 @@ func renderShortcode(sc shortcode, parent *ShortcodeWithPage, p *Page, t tpl.Tem return renderShortcodeWithPage(tmpl, data) } -func extractAndRenderShortcodes(stringToParse string, p *Page, t tpl.Template) (string, map[string]func() (string, error), error) { +func extractAndRenderShortcodes(stringToParse string, p *Page) (string, map[string]func() (string, error), error) { - content, shortcodes, err := extractShortcodes(stringToParse, p, t) + content, shortcodes, err := extractShortcodes(stringToParse, p) if err != nil { // try to render what we have whilst logging the error @@ -293,7 +293,7 @@ func extractAndRenderShortcodes(stringToParse string, p *Page, t tpl.Template) ( // TODO(bep) refactor this p.shortcodes = shortcodes - renderedShortcodes := renderShortcodes(shortcodes, p, t) + renderedShortcodes := renderShortcodes(shortcodes, p) return content, renderedShortcodes, err @@ -315,7 +315,7 @@ func executeShortcodeFuncMap(funcs map[string]func() (string, error)) (map[strin return result, nil } -func renderShortcodes(shortcodes map[string]shortcode, p *Page, t tpl.Template) map[string]func() (string, error) { +func renderShortcodes(shortcodes map[string]shortcode, p *Page) map[string]func() (string, error) { renderedShortcodes := make(map[string]func() (string, error)) for key, sc := range shortcodes { @@ -324,7 +324,7 @@ func renderShortcodes(shortcodes map[string]shortcode, p *Page, t tpl.Template) renderedShortcodes[key] = emptyShortcodeFn } else { shorctode := sc - renderedShortcodes[key] = func() (string, error) { return renderShortcode(shorctode, nil, p, t), nil } + renderedShortcodes[key] = func() (string, error) { return renderShortcode(shorctode, nil, p), nil } } } @@ -336,7 +336,7 @@ var errShortCodeIllegalState = errors.New("Illegal shortcode state") // pageTokens state: // - before: positioned just before the shortcode start // - after: shortcode(s) consumed (plural when they are nested) -func extractShortcode(pt *pageTokens, p *Page, t tpl.Template) (shortcode, error) { +func extractShortcode(pt *pageTokens, p *Page) (shortcode, error) { sc := shortcode{} var isInner = false @@ -357,7 +357,7 @@ Loop: if cnt > 0 { // nested shortcode; append it to inner content pt.backup3(currItem, next) - nested, err := extractShortcode(pt, p, t) + nested, err := extractShortcode(pt, p) if err == nil { sc.inner = append(sc.inner, nested) } else { @@ -398,7 +398,7 @@ Loop: sc.inner = append(sc.inner, currItem.val) case tScName: sc.name = currItem.val - tmpl := getShortcodeTemplate(sc.name, t) + tmpl := getShortcodeTemplate(sc.name, p.s.tmpl) if tmpl == nil { return sc, fmt.Errorf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName()) @@ -454,7 +454,7 @@ Loop: return sc, nil } -func extractShortcodes(stringToParse string, p *Page, t tpl.Template) (string, map[string]shortcode, error) { +func extractShortcodes(stringToParse string, p *Page) (string, map[string]shortcode, error) { shortCodes := make(map[string]shortcode) @@ -492,7 +492,7 @@ Loop: case tLeftDelimScWithMarkup, tLeftDelimScNoMarkup: // let extractShortcode handle left delim (will do so recursively) pt.backup() - if currShortcode, err = extractShortcode(pt, p, t); err != nil { + if currShortcode, err = extractShortcode(pt, p); err != nil { return result.String(), shortCodes, err } diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 01cdd97ae..243705345 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -32,8 +32,13 @@ import ( ) // TODO(bep) remove -func pageFromString(in, filename string) (*Page, error) { - return pageTestSite.NewPageFrom(strings.NewReader(in), filename) +func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template) error) (*Page, error) { + s := pageTestSite + if len(withTemplate) > 0 { + // Have to create a new site + s = NewSiteDefaultLang(withTemplate...) + } + return s.NewPageFrom(strings.NewReader(in), filename) } func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) { @@ -83,10 +88,10 @@ title: "Title" } func TestShortcodeGoFuzzReports(t *testing.T) { - tem := tpl.New(logger) - tem.AddInternalShortcode("sc.html", `foo`) - p, _ := pageFromString(simplePage, "simple.md") + p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error { + return templ.AddInternalShortcode("sc.html", `foo`) + }) for i, this := range []struct { data string @@ -94,7 +99,7 @@ func TestShortcodeGoFuzzReports(t *testing.T) { }{ {"{{}} void do(); {{< /highlight >}}` p, _ := pageFromString(simplePage, "simple.md") - output, err := HandleShortcodes(code, p, templ) + output, err := HandleShortcodes(code, p) if err != nil { t.Fatal("Handle shortcode error", err) @@ -379,16 +382,17 @@ func TestExtractShortcodes(t *testing.T) { fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""}, } { - p, _ := pageFromString(simplePage, "simple.md") - tem := tpl.New(logger) - tem.AddInternalShortcode("tag.html", `tag`) - tem.AddInternalShortcode("sc1.html", `sc1`) - tem.AddInternalShortcode("sc2.html", `sc2`) - tem.AddInternalShortcode("inner.html", `{{with .Inner }}{{ . }}{{ end }}`) - tem.AddInternalShortcode("inner2.html", `{{.Inner}}`) - tem.AddInternalShortcode("inner3.html", `{{.Inner}}`) + p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.Template) error { + templ.AddInternalShortcode("tag.html", `tag`) + templ.AddInternalShortcode("sc1.html", `sc1`) + templ.AddInternalShortcode("sc2.html", `sc2`) + templ.AddInternalShortcode("inner.html", `{{with .Inner }}{{ . }}{{ end }}`) + templ.AddInternalShortcode("inner2.html", `{{.Inner}}`) + templ.AddInternalShortcode("inner3.html", `{{.Inner}}`) + return nil + }) - content, shortCodes, err := extractShortcodes(this.input, p, tem) + content, shortCodes, err := extractShortcodes(this.input, p) if b, ok := this.expect.(bool); ok && !b { if err == nil { diff --git a/hugolib/site.go b/hugolib/site.go index 0abc4cb34..c887a9305 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -115,19 +115,23 @@ func (s *Site) reset() *Site { } // newSite creates a new site in the given language. -func newSite(lang *helpers.Language) *Site { +func newSite(lang *helpers.Language, deps *deps, withTemplate ...func(templ tpl.Template) error) *Site { c := newPageCollections() - // TODO(bep) globals (also see other Site creation places) - deps := newDeps(DepsCfg{}) // TODO(bep) globals viper.Set("currentContentLanguage", lang) + + if deps == nil { + depsCfg := DepsCfg{WithTemplate: withTemplate} + deps = newDeps(depsCfg) + } + return &Site{deps: deps, Language: lang, PageCollections: c, Info: newSiteInfo(siteBuilderCfg{pageCollections: c, language: lang})} } // NewSiteDefaultLang creates a new site in the default language. -func NewSiteDefaultLang() *Site { - return newSite(helpers.NewDefaultLanguage()) +func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) *Site { + return newSite(helpers.NewDefaultLanguage(), nil, withTemplate...) } // Convenience func used in tests. @@ -656,24 +660,23 @@ func (s *Site) reProcess(events []fsnotify.Event) (whatChanged, error) { } -func (s *Site) loadTemplates() { - s.owner.tmpl = tpl.InitializeT(s.log) - s.owner.tmpl.LoadTemplates(s.absLayoutDir()) - if s.hasTheme() { - s.owner.tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme") - } -} - func (s *Site) prepTemplates(withTemplate func(templ tpl.Template) error) error { - s.loadTemplates() - if withTemplate != nil { - if err := withTemplate(s.owner.tmpl); err != nil { - return err + wt := func(tmpl tpl.Template) error { + // TODO(bep) global error handling + tmpl.LoadTemplates(s.absLayoutDir()) + if s.hasTheme() { + tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme") } + if withTemplate != nil { + if err := withTemplate(tmpl); err != nil { + return err + } + } + return nil } - s.owner.tmpl.MarkReady() + s.refreshTemplates(wt) return nil } @@ -778,6 +781,7 @@ func (s *Site) process(config BuildCfg) (err error) { if err = s.initialize(); err != nil { return } + s.prepTemplates(config.withTemplate) s.owner.tmpl.PrintErrors() s.timerStep("initialize & template prep") diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 14b75a112..342cae615 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -109,8 +109,13 @@ func TestRenderWithInvalidTemplate(t *testing.T) { t.Fatalf("Got build error: %s", err) } - if s.log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError) != 1 { - t.Fatalf("Expecting the template to log an ERROR") + errCount := s.log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError) + + // TODO(bep) globals clean up the template error handling + // The template errors are stored in a slice etc. so we get 4 log entries + // When we should get only 1 + if errCount == 0 { + t.Fatalf("Expecting the template to log 1 ERROR, got %d", errCount) } } diff --git a/tpl/template.go b/tpl/template.go index db6a912ff..b26490f0c 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -30,10 +30,8 @@ import ( "github.com/yosssi/ace" ) -var localTemplates *template.Template - -// TODO(bep) globals get rid of the reset of the jww.ERR etc. -var tmpl *GoHTMLTemplate +// TODO(bep) globals get rid of the rest of the jww.ERR etc. +//var tmpl *GoHTMLTemplate // TODO(bep) an interface with hundreds of methods ... remove it. // And unexport most of these methods. @@ -45,13 +43,13 @@ type Template interface { GetClone() *template.Template LoadTemplates(absPath string) LoadTemplatesWithPrefix(absPath, prefix string) - MarkReady() AddTemplate(name, tpl string) error AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error AddAceTemplate(name, basePath, innerPath string, baseContent, innerContent []byte) error AddInternalTemplate(prefix, name, tpl string) error AddInternalShortcode(name, tpl string) error PrintErrors() + Funcs(funcMap template.FuncMap) } type templateErr struct { @@ -60,7 +58,8 @@ type templateErr struct { } type GoHTMLTemplate struct { - template.Template + *template.Template + clone *template.Template // a separate storage for the overlays created from cloned master templates. @@ -69,41 +68,54 @@ type GoHTMLTemplate struct { errors []*templateErr + funcster *templateFuncster + // TODO(bep) globals template log *jww.Notepad } -// InitializeT resets the internal template state to its initial state -func InitializeT(logger *jww.Notepad) *GoHTMLTemplate { - tmpl = New(logger) - return tmpl -} - // New returns a new Hugo Template System // with all the additional features, templates & functions -func New(logger *jww.Notepad) *GoHTMLTemplate { - var templates = &GoHTMLTemplate{ - Template: *template.New(""), +func New(logger *jww.Notepad, withTemplate ...func(templ Template) error) *GoHTMLTemplate { + tmpl := &GoHTMLTemplate{ + Template: template.New(""), overlays: make(map[string]*template.Template), errors: make([]*templateErr, 0), log: logger, } - localTemplates = &templates.Template + tmpl.funcster = newTemplateFuncster(tmpl) // The URL funcs in the funcMap is somewhat language dependent, // so we need to wait until the language and site config is loaded. - initFuncMap() + // TODO(bep) globals + tmpl.funcster.initFuncMap() - for k, v := range funcMap { + // TODO(bep) globals + for k, v := range tmpl.funcster.funcMap { amber.FuncMap[k] = v } - templates.Funcs(funcMap) - templates.LoadEmbedded() - return templates + + tmpl.LoadEmbedded() + + for _, wt := range withTemplate { + err := wt(tmpl) + if err != nil { + tmpl.errors = append(tmpl.errors, &templateErr{"init", err}) + } + + } + + tmpl.markReady() + + return tmpl } -func partial(name string, contextList ...interface{}) template.HTML { +func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) { + t.Template.Funcs(funcMap) +} + +func (t *GoHTMLTemplate) partial(name string, contextList ...interface{}) template.HTML { if strings.HasPrefix("partials/", name) { name = name[8:] } @@ -114,16 +126,16 @@ func partial(name string, contextList ...interface{}) template.HTML { } else { context = contextList[0] } - return ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name) + return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name) } -func executeTemplate(context interface{}, w io.Writer, layouts ...string) { +func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) { var worked bool for _, layout := range layouts { - templ := Lookup(layout) + templ := t.Lookup(layout) if templ == nil { layout += ".html" - templ = Lookup(layout) + templ = t.Lookup(layout) } if templ != nil { @@ -136,28 +148,20 @@ func executeTemplate(context interface{}, w io.Writer, layouts ...string) { } } if !worked { - tmpl.log.ERROR.Println("Unable to render", layouts) - tmpl.log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts) + t.log.ERROR.Println("Unable to render", layouts) + t.log.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts) } } -func ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML { +func (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML { b := bp.GetBuffer() defer bp.PutBuffer(b) - executeTemplate(context, b, layouts...) + t.executeTemplate(context, b, layouts...) return template.HTML(b.String()) } -func Lookup(name string) *template.Template { - return tmpl.Lookup(name) -} - func (t *GoHTMLTemplate) Lookup(name string) *template.Template { - if templ := localTemplates.Lookup(name); templ != nil { - return templ - } - if t.overlays != nil { if templ, ok := t.overlays[name]; ok { return templ @@ -183,9 +187,9 @@ func (t *GoHTMLTemplate) LoadEmbedded() { t.EmbedTemplates() } -// MarkReady marks the template as "ready for execution". No changes allowed +// markReady marks the template as "ready for execution". No changes allowed // after this is set. -func (t *GoHTMLTemplate) MarkReady() { +func (t *GoHTMLTemplate) markReady() { if t.clone == nil { t.clone = template.Must(t.Template.Clone()) } @@ -522,7 +526,7 @@ func (t *GoHTMLTemplate) LoadTemplates(absPath string) { } func (t *GoHTMLTemplate) PrintErrors() { - for _, e := range t.errors { - t.log.ERROR.Println(e.err) + for i, e := range t.errors { + t.log.ERROR.Println(i, ":", e.err) } } diff --git a/tpl/template_ast_transformers_test.go b/tpl/template_ast_transformers_test.go index c78c521c9..8ffb1cab1 100644 --- a/tpl/template_ast_transformers_test.go +++ b/tpl/template_ast_transformers_test.go @@ -18,7 +18,6 @@ import ( "html/template" - jww "github.com/spf13/jwalterweatherman" "github.com/stretchr/testify/require" ) @@ -265,7 +264,3 @@ P2: {{ .Params.LOWER }} require.Contains(t, result, "P1: P1L") require.Contains(t, result, "P2: P1L") } - -func init() { - jww.SetStdoutThreshold(jww.LevelCritical) -} diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 596902fcd..6c8a9957e 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -54,9 +54,19 @@ import ( _ "image/png" ) -var ( - funcMap template.FuncMap -) +// Some of the template funcs are'nt entirely stateless. +type templateFuncster struct { + t *GoHTMLTemplate + funcMap template.FuncMap + cachedPartials partialCache +} + +func newTemplateFuncster(t *GoHTMLTemplate) *templateFuncster { + return &templateFuncster{ + t: t, + cachedPartials: partialCache{p: make(map[string]template.HTML)}, + } +} // eq returns the boolean truth of arg1 == arg2. func eq(x, y interface{}) bool { @@ -1003,7 +1013,7 @@ func where(seq, key interface{}, args ...interface{}) (interface{}, error) { } // apply takes a map, array, or slice and returns a new slice with the function fname applied over it. -func apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { +func (tf *templateFuncster) apply(seq interface{}, fname string, args ...interface{}) (interface{}, error) { if seq == nil { return make([]interface{}, 0), nil } @@ -1018,7 +1028,7 @@ func apply(seq interface{}, fname string, args ...interface{}) (interface{}, err return nil, errors.New("can't iterate over a nil value") } - fn, found := funcMap[fname] + fn, found := tf.funcMap[fname] if !found { return nil, errors.New("can't find function " + fname) } @@ -1518,41 +1528,39 @@ type partialCache struct { // Get retrieves partial output from the cache based upon the partial name. // If the partial is not found in the cache, the partial is rendered and added // to the cache. -func (c *partialCache) Get(key, name string, context interface{}) (p template.HTML) { +func (tf *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) { var ok bool - c.RLock() - p, ok = c.p[key] - c.RUnlock() + tf.cachedPartials.RLock() + p, ok = tf.cachedPartials.p[key] + tf.cachedPartials.RUnlock() if ok { return p } - c.Lock() - if p, ok = c.p[key]; !ok { - p = partial(name, context) - c.p[key] = p + tf.cachedPartials.Lock() + if p, ok = tf.cachedPartials.p[key]; !ok { + p = tf.t.partial(name, context) + tf.cachedPartials.p[key] = p } - c.Unlock() + tf.cachedPartials.Unlock() return p } -var cachedPartials = partialCache{p: make(map[string]template.HTML)} - // partialCached executes and caches partial templates. An optional variant // string parameter (a string slice actually, but be only use a variadic // argument to make it optional) can be passed so that a given partial can have // multiple uses. The cache is created with name+variant as the key. -func partialCached(name string, context interface{}, variant ...string) template.HTML { +func (tf *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML { key := name if len(variant) > 0 { for i := 0; i < len(variant); i++ { key += variant[i] } } - return cachedPartials.Get(key, name, context) + return tf.Get(key, name, context) } // regexpCache represents a cache of regexp objects protected by a mutex. @@ -2090,8 +2098,8 @@ func getenv(key interface{}) (string, error) { return os.Getenv(skey), nil } -func initFuncMap() { - funcMap = template.FuncMap{ +func (tf *templateFuncster) initFuncMap() { + funcMap := template.FuncMap{ "absURL": absURL, "absLangURL": func(i interface{}) (template.HTML, error) { s, err := cast.ToStringE(i) @@ -2102,7 +2110,7 @@ func initFuncMap() { }, "add": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') }, "after": after, - "apply": apply, + "apply": tf.apply, "base64Decode": base64Decode, "base64Encode": base64Encode, "chomp": chomp, @@ -2147,8 +2155,8 @@ func initFuncMap() { "mul": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') }, "ne": ne, "now": func() time.Time { return time.Now() }, - "partial": partial, - "partialCached": partialCached, + "partial": tf.t.partial, + "partialCached": tf.partialCached, "plainify": plainify, "pluralize": pluralize, "querify": querify, @@ -2195,4 +2203,7 @@ func initFuncMap() { "i18n": i18nTranslate, "T": i18nTranslate, } + + tf.funcMap = funcMap + tf.t.Funcs(funcMap) } diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index c3fbb6ad8..6bbbf0146 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -1960,40 +1960,43 @@ func TestMarkdownify(t *testing.T) { } func TestApply(t *testing.T) { + + f := newTestFuncster() + strings := []interface{}{"a\n", "b\n"} noStringers := []interface{}{tstNoStringer{}, tstNoStringer{}} - chomped, _ := apply(strings, "chomp", ".") + chomped, _ := f.apply(strings, "chomp", ".") assert.Equal(t, []interface{}{template.HTML("a"), template.HTML("b")}, chomped) - chomped, _ = apply(strings, "chomp", "c\n") + chomped, _ = f.apply(strings, "chomp", "c\n") assert.Equal(t, []interface{}{template.HTML("c"), template.HTML("c")}, chomped) - chomped, _ = apply(nil, "chomp", ".") + chomped, _ = f.apply(nil, "chomp", ".") assert.Equal(t, []interface{}{}, chomped) - _, err := apply(strings, "apply", ".") + _, err := f.apply(strings, "apply", ".") if err == nil { t.Errorf("apply with apply should fail") } var nilErr *error - _, err = apply(nilErr, "chomp", ".") + _, err = f.apply(nilErr, "chomp", ".") if err == nil { t.Errorf("apply with nil in seq should fail") } - _, err = apply(strings, "dobedobedo", ".") + _, err = f.apply(strings, "dobedobedo", ".") if err == nil { t.Errorf("apply with unknown func should fail") } - _, err = apply(noStringers, "chomp", ".") + _, err = f.apply(noStringers, "chomp", ".") if err == nil { t.Errorf("apply when func fails should fail") } - _, err = apply(tstNoStringer{}, "chomp", ".") + _, err = f.apply(tstNoStringer{}, "chomp", ".") if err == nil { t.Errorf("apply with non-sequence should fail") } @@ -2780,7 +2783,6 @@ func TestPartialCached(t *testing.T) { data.Params = map[string]interface{}{"langCode": "en"} tstInitTemplates() - InitializeT(logger) for i, tc := range testCases { var tmp string if tc.variant != "" { @@ -2831,7 +2833,6 @@ func TestPartialCached(t *testing.T) { } func BenchmarkPartial(b *testing.B) { - InitializeT(logger) tmpl, err := New(logger).New("testroot").Parse(`{{ partial "bench1" . }}`) if err != nil { b.Fatalf("unable to create new html template: %s", err) @@ -2851,7 +2852,6 @@ func BenchmarkPartial(b *testing.B) { } func BenchmarkPartialCached(b *testing.B) { - InitializeT(logger) tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . }}`) if err != nil { b.Fatalf("unable to create new html template: %s", err) @@ -2871,7 +2871,6 @@ func BenchmarkPartialCached(b *testing.B) { } func BenchmarkPartialCachedVariants(b *testing.B) { - InitializeT(logger) tmpl, err := New(logger).New("testroot").Parse(`{{ partialCached "bench1" . "header" }}`) if err != nil { b.Fatalf("unable to create new html template: %s", err) @@ -2889,3 +2888,7 @@ func BenchmarkPartialCachedVariants(b *testing.B) { buf.Reset() } } + +func newTestFuncster() *templateFuncster { + return New(logger).funcster +} diff --git a/tpl/template_test.go b/tpl/template_test.go index 2f4946598..cf691858b 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -55,8 +55,6 @@ html lang=en for _, root := range []string{"", os.TempDir()} { - templ := New(logger) - basePath := this.basePath innerPath := this.innerPath @@ -70,17 +68,20 @@ html lang=en d := "DATA" - err := templ.AddAceTemplate("mytemplate.ace", basePath, innerPath, - []byte(this.baseContent), []byte(this.innerContent)) + templ := New(logger, func(templ Template) error { + return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath, + []byte(this.baseContent), []byte(this.innerContent)) - if err != nil && this.expectErr == 0 { - t.Errorf("Test %d with root '%s' errored: %s", i, root, err) - } else if err == nil && this.expectErr == 1 { + }) + + if len(templ.errors) > 0 && this.expectErr == 0 { + t.Errorf("Test %d with root '%s' errored: %v", i, root, templ.errors) + } else if len(templ.errors) == 0 && this.expectErr == 1 { t.Errorf("#1 Test %d with root '%s' should have errored", i, root) } var buff bytes.Buffer - err = templ.ExecuteTemplate(&buff, "mytemplate.html", d) + err := templ.ExecuteTemplate(&buff, "mytemplate.html", d) if err != nil && this.expectErr == 0 { t.Errorf("Test %d with root '%s' errored: %s", i, root, err) @@ -245,7 +246,6 @@ func TestTplGoFuzzReports(t *testing.T) { // Issue #1095 {"{{apply .C \"urlize\" " + "\".\"}}", 2}} { - templ := New(logger) d := &Data{ A: 42, @@ -258,15 +258,17 @@ func TestTplGoFuzzReports(t *testing.T) { H: "a,b,c,d,e,f", } - err := templ.AddTemplate("fuzz", this.data) + templ := New(logger, func(templ Template) error { + return templ.AddTemplate("fuzz", this.data) - if err != nil && this.expectErr == 0 { - t.Fatalf("Test %d errored: %s", i, err) - } else if err == nil && this.expectErr == 1 { - t.Fatalf("#1 Test %d should have errored", i) + }) + + if len(templ.errors) > 0 && this.expectErr == 0 { + t.Errorf("Test %d errored: %v", i, templ.errors) + } else if len(templ.errors) == 0 && this.expectErr == 1 { + t.Errorf("#1 Test %d should have errored", i) } - - err = templ.ExecuteTemplate(ioutil.Discard, "fuzz", d) + err := templ.ExecuteTemplate(ioutil.Discard, "fuzz", d) if err != nil && this.expectErr == 0 { t.Fatalf("Test %d errored: %s", i, err) diff --git a/vendor/vendor.json b/vendor/vendor.json index c5e198a37..f3973936b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -281,10 +281,10 @@ "revisionTime": "2016-11-30T04:45:28Z" }, { - "checksumSHA1": "HWDERqbEvvfLwzP7Dvh2fvu+sng=", + "checksumSHA1": "9pkkhgKp3mwSreiML3plQlQYdLQ=", "path": "github.com/spf13/jwalterweatherman", - "revision": "bccdd23ae5e51bd2b081814db093646c7af3d34d", - "revisionTime": "2017-01-05T10:55:09Z" + "revision": "fa7ca7e836cf3a8bb4ebf799f472c12d7e903d66", + "revisionTime": "2017-01-09T13:33:55Z" }, { "checksumSHA1": "zLJY+lsX1e5OO6gRxQd5RfKgdQY=",