From c507e2717df7dd4b870478033bc5ece0b039a8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 17 Feb 2017 13:30:50 +0100 Subject: tpl: Refactor package Now: * The template API lives in /tpl * The rest lives in /tpl/tplimpl This is bound te be more improved in the future. Updates #2701 --- deps/deps.go | 8 +- hugolib/embedded_shortcodes_test.go | 6 +- hugolib/hugo_sites.go | 12 +- hugolib/shortcode.go | 4 +- hugolib/shortcode_test.go | 38 +- hugolib/site.go | 10 +- hugolib/sitemap_test.go | 4 +- hugolib/testhelpers_test.go | 6 +- tpl/amber_compiler.go | 42 - tpl/reflect_helpers.go | 70 - tpl/template.go | 586 +---- tpl/template_ast_transformers.go | 259 --- tpl/template_ast_transformers_test.go | 269 --- tpl/template_embedded.go | 266 --- tpl/template_func_truncate.go | 156 -- tpl/template_func_truncate_test.go | 83 - tpl/template_funcs.go | 2217 ------------------ tpl/template_funcs_test.go | 2993 ------------------------- tpl/template_resources.go | 253 --- tpl/template_resources_test.go | 302 --- tpl/template_test.go | 347 --- tpl/tplimpl/amber_compiler.go | 42 + tpl/tplimpl/reflect_helpers.go | 70 + tpl/tplimpl/template.go | 575 +++++ tpl/tplimpl/template_ast_transformers.go | 259 +++ tpl/tplimpl/template_ast_transformers_test.go | 269 +++ tpl/tplimpl/template_embedded.go | 266 +++ tpl/tplimpl/template_func_truncate.go | 156 ++ tpl/tplimpl/template_func_truncate_test.go | 83 + tpl/tplimpl/template_funcs.go | 2217 ++++++++++++++++++ tpl/tplimpl/template_funcs_test.go | 2993 +++++++++++++++++++++++++ tpl/tplimpl/template_resources.go | 253 +++ tpl/tplimpl/template_resources_test.go | 302 +++ tpl/tplimpl/template_test.go | 347 +++ tplapi/template.go | 28 - 35 files changed, 7895 insertions(+), 7896 deletions(-) delete mode 100644 tpl/amber_compiler.go delete mode 100644 tpl/reflect_helpers.go delete mode 100644 tpl/template_ast_transformers.go delete mode 100644 tpl/template_ast_transformers_test.go delete mode 100644 tpl/template_embedded.go delete mode 100644 tpl/template_func_truncate.go delete mode 100644 tpl/template_func_truncate_test.go delete mode 100644 tpl/template_funcs.go delete mode 100644 tpl/template_funcs_test.go delete mode 100644 tpl/template_resources.go delete mode 100644 tpl/template_resources_test.go delete mode 100644 tpl/template_test.go create mode 100644 tpl/tplimpl/amber_compiler.go create mode 100644 tpl/tplimpl/reflect_helpers.go create mode 100644 tpl/tplimpl/template.go create mode 100644 tpl/tplimpl/template_ast_transformers.go create mode 100644 tpl/tplimpl/template_ast_transformers_test.go create mode 100644 tpl/tplimpl/template_embedded.go create mode 100644 tpl/tplimpl/template_func_truncate.go create mode 100644 tpl/tplimpl/template_func_truncate_test.go create mode 100644 tpl/tplimpl/template_funcs.go create mode 100644 tpl/tplimpl/template_funcs_test.go create mode 100644 tpl/tplimpl/template_resources.go create mode 100644 tpl/tplimpl/template_resources_test.go create mode 100644 tpl/tplimpl/template_test.go delete mode 100644 tplapi/template.go diff --git a/deps/deps.go b/deps/deps.go index 39a3d31a4..de1b955cb 100644 --- a/deps/deps.go +++ b/deps/deps.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/hugo/config" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl" jww "github.com/spf13/jwalterweatherman" ) @@ -20,7 +20,7 @@ type Deps struct { Log *jww.Notepad `json:"-"` // The templates to use. - Tmpl tplapi.Template `json:"-"` + Tmpl tpl.Template `json:"-"` // The file systems to use. Fs *hugofs.Fs `json:"-"` @@ -40,7 +40,7 @@ type Deps struct { Language *helpers.Language templateProvider ResourceProvider - WithTemplate func(templ tplapi.Template) error `json:"-"` + WithTemplate func(templ tpl.Template) error `json:"-"` translationProvider ResourceProvider } @@ -147,7 +147,7 @@ type DepsCfg struct { // Template handling. TemplateProvider ResourceProvider - WithTemplate func(templ tplapi.Template) error + WithTemplate func(templ tpl.Template) error // i18n handling. TranslationProvider ResourceProvider diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go index 513767eb4..a98ca1369 100644 --- a/hugolib/embedded_shortcodes_test.go +++ b/hugolib/embedded_shortcodes_test.go @@ -25,7 +25,7 @@ import ( "github.com/spf13/hugo/deps" "github.com/spf13/hugo/helpers" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl" "github.com/stretchr/testify/require" ) @@ -335,7 +335,7 @@ func TestShortcodeTweet(t *testing.T) { th = testHelper{cfg} ) - withTemplate := func(templ tplapi.Template) error { + withTemplate := func(templ tpl.Template) error { templ.Funcs(tweetFuncMap) return nil } @@ -390,7 +390,7 @@ func TestShortcodeInstagram(t *testing.T) { th = testHelper{cfg} ) - withTemplate := func(templ tplapi.Template) error { + withTemplate := func(templ tpl.Template) error { templ.Funcs(instagramFuncMap) return nil } diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index f0feec23e..3cbe4fa90 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -24,7 +24,7 @@ import ( "github.com/spf13/hugo/i18n" "github.com/spf13/hugo/tpl" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl/tplimpl" ) // HugoSites represents the sites to build. Each site represents a language. @@ -72,7 +72,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error { if cfg.TemplateProvider == nil { - cfg.TemplateProvider = tpl.DefaultTemplateProvider + cfg.TemplateProvider = tplimpl.DefaultTemplateProvider } if cfg.TranslationProvider == nil { @@ -121,8 +121,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { return newHugoSites(cfg, sites...) } -func (s *Site) withSiteTemplates(withTemplates ...func(templ tplapi.Template) error) func(templ tplapi.Template) error { - return func(templ tplapi.Template) error { +func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.Template) error) func(templ tpl.Template) error { + return func(templ tpl.Template) error { templ.LoadTemplates(s.absLayoutDir()) if s.hasTheme() { templ.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme") @@ -191,7 +191,7 @@ func (h *HugoSites) reset() { h.Sites[i] = s.reset() } - tpl.ResetCaches() + tplimpl.ResetCaches() } func (h *HugoSites) createSitesFromConfig() error { @@ -553,7 +553,7 @@ func (h *HugoSites) Pages() Pages { return h.Sites[0].AllPages } -func handleShortcodes(p *Page, t tplapi.Template, rawContentCopy []byte) ([]byte, error) { +func handleShortcodes(p *Page, t tpl.Template, rawContentCopy []byte) ([]byte, error) { if len(p.contentShortCodes) > 0 { p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.contentShortCodes), p.BaseFileName()) shortcodes, err := executeShortcodeFuncMap(p.contentShortCodes) diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 4ce6df7e9..775c57e8c 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -26,7 +26,7 @@ import ( bp "github.com/spf13/hugo/bufferpool" "github.com/spf13/hugo/helpers" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl" ) // ShortcodeWithPage is the "." context in a shortcode template. @@ -541,7 +541,7 @@ func replaceShortcodeTokens(source []byte, prefix string, replacements map[strin return source, nil } -func getShortcodeTemplate(name string, t tplapi.Template) *template.Template { +func getShortcodeTemplate(name string, t tpl.Template) *template.Template { if x := t.Lookup("shortcodes/" + name + ".html"); x != nil { return x } diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index b1f28d53b..665e3a944 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -25,12 +25,12 @@ import ( "github.com/spf13/hugo/deps" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/source" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl" "github.com/stretchr/testify/require" ) // TODO(bep) remove -func pageFromString(in, filename string, withTemplate ...func(templ tplapi.Template) error) (*Page, error) { +func pageFromString(in, filename string, withTemplate ...func(templ tpl.Template) error) (*Page, error) { s := newTestSite(nil) if len(withTemplate) > 0 { // Have to create a new site @@ -47,11 +47,11 @@ func pageFromString(in, filename string, withTemplate ...func(templ tplapi.Templ return s.NewPageFrom(strings.NewReader(in), filename) } -func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error) { +func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) { CheckShortCodeMatchAndError(t, input, expected, withTemplate, false) } -func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tplapi.Template) error, expectError bool) { +func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) { cfg, fs := newTestCfg() @@ -100,7 +100,7 @@ func TestNonSC(t *testing.T) { // Issue #929 func TestHyphenatedSC(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`) return nil } @@ -111,7 +111,7 @@ func TestHyphenatedSC(t *testing.T) { // Issue #1753 func TestNoTrailingNewline(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`) return nil } @@ -121,7 +121,7 @@ func TestNoTrailingNewline(t *testing.T) { func TestPositionalParamSC(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`) return nil } @@ -135,7 +135,7 @@ func TestPositionalParamSC(t *testing.T) { func TestPositionalParamIndexOutOfBounds(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`) return nil } @@ -146,7 +146,7 @@ func TestPositionalParamIndexOutOfBounds(t *testing.T) { func TestNamedParamSC(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("img.html", ``) return nil } @@ -161,7 +161,7 @@ func TestNamedParamSC(t *testing.T) { // Issue #2294 func TestNestedNamedMissingParam(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("acc.html", `
{{ .Inner }}
`) tem.AddInternalShortcode("div.html", `
{{ .Inner }}
`) tem.AddInternalShortcode("div2.html", `
{{ .Inner }}
`) @@ -174,7 +174,7 @@ func TestNestedNamedMissingParam(t *testing.T) { func TestIsNamedParamsSC(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("byposition.html", `
`) tem.AddInternalShortcode("byname.html", `
`) tem.AddInternalShortcode("ifnamedparams.html", `
`) @@ -190,7 +190,7 @@ func TestIsNamedParamsSC(t *testing.T) { func TestInnerSC(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("inside.html", `{{ .Inner }}
`) return nil } @@ -201,7 +201,7 @@ func TestInnerSC(t *testing.T) { func TestInnerSCWithMarkdown(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("inside.html", `{{ .Inner }}
`) return nil } @@ -215,7 +215,7 @@ func TestInnerSCWithMarkdown(t *testing.T) { func TestInnerSCWithAndWithoutMarkdown(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("inside.html", `{{ .Inner }}
`) return nil } @@ -246,7 +246,7 @@ func TestEmbeddedSC(t *testing.T) { func TestNestedSC(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("scn1.html", `
Outer, inner is {{ .Inner }}
`) tem.AddInternalShortcode("scn2.html", `
SC2
`) return nil @@ -258,7 +258,7 @@ func TestNestedSC(t *testing.T) { func TestNestedComplexSC(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`) tem.AddInternalShortcode("column.html", `-col-{{.Inner }}-colStop-`) tem.AddInternalShortcode("aside.html", `-aside-{{ .Inner }}-asideStop-`) @@ -274,7 +274,7 @@ func TestNestedComplexSC(t *testing.T) { func TestParentShortcode(t *testing.T) { t.Parallel() - wt := func(tem tplapi.Template) error { + wt := func(tem tpl.Template) error { tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`) tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`) tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`) @@ -342,7 +342,7 @@ func TestExtractShortcodes(t *testing.T) { fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""}, } { - p, _ := pageFromString(simplePage, "simple.md", func(templ tplapi.Template) error { + 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`) @@ -514,7 +514,7 @@ tags: sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)} } - addTemplates := func(templ tplapi.Template) error { + addTemplates := func(templ tpl.Template) error { templ.AddTemplate("_default/single.html", "{{.Content}}") templ.AddInternalShortcode("b.html", `b`) diff --git a/hugolib/site.go b/hugolib/site.go index bd0156849..a5555d0e4 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -40,7 +40,7 @@ import ( "github.com/spf13/hugo/parser" "github.com/spf13/hugo/source" "github.com/spf13/hugo/target" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl" "github.com/spf13/hugo/transform" "github.com/spf13/nitro" "github.com/spf13/viper" @@ -149,7 +149,7 @@ func NewSite(cfg deps.DepsCfg) (*Site, error) { // NewSiteDefaultLang creates a new site in the default language. // The site will have a template system loaded and ready to use. // Note: This is mainly used in single site tests. -func NewSiteDefaultLang(withTemplate ...func(templ tplapi.Template) error) (*Site, error) { +func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) (*Site, error) { v := viper.New() loadDefaultSettingsFor(v) return newSiteForLang(helpers.NewDefaultLanguage(v), withTemplate...) @@ -158,15 +158,15 @@ func NewSiteDefaultLang(withTemplate ...func(templ tplapi.Template) error) (*Sit // NewEnglishSite creates a new site in English language. // The site will have a template system loaded and ready to use. // Note: This is mainly used in single site tests. -func NewEnglishSite(withTemplate ...func(templ tplapi.Template) error) (*Site, error) { +func NewEnglishSite(withTemplate ...func(templ tpl.Template) error) (*Site, error) { v := viper.New() loadDefaultSettingsFor(v) return newSiteForLang(helpers.NewLanguage("en", v), withTemplate...) } // newSiteForLang creates a new site in the given language. -func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tplapi.Template) error) (*Site, error) { - withTemplates := func(templ tplapi.Template) error { +func newSiteForLang(lang *helpers.Language, withTemplate ...func(templ tpl.Template) error) (*Site, error) { + withTemplates := func(templ tpl.Template) error { for _, wt := range withTemplate { if err := wt(templ); err != nil { return err diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go index 8bbcb487b..daefde524 100644 --- a/hugolib/sitemap_test.go +++ b/hugolib/sitemap_test.go @@ -19,7 +19,7 @@ import ( "reflect" "github.com/spf13/hugo/deps" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl" ) const sitemapTemplate = ` @@ -48,7 +48,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) { depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg} if !internal { - depsCfg.WithTemplate = func(templ tplapi.Template) error { + depsCfg.WithTemplate = func(templ tpl.Template) error { templ.AddTemplate("sitemap.xml", sitemapTemplate) return nil } diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index 33e78e121..f0fcd9530 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/hugo/deps" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/source" - "github.com/spf13/hugo/tplapi" + "github.com/spf13/hugo/tpl" "github.com/spf13/viper" "io/ioutil" @@ -66,9 +66,9 @@ func newDebugLogger() *jww.Notepad { return jww.NewNotepad(jww.LevelDebug, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) } -func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tplapi.Template) error { +func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.Template) error { - return func(templ tplapi.Template) error { + return func(templ tpl.Template) error { for i := 0; i < len(additionalTemplates); i += 2 { err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1]) if err != nil { diff --git a/tpl/amber_compiler.go b/tpl/amber_compiler.go deleted file mode 100644 index 4477f6ac0..000000000 --- a/tpl/amber_compiler.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2017 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 tpl - -import ( - "html/template" - - "github.com/eknkc/amber" -) - -func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) { - c := amber.New() - - if err := c.ParseData(b, path); err != nil { - return nil, err - } - - data, err := c.CompileString() - - if err != nil { - return nil, err - } - - tpl, err := t.Funcs(gt.amberFuncMap).Parse(data) - - if err != nil { - return nil, err - } - - return tpl, nil -} diff --git a/tpl/reflect_helpers.go b/tpl/reflect_helpers.go deleted file mode 100644 index f2ce722a2..000000000 --- a/tpl/reflect_helpers.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2016 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 tpl - -import ( - "reflect" - "time" -) - -// toInt returns the int value if possible, -1 if not. -func toInt(v reflect.Value) int64 { - switch v.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() - case reflect.Interface: - return toInt(v.Elem()) - } - return -1 -} - -// toString returns the string value if possible, "" if not. -func toString(v reflect.Value) string { - switch v.Kind() { - case reflect.String: - return v.String() - case reflect.Interface: - return toString(v.Elem()) - } - return "" -} - -var ( - zero reflect.Value - errorType = reflect.TypeOf((*error)(nil)).Elem() - timeType = reflect.TypeOf((*time.Time)(nil)).Elem() -) - -func toTimeUnix(v reflect.Value) int64 { - if v.Kind() == reflect.Interface { - return toTimeUnix(v.Elem()) - } - if v.Type() != timeType { - panic("coding error: argument must be time.Time type reflect Value") - } - return v.MethodByName("Unix").Call([]reflect.Value{})[0].Int() -} - -// indirect is taken from 'text/template/exec.go' -func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { - for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { - if v.IsNil() { - return v, true - } - if v.Kind() == reflect.Interface && v.NumMethod() > 0 { - break - } - } - return v, false -} diff --git a/tpl/template.go b/tpl/template.go index 9a6364d5a..aaf7fc8c7 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -1,575 +1,27 @@ -// Copyright 2016 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 tpl import ( - "fmt" "html/template" "io" - "os" - "path/filepath" - "strings" - - "sync" - - "github.com/eknkc/amber" - "github.com/spf13/afero" - bp "github.com/spf13/hugo/bufferpool" - "github.com/spf13/hugo/deps" - "github.com/spf13/hugo/helpers" - "github.com/yosssi/ace" ) -// TODO(bep) globals get rid of the rest of the jww.ERR etc. - -// Protecting global map access (Amber) -var amberMu sync.Mutex - -type templateErr struct { - name string - err error -} - -type GoHTMLTemplate struct { - *template.Template - - clone *template.Template - - // 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 - - errors []*templateErr - - funcster *templateFuncster - - amberFuncMap template.FuncMap - - *deps.Deps -} - -type TemplateProvider struct{} - -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 { - // TODO(bep) check that this isn't called too many times. - tmpl := &GoHTMLTemplate{ - Template: template.New(""), - overlays: make(map[string]*template.Template), - errors: make([]*templateErr, 0), - Deps: deps, - } - - deps.Tmpl = tmpl - - tmpl.initFuncs(deps) - - tmpl.LoadEmbedded() - - if deps.WithTemplate != nil { - err := deps.WithTemplate(tmpl) - if err != nil { - tmpl.errors = append(tmpl.errors, &templateErr{"init", err}) - } - - } - - tmpl.MarkReady() - - return nil - -} - -// Clone clones -func (*TemplateProvider) Clone(d *deps.Deps) error { - - t := d.Tmpl.(*GoHTMLTemplate) - - // 1. Clone the clone with new template funcs - // 2. Clone any overlays with new template funcs - - tmpl := &GoHTMLTemplate{ - Template: template.Must(t.Template.Clone()), - overlays: make(map[string]*template.Template), - errors: make([]*templateErr, 0), - Deps: d, - } - - d.Tmpl = tmpl - tmpl.initFuncs(d) - - for k, v := range t.overlays { - vc := template.Must(v.Clone()) - // The extra lookup is a workaround, see - // * https://github.com/golang/go/issues/16101 - // * https://github.com/spf13/hugo/issues/2549 - vc = vc.Lookup(vc.Name()) - vc.Funcs(tmpl.funcster.funcMap) - tmpl.overlays[k] = vc - } - - tmpl.MarkReady() - - return nil -} - -func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) { - - t.funcster = newTemplateFuncster(d) - - // The URL funcs in the funcMap is somewhat language dependent, - // so we need to wait until the language and site config is loaded. - t.funcster.initFuncMap() - - t.amberFuncMap = template.FuncMap{} - - amberMu.Lock() - for k, v := range amber.FuncMap { - t.amberFuncMap[k] = v - } - - for k, v := range t.funcster.funcMap { - t.amberFuncMap[k] = v - // Hacky, but we need to make sure that the func names are in the global map. - amber.FuncMap[k] = func() string { - panic("should never be invoked") - } - } - amberMu.Unlock() - -} - -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:] - } - var context interface{} - - if len(contextList) == 0 { - context = nil - } else { - context = contextList[0] - } - return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name) -} - -func (t *GoHTMLTemplate) executeTemplate(context interface{}, w io.Writer, layouts ...string) { - var worked bool - for _, layout := range layouts { - templ := t.Lookup(layout) - if templ == nil { - layout += ".html" - templ = t.Lookup(layout) - } - - if templ != nil { - if err := templ.Execute(w, context); err != nil { - helpers.DistinctErrorLog.Println(layout, err) - } - worked = true - break - } - } - if !worked { - 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 (t *GoHTMLTemplate) ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML { - b := bp.GetBuffer() - defer bp.PutBuffer(b) - t.executeTemplate(context, b, layouts...) - return template.HTML(b.String()) -} - -func (t *GoHTMLTemplate) Lookup(name string) *template.Template { - - if templ := t.Template.Lookup(name); templ != nil { - return templ - } - - if t.overlays != nil { - if templ, ok := t.overlays[name]; ok { - return templ - } - } - - // The clone is used for the non-renderable HTML pages (p.IsRenderable == false) that is parsed - // as Go templates late in the build process. - if t.clone != nil { - if templ := t.clone.Lookup(name); templ != nil { - return templ - } - } - - 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) - } - return t.AddTemplate("_internal/"+name, tpl) -} - -func (t *GoHTMLTemplate) AddInternalShortcode(name, content string) error { - return t.AddInternalTemplate("shortcodes", name, content) -} - -func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error { - t.checkState() - templ, err := t.New(name).Parse(tpl) - if err != nil { - t.errors = append(t.errors, &templateErr{name: name, err: err}) - return err - } - if err := applyTemplateTransformers(templ); err != nil { - return err - } - - return nil -} - -func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error { - - // There is currently no known way to associate a cloned template with an existing one. - // This funky master/overlay design will hopefully improve in a future version of Go. - // - // Simplicity is hard. - // - // Until then we'll have to live with this hackery. - // - // See https://github.com/golang/go/issues/14285 - // - // So, to do minimum amount of changes to get this to work: - // - // 1. Lookup or Parse the master - // 2. Parse and store the overlay in a separate map - - masterTpl := t.Lookup(masterFilename) - - if masterTpl == nil { - b, err := afero.ReadFile(t.Fs.Source, masterFilename) - if err != nil { - return err - } - masterTpl, err = t.New(masterFilename).Parse(string(b)) - - if err != nil { - // TODO(bep) Add a method that does this - t.errors = append(t.errors, &templateErr{name: name, err: err}) - return err - } - } - - b, err := afero.ReadFile(t.Fs.Source, overlayFilename) - if err != nil { - return err - } - - overlayTpl, err := template.Must(masterTpl.Clone()).Parse(string(b)) - if err != nil { - t.errors = append(t.errors, &templateErr{name: name, err: err}) - } else { - // The extra lookup is a workaround, see - // * https://github.com/golang/go/issues/16101 - // * https://github.com/spf13/hugo/issues/2549 - overlayTpl = overlayTpl.Lookup(overlayTpl.Name()) - if err := applyTemplateTransformers(overlayTpl); err != nil { - return err - } - t.overlays[name] = overlayTpl - } - - return err -} - -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" - - // Fixes issue #1178 - basePath = strings.Replace(basePath, "\\", "/", -1) - innerPath = strings.Replace(innerPath, "\\", "/", -1) - - if basePath != "" { - base = ace.NewFile(basePath, baseContent) - inner = ace.NewFile(innerPath, innerContent) - } else { - base = ace.NewFile(innerPath, innerContent) - inner = ace.NewFile("", []byte{}) - } - parsed, err := ace.ParseSource(ace.NewSource(base, inner, []*ace.File{}), nil) - if err != nil { - t.errors = append(t.errors, &templateErr{name: name, err: err}) - return err - } - templ, err := ace.CompileResultWithTemplate(t.New(name), parsed, nil) - if err != nil { - t.errors = append(t.errors, &templateErr{name: name, err: err}) - return err - } - return applyTemplateTransformers(templ) -} - -func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) error { - t.checkState() - // get the suffix and switch on that - ext := filepath.Ext(path) - switch ext { - case ".amber": - templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html" - b, err := afero.ReadFile(t.Fs.Source, path) - - if err != nil { - return err - } - - amberMu.Lock() - templ, err := t.CompileAmberWithTemplate(b, path, t.New(templateName)) - amberMu.Unlock() - if err != nil { - return err - } - - return applyTemplateTransformers(templ) - case ".ace": - var innerContent, baseContent []byte - innerContent, err := afero.ReadFile(t.Fs.Source, path) - - if err != nil { - return err - } - - if baseTemplatePath != "" { - baseContent, err = afero.ReadFile(t.Fs.Source, baseTemplatePath) - if err != nil { - return err - } - } - - return t.AddAceTemplate(name, baseTemplatePath, path, baseContent, innerContent) - default: - - if baseTemplatePath != "" { - return t.AddTemplateFileWithMaster(name, path, baseTemplatePath) - } - - b, err := afero.ReadFile(t.Fs.Source, path) - - if err != nil { - return err - } - - t.Log.DEBUG.Printf("Add template file from path %s", path) - - return t.AddTemplate(name, string(b)) - } - -} - -func (t *GoHTMLTemplate) GenerateTemplateNameFrom(base, path string) string { - name, _ := filepath.Rel(base, path) - return filepath.ToSlash(name) -} - -func isDotFile(path string) bool { - return filepath.Base(path)[0] == '.' -} - -func isBackupFile(path string) bool { - return path[len(path)-1] == '~' -} - -const baseFileBase = "baseof" - -var aceTemplateInnerMarkers = [][]byte{[]byte("= content")} -var goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define")} - -func isBaseTemplate(path string) bool { - return strings.Contains(path, baseFileBase) -} - -func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) { - t.Log.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix) - walker := func(path string, fi os.FileInfo, err error) error { - if err != nil { - return nil - } - t.Log.DEBUG.Println("Template path", path) - if fi.Mode()&os.ModeSymlink == os.ModeSymlink { - link, err := filepath.EvalSymlinks(absPath) - if err != nil { - t.Log.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err) - return nil - } - linkfi, err := t.Fs.Source.Stat(link) - if err != nil { - t.Log.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) - return nil - } - if !linkfi.Mode().IsRegular() { - t.Log.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", absPath) - } - return nil - } - - if !fi.IsDir() { - if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) { - return nil - } - - tplName := t.GenerateTemplateNameFrom(absPath, path) - - if prefix != "" { - tplName = strings.Trim(prefix, "/") + "/" + tplName - } - - var baseTemplatePath string - - // Ace and Go templates may have both a base and inner template. - pathDir := filepath.Dir(path) - if filepath.Ext(path) != ".amber" && !strings.HasSuffix(pathDir, "partials") && !strings.HasSuffix(pathDir, "shortcodes") { - - innerMarkers := goTemplateInnerMarkers - baseFileName := fmt.Sprintf("%s.html", baseFileBase) - - if filepath.Ext(path) == ".ace" { - innerMarkers = aceTemplateInnerMarkers - baseFileName = fmt.Sprintf("%s.ace", baseFileBase) - } - - // This may be a view that shouldn't have base template - // Have to look inside it to make sure - needsBase, err := helpers.FileContainsAny(path, innerMarkers, t.Fs.Source) - if err != nil { - return err - } - if needsBase { - - layoutDir := t.PathSpec.GetLayoutDirPath() - currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName) - templateDir := filepath.Dir(path) - themeDir := filepath.Join(t.PathSpec.GetThemeDir()) - relativeThemeLayoutsDir := filepath.Join(t.PathSpec.GetRelativeThemeDir(), "layouts") - - var baseTemplatedDir string - - if strings.HasPrefix(templateDir, relativeThemeLayoutsDir) { - baseTemplatedDir = strings.TrimPrefix(templateDir, relativeThemeLayoutsDir) - } else { - baseTemplatedDir = strings.TrimPrefix(templateDir, layoutDir) - } - - baseTemplatedDir = strings.TrimPrefix(baseTemplatedDir, helpers.FilePathSeparator) - - // Look for base template in the follwing order: - // 1. /-baseof., e.g. list-baseof.. - // 2. /baseof. - // 3. _default/-baseof., e.g. list-baseof.. - // 4. _default/baseof. - // For each of the steps above, it will first look in the project, then, if theme is set, - // in the theme's layouts folder. - - pairsToCheck := [][]string{ - []string{baseTemplatedDir, currBaseFilename}, - []string{baseTemplatedDir, baseFileName}, - []string{"_default", currBaseFilename}, - []string{"_default", baseFileName}, - } - - Loop: - for _, pair := range pairsToCheck { - pathsToCheck := basePathsToCheck(pair, layoutDir, themeDir) - for _, pathToCheck := range pathsToCheck { - if ok, err := helpers.Exists(pathToCheck, t.Fs.Source); err == nil && ok { - baseTemplatePath = pathToCheck - break Loop - } - } - } - } - } - - if err := t.AddTemplateFile(tplName, baseTemplatePath, path); err != nil { - t.Log.ERROR.Printf("Failed to add template %s in path %s: %s", tplName, path, err) - } - - } - return nil - } - if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil { - t.Log.ERROR.Printf("Failed to load templates: %s", err) - } -} - -func basePathsToCheck(path []string, layoutDir, themeDir string) []string { - // Always look in the project. - pathsToCheck := []string{filepath.Join((append([]string{layoutDir}, path...))...)} - - // May have a theme - if themeDir != "" { - pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeDir, "layouts"}, path...))...)) - } - - return pathsToCheck - -} - -func (t *GoHTMLTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) { - t.loadTemplates(absPath, prefix) -} - -func (t *GoHTMLTemplate) LoadTemplates(absPath string) { - t.loadTemplates(absPath, "") -} - -func (t *GoHTMLTemplate) PrintErrors() { - for i, e := range t.errors { - t.Log.ERROR.Println(i, ":", e.err) - } +// TODO(bep) make smaller +type Template interface { + ExecuteTemplate(wr io.Writer, name string, data interface{}) error + ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML + Lookup(name string) *template.Template + Templates() []*template.Template + New(name string) *template.Template + GetClone() *template.Template + LoadTemplates(absPath string) + LoadTemplatesWithPrefix(absPath, prefix string) + 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 + Partial(name string, contextList ...interface{}) template.HTML + PrintErrors() + Funcs(funcMap template.FuncMap) + MarkReady() } diff --git a/tpl/template_ast_transformers.go b/tpl/template_ast_transformers.go deleted file mode 100644 index 19b772add..000000000 --- a/tpl/template_ast_transformers.go +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2016 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 tpl - -import ( - "errors" - "html/template" - "strings" - "text/template/parse" -) - -// decl keeps track of the variable mappings, i.e. $mysite => .Site etc. -type decl map[string]string - -var paramsPaths = [][]string{ - {"Params"}, - {"Site", "Params"}, - - // Site and Pag referenced from shortcodes - {"Page", "Site", "Params"}, - {"Page", "Params"}, - - {"Site", "Language", "Params"}, -} - -type templateContext struct { - decl decl - templ *template.Template -} - -func newTemplateContext(templ *template.Template) *templateContext { - return &templateContext{templ: templ, decl: make(map[string]string)} - -} - -func applyTemplateTransformers(templ *template.Template) error { - if templ == nil || templ.Tree == nil { - return errors.New("expected template, but none provided") - } - - c := newTemplateContext(templ) - - c.paramsKeysToLower(templ.Tree.Root) - - return nil -} - -// paramsKeysToLower is made purposely non-generic to make it not so tempting -// to do more of these hard-to-maintain AST transformations. -func (c *templateContext) paramsKeysToLower(n parse.Node) { - - switch x := n.(type) { - case *parse.ListNode: - if x != nil { - c.paramsKeysToLowerForNodes(x.Nodes...) - } - case *parse.ActionNode: - c.paramsKeysToLowerForNodes(x.Pipe) - case *parse.IfNode: - c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList) - case *parse.WithNode: - c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList) - case *parse.RangeNode: - c.paramsKeysToLowerForNodes(x.Pipe, x.List, x.ElseList) - case *parse.TemplateNode: - subTempl := c.templ.Lookup(x.Name) - if subTempl != nil { - c.paramsKeysToLowerForNodes(subTempl.Tree.Root) - } - case *parse.PipeNode: - for i, elem := range x.Decl { - if len(x.Cmds) > i { - // maps $site => .Site etc. - c.decl[elem.Ident[0]] = x.Cmds[i].String() - } - } - - for _, cmd := range x.Cmds { - c.paramsKeysToLower(cmd) - } - - case *parse.CommandNode: - for _, elem := range x.Args { - switch an := elem.(type) { - case *parse.FieldNode: - c.updateIdentsIfNeeded(an.Ident) - case *parse.VariableNode: - c.updateIdentsIfNeeded(an.Ident) - case *parse.PipeNode: - c.paramsKeysToLower(an) - } - - } - } -} - -func (c *templateContext) paramsKeysToLowerForNodes(nodes ...parse.Node) { - for _, node := range nodes { - c.paramsKeysToLower(node) - } -} - -func (c *templateContext) updateIdentsIfNeeded(idents []string) { - index := c.decl.indexOfReplacementStart(idents) - - if index == -1 { - return - } - - for i := index; i < len(idents); i++ { - idents[i] = strings.ToLower(idents[i]) - } -} - -// indexOfReplacementStart will return the index of where to start doing replacement, -// -1 if none needed. -func (d decl) indexOfReplacementStart(idents []string) int { - - l := len(idents) - - if l == 0 { - return -1 - } - - first := idents[0] - firstIsVar := first[0] == '$' - - if l == 1 && !firstIsVar { - // This can not be a Params.x - return -1 - } - - if !firstIsVar { - found := false - for _, paramsPath := range paramsPaths { - if first == paramsPath[0] { - found = true - break - } - } - if !found { - return -1 - } - } - - var ( - resolvedIdents []string - replacements []string - replaced []string - ) - - // An Ident can start out as one of - // [Params] [$blue] [$colors.Blue] - // We need to resolve the variables, so - // $blue => [Params Colors Blue] - // etc. - replacements = []string{idents[0]} - - // Loop until there are no more $vars to resolve. - for i := 0; i < len(replacements); i++ { - - if i > 20 { - // bail out - return -1 - } - - potentialVar := replacements[i] - - if potentialVar == "$" { - continue - } - - if potentialVar == "" || potentialVar[0] != '$' { - // leave it as is - replaced = append(replaced, strings.Split(potentialVar, ".")...) - continue - } - - replacement, ok := d[potentialVar] - - if !ok { - // Temporary range vars. We do not care about those. - return -1 - } - - replacement = strings.TrimPrefix(replacement, ".") - - if replacement == "" { - continue - } - - if replacement[0] == '$' { - // Needs further expansion - replacements = append(replacements, strings.Split(replacement, ".")...) - } else { - replaced = append(replaced, strings.Split(replacement, ".")...) - } - } - - resolvedIdents = append(replaced, idents[1:]...) - - for _, paramPath := range paramsPaths { - if index := indexOfFirstRealIdentAfterWords(resolvedIdents, idents, paramPath...); index != -1 { - return index - } - } - - return -1 - -} - -func indexOfFirstRealIdentAfterWords(resolvedIdents, idents []string, words ...string) int { - if !sliceStartsWith(resolvedIdents, words...) { - return -1 - } - - for i, ident := range idents { - if ident == "" || ident[0] == '$' { - continue - } - found := true - for _, word := range words { - if ident == word { - found = false - break - } - } - if found { - return i - } - } - - return -1 -} - -func sliceStartsWith(slice []string, words ...string) bool { - - if len(slice) < len(words) { - return false - } - - for i, word := range words { - if word != slice[i] { - return false - } - } - return true -} diff --git a/tpl/template_ast_transformers_test.go b/tpl/template_ast_transformers_test.go deleted file mode 100644 index 43d78284c..000000000 --- a/tpl/template_ast_transformers_test.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2016 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 tpl - -import ( - "bytes" - "testing" - - "html/template" - - "github.com/stretchr/testify/require" -) - -var ( - testFuncs = map[string]interface{}{ - "Echo": func(v interface{}) interface{} { return v }, - } - - paramsData = map[string]interface{}{ - "NotParam": "Hi There", - "Slice": []int{1, 3}, - "Params": map[string]interface{}{ - "lower": "P1L", - }, - "Site": map[string]interface{}{ - "Params": map[string]interface{}{ - "lower": "P2L", - "slice": []int{1, 3}, - }, - "Language": map[string]interface{}{ - "Params": map[string]interface{}{ - "lower": "P22L", - }, - }, - "Data": map[string]interface{}{ - "Params": map[string]interface{}{ - "NOLOW": "P3H", - }, - }, - }, - } - - paramsTempl = ` -{{ $page := . }} -{{ $pageParams := .Params }} -{{ $site := .Site }} -{{ $siteParams := .Site.Params }} -{{ $data := .Site.Data }} -{{ $notparam := .NotParam }} - -P1: {{ .Params.LOWER }} -P1_2: {{ $.Params.LOWER }} -P1_3: {{ $page.Params.LOWER }} -P1_4: {{ $pageParams.LOWER }} -P2: {{ .Site.Params.LOWER }} -P2_2: {{ $.Site.Params.LOWER }} -P2_3: {{ $site.Params.LOWER }} -P2_4: {{ $siteParams.LOWER }} -P22: {{ .Site.Language.Params.LOWER }} -P3: {{ .Site.Data.Params.NOLOW }} -P3_2: {{ $.Site.Data.Params.NOLOW }} -P3_3: {{ $site.Data.Params.NOLOW }} -P3_4: {{ $data.Params.NOLOW }} -P4: {{ range $i, $e := .Site.Params.SLICE }}{{ $e }}{{ end }} -P5: {{ Echo .Params.LOWER }} -P5_2: {{ Echo $site.Params.LOWER }} -{{ if .Params.LOWER }} -IF: {{ .Params.LOWER }} -{{ end }} -{{ if .Params.NOT_EXIST }} -{{ else }} -ELSE: {{ .Params.LOWER }} -{{ end }} - - -{{ with .Params.LOWER }} -WITH: {{ . }} -{{ end }} - - -{{ range .Slice }} -RANGE: {{ . }}: {{ $.Params.LOWER }} -{{ end }} -{{ index .Slice 1 }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ .NotParam }} -{{ $notparam }} - - -{{ $lower := .Site.Params.LOWER }} -F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }} -F2: {{ Echo (printf "themes/%s-theme" $lower) }} -F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }} -` -) - -func TestParamsKeysToLower(t *testing.T) { - t.Parallel() - - require.Error(t, applyTemplateTransformers(nil)) - - templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl) - - require.NoError(t, err) - - c := newTemplateContext(templ) - - require.Equal(t, -1, c.decl.indexOfReplacementStart([]string{})) - - c.paramsKeysToLower(templ.Tree.Root) - - var b bytes.Buffer - - require.NoError(t, templ.Execute(&b, paramsData)) - - result := b.String() - - require.Contains(t, result, "P1: P1L") - require.Contains(t, result, "P1_2: P1L") - require.Contains(t, result, "P1_3: P1L") - require.Contains(t, result, "P1_4: P1L") - require.Contains(t, result, "P2: P2L") - require.Contains(t, result, "P2_2: P2L") - require.Contains(t, result, "P2_3: P2L") - require.Contains(t, result, "P2_4: P2L") - require.Contains(t, result, "P22: P22L") - require.Contains(t, result, "P3: P3H") - require.Contains(t, result, "P3_2: P3H") - require.Contains(t, result, "P3_3: P3H") - require.Contains(t, result, "P3_4: P3H") - require.Contains(t, result, "P4: 13") - require.Contains(t, result, "P5: P1L") - require.Contains(t, result, "P5_2: P2L") - - require.Contains(t, result, "IF: P1L") - require.Contains(t, result, "ELSE: P1L") - - require.Contains(t, result, "WITH: P1L") - - require.Contains(t, result, "RANGE: 3: P1L") - - require.Contains(t, result, "Hi There") - - // Issue #2740 - require.Contains(t, result, "F1: themes/P2L-theme") - require.Contains(t, result, "F2: themes/P2L-theme") - require.Contains(t, result, "F3: themes/P2L-theme") - -} - -func BenchmarkTemplateParamsKeysToLower(b *testing.B) { - templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl) - - if err != nil { - b.Fatal(err) - } - - templates := make([]*template.Template, b.N) - - for i := 0; i < b.N; i++ { - templates[i], err = templ.Clone() - if err != nil { - b.Fatal(err) - } - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - c := newTemplateContext(templates[i]) - c.paramsKeysToLower(templ.Tree.Root) - } -} - -func TestParamsKeysToLowerVars(t *testing.T) { - t.Parallel() - var ( - ctx = map[string]interface{}{ - "Params": map[string]interface{}{ - "colors": map[string]interface{}{ - "blue": "Amber", - }, - }, - } - - // This is how Amber behaves: - paramsTempl = ` -{{$__amber_1 := .Params.Colors}} -{{$__amber_2 := $__amber_1.Blue}} -Color: {{$__amber_2}} -Blue: {{ $__amber_1.Blue}} -` - ) - - templ, err := template.New("foo").Parse(paramsTempl) - - require.NoError(t, err) - - c := newTemplateContext(templ) - - c.paramsKeysToLower(templ.Tree.Root) - - var b bytes.Buffer - - require.NoError(t, templ.Execute(&b, ctx)) - - result := b.String() - - require.Contains(t, result, "Color: Amber") - -} - -func TestParamsKeysToLowerInBlockTemplate(t *testing.T) { - t.Parallel() - - var ( - ctx = map[string]interface{}{ - "Params": map[string]interface{}{ - "lower": "P1L", - }, - } - - master = ` -P1: {{ .Params.LOWER }} -{{ block "main" . }}DEFAULT{{ end }}` - overlay = ` -{{ define "main" }} -P2: {{ .Params.LOWER }} -{{ end }}` - ) - - masterTpl, err := template.New("foo").Parse(master) - require.NoError(t, err) - - overlayTpl, err := template.Must(masterTpl.Clone()).Parse(overlay) - require.NoError(t, err) - overlayTpl = overlayTpl.Lookup(overlayTpl.Name()) - - c := newTemplateContext(overlayTpl) - - c.paramsKeysToLower(overlayTpl.Tree.Root) - - var b bytes.Buffer - - require.NoError(t, overlayTpl.Execute(&b, ctx)) - - result := b.String() - - require.Contains(t, result, "P1: P1L") - require.Contains(t, result, "P2: P1L") -} diff --git a/tpl/template_embedded.go b/tpl/template_embedded.go deleted file mode 100644 index f782a31e9..000000000 --- a/tpl/template_embedded.go +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2015 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 tpl - -type Tmpl struct { - Name string - Data string -} - -func (t *GoHTMLTemplate) EmbedShortcodes() { - t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`) - t.AddInternalShortcode("relref.html", `{{ .Get 0 | relref .Page }}`) - t.AddInternalShortcode("highlight.html", `{{ if len .Params | eq 2 }}{{ highlight .Inner (.Get 0) (.Get 1) }}{{ else }}{{ highlight .Inner (.Get 0) "" }}{{ end }}`) - t.AddInternalShortcode("test.html", `This is a simple Test`) - t.AddInternalShortcode("figure.html", ` -
- {{ with .Get "link"}}{{ end }} - - {{ if .Get "link"}}{{ end }} - {{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}} -
{{ if isset .Params "title" }} -

{{ .Get "title" }}

{{ end }} - {{ if or (.Get "caption") (.Get "attr")}}

- {{ .Get "caption" }} - {{ with .Get "attrlink"}} {{ end }} - {{ .Get "attr" }} - {{ if .Get "attrlink"}} {{ end }} -

{{ end }} -
- {{ end }} -
-`) - t.AddInternalShortcode("speakerdeck.html", "") - t.AddInternalShortcode("youtube.html", `{{ if .IsNamedParams }} -
- -
{{ else }} -
- -
-{{ end }}`) - t.AddInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}
- -
{{ else }} -
- -
-{{ end }}`) - t.AddInternalShortcode("gist.html", ``) - t.AddInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`) - t.AddInternalShortcode("instagram.html", `{{ if len .Params | eq 2 }}{{ if eq (.Get 1) "hidecaption" }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1").html | safeHTML }}{{ end }}{{ else }}{{ (getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0").html | safeHTML }}{{ end }}`) -} - -func (t *GoHTMLTemplate) EmbedTemplates() { - - t.AddInternalTemplate("_default", "rss.xml", ` - - {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }} - {{ .Permalink }} - Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }} - Hugo -- gohugo.io{{ with .Site.LanguageCode }} - {{.}}{{end}}{{ with .Site.Author.email }} - {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Author.email }} - {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Copyright }} - {{.}}{{end}}{{ if not .Date.IsZero }} - {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}{{ end }} - - {{ range first 15 .Data.Pages }} - - {{ .Title }} - {{ .Permalink }} - {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} - {{ with .Site.Author.email }}{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}} - {{ .Permalink }} - {{ .Content | html }} - - {{ end }} - -`) - - t.AddInternalTemplate("_default", "sitemap.xml", ` - {{ range .Data.Pages }} - - {{ .Permalink }}{{ if not .Lastmod.IsZero }} - {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}{{ end }}{{ with .Sitemap.ChangeFreq }} - {{ . }}{{ end }}{{ if ge .Sitemap.Priority 0.0 }} - {{ .Sitemap.Priority }}{{ end }} - - {{ end }} -`) - - // For multilanguage sites - t.AddInternalTemplate("_default", "sitemapindex.xml", ` - {{ range . }} - - {{ .SitemapAbsURL }} - {{ if not .LastChange.IsZero }} - {{ .LastChange.Format "2006-01-02T15:04:05-07:00" | safeHTML }} - {{ end }} - - {{ end }} - -`) - - t.AddInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }} - {{ if gt $pag.TotalPages 1 }} -
    - {{ with $pag.First }} -
  • - -
  • - {{ end }} -
  • - -
  • - {{ range $pag.Pagers }} -
  • {{ .PageNumber }}
  • - {{ end }} -
  • - -
  • - {{ with $pag.Last }} -
  • - -
  • - {{ end }} -
- {{ end }}`) - - t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}
- - -comments powered by Disqus{{end}}`) - - // Add SEO & Social metadata - t.AddInternalTemplate("", "opengraph.html", ` - - - -{{ with .Params.images }}{{ range first 6 . }} - -{{ end }}{{ end }} - -{{ if .IsPage }} -{{ if not .PublishDate.IsZero }} -{{ else if not .Date.IsZero }}{{ end }} -{{ if not .Lastmod.IsZero }}{{ end }} -{{ else }} -{{ if not .Date.IsZero }}{{ end }} -{{ end }}{{ with .Params.audio }} -{{ end }}{{ with .Params.locale }} -{{ end }}{{ with .Site.Params.title }} -{{ end }}{{ with .Params.videos }} -{{ range .Params.videos }} - -{{ end }}{{ end }} - - -{{ $permalink := .Permalink }} -{{ $siteSeries := .Site.Taxonomies.series }}{{ with .Params.series }} -{{ range $name := . }} - {{ $series := index $siteSeries $name }} - {{ range $page := first 6 $series.Pages }} - {{ if ne $page.Permalink $permalink }}{{ end }} - {{ end }} -{{ end }}{{ end }} - -{{ if .IsPage }} -{{ range .Site.Authors }}{{ with .Social.facebook }} -{{ end }}{{ with .Site.Social.facebook }} -{{ end }} - -{{ with .Params.tags }}{{ range first 6 . }} - {{ end }}{{ end }} -{{ end }}{{ end }} - - -{{ with .Site.Social.facebook_admin }}{{ end }}`) - - t.AddInternalTemplate("", "twitter_cards.html", `{{ if .IsPage }} -{{ with .Params.images }} - - - -{{ else }} - -{{ end }} - - -