summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-12-10 19:56:44 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-12-12 10:04:35 +0100
commita03c631c420a03f9d90699abdf9be7e4fca0ff61 (patch)
treefcc245c75aa6cc6dc5be40a614700b6aca26c84f
parent167c01530bb295c8b8d35921eb27ffa5bee76dfe (diff)
Rework template handling for function and map lookups
This is a big commit, but it deletes lots of code and simplifies a lot. * Resolving the template funcs at execution time means we don't have to create template clones per site * Having a custom map resolver means that we can remove the AST lower case transformation for the special lower case Params map Not only is the above easier to reason about, it's also faster, especially if you have more than one language, as in the benchmark below: ``` name old time/op new time/op delta SiteNew/Deep_content_tree-16 53.7ms ± 0% 48.1ms ± 2% -10.38% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Deep_content_tree-16 41.0MB ± 0% 36.8MB ± 0% -10.26% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Deep_content_tree-16 481k ± 0% 410k ± 0% -14.66% (p=0.029 n=4+4) ``` This should be even better if you also have lots of templates. Closes #6594
-rw-r--r--commands/server.go23
-rw-r--r--create/content_template_handler.go4
-rw-r--r--deps/deps.go54
-rw-r--r--hugolib/alias.go43
-rw-r--r--hugolib/case_insensitive_test.go74
-rw-r--r--hugolib/embedded_shortcodes_test.go15
-rw-r--r--hugolib/hugo_modules_test.go4
-rw-r--r--hugolib/hugo_sites.go4
-rw-r--r--hugolib/hugo_smoke_test.go21
-rw-r--r--hugolib/page.go2
-rw-r--r--hugolib/page__per_output.go4
-rw-r--r--hugolib/page_test.go2
-rw-r--r--hugolib/shortcode.go6
-rw-r--r--hugolib/shortcode_test.go36
-rw-r--r--hugolib/site.go10
-rw-r--r--hugolib/sitemap_test.go2
-rw-r--r--hugolib/template_engines_test.go97
-rw-r--r--hugolib/testhelpers_test.go4
-rw-r--r--langs/language.go22
-rw-r--r--resources/resource_transformers/templates/execute_as_template.go27
-rw-r--r--scripts/fork_go_templates/main.go2
-rw-r--r--tpl/collections/apply.go13
-rw-r--r--tpl/collections/apply_test.go15
-rw-r--r--tpl/internal/go_templates/texttemplate/exec.go4
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_exec.go38
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_template.go158
-rw-r--r--tpl/internal/go_templates/texttemplate/hugo_template_test.go71
-rw-r--r--tpl/partials/partials.go2
-rw-r--r--tpl/resources/resources.go2
-rw-r--r--tpl/template.go218
-rw-r--r--tpl/tplimpl/embedded/templates.autogen.go1
-rw-r--r--tpl/tplimpl/embedded/templates/alias.html1
-rw-r--r--tpl/tplimpl/shortcodes.go5
-rw-r--r--tpl/tplimpl/template.go1228
-rw-r--r--tpl/tplimpl/templateFuncster.go19
-rw-r--r--tpl/tplimpl/templateProvider.go4
-rw-r--r--tpl/tplimpl/template_ast_transformers.go203
-rw-r--r--tpl/tplimpl/template_ast_transformers_test.go396
-rw-r--r--tpl/tplimpl/template_funcs.go66
-rw-r--r--tpl/tplimpl/template_funcs_test.go8
-rw-r--r--tpl/tplimpl/template_info_test.go2
41 files changed, 1103 insertions, 1807 deletions
diff --git a/commands/server.go b/commands/server.go
index 709181507..7d884096c 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -16,6 +16,7 @@ package commands
import (
"bytes"
"fmt"
+ "io"
"net"
"net/http"
"net/url"
@@ -33,7 +34,6 @@ import (
"github.com/pkg/errors"
"github.com/gohugoio/hugo/livereload"
- "github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
@@ -287,7 +287,7 @@ func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
type fileServer struct {
baseURLs []string
roots []string
- errorTemplate tpl.Template
+ errorTemplate func(err interface{}) (io.Reader, error)
c *commandeer
s *serverCmd
}
@@ -335,8 +335,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
err := f.c.getErrorWithContext()
if err != nil {
w.WriteHeader(500)
- var b bytes.Buffer
- err := f.errorTemplate.Execute(&b, err)
+ r, err := f.errorTemplate(err)
if err != nil {
f.c.logger.ERROR.Println(err)
}
@@ -344,7 +343,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
if !f.c.paused {
port = f.c.Cfg.GetInt("liveReloadPort")
}
- fmt.Fprint(w, injectLiveReloadScript(&b, port))
+ fmt.Fprint(w, injectLiveReloadScript(r, port))
return
}
@@ -422,11 +421,15 @@ func (c *commandeer) serve(s *serverCmd) error {
}
srv := &fileServer{
- baseURLs: baseURLs,
- roots: roots,
- c: c,
- s: s,
- errorTemplate: templ,
+ baseURLs: baseURLs,
+ roots: roots,
+ c: c,
+ s: s,
+ errorTemplate: func(ctx interface{}) (io.Reader, error) {
+ b := &bytes.Buffer{}
+ err := c.hugo().Tmpl.Execute(templ, b, ctx)
+ return b, err
+ },
}
doLiveReload := !c.Cfg.GetBool("disableLiveReload")
diff --git a/create/content_template_handler.go b/create/content_template_handler.go
index 1576fabdb..b70cf02eb 100644
--- a/create/content_template_handler.go
+++ b/create/content_template_handler.go
@@ -129,7 +129,7 @@ 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.TemplateHandler)
+ templateHandler := s.Deps.Tmpl.(tpl.TemplateManager)
templateName := "_text/" + helpers.Filename(archetypeFilename)
if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil {
return nil, errors.Wrapf(err, "Failed to parse archetype file %q:", archetypeFilename)
@@ -138,7 +138,7 @@ func executeArcheTypeAsTemplate(s *hugolib.Site, name, kind, targetPath, archety
templ, _ := templateHandler.Lookup(templateName)
var buff bytes.Buffer
- if err := templ.Execute(&buff, data); err != nil {
+ if err := templateHandler.Execute(templ, &buff, data); err != nil {
return nil, errors.Wrapf(err, "Failed to process archetype file %q:", archetypeFilename)
}
diff --git a/deps/deps.go b/deps/deps.go
index d7b381ce9..ecbba2e56 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -37,8 +37,8 @@ type Deps struct {
// Used to log warnings that may repeat itself many times.
DistinctWarningLog *helpers.DistinctLogger
- // The templates to use. This will usually implement the full tpl.TemplateHandler.
- Tmpl tpl.TemplateFinder `json:"-"`
+ // The templates to use. This will usually implement the full tpl.TemplateManager.
+ Tmpl tpl.TemplateHandler `json:"-"`
// We use this to parse and execute ad-hoc text templates.
TextTmpl tpl.TemplateParseFinder `json:"-"`
@@ -77,7 +77,10 @@ type Deps struct {
OutputFormatsConfig output.Formats
templateProvider ResourceProvider
- WithTemplate func(templ tpl.TemplateHandler) error `json:"-"`
+ WithTemplate func(templ tpl.TemplateManager) error `json:"-"`
+
+ // Used in tests
+ OverloadedTemplateFuncs map[string]interface{}
translationProvider ResourceProvider
@@ -151,8 +154,8 @@ type ResourceProvider interface {
}
// TemplateHandler returns the used tpl.TemplateFinder as tpl.TemplateHandler.
-func (d *Deps) TemplateHandler() tpl.TemplateHandler {
- return d.Tmpl.(tpl.TemplateHandler)
+func (d *Deps) TemplateHandler() tpl.TemplateManager {
+ return d.Tmpl.(tpl.TemplateManager)
}
// LoadResources loads translations and templates.
@@ -239,24 +242,25 @@ func New(cfg DepsCfg) (*Deps, error) {
distinctWarnLogger := helpers.NewDistinctLogger(logger.WARN)
d := &Deps{
- Fs: fs,
- Log: logger,
- DistinctErrorLog: distinctErrorLogger,
- DistinctWarningLog: distinctWarnLogger,
- templateProvider: cfg.TemplateProvider,
- translationProvider: cfg.TranslationProvider,
- WithTemplate: cfg.WithTemplate,
- PathSpec: ps,
- ContentSpec: contentSpec,
- SourceSpec: sp,
- ResourceSpec: resourceSpec,
- Cfg: cfg.Language,
- Language: cfg.Language,
- Site: cfg.Site,
- FileCaches: fileCaches,
- BuildStartListeners: &Listeners{},
- Timeout: time.Duration(timeoutms) * time.Millisecond,
- globalErrHandler: &globalErrHandler{},
+ Fs: fs,
+ Log: logger,
+ DistinctErrorLog: distinctErrorLogger,
+ DistinctWarningLog: distinctWarnLogger,
+ templateProvider: cfg.TemplateProvider,
+ translationProvider: cfg.TranslationProvider,
+ WithTemplate: cfg.WithTemplate,
+ OverloadedTemplateFuncs: cfg.OverloadedTemplateFuncs,
+ PathSpec: ps,
+ ContentSpec: contentSpec,
+ SourceSpec: sp,
+ ResourceSpec: resourceSpec,
+ Cfg: cfg.Language,
+ Language: cfg.Language,
+ Site: cfg.Site,
+ FileCaches: fileCaches,
+ BuildStartListeners: &Listeners{},
+ Timeout: time.Duration(timeoutms) * time.Millisecond,
+ globalErrHandler: &globalErrHandler{},
}
if cfg.Cfg.GetBool("templateMetrics") {
@@ -344,7 +348,9 @@ type DepsCfg struct {
// Template handling.
TemplateProvider ResourceProvider
- WithTemplate func(templ tpl.TemplateHandler) error
+ WithTemplate func(templ tpl.TemplateManager) error
+ // Used in tests
+ OverloadedTemplateFuncs map[string]interface{}
// i18n handling.
TranslationProvider ResourceProvider
diff --git a/hugolib/alias.go b/hugolib/alias.go
index 972f7b01c..c80e7d0d2 100644
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -15,6 +15,7 @@ package hugolib
import (
"bytes"
+ "errors"
"fmt"
"html/template"
"io"
@@ -31,27 +32,15 @@ import (
"github.com/gohugoio/hugo/tpl"
)
-const (
- alias = "<!DOCTYPE html><html><head><title>{{ .Permalink }}</title><link rel=\"canonical\" href=\"{{ .Permalink }}\"/><meta name=\"robots\" content=\"noindex\"><meta charset=\"utf-8\" /><meta http-equiv=\"refresh\" content=\"0; url={{ .Permalink }}\" /></head></html>"
- aliasXHtml = "<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>{{ .Permalink }}</title><link rel=\"canonical\" href=\"{{ .Permalink }}\"/><meta name=\"robots\" content=\"noindex\"><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" /><meta http-equiv=\"refresh\" content=\"0; url={{ .Permalink }}\" /></head></html>"
-)
-
var defaultAliasTemplates *template.Template
-func init() {
- //TODO(bep) consolidate
- defaultAliasTemplates = template.New("")
- template.Must(defaultAliasTemplates.New("alias").Parse(alias))
- template.Must(defaultAliasTemplates.New("alias-xhtml").Parse(aliasXHtml))
-}
-
type aliasHandler struct {
- t tpl.TemplateFinder
+ t tpl.TemplateHandler
log *loggers.Logger
allowRoot bool
}
-func newAliasHandler(t tpl.TemplateFinder, l *loggers.Logger, allowRoot bool) aliasHandler {
+func newAliasHandler(t tpl.TemplateHandler, l *loggers.Logger, allowRoot bool) aliasHandler {
return aliasHandler{t, l, allowRoot}
}
@@ -60,33 +49,27 @@ type aliasPage struct {
page.Page
}
-func (a aliasHandler) renderAlias(isXHTML bool, permalink string, p page.Page) (io.Reader, error) {
- t := "alias"
- if isXHTML {
- t = "alias-xhtml"
- }
+func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, error) {
var templ tpl.Template
var found bool
- if a.t != nil {
- templ, found = a.t.Lookup("alias.html")
- }
-
+ templ, found = a.t.Lookup("alias.html")
if !found {
- def := defaultAliasTemplates.Lookup(t)
- if def != nil {
- templ = &tpl.TemplateAdapter{Template: def}
+ // TODO(bep) consolidate
+ templ, found = a.t.Lookup("_internal/alias.html")
+ if !found {
+ return nil, errors.New("no alias template found")
}
-
}
+
data := aliasPage{
permalink,
p,
}
buffer := new(bytes.Buffer)
- err := templ.Execute(buffer, data)
+ err := a.t.Execute(templ, buffer, data)
if err != nil {
return nil, err
}
@@ -100,8 +83,6 @@ 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)
- isXHTML := strings.HasSuffix(path, ".xhtml")
-
s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
targetPath, err := handler.targetPathAlias(path)
@@ -109,7 +90,7 @@ func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFo
return err
}
- aliasContent, err := handler.renderAlias(isXHTML, permalink, p)
+ aliasContent, err := handler.renderAlias(permalink, p)
if err != nil {
return err
}
diff --git a/hugolib/case_insensitive_test.go b/hugolib/case_insensitive_test.go
index a8616ab06..42b9d7ef6 100644
--- a/hugolib/case_insensitive_test.go
+++ b/hugolib/case_insensitive_test.go
@@ -14,7 +14,6 @@
package hugolib
import (
- "fmt"
"path/filepath"
"testing"
@@ -232,76 +231,3 @@ Page2: {{ $page2.Params.ColoR }}
"index2|Site: yellow|",
)
}
-
-// TODO1
-func TestCaseInsensitiveConfigurationForAllTemplateEngines(t *testing.T) {
- t.Parallel()
-
- noOp := func(s string) string {
- return s
- }
-
- for _, config := range []struct {
- suffix string
- templateFixer func(s string) string
- }{
- //{"amber", amberFixer},
- {"html", noOp},
- //{"ace", noOp},
- } {
- doTestCaseInsensitiveConfigurationForTemplateEngine(t, config.suffix, config.templateFixer)
-
- }
-
-}
-
-func doTestCaseInsensitiveConfigurationForTemplateEngine(t *testing.T, suffix string, templateFixer func(s string) string) {
- c := qt.New(t)
- mm := afero.NewMemMapFs()
-
- caseMixingTestsWriteCommonSources(t, mm)
-
- cfg, err := LoadConfigDefault(mm)
- c.Assert(err, qt.IsNil)
-
- fs := hugofs.NewFrom(mm, cfg)
-
- th := newTestHelper(cfg, fs, t)
-
- t.Log("Testing", suffix)
-
- templTemplate := `
-p
- |
- | Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }}
- | Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
- | {{ .Content }}
-
-`
-
- templ := templateFixer(templTemplate)
-
- t.Log(templ)
-
- writeSource(t, fs, filepath.Join("layouts", "_default", fmt.Sprintf("single.%s", suffix)), templ)
-
- sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
-
- if err != nil {
- t.Fatalf("Failed to create sites: %s", err)
- }
-
- err = sites.Build(BuildCfg{})
-
- if err != nil {
- t.Fatalf("Failed to build sites: %s", err)
- }
-
- th.assertFileContent(filepath.Join("public", "nn", "sect1", "page1", "index.html"),
- "Page Colors: red|heavenly",
- "Site Colors: green|yellow",
- "Shortcode Page: red|heavenly",
- "Shortcode Site: green|yellow",
- )
-
-}
diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go
index 64f2203e9..a998b85b7 100644
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -27,7 +27,6 @@ import (
"github.com/gohugoio/hugo/deps"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/tpl"
)
const (
@@ -334,18 +333,13 @@ func TestShortcodeTweet(t *testing.T) {
cfg.Set("privacy", this.privacy)
- withTemplate := func(templ tpl.TemplateHandler) error {
- templ.(tpl.TemplateTestMocker).SetFuncs(tweetFuncMap)
- return nil
- }
-
writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
title: Shorty
---
%s`, this.in))
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`)
- buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: tweetFuncMap}, BuildCfg{})
th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected)
@@ -389,18 +383,13 @@ func TestShortcodeInstagram(t *testing.T) {
th = newTestHelper(cfg, fs, t)
)
- withTemplate := func(templ tpl.TemplateHandler) error {
- templ.(tpl.TemplateTestMocker).SetFuncs(instagramFuncMap)
- return nil
- }
-
writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`---
title: Shorty
---
%s`, this.in))
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content | safeHTML }}`)
- buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{})
+ buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, OverloadedTemplateFuncs: instagramFuncMap}, BuildCfg{})
th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected)
diff --git a/hugolib/hugo_modules_test.go b/hugolib/hugo_modules_test.go
index 9ba039c74..40185e051 100644
--- a/hugolib/hugo_modules_test.go
+++ b/hugolib/hugo_modules_test.go
@@ -42,10 +42,10 @@ import (
func TestHugoModules(t *testing.T) {
t.Parallel()
- if hugo.GoMinorVersion() < 12 {
+ if !isCI() || hugo.GoMinorVersion() < 12 {
// https://github.com/golang/go/issues/26794
// There were some concurrent issues with Go modules in < Go 12.
- t.Skip("skip this for Go <= 1.11 due to a bug in Go's stdlib")
+ t.Skip("skip this on local host and for Go <= 1.11 due to a bug in Go's stdlib")
}
if testing.Short() {
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index c0d75c09f..c71dcaa59 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -426,8 +426,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
return newHugoSites(cfg, sites...)
}
-func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateHandler) error) func(templ tpl.TemplateHandler) error {
- return func(templ tpl.TemplateHandler) 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
}
diff --git a/hugolib/hugo_smoke_test.go b/hugolib/hugo_smoke_test.go
index 539e79729..406255d51 100644
--- a/hugolib/hugo_smoke_test.go
+++ b/hugolib/hugo_smoke_test.go
@@ -21,6 +21,27 @@ import (
qt "github.com/frankban/quicktest"
)
+// The most basic build test.
+func TestHello(t *testing.T) {
+ t.Parallel()
+ b := newTestSitesBuilder(t)
+ b.WithConfigFile("toml", `
+baseURL="https://example.org"
+disableKinds = ["taxonomy", "taxonomyTerm", "section", "page"]
+`)
+ b.WithContent("p1", `
+---
+title: Page
+---
+
+`)
+ b.WithTemplates("index.html", `Site: {{ .Site.Language.Lang | upper }}`)
+
+ b.Build(BuildCfg{})
+
+