`)
return nil
}
CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `
`, wt)
@@ -191,8 +190,8 @@ func TestIsNamedParamsSC(t *testing.T) {
func TestInnerSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
- tem.AddTemplate("_internal/shortcodes/inside.html", `
{{ .Inner }}
`)
+ wt := func(tem tpl.Template) error {
+ tem.AddInternalShortcode("inside.html", `
{{ .Inner }}
`)
return nil
}
CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `
`, wt)
@@ -202,8 +201,8 @@ func TestInnerSC(t *testing.T) {
func TestInnerSCWithMarkdown(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
- tem.AddTemplate("_internal/shortcodes/inside.html", `
{{ .Inner }}
`)
+ wt := func(tem tpl.Template) error {
+ tem.AddInternalShortcode("inside.html", `
{{ .Inner }}
`)
return nil
}
CheckShortCodeMatch(t, `{{% inside %}}
@@ -216,8 +215,8 @@ func TestInnerSCWithMarkdown(t *testing.T) {
func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
- tem.AddTemplate("_internal/shortcodes/inside.html", `
{{ .Inner }}
`)
+ wt := func(tem tpl.Template) error {
+ tem.AddInternalShortcode("inside.html", `
{{ .Inner }}
`)
return nil
}
CheckShortCodeMatch(t, `{{% inside %}}
@@ -247,9 +246,9 @@ func TestEmbeddedSC(t *testing.T) {
func TestNestedSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
- tem.AddTemplate("_internal/shortcodes/scn1.html", `
Outer, inner is {{ .Inner }}
`)
- tem.AddTemplate("_internal/shortcodes/scn2.html", `
SC2
`)
+ wt := func(tem tpl.Template) error {
+ tem.AddInternalShortcode("scn1.html", `
Outer, inner is {{ .Inner }}
`)
+ tem.AddInternalShortcode("scn2.html", `
SC2
`)
return nil
}
CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "
", wt)
@@ -259,10 +258,10 @@ func TestNestedSC(t *testing.T) {
func TestNestedComplexSC(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
- tem.AddTemplate("_internal/shortcodes/row.html", `-row-{{ .Inner}}-rowStop-`)
- tem.AddTemplate("_internal/shortcodes/column.html", `-col-{{.Inner }}-colStop-`)
- tem.AddTemplate("_internal/shortcodes/aside.html", `-aside-{{ .Inner }}-asideStop-`)
+ 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-`)
return nil
}
CheckShortCodeMatch(t, `{{< row >}}1-s{{% column %}}2-**s**{{< aside >}}3-**s**{{< /aside >}}4-s{{% /column %}}5-s{{< /row >}}6-s`,
@@ -275,10 +274,10 @@ func TestNestedComplexSC(t *testing.T) {
func TestParentShortcode(t *testing.T) {
t.Parallel()
- wt := func(tem tpl.TemplateHandler) error {
- tem.AddTemplate("_internal/shortcodes/r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
- tem.AddTemplate("_internal/shortcodes/r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
- tem.AddTemplate("_internal/shortcodes/r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
+ 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 }}`)
return nil
}
CheckShortCodeMatch(t, `{{< r1 pr1="p1" >}}1: {{< r2 pr2="p2" >}}2: {{< r3 pr3="p3" >}}{{< /r3 >}}{{< /r2 >}}{{< /r1 >}}`,
@@ -343,13 +342,13 @@ func TestExtractShortcodes(t *testing.T) {
fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
} {
- p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.TemplateHandler) error {
- templ.AddTemplate("_internal/shortcodes/tag.html", `tag`)
- templ.AddTemplate("_internal/shortcodes/sc1.html", `sc1`)
- templ.AddTemplate("_internal/shortcodes/sc2.html", `sc2`)
- templ.AddTemplate("_internal/shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`)
- templ.AddTemplate("_internal/shortcodes/inner2.html", `{{.Inner}}`)
- templ.AddTemplate("_internal/shortcodes/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
})
@@ -518,14 +517,14 @@ tags:
sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)}
}
- addTemplates := func(templ tpl.TemplateHandler) error {
+ addTemplates := func(templ tpl.Template) error {
templ.AddTemplate("_default/single.html", "{{.Content}}")
- templ.AddTemplate("_internal/shortcodes/b.html", `b`)
- templ.AddTemplate("_internal/shortcodes/c.html", `c`)
- templ.AddTemplate("_internal/shortcodes/d.html", `d`)
- templ.AddTemplate("_internal/shortcodes/menu.html", `{{ len (index .Page.Menus "main").Children }}`)
- templ.AddTemplate("_internal/shortcodes/tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
+ templ.AddInternalShortcode("b.html", `b`)
+ templ.AddInternalShortcode("c.html", `c`)
+ templ.AddInternalShortcode("d.html", `d`)
+ templ.AddInternalShortcode("menu.html", `{{ len (index .Page.Menus "main").Children }}`)
+ templ.AddInternalShortcode("tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
return nil
diff --git a/hugolib/site.go b/hugolib/site.go
index 613485864..c42b938e8 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -188,7 +188,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 tpl.TemplateHandler) error) (*Site, error) {
+func NewSiteDefaultLang(withTemplate ...func(templ tpl.Template) error) (*Site, error) {
v := viper.New()
loadDefaultSettingsFor(v)
return newSiteForLang(helpers.NewDefaultLanguage(v), withTemplate...)
@@ -197,15 +197,15 @@ func NewSiteDefaultLang(withTemplate ...func(templ tpl.TemplateHandler) error) (
// 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 tpl.TemplateHandler) 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 tpl.TemplateHandler) error) (*Site, error) {
- withTemplates := func(templ tpl.TemplateHandler) 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
@@ -1906,14 +1906,13 @@ Your rendered home page is blank: /index.html is zero-length
}
func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts ...string) error {
- templ := s.findFirstTemplate(layouts...)
- if templ == nil {
+ layout, found := s.findFirstLayout(layouts...)
+ if !found {
helpers.DistinctWarnLog.Printf("[%s] Unable to locate layout for %s: %s\n", s.Language.Lang, name, layouts)
-
return nil
}
- if err := templ.Execute(w, d); err != nil {
+ if err := s.renderThing(d, layout, w); err != nil {
// Behavior here should be dependent on if running in server or watch mode.
helpers.DistinctErrorLog.Printf("Error while rendering %q: %s", name, err)
@@ -1928,13 +1927,23 @@ func (s *Site) renderForLayouts(name string, d interface{}, w io.Writer, layouts
return nil
}
-func (s *Site) findFirstTemplate(layouts ...string) tpl.Template {
+func (s *Site) findFirstLayout(layouts ...string) (string, bool) {
for _, layout := range layouts {
- if templ := s.Tmpl.Lookup(layout); templ != nil {
- return templ
+ if s.Tmpl.Lookup(layout) != nil {
+ return layout, true
}
}
- return nil
+ return "", false
+}
+
+func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {
+
+ // If the template doesn't exist, then return, but leave the Writer open
+ if templ := s.Tmpl.Lookup(layout); templ != nil {
+ return templ.Execute(w, d)
+ }
+ return fmt.Errorf("Layout not found: %s", layout)
+
}
func (s *Site) publish(path string, r io.Reader) (err error) {
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go
index 06c55a69c..86e1a55ca 100644
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -90,15 +90,9 @@ outputs: %s
Alt Output: {{ .Name -}}|
{{- end -}}|
{{- range .OutputFormats -}}
-Output/Rel: {{ .Name -}}/{{ .Rel }}|{{ .MediaType }}
+Output/Rel: {{ .Name -}}/{{ .Rel }}|
{{- end -}}
- {{ with .OutputFormats.Get "JSON" }}
-
-{{ end }}
`,
- "layouts/_default/list.html", `List HTML|{{ with .OutputFormats.Get "HTML" -}}
-
-{{- end -}}`,
)
require.Len(t, h.Sites, 1)
@@ -119,6 +113,7 @@ Output/Rel: {{ .Name -}}/{{ .Rel }}|{{ .MediaType }}
require.Len(t, home.outputFormats, lenOut)
+ // TODO(bep) output assert template/text
// There is currently always a JSON output to make it simpler ...
altFormats := lenOut - 1
hasHTML := helpers.InStringArray(outputs, "html")
@@ -133,16 +128,9 @@ Output/Rel: {{ .Name -}}/{{ .Rel }}|{{ .MediaType }}
"Output/Rel: JSON/alternate|",
"Output/Rel: HTML/canonical|",
)
- th.assertFileContent("public/index.html",
- // The HTML entity is a deliberate part of this test: The HTML templates are
- // parsed with html/template.
- `List HTML|
`,
- )
} else {
th.assertFileContent("public/index.json",
"Output/Rel: JSON/canonical|",
- // JSON is plain text, so no need to safeHTML this and that
- `
`,
)
}
diff --git a/hugolib/site_test.go b/hugolib/site_test.go
index 5f66b153c..a3ec66880 100644
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -52,6 +52,24 @@ func pageMust(p *Page, err error) *Page {
return p
}
+func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
+ t.Parallel()
+ cfg, fs := newTestCfg()
+
+ writeSource(t, fs, filepath.Join("content", "a", "file.md"), pageSimpleTitle)
+
+ s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
+
+ require.Len(t, s.RegularPages, 1)
+
+ p := s.RegularPages[0]
+
+ err := s.renderThing(p, "foobar", nil)
+ if err == nil {
+ t.Errorf("Expected err to be returned when missing the template.")
+ }
+}
+
func TestRenderWithInvalidTemplate(t *testing.T) {
t.Parallel()
cfg, fs := newTestCfg()
diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go
index 47f29c947..df342953b 100644
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -48,7 +48,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
depsCfg := deps.DepsCfg{Fs: fs, Cfg: cfg}
if !internal {
- depsCfg.WithTemplate = func(templ tpl.TemplateHandler) 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 fbaab7192..d50514529 100644
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -164,9 +164,9 @@ func newDebugLogger() *jww.Notepad {
func newErrorLogger() *jww.Notepad {
return jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
}
-func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.TemplateHandler) error {
+func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.Template) error {
- return func(templ tpl.TemplateHandler) 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/output/layout.go b/output/layout.go
index 6dba7f3b4..a2bfd7717 100644
--- a/output/layout.go
+++ b/output/layout.go
@@ -152,11 +152,9 @@ func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format)
}
}
- layouts = layoutsWithThemeLayouts
+ return layoutsWithThemeLayouts, nil
}
- layouts = prependTextPrefixIfNeeded(f, layouts...)
-
l.mu.Lock()
l.cache[key] = layouts
l.mu.Unlock()
@@ -186,26 +184,10 @@ func resolveListTemplate(d LayoutDescriptor, f Format,
}
func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string {
- layouts := strings.Fields(replaceKeyValues(templ,
+ return strings.Fields(replaceKeyValues(templ,
"SUFFIX", f.MediaType.Suffix,
"NAME", strings.ToLower(f.Name),
"SECTION", d.Section))
-
- return layouts
-}
-
-func prependTextPrefixIfNeeded(f Format, layouts ...string) []string {
- if !f.IsPlainText {
- return layouts
- }
-
- newLayouts := make([]string, len(layouts))
-
- for i, l := range layouts {
- newLayouts[i] = "_text/" + l
- }
-
- return newLayouts
}
func replaceKeyValues(s string, oldNew ...string) string {
@@ -213,9 +195,7 @@ func replaceKeyValues(s string, oldNew ...string) string {
return replacer.Replace(s)
}
-func regularPageLayouts(types string, layout string, f Format) []string {
- var layouts []string
-
+func regularPageLayouts(types string, layout string, f Format) (layouts []string) {
if layout == "" {
layout = "single"
}
@@ -239,5 +219,5 @@ func regularPageLayouts(types string, layout string, f Format) []string {
layouts = append(layouts, fmt.Sprintf("_default/%s.%s.%s", layout, name, suffix))
layouts = append(layouts, fmt.Sprintf("_default/%s.%s", layout, suffix))
- return layouts
+ return
}
diff --git a/output/layout_base.go b/output/layout_base.go
index a0d2bc4eb..2bb89c20d 100644
--- a/output/layout_base.go
+++ b/output/layout_base.go
@@ -29,10 +29,7 @@ var (
)
type TemplateNames struct {
- // The name used as key in the template map. Note that this will be
- // prefixed with "_text/" if it should be parsed with text/template.
- Name string
-
+ Name string
OverlayFilename string
MasterFilename string
}
@@ -54,10 +51,6 @@ type TemplateLookupDescriptor struct {
// The theme name if active.
Theme string
- // All the output formats in play. This is used to decide if text/template or
- // html/template.
- OutputFormats Formats
-
FileExists func(filename string) (bool, error)
ContainsAny func(filename string, subslices [][]byte) (bool, error)
}
@@ -81,12 +74,6 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
// index.amp.html
// index.json
filename := filepath.Base(d.RelPath)
- isPlainText := false
- outputFormat, found := d.OutputFormats.FromFilename(filename)
-
- if found && outputFormat.IsPlainText {
- isPlainText = true
- }
var ext, outFormat string
@@ -103,10 +90,6 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
id.OverlayFilename = fullPath
id.Name = name
- if isPlainText {
- id.Name = "_text/" + id.Name
- }
-
// Ace and Go templates may have both a base and inner template.
pathDir := filepath.Dir(fullPath)
diff --git a/output/layout_base_test.go b/output/layout_base_test.go
index 16be615f2..f20d99bef 100644
--- a/output/layout_base_test.go
+++ b/output/layout_base_test.go
@@ -141,7 +141,6 @@ func TestLayoutBase(t *testing.T) {
return this.needsBase, nil
}
- this.d.OutputFormats = Formats{AMPFormat, HTMLFormat, RSSFormat, JSONFormat}
this.d.WorkingDir = filepath.FromSlash(this.d.WorkingDir)
this.d.LayoutDir = filepath.FromSlash(this.d.LayoutDir)
this.d.RelPath = filepath.FromSlash(this.d.RelPath)
@@ -151,11 +150,6 @@ func TestLayoutBase(t *testing.T) {
this.expect.MasterFilename = filepath.FromSlash(this.expect.MasterFilename)
this.expect.OverlayFilename = filepath.FromSlash(this.expect.OverlayFilename)
- if strings.Contains(this.d.RelPath, "json") {
- // currently the only plain text templates in this test.
- this.expect.Name = "_text/" + this.expect.Name
- }
-
id, err := CreateTemplateNames(this.d)
require.NoError(t, err)
diff --git a/output/layout_test.go b/output/layout_test.go
index 6ea5a7617..e59a16fcb 100644
--- a/output/layout_test.go
+++ b/output/layout_test.go
@@ -64,10 +64,6 @@ func TestLayout(t *testing.T) {
[]string{"taxonomy/tag.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
{"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, "", RSSFormat,
[]string{"taxonomy/tag.terms.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
- {"Home plain text", LayoutDescriptor{Kind: "home"}, true, "", JSONFormat,
- []string{"_text/index.json.json", "_text/index.json", "_text/_default/list.json.json", "_text/_default/list.json", "_text/theme/index.json.json", "_text/theme/index.json"}},
- {"Page plain text", LayoutDescriptor{Kind: "page"}, true, "", JSONFormat,
- []string{"_text/_default/single.json.json", "_text/_default/single.json", "_text/theme/_default/single.json.json"}},
} {
t.Run(this.name, func(t *testing.T) {
l := NewLayoutHandler(this.hasTheme)
diff --git a/output/outputFormat.go b/output/outputFormat.go
index 9d43b135a..76329a936 100644
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -33,7 +33,6 @@ var (
IsHTML: true,
}
- // CalendarFormat is AAA
CalendarFormat = Format{
Name: "Calendar",
MediaType: media.CalendarType,
@@ -105,45 +104,6 @@ func (formats Formats) GetByName(name string) (f Format, found bool) {
return
}
-func (formats Formats) GetBySuffix(name string) (f Format, found bool) {
- for _, ff := range formats {
- if name == ff.MediaType.Suffix {
- if found {
- // ambiguous
- found = false
- return
- }
- f = ff
- found = true
- }
- }
- return
-}
-
-func (formats Formats) FromFilename(filename string) (f Format, found bool) {
- // mytemplate.amp.html
- // mytemplate.html
- // mytemplate
- var ext, outFormat string
-
- parts := strings.Split(filename, ".")
- if len(parts) > 2 {
- outFormat = parts[1]
- ext = parts[2]
- } else if len(parts) > 1 {
- ext = parts[1]
- }
-
- if outFormat != "" {
- return formats.GetByName(outFormat)
- }
-
- if ext != "" {
- return formats.GetBySuffix(ext)
- }
- return
-}
-
// Format represents an output representation, usually to a file on disk.
type Format struct {
// The Name is used as an identifier. Internal output formats (i.e. HTML and RSS)
diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go
index b73e53f82..e742012ba 100644
--- a/output/outputFormat_test.go
+++ b/output/outputFormat_test.go
@@ -65,7 +65,7 @@ func TestDefaultTypes(t *testing.T) {
}
-func TestGetFormat(t *testing.T) {
+func TestGetType(t *testing.T) {
tp, _ := GetFormat("html")
require.Equal(t, HTMLFormat, tp)
tp, _ = GetFormat("HTML")
@@ -73,28 +73,3 @@ func TestGetFormat(t *testing.T) {
_, found := GetFormat("FOO")
require.False(t, found)
}
-
-func TestGeGetFormatByName(t *testing.T) {
- formats := Formats{AMPFormat, CalendarFormat}
- tp, _ := formats.GetByName("AMP")
- require.Equal(t, AMPFormat, tp)
- _, found := formats.GetByName("HTML")
- require.False(t, found)
- _, found = formats.GetByName("FOO")
- require.False(t, found)
-}
-
-func TestGeGetFormatByExt(t *testing.T) {
- formats1 := Formats{AMPFormat, CalendarFormat}
- formats2 := Formats{AMPFormat, HTMLFormat, CalendarFormat}
- tp, _ := formats1.GetBySuffix("html")
- require.Equal(t, AMPFormat, tp)
- tp, _ = formats1.GetBySuffix("ics")
- require.Equal(t, CalendarFormat, tp)
- _, found := formats1.GetBySuffix("not")
- require.False(t, found)
-
- // ambiguous
- _, found = formats2.GetByName("html")
- require.False(t, found)
-}
diff --git a/tpl/template.go b/tpl/template.go
index 617aa84ec..b94fc3242 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -1,103 +1,28 @@
-// Copyright 2017-present 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 (
- "io"
-
- "text/template/parse"
-
"html/template"
- texttemplate "text/template"
-
- bp "github.com/spf13/hugo/bufferpool"
+ "io"
)
-var (
- _ TemplateExecutor = (*TemplateAdapter)(nil)
-)
-
-// TemplateHandler manages the collection of templates.
-type TemplateHandler interface {
- TemplateFinder
- AddTemplate(name, tpl string) error
- AddLateTemplate(name, tpl string) error
- LoadTemplates(absPath, prefix string)
- PrintErrors()
-
- MarkReady()
- RebuildClone()
-}
-
-// TemplateFinder finds templates.
-type TemplateFinder interface {
- Lookup(name string) *TemplateAdapter
-}
-
-// Template is the common interface between text/template and html/template.
+// TODO(bep) make smaller
type Template interface {
- Execute(wr io.Writer, data interface{}) error
- Name() string
-}
-
-// TemplateExecutor adds some extras to Template.
-type TemplateExecutor interface {
- Template
- ExecuteToString(data interface{}) (string, error)
- Tree() string
-}
-
-// TemplateAdapter implements the TemplateExecutor interface.
-type TemplateAdapter struct {
- Template
-}
-
-// ExecuteToString executes the current template and returns the result as a
-// string.
-func (t *TemplateAdapter) ExecuteToString(data interface{}) (string, error) {
- b := bp.GetBuffer()
- defer bp.PutBuffer(b)
- if err := t.Execute(b, data); err != nil {
- return "", err
- }
- return b.String(), nil
-}
-
-// Tree returns the template Parse tree as a string.
-// Note: this isn't safe for parallel execution on the same template
-// vs Lookup and Execute.
-func (t *TemplateAdapter) Tree() string {
- var tree *parse.Tree
- switch tt := t.Template.(type) {
- case *template.Template:
- tree = tt.Tree
- case *texttemplate.Template:
- tree = tt.Tree
- default:
- panic("Unknown template")
- }
-
- if tree.Root == nil {
- return ""
- }
- s := tree.Root.String()
-
- return s
-}
-
-// TemplateTestMocker adds a way to override some template funcs during tests.
-// The interface is named so it's not used in regular application code.
-type TemplateTestMocker interface {
- SetFuncs(funcMap map[string]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
+ RebuildClone() *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/tplimpl/ace.go b/tpl/tplimpl/ace.go
deleted file mode 100644
index fc3a1e1b1..000000000
--- a/tpl/tplimpl/ace.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2017-present 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 tplimpl
-
-import (
- "path/filepath"
-
- "strings"
-
- "github.com/yosssi/ace"
-)
-
-func (t *templateHandler) 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.html.t.New(name), parsed, nil)
- if err != nil {
- t.errors = append(t.errors, &templateErr{name: name, err: err})
- return err
- }
- return applyTemplateTransformersToHMLTTemplate(templ)
-}
diff --git a/tpl/tplimpl/amber_compiler.go b/tpl/tplimpl/amber_compiler.go
index 10ed0443c..252c39ffb 100644
--- a/tpl/tplimpl/amber_compiler.go
+++ b/tpl/tplimpl/amber_compiler.go
@@ -19,7 +19,7 @@ import (
"github.com/eknkc/amber"
)
-func (t *templateHandler) compileAmberWithTemplate(b []byte, path string, templ *template.Template) (*template.Template, error) {
+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 {
@@ -32,7 +32,7 @@ func (t *templateHandler) compileAmberWithTemplate(b []byte, path string, templ
return nil, err
}
- tpl, err := templ.Funcs(t.amberFuncMap).Parse(data)
+ tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
if err != nil {
return nil, err
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index 49be4a769..ea12cde7a 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -1,4 +1,4 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
+// 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.
@@ -15,39 +15,23 @@ package tplimpl
import (
"html/template"
- "strings"
- texttemplate "text/template"
-
- "github.com/eknkc/amber"
-
+ "io"
"os"
-
- "github.com/spf13/hugo/output"
-
"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/spf13/hugo/tpl"
+ "github.com/spf13/hugo/output"
+ "github.com/yosssi/ace"
)
-const (
- textTmplNamePrefix = "_text/"
-)
-
-var (
- _ tpl.TemplateHandler = (*templateHandler)(nil)
- _ tpl.TemplateTestMocker = (*templateHandler)(nil)
- _ tpl.TemplateFinder = (*htmlTemplates)(nil)
- _ tpl.TemplateFinder = (*textTemplates)(nil)
- _ templateLoader = (*htmlTemplates)(nil)
- _ templateLoader = (*textTemplates)(nil)
- _ templateLoader = (*templateHandler)(nil)
- _ templateFuncsterTemplater = (*htmlTemplates)(nil)
- _ templateFuncsterTemplater = (*textTemplates)(nil)
-)
+// TODO(bep) globals get rid of the rest of the jww.ERR etc.
// Protecting global map access (Amber)
var amberMu sync.Mutex
@@ -57,120 +41,8 @@ type templateErr struct {
err error
}
-type templateLoader interface {
- handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error
- addTemplate(name, tpl string) error
- addLateTemplate(name, tpl string) error
-}
-
-type templateFuncsterTemplater interface {
- tpl.TemplateFinder
- setFuncs(funcMap map[string]interface{})
- setTemplateFuncster(f *templateFuncster)
-}
-
-// templateHandler holds the templates in play.
-// It implements the templateLoader and tpl.TemplateHandler interfaces.
-type templateHandler struct {
- // text holds all the pure text templates.
- text *textTemplates
- html *htmlTemplates
-
- amberFuncMap template.FuncMap
-
- errors []*templateErr
-
- *deps.Deps
-}
-
-func (t *templateHandler) addError(name string, err error) {
- t.errors = append(t.errors, &templateErr{name, err})
-}
-
-// PrintErrors prints the accumulated errors as ERROR to the log.
-func (t *templateHandler) PrintErrors() {
- for _, e := range t.errors {
- t.Log.ERROR.Println(e.name, ":", e.err)
- }
-}
-
-// Lookup tries to find a template with the given name in both template
-// collections: First HTML, then the plain text template collection.
-func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
- var te *tpl.TemplateAdapter
-
- isTextTemplate := strings.HasPrefix(name, textTmplNamePrefix)
-
- if isTextTemplate {
- // The templates are stored without the prefix identificator.
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- te = t.text.Lookup(name)
- } else {
- te = t.html.Lookup(name)
- }
-
- if te == nil {
- return nil
- }
-
- return te
-}
-
-func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
- c := &templateHandler{
- Deps: d,
- html: &htmlTemplates{t: template.Must(t.html.t.Clone()), overlays: make(map[string]*template.Template)},
- text: &textTemplates{t: texttemplate.Must(t.text.t.Clone()), overlays: make(map[string]*texttemplate.Template)},
- errors: make([]*templateErr, 0),
- }
-
- d.Tmpl = c
-
- c.initFuncs()
-
- for k, v := range t.html.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(t.html.funcster.funcMap)
- c.html.overlays[k] = vc
- }
-
- for k, v := range t.text.overlays {
- vc := texttemplate.Must(v.Clone())
- vc = vc.Lookup(vc.Name())
- vc.Funcs(texttemplate.FuncMap(t.text.funcster.funcMap))
- c.text.overlays[k] = vc
- }
-
- return c
-
-}
-
-func newTemplateAdapter(deps *deps.Deps) *templateHandler {
- htmlT := &htmlTemplates{
- t: template.New(""),
- overlays: make(map[string]*template.Template),
- }
- textT := &textTemplates{
- t: texttemplate.New(""),
- overlays: make(map[string]*texttemplate.Template),
- }
- return &templateHandler{
- Deps: deps,
- html: htmlT,
- text: textT,
- errors: make([]*templateErr, 0),
- }
-
-}
-
-type htmlTemplates struct {
- funcster *templateFuncster
-
- t *template.Template
+type GoHTMLTemplate struct {
+ *template.Template
// This looks, and is, strange.
// The clone is used by non-renderable content pages, and these need to be
@@ -182,201 +54,397 @@ type htmlTemplates struct {
// 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
-}
-func (t *htmlTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
-}
+ errors []*templateErr
-func (t *htmlTemplates) Lookup(name string) *tpl.TemplateAdapter {
- templ := t.lookup(name)
- if templ == nil {
- return nil
- }
- return &tpl.TemplateAdapter{templ}
-}
-
-func (t *htmlTemplates) lookup(name string) *template.Template {
- if templ := t.t.Lookup(name); templ != nil {
- return templ
- }
- if t.overlays != nil {
- if templ, ok := t.overlays[name]; ok {
- return templ
- }
- }
-
- if t.clone != nil {
- return t.clone.Lookup(name)
- }
-
- return nil
-}
-
-type textTemplates struct {
funcster *templateFuncster
- t *texttemplate.Template
+ amberFuncMap template.FuncMap
- clone *texttemplate.Template
- cloneClone *texttemplate.Template
-
- overlays map[string]*texttemplate.Template
+ *deps.Deps
}
-func (t *textTemplates) setTemplateFuncster(f *templateFuncster) {
- t.funcster = f
-}
+type TemplateProvider struct{}
-func (t *textTemplates) Lookup(name string) *tpl.TemplateAdapter {
- templ := t.lookup(name)
- if templ == nil {
- return nil
+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 {
+ tmpl := &GoHTMLTemplate{
+ Template: template.New(""),
+ overlays: make(map[string]*template.Template),
+ errors: make([]*templateErr, 0),
+ Deps: deps,
}
- return &tpl.TemplateAdapter{templ}
+
+ 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
+
}
-func (t *textTemplates) lookup(name string) *texttemplate.Template {
- if templ := t.t.Lookup(name); templ != 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 {
+ // TODO(bep) output
+ 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 {
- return t.clone.Lookup(name)
+ if templ := t.clone.Lookup(name); templ != nil {
+ return templ
+ }
}
return nil
-}
-
-func (t *templateHandler) setFuncs(funcMap map[string]interface{}) {
- t.html.setFuncs(funcMap)
- t.text.setFuncs(funcMap)
-}
-
-// SetFuncs replaces the funcs in the func maps with new definitions.
-// This is only used in tests.
-func (t *templateHandler) SetFuncs(funcMap map[string]interface{}) {
- t.setFuncs(funcMap)
-}
-
-func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) {
- t.t.Funcs(funcMap)
-}
-
-func (t *textTemplates) setFuncs(funcMap map[string]interface{}) {
- t.t.Funcs(funcMap)
-}
-
-// LoadTemplates loads the templates, starting from the given absolute path.
-// A prefix can be given to indicate a template namespace to load the templates
-// into, i.e. "_internal" etc.
-func (t *templateHandler) LoadTemplates(absPath, prefix string) {
- // TODO(bep) output formats. Will have to get to complete list when that is ready.
- t.loadTemplates(absPath, prefix, output.Formats{output.HTMLFormat, output.RSSFormat, output.CalendarFormat, output.AMPFormat, output.JSONFormat})
}
-func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) error {
- templ, err := tt.New(name).Parse(tpl)
- if err != nil {
- return err
- }
-
- if err := applyTemplateTransformersToHMLTTemplate(templ); err != nil {
- return err
- }
-
- return nil
+func (t *GoHTMLTemplate) GetClone() *template.Template {
+ return t.clone
}
-func (t *htmlTemplates) addTemplate(name, tpl string) error {
- return t.addTemplateIn(t.t, name, tpl)
+func (t *GoHTMLTemplate) RebuildClone() *template.Template {
+ t.clone = template.Must(t.cloneClone.Clone())
+ return t.clone
}
-func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
- return t.addTemplateIn(t.clone, name, tpl)
+func (t *GoHTMLTemplate) LoadEmbedded() {
+ t.EmbedShortcodes()
+ t.EmbedTemplates()
}
-func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) error {
- name = strings.TrimPrefix(name, textTmplNamePrefix)
- templ, err := tt.New(name).Parse(tpl)
- if err != nil {
- return err
- }
-
- if err := applyTemplateTransformersToTextTemplate(templ); err != nil {
- return err
- }
-
- return nil
-}
-
-func (t *textTemplates) addTemplate(name, tpl string) error {
- return t.addTemplateIn(t.t, name, tpl)
-}
-
-func (t *textTemplates) addLateTemplate(name, tpl string) error {
- return t.addTemplateIn(t.clone, name, tpl)
-}
-
-func (t *templateHandler) addTemplate(name, tpl string) error {
- return t.AddTemplate(name, tpl)
-}
-
-func (t *templateHandler) addLateTemplate(name, tpl string) error {
- return t.AddLateTemplate(name, tpl)
-}
-
-// AddLateTemplate is used to add a template late, i.e. after the
-// regular templates have started its execution.
-func (t *templateHandler) AddLateTemplate(name, tpl string) error {
- h := t.getTemplateHandler(name)
- if err := h.addLateTemplate(name, tpl); err != nil {
- t.addError(name, err)
- return err
- }
- return nil
-}
-
-// AddTemplate parses and adds a template to the collection.
-// Templates with name prefixed with "_text" will be handled as plain
-// text templates.
-func (t *templateHandler) AddTemplate(name, tpl string) error {
- h := t.getTemplateHandler(name)
- if err := h.addTemplate(name, tpl); err != nil {
- t.addError(name, err)
- return err
- }
- return nil
-}
-
-// MarkReady marks the templates as "ready for execution". No changes allowed
+// MarkReady marks the template as "ready for execution". No changes allowed
// after this is set.
// TODO(bep) if this proves to be resource heavy, we could detect
// earlier if we really need this, or make it lazy.
-func (t *templateHandler) MarkReady() {
- if t.html.clone == nil {
- t.html.clone = template.Must(t.html.t.Clone())
- t.html.cloneClone = template.Must(t.html.clone.Clone())
- }
- if t.text.clone == nil {
- t.text.clone = texttemplate.Must(t.text.t.Clone())
- t.text.cloneClone = texttemplate.Must(t.text.clone.Clone())
+func (t *GoHTMLTemplate) MarkReady() {
+ if t.clone == nil {
+ t.clone = template.Must(t.Template.Clone())
+ t.cloneClone = template.Must(t.clone.Clone())
}
}
-// RebuildClone rebuilds the cloned templates. Used for live-reloads.
-func (t *templateHandler) RebuildClone() {
- t.html.clone = template.Must(t.html.cloneClone.Clone())
- t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
+func (t *GoHTMLTemplate) checkState() {
+ if t.clone != nil {
+ panic("template is cloned and cannot be modfified")
+ }
}
-func (t *templateHandler) loadTemplates(absPath string, prefix string, formats output.Formats) {
+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"
+
+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 {
@@ -423,12 +491,11 @@ func (t *templateHandler) loadTemplates(absPath string, prefix string, formats o
relPath := path[li:]
descriptor := output.TemplateLookupDescriptor{
- WorkingDir: workingDir,
- LayoutDir: layoutDir,
- RelPath: relPath,
- Prefix: prefix,
- Theme: t.PathSpec.Theme(),
- OutputFormats: formats,
+ WorkingDir: workingDir,
+ LayoutDir: layoutDir,
+ RelPath: relPath,
+ Prefix: prefix,
+ Theme: t.PathSpec.Theme(),
FileExists: func(filename string) (bool, error) {
return helpers.Exists(filename, t.Fs.Source)
},
@@ -444,7 +511,7 @@ func (t *templateHandler) loadTemplates(absPath string, prefix string, formats o
return nil
}
- if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
+ if err := t.AddTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
t.Log.ERROR.Printf("Failed to add template %q in path %q: %s", tplID.Name, path, err)
}
@@ -456,223 +523,16 @@ func (t *templateHandler) loadTemplates(absPath string, prefix string, formats o
}
}
-func (t *templateHandler) initFuncs() {
-
- // The template funcs need separation between text and html templates.
- for _, funcsterHolder := range []templateFuncsterTemplater{t.html, t.text} {
- funcster := newTemplateFuncster(t.Deps, funcsterHolder)
-
- // The URL funcs in the funcMap is somewhat language dependent,
- // so we need to wait until the language and site config is loaded.
- funcster.initFuncMap()
-
- funcsterHolder.setTemplateFuncster(funcster)
-
- }
-
- // Amber is HTML only.
- t.amberFuncMap = template.FuncMap{}
-
- amberMu.Lock()
- for k, v := range amber.FuncMap {
- t.amberFuncMap[k] = v
- }
-
- for k, v := range t.html.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) LoadTemplatesWithPrefix(absPath string, prefix string) {
+ t.loadTemplates(absPath, prefix)
}
-func (t *templateHandler) getTemplateHandler(name string) templateLoader {
- if strings.HasPrefix(name, textTmplNamePrefix) {
- return t.text
- }
- return t.html
+func (t *GoHTMLTemplate) LoadTemplates(absPath string) {
+ t.loadTemplates(absPath, "")
}
-func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
- h := t.getTemplateHandler(name)
- return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
-}
-
-func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
- masterTpl := t.lookup(masterFilename)
-
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- return err
- }
-
- masterTpl, err = t.t.New(overlayFilename).Parse(templ)
- if err != nil {
- return err
- }
- }
-
- templ, err := onMissing(overlayFilename)
- if err != nil {
- return err
- }
-
- overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ)
- if err != nil {
- return err
- }
-
- // 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 := applyTemplateTransformersToHMLTTemplate(overlayTpl); err != nil {
- return err
- }
- t.overlays[name] = overlayTpl
-
- return err
-
-}
-
-func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
- masterTpl := t.lookup(masterFilename)
-
- if masterTpl == nil {
- templ, err := onMissing(masterFilename)
- if err != nil {
- return err
- }
-
- masterTpl, err = t.t.New(overlayFilename).Parse(templ)
- if err != nil {
- return err
- }
- }
-
- templ, err := onMissing(overlayFilename)
- if err != nil {
- return err
- }
-
- overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ)
- if err != nil {
- return err
- }
-
- overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- if err := applyTemplateTransformersToTextTemplate(overlayTpl); err != nil {
- return err
- }
- t.overlays[name] = overlayTpl
-
- return err
-
-}
-
-func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
- t.checkState()
-
- getTemplate := func(filename string) (string, error) {
- b, err := afero.ReadFile(t.Fs.Source, filename)
- if err != nil {
- return "", err
- }
- return string(b), nil
- }
-
- // get the suffix and switch on that
- ext := filepath.Ext(path)
- switch ext {
- case ".amber":
- // Only HTML support for 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.html.t.New(templateName))
- amberMu.Unlock()
- if err != nil {
- return err
- }
-
- return applyTemplateTransformersToHMLTTemplate(templ)
- case ".ace":
- // Only HTML support for 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.handleMaster(name, path, baseTemplatePath, getTemplate)
- }
-
- templ, err := getTemplate(path)
-
- if err != nil {
- return err
- }
-
- t.Log.DEBUG.Printf("Add template file from path %s", path)
-
- return t.AddTemplate(name, templ)
- }
-
-}
-
-func (t *templateHandler) loadEmbedded() {
- t.embedShortcodes()
- t.embedTemplates()
-}
-
-func (t *templateHandler) addInternalTemplate(prefix, name, tpl string) error {
- if prefix != "" {
- return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
- }
- return t.AddTemplate("_internal/"+name, tpl)
-}
-
-func (t *templateHandler) addInternalShortcode(name, content string) error {
- return t.addInternalTemplate("shortcodes", name, content)
-}
-
-func (t *templateHandler) checkState() {
- if t.html.clone != nil || t.text.clone != nil {
- panic("template is cloned and cannot be modfified")
+func (t *GoHTMLTemplate) PrintErrors() {
+ for i, e := range t.errors {
+ t.Log.ERROR.Println(i, ":", e.err)
}
}
-
-func isDotFile(path string) bool {
- return filepath.Base(path)[0] == '.'
-}
-
-func isBackupFile(path string) bool {
- return path[len(path)-1] == '~'
-}
-
-const baseFileBase = "baseof"
-
-func isBaseTemplate(path string) bool {
- return strings.Contains(path, baseFileBase)
-}
diff --git a/tpl/tplimpl/templateFuncster.go b/tpl/tplimpl/templateFuncster.go
deleted file mode 100644
index 1fbaebd43..000000000
--- a/tpl/tplimpl/templateFuncster.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2017-present 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 tplimpl
-
-import (
- "fmt"
- "html/template"
- "strings"
-
- bp "github.com/spf13/hugo/bufferpool"
-
- "image"
-
- "github.com/spf13/hugo/deps"
-)
-
-// Some of the template funcs are'nt entirely stateless.
-type templateFuncster struct {
- funcMap template.FuncMap
- cachedPartials partialCache
- image *imageHandler
-
- // Make sure each funcster gets its own TemplateFinder to get
- // proper text and HTML template separation.
- Tmpl templateFuncsterTemplater
-
- *deps.Deps
-}
-
-func newTemplateFuncster(deps *deps.Deps, t templateFuncsterTemplater) *templateFuncster {
- return &templateFuncster{
- Deps: deps,
- Tmpl: t,
- cachedPartials: partialCache{p: make(map[string]interface{})},
- image: &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
- }
-}
-
-// Partial executes the named partial and returns either a string,
-// when called from text/template, for or a template.HTML.
-func (t *templateFuncster) partial(name string, contextList ...interface{}) (interface{}, error) {
- if strings.HasPrefix("partials/", name) {
- name = name[8:]
- }
- var context interface{}
-
- if len(contextList) == 0 {
- context = nil
- } else {
- context = contextList[0]
- }
-
- for _, n := range []string{"partials/" + name, "theme/partials/" + name} {
- templ := t.Tmpl.Lookup(n)
- if templ != nil {
- b := bp.GetBuffer()
- defer bp.PutBuffer(b)
-
- if err := templ.Execute(b, context); err != nil {
- return "", err
- }
-
- switch t.Tmpl.(type) {
- case *htmlTemplates:
- return template.HTML(b.String()), nil
- case *textTemplates:
- return b.String(), nil
- default:
- panic("Unknown type")
- }
- }
- }
-
- return "", fmt.Errorf("Partial %q not found", name)
-}
diff --git a/tpl/tplimpl/templateProvider.go b/tpl/tplimpl/templateProvider.go
deleted file mode 100644
index 87cac01e5..000000000
--- a/tpl/tplimpl/templateProvider.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2017-present 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 tplimpl
-
-import (
- "github.com/spf13/hugo/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 {
-
- newTmpl := newTemplateAdapter(deps)
- deps.Tmpl = newTmpl
-
- newTmpl.initFuncs()
- newTmpl.loadEmbedded()
-
- if deps.WithTemplate != nil {
- err := deps.WithTemplate(newTmpl)
- if err != nil {
- newTmpl.addError("init", err)
- }
-
- }
-
- newTmpl.MarkReady()
-
- return nil
-
-}
-
-// Clone clones.
-func (*TemplateProvider) Clone(d *deps.Deps) error {
-
- t := d.Tmpl.(*templateHandler)
- clone := t.clone(d)
-
- d.Tmpl = clone
-
- clone.MarkReady()
-
- return nil
-}
diff --git a/tpl/tplimpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go
index bbd0f28a4..339e2264a 100644
--- a/tpl/tplimpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -17,7 +17,6 @@ import (
"errors"
"html/template"
"strings"
- texttemplate "text/template"
"text/template/parse"
)
@@ -36,57 +35,32 @@ var paramsPaths = [][]string{
}
type templateContext struct {
- decl decl
- visited map[string]bool
- lookupFn func(name string) *parse.Tree
+ decl decl
+ templ *template.Template
+ visited map[string]bool
}
-func (c templateContext) getIfNotVisited(name string) *parse.Tree {
+func (c templateContext) getIfNotVisited(name string) *template.Template {
if c.visited[name] {
return nil
}
c.visited[name] = true
- return c.lookupFn(name)
+ return c.templ.Lookup(name)
}
-func newTemplateContext(lookupFn func(name string) *parse.Tree) *templateContext {
- return &templateContext{lookupFn: lookupFn, decl: make(map[string]string), visited: make(map[string]bool)}
+func newTemplateContext(templ *template.Template) *templateContext {
+ return &templateContext{templ: templ, decl: make(map[string]string), visited: make(map[string]bool)}
}
-func createParseTreeLookup(templ *template.Template) func(nn string) *parse.Tree {
- return func(nn string) *parse.Tree {
- tt := templ.Lookup(nn)
- if tt != nil {
- return tt.Tree
- }
- return nil
- }
-}
-
-func applyTemplateTransformersToHMLTTemplate(templ *template.Template) error {
- return applyTemplateTransformers(templ.Tree, createParseTreeLookup(templ))
-}
-
-func applyTemplateTransformersToTextTemplate(templ *texttemplate.Template) error {
- return applyTemplateTransformers(templ.Tree,
- func(nn string) *parse.Tree {
- tt := templ.Lookup(nn)
- if tt != nil {
- return tt.Tree
- }
- return nil
- })
-}
-
-func applyTemplateTransformers(templ *parse.Tree, lookupFn func(name string) *parse.Tree) error {
- if templ == nil {
+func applyTemplateTransformers(templ *template.Template) error {
+ if templ == nil || templ.Tree == nil {
return errors.New("expected template, but none provided")
}
- c := newTemplateContext(lookupFn)
+ c := newTemplateContext(templ)
- c.paramsKeysToLower(templ.Root)
+ c.paramsKeysToLower(templ.Tree.Root)
return nil
}
@@ -110,7 +84,7 @@ func (c *templateContext) paramsKeysToLower(n parse.Node) {
case *parse.TemplateNode:
subTempl := c.getIfNotVisited(x.Name)
if subTempl != nil {
- c.paramsKeysToLowerForNodes(subTempl.Root)
+ c.paramsKeysToLowerForNodes(subTempl.Tree.Root)
}
case *parse.PipeNode:
for i, elem := range x.Decl {
diff --git a/tpl/tplimpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go
index c3cf54940..deeeae0a7 100644
--- a/tpl/tplimpl/template_ast_transformers_test.go
+++ b/tpl/tplimpl/template_ast_transformers_test.go
@@ -115,13 +115,13 @@ F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}
func TestParamsKeysToLower(t *testing.T) {
t.Parallel()
- require.Error(t, applyTemplateTransformers(nil, nil))
+ require.Error(t, applyTemplateTransformers(nil))
templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
require.NoError(t, err)
- c := newTemplateContext(createParseTreeLookup(templ))
+ c := newTemplateContext(templ)
require.Equal(t, -1, c.decl.indexOfReplacementStart([]string{}))
@@ -185,7 +185,7 @@ func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- c := newTemplateContext(createParseTreeLookup(templates[i]))
+ c := newTemplateContext(templates[i])
c.paramsKeysToLower(templ.Tree.Root)
}
}
@@ -214,7 +214,7 @@ Blue: {{ $__amber_1.Blue}}
require.NoError(t, err)
- c := newTemplateContext(createParseTreeLookup(templ))
+ c := newTemplateContext(templ)
c.paramsKeysToLower(templ.Tree.Root)
@@ -254,7 +254,7 @@ P2: {{ .Params.LOWER }}
require.NoError(t, err)
overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
- c := newTemplateContext(createParseTreeLookup(overlayTpl))
+ c := newTemplateContext(overlayTpl)
c.paramsKeysToLower(overlayTpl.Tree.Root)
@@ -284,7 +284,7 @@ func TestTransformRecursiveTemplate(t *testing.T) {
templ, err := template.New("foo").Parse(recursive)
require.NoError(t, err)
- c := newTemplateContext(createParseTreeLookup(templ))
+ c := newTemplateContext(templ)
c.paramsKeysToLower(templ.Tree.Root)
}
diff --git a/tpl/tplimpl/template_embedded.go b/tpl/tplimpl/template_embedded.go
index b1562a0e7..56819b764 100644
--- a/tpl/tplimpl/template_embedded.go
+++ b/tpl/tplimpl/template_embedded.go
@@ -1,4 +1,4 @@
-// Copyright 2017-present The Hugo Authors. All rights reserved.
+// 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.
@@ -13,12 +13,17 @@
package tplimpl
-func (t *templateHandler) 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", `
+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", `
`)
- t.addInternalShortcode("speakerdeck.html", "")
- t.addInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
+ t.AddInternalShortcode("speakerdeck.html", "")
+ t.AddInternalShortcode("youtube.html", `{{ if .IsNamedParams }}
@@ -46,21 +51,21 @@ func (t *templateHandler) embedShortcodes() {
{{ end }}`)
- t.addInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}
+ 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" }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1" }}{{ .html | safeHTML }}{{ end }}{{ end }}{{ else }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0" }}{{ .html | safeHTML }}{{ end }}{{ 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" }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1" }}{{ .html | safeHTML }}{{ end }}{{ end }}{{ else }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0" }}{{ .html | safeHTML }}{{ end }}{{ end }}`)
}
-func (t *templateHandler) embedTemplates() {
+func (t *GoHTMLTemplate) EmbedTemplates() {
- t.addInternalTemplate("_default", "rss.xml", `
+ t.AddInternalTemplate("_default", "rss.xml", `
{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}
{{ .Permalink }}
@@ -87,7 +92,7 @@ func (t *templateHandler) embedTemplates() {
`)
- t.addInternalTemplate("_default", "sitemap.xml", `
+ t.AddInternalTemplate("_default", "sitemap.xml", `
{{ range .Data.Pages }}
{{ .Permalink }}{{ if not .Lastmod.IsZero }}
@@ -99,7 +104,7 @@ func (t *templateHandler) embedTemplates() {
`)
// For multilanguage sites
- t.addInternalTemplate("_default", "sitemapindex.xml", `
+ t.AddInternalTemplate("_default", "sitemapindex.xml", `
{{ range . }}
{{ .SitemapAbsURL }}
@@ -111,7 +116,7 @@ func (t *templateHandler) embedTemplates() {
`)
- t.addInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }}
+ t.AddInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }}
{{ if gt $pag.TotalPages 1 }}
{{ end }}`)
- t.addInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}
+ t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}
{{ end }}`)
- t.addInternalTemplate("", "google_analytics_async.html", `{{ with .Site.GoogleAnalytics }}
+ t.AddInternalTemplate("", "google_analytics_async.html", `{{ with .Site.GoogleAnalytics }}
{{ end }}`)
- t.addInternalTemplate("_default", "robots.txt", "User-agent: *")
+ t.AddInternalTemplate("_default", "robots.txt", "User-agent: *")
}
diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go
index 9703f6cff..1ec05b0c7 100644
--- a/tpl/tplimpl/template_funcs.go
+++ b/tpl/tplimpl/template_funcs.go
@@ -45,6 +45,7 @@ import (
"github.com/bep/inflect"
"github.com/spf13/afero"
"github.com/spf13/cast"
+ "github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
jww "github.com/spf13/jwalterweatherman"
@@ -54,6 +55,22 @@ import (
_ "image/png"
)
+// Some of the template funcs are'nt entirely stateless.
+type templateFuncster struct {
+ funcMap template.FuncMap
+ cachedPartials partialCache
+ image *imageHandler
+ *deps.Deps
+}
+
+func newTemplateFuncster(deps *deps.Deps) *templateFuncster {
+ return &templateFuncster{
+ Deps: deps,
+ cachedPartials: partialCache{p: make(map[string]template.HTML)},
+ image: &imageHandler{fs: deps.Fs, imageConfigCache: map[string]image.Config{}},
+ }
+}
+
// eq returns the boolean truth of arg1 == arg2.
func eq(x, y interface{}) bool {
normalize := func(v interface{}) interface{} {
@@ -1541,13 +1558,13 @@ func replace(a, b, c interface{}) (string, error) {
// partialCache represents a cache of partials protected by a mutex.
type partialCache struct {
sync.RWMutex
- p map[string]interface{}
+ p map[string]template.HTML
}
// 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 (t *templateFuncster) Get(key, name string, context interface{}) (p interface{}, err error) {
+func (t *templateFuncster) Get(key, name string, context interface{}) (p template.HTML) {
var ok bool
t.cachedPartials.RLock()
@@ -1555,13 +1572,13 @@ func (t *templateFuncster) Get(key, name string, context interface{}) (p interfa
t.cachedPartials.RUnlock()
if ok {
- return
+ return p
}
t.cachedPartials.Lock()
if p, ok = t.cachedPartials.p[key]; !ok {
t.cachedPartials.Unlock()
- p, err = t.partial(name, context)
+ p = t.Tmpl.Partial(name, context)
t.cachedPartials.Lock()
t.cachedPartials.p[key] = p
@@ -1569,14 +1586,14 @@ func (t *templateFuncster) Get(key, name string, context interface{}) (p interfa
}
t.cachedPartials.Unlock()
- return
+ return p
}
// 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 (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) (interface{}, error) {
+func (t *templateFuncster) partialCached(name string, context interface{}, variant ...string) template.HTML {
key := name
if len(variant) > 0 {
for i := 0; i < len(variant); i++ {
@@ -2178,7 +2195,7 @@ func (t *templateFuncster) initFuncMap() {
"mul": func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '*') },
"ne": ne,
"now": func() time.Time { return time.Now() },
- "partial": t.partial,
+ "partial": t.Tmpl.Partial,
"partialCached": t.partialCached,
"plainify": plainify,
"pluralize": pluralize,
@@ -2232,5 +2249,5 @@ func (t *templateFuncster) initFuncMap() {
}
t.funcMap = funcMap
- t.Tmpl.setFuncs(funcMap)
+ t.Tmpl.Funcs(funcMap)
}
diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
index b50765fcb..a9cf5e58b 100644
--- a/tpl/tplimpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -281,8 +281,8 @@ urlize: bat-man
v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v))
config := newDepsConfig(v)
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
- if err := templ.AddTemplate("test", in); err != nil {
+ config.WithTemplate = func(templ tpl.Template) error {
+ if _, err := templ.New("test").Parse(in); err != nil {
t.Fatal("Got error on parse", err)
}
return nil
@@ -2858,96 +2858,6 @@ func TestReadFile(t *testing.T) {
}
}
-func TestPartialHTMLAndText(t *testing.T) {
- t.Parallel()
- config := newDepsConfig(viper.New())
-
- data := struct {
- Name string
- }{
- Name: "a+b+c", // This should get encoded in HTML.
- }
-
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
- if err := templ.AddTemplate("htmlTemplate.html", `HTML Test Partial: {{ partial "test.foo" . -}}`); err != nil {
- return err
- }
-
- if err := templ.AddTemplate("_text/textTemplate.txt", `Text Test Partial: {{ partial "test.foo" . -}}`); err != nil {
- return err
- }
-
- // Use "foo" here to say that the extension doesn't really matter in this scenario.
- // It will look for templates in "partials/test.foo" and "partials/test.foo.html".
- if err := templ.AddTemplate("partials/test.foo", "HTML Name: {{ .Name }}"); err != nil {
- return err
- }
- if err := templ.AddTemplate("_text/partials/test.foo", "Text Name: {{ .Name }}"); err != nil {
- return err
- }
-
- return nil
- }
-
- de, err := deps.New(config)
- require.NoError(t, err)
- require.NoError(t, de.LoadResources())
-
- templ := de.Tmpl.Lookup("htmlTemplate.html")
- require.NotNil(t, templ)
- resultHTML, err := templ.ExecuteToString(data)
- require.NoError(t, err)
-
- templ = de.Tmpl.Lookup("_text/textTemplate.txt")
- require.NotNil(t, templ)
- resultText, err := templ.ExecuteToString(data)
- require.NoError(t, err)
-
- require.Contains(t, resultHTML, "HTML Test Partial: HTML Name: a+b+c")
- require.Contains(t, resultText, "Text Test Partial: Text Name: a+b+c")
-
-}
-
-func TestPartialWithError(t *testing.T) {
- t.Parallel()
- config := newDepsConfig(viper.New())
-
- data := struct {
- Name string
- }{
- Name: "bep",
- }
-
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
- if err := templ.AddTemplate("container.html", `HTML Test Partial: {{ partial "fail.foo" . -}}`); err != nil {
- return err
- }
-
- if err := templ.AddTemplate("partials/fail.foo", "Template: {{ .DoesNotExist }}"); err != nil {
- return err
- }
-
- return nil
- }
-
- de, err := deps.New(config)
- require.NoError(t, err)
- require.NoError(t, de.LoadResources())
-
- templ := de.Tmpl.Lookup("container.html")
- require.NotNil(t, templ)
- result, err := templ.ExecuteToString(data)
- require.Error(t, err)
-
- errStr := err.Error()
-
- require.Contains(t, errStr, `template: container.html:1:22: executing "container.html" at `)
- require.Contains(t, errStr, `can't evaluate field DoesNotExist`)
-
- require.Empty(t, result)
-
-}
-
func TestPartialCached(t *testing.T) {
t.Parallel()
testCases := []struct {
@@ -2983,7 +2893,7 @@ func TestPartialCached(t *testing.T) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate("testroot", tmp)
if err != nil {
return err
@@ -3023,7 +2933,7 @@ func TestPartialCached(t *testing.T) {
func BenchmarkPartial(b *testing.B) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
if err != nil {
return err
@@ -3055,7 +2965,7 @@ func BenchmarkPartial(b *testing.B) {
func BenchmarkPartialCached(b *testing.B) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
if err != nil {
return err
@@ -3100,12 +3010,12 @@ func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster {
panic(err)
}
- return d.Tmpl.(*templateHandler).html.funcster
+ return d.Tmpl.(*GoHTMLTemplate).funcster
}
-func newTestTemplate(t *testing.T, name, template string) tpl.Template {
+func newTestTemplate(t *testing.T, name, template string) *template.Template {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate(name, template)
if err != nil {
return err
diff --git a/tpl/tplimpl/template_test.go b/tpl/tplimpl/template_test.go
index 998915a46..43b834df2 100644
--- a/tpl/tplimpl/template_test.go
+++ b/tpl/tplimpl/template_test.go
@@ -14,10 +14,17 @@
package tplimpl
import (
+ "bytes"
"errors"
+ "html/template"
"io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
"testing"
+ "github.com/spf13/afero"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/tpl"
@@ -25,6 +32,223 @@ import (
"github.com/stretchr/testify/require"
)
+// Some tests for Issue #1178 -- Ace
+func TestAceTemplates(t *testing.T) {
+ t.Parallel()
+
+ for i, this := range []struct {
+ basePath string
+ innerPath string
+ baseContent string
+ innerContent string
+ expect string
+ expectErr int
+ }{
+ {"", filepath.FromSlash("_default/single.ace"), "", "{{ . }}", "DATA", 0},
+ {filepath.FromSlash("_default/baseof.ace"), filepath.FromSlash("_default/single.ace"),
+ `= content main
+ h2 This is a content named "main" of an inner template. {{ . }}`,
+ `= doctype html
+html lang=en
+ head
+ meta charset=utf-8
+ title Base and Inner Template
+ body
+ h1 This is a base template {{ . }}
+ = yield main`, `Base and Inner TemplateThis is a base template DATA
`, 0},
+ } {
+
+ for _, root := range []string{"", os.TempDir()} {
+
+ basePath := this.basePath
+ innerPath := this.innerPath
+
+ if basePath != "" && root != "" {
+ basePath = filepath.Join(root, basePath)
+ }
+
+ if innerPath != "" && root != "" {
+ innerPath = filepath.Join(root, innerPath)
+ }
+
+ d := "DATA"
+
+ config := newDepsConfig(viper.New())
+ config.WithTemplate = func(templ tpl.Template) error {
+ return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
+ []byte(this.baseContent), []byte(this.innerContent))
+ }
+
+ a, err := deps.New(config)
+ require.NoError(t, err)
+
+ if err := a.LoadResources(); err != nil {
+ t.Fatal(err)
+ }
+
+ templ := a.Tmpl.(*GoHTMLTemplate)
+
+ 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 = a.Tmpl.ExecuteTemplate(&buff, "mytemplate.html", d)
+
+ if err != nil && this.expectErr == 0 {
+ t.Errorf("Test %d with root '%s' errored: %s", i, root, err)
+ } else if err == nil && this.expectErr == 2 {
+ t.Errorf("#2 Test with root '%s' %d should have errored", root, i)
+ } else {
+ result := buff.String()
+ if result != this.expect {
+ t.Errorf("Test %d with root '%s' got\n%s\nexpected\n%s", i, root, result, this.expect)
+ }
+ }
+
+ }
+ }
+
+}
+
+func isAtLeastGo16() bool {
+ version := runtime.Version()
+ return strings.Contains(version, "1.6") || strings.Contains(version, "1.7")
+}
+
+func TestAddTemplateFileWithMaster(t *testing.T) {
+ t.Parallel()
+
+ if !isAtLeastGo16() {
+ t.Skip("This test only runs on Go >= 1.6")
+ }
+
+ for i, this := range []struct {
+ masterTplContent string
+ overlayTplContent string
+ writeSkipper int
+ expect interface{}
+ }{
+ {`A{{block "main" .}}C{{end}}C`, `{{define "main"}}B{{end}}`, 0, "ABC"},
+ {`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}`, 0, "ABCDE"},
+ {`A{{block "main" .}}C{{end}}C{{block "sub" .}}D{{end}}E`, `{{define "main"}}B{{end}}{{define "sub"}}Z{{end}}`, 0, "ABCZE"},
+ {`tpl`, `tpl`, 1, false},
+ {`tpl`, `tpl`, 2, false},
+ {`{{.0.E}}`, `tpl`, 0, false},
+ {`tpl`, `{{.0.E}}`, 0, false},
+ } {
+
+ overlayTplName := "ot"
+ masterTplName := "mt"
+ finalTplName := "tp"
+
+ config := newDepsConfig(viper.New())
+ config.WithTemplate = func(templ tpl.Template) error {
+
+ err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
+
+ if b, ok := this.expect.(bool); ok && !b {
+ if err == nil {
+ t.Errorf("[%d] AddTemplateFileWithMaster didn't return an expected error", i)
+ }
+ } else {
+
+ if err != nil {
+ t.Errorf("[%d] AddTemplateFileWithMaster failed: %s", i, err)
+ return nil
+ }
+
+ resultTpl := templ.Lookup(finalTplName)
+
+ if resultTpl == nil {
+ t.Errorf("[%d] AddTemplateFileWithMaster: Result template not found", i)
+ return nil
+ }
+
+ var b bytes.Buffer
+ err := resultTpl.Execute(&b, nil)
+
+ if err != nil {
+ t.Errorf("[%d] AddTemplateFileWithMaster execute failed: %s", i, err)
+ return nil
+ }
+ resultContent := b.String()
+
+ if resultContent != this.expect {
+ t.Errorf("[%d] AddTemplateFileWithMaster got \n%s but expected \n%v", i, resultContent, this.expect)
+ }
+ }
+
+ return nil
+ }
+
+ if this.writeSkipper != 1 {
+ afero.WriteFile(config.Fs.Source, masterTplName, []byte(this.masterTplContent), 0644)
+ }
+ if this.writeSkipper != 2 {
+ afero.WriteFile(config.Fs.Source, overlayTplName, []byte(this.overlayTplContent), 0644)
+ }
+
+ deps.New(config)
+
+ }
+
+}
+
+// A Go stdlib test for linux/arm. Will remove later.
+// See #1771
+func TestBigIntegerFunc(t *testing.T) {
+ t.Parallel()
+ var func1 = func(v int64) error {
+ return nil
+ }
+ var funcs = map[string]interface{}{
+ "A": func1,
+ }
+
+ tpl, err := template.New("foo").Funcs(funcs).Parse("{{ A 3e80 }}")
+ if err != nil {
+ t.Fatal("Parse failed:", err)
+ }
+ err = tpl.Execute(ioutil.Discard, "foo")
+
+ if err == nil {
+ t.Fatal("Execute should have failed")
+ }
+
+ t.Log("Got expected error:", err)
+
+}
+
+// A Go stdlib test for linux/arm. Will remove later.
+// See #1771
+type BI struct {
+}
+
+func (b BI) A(v int64) error {
+ return nil
+}
+func TestBigIntegerMethod(t *testing.T) {
+ t.Parallel()
+
+ data := &BI{}
+
+ tpl, err := template.New("foo2").Parse("{{ .A 3e80 }}")
+ if err != nil {
+ t.Fatal("Parse failed:", err)
+ }
+ err = tpl.ExecuteTemplate(ioutil.Discard, "foo2", data)
+
+ if err == nil {
+ t.Fatal("Execute should have failed")
+ }
+
+ t.Log("Got expected error:", err)
+
+}
+
// Test for bugs discovered by https://github.com/dvyukov/go-fuzz
func TestTplGoFuzzReports(t *testing.T) {
t.Parallel()
@@ -61,7 +285,7 @@ func TestTplGoFuzzReports(t *testing.T) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tpl.TemplateHandler) error {
+ config.WithTemplate = func(templ tpl.Template) error {
return templ.AddTemplate("fuzz", this.data)
}
@@ -69,7 +293,7 @@ func TestTplGoFuzzReports(t *testing.T) {
require.NoError(t, err)
require.NoError(t, de.LoadResources())
- templ := de.Tmpl.(*templateHandler)
+ templ := de.Tmpl.(*GoHTMLTemplate)
if len(templ.errors) > 0 && this.expectErr == 0 {
t.Errorf("Test %d errored: %v", i, templ.errors)
@@ -77,9 +301,7 @@ func TestTplGoFuzzReports(t *testing.T) {
t.Errorf("#1 Test %d should have errored", i)
}
- tt := de.Tmpl.Lookup("fuzz")
- require.NotNil(t, tt)
- err = tt.Execute(ioutil.Discard, d)
+ err = de.Tmpl.ExecuteTemplate(ioutil.Discard, "fuzz", d)
if err != nil && this.expectErr == 0 {
t.Fatalf("Test %d errored: %s", i, err)