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/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/amber_compiler.go b/tpl/tplimpl/amber_compiler.go
similarity index 98%
rename from tpl/amber_compiler.go
rename to tpl/tplimpl/amber_compiler.go
index 4477f6ac0..252c39ffb 100644
--- a/tpl/amber_compiler.go
+++ b/tpl/tplimpl/amber_compiler.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"html/template"
diff --git a/tpl/reflect_helpers.go b/tpl/tplimpl/reflect_helpers.go
similarity index 96%
rename from tpl/reflect_helpers.go
rename to tpl/tplimpl/reflect_helpers.go
index f2ce722a2..7463683fc 100644
--- a/tpl/reflect_helpers.go
+++ b/tpl/tplimpl/reflect_helpers.go
@@ -1,4 +1,4 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
+// 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"reflect"
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
new file mode 100644
index 000000000..cf1fc5620
--- /dev/null
+++ b/tpl/tplimpl/template.go
@@ -0,0 +1,575 @@
+// 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 tplimpl
+
+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)
+ }
+}
diff --git a/tpl/template_ast_transformers.go b/tpl/tplimpl/template_ast_transformers.go
similarity index 99%
rename from tpl/template_ast_transformers.go
rename to tpl/tplimpl/template_ast_transformers.go
index 19b772add..68090497b 100644
--- a/tpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"errors"
diff --git a/tpl/template_ast_transformers_test.go b/tpl/tplimpl/template_ast_transformers_test.go
similarity index 99%
rename from tpl/template_ast_transformers_test.go
rename to tpl/tplimpl/template_ast_transformers_test.go
index 43d78284c..048d52fee 100644
--- a/tpl/template_ast_transformers_test.go
+++ b/tpl/tplimpl/template_ast_transformers_test.go
@@ -10,7 +10,7 @@
// 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
+package tplimpl
import (
"bytes"
diff --git a/tpl/template_embedded.go b/tpl/tplimpl/template_embedded.go
similarity index 99%
rename from tpl/template_embedded.go
rename to tpl/tplimpl/template_embedded.go
index f782a31e9..50397b28c 100644
--- a/tpl/template_embedded.go
+++ b/tpl/tplimpl/template_embedded.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
type Tmpl struct {
Name string
diff --git a/tpl/template_func_truncate.go b/tpl/tplimpl/template_func_truncate.go
similarity index 99%
rename from tpl/template_func_truncate.go
rename to tpl/tplimpl/template_func_truncate.go
index b5886edae..d4bb63272 100644
--- a/tpl/template_func_truncate.go
+++ b/tpl/tplimpl/template_func_truncate.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"errors"
diff --git a/tpl/template_func_truncate_test.go b/tpl/tplimpl/template_func_truncate_test.go
similarity index 99%
rename from tpl/template_func_truncate_test.go
rename to tpl/tplimpl/template_func_truncate_test.go
index 9213c6faa..9c4beecff 100644
--- a/tpl/template_func_truncate_test.go
+++ b/tpl/tplimpl/template_func_truncate_test.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"html/template"
diff --git a/tpl/template_funcs.go b/tpl/tplimpl/template_funcs.go
similarity index 99%
rename from tpl/template_funcs.go
rename to tpl/tplimpl/template_funcs.go
index 9777bf619..dae621ac3 100644
--- a/tpl/template_funcs.go
+++ b/tpl/tplimpl/template_funcs.go
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"bytes"
diff --git a/tpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go
similarity index 99%
rename from tpl/template_funcs_test.go
rename to tpl/tplimpl/template_funcs_test.go
index 35214b649..0fba97bd3 100644
--- a/tpl/template_funcs_test.go
+++ b/tpl/tplimpl/template_funcs_test.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"bytes"
@@ -31,7 +31,7 @@ import (
"testing"
"time"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/deps"
"github.com/spf13/hugo/helpers"
@@ -273,7 +273,7 @@ urlize: bat-man
v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v))
config := newDepsConfig(v)
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
if _, err := templ.New("test").Parse(in); err != nil {
t.Fatal("Got error on parse", err)
}
@@ -2862,7 +2862,7 @@ func TestPartialCached(t *testing.T) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate("testroot", tmp)
if err != nil {
return err
@@ -2901,7 +2901,7 @@ func TestPartialCached(t *testing.T) {
func BenchmarkPartial(b *testing.B) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`)
if err != nil {
return err
@@ -2932,7 +2932,7 @@ func BenchmarkPartial(b *testing.B) {
func BenchmarkPartialCached(b *testing.B) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`)
if err != nil {
return err
@@ -2978,7 +2978,7 @@ func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster {
func newTestTemplate(t *testing.T, name, template string) *template.Template {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplate(name, template)
if err != nil {
return err
diff --git a/tpl/template_resources.go b/tpl/tplimpl/template_resources.go
similarity index 99%
rename from tpl/template_resources.go
rename to tpl/tplimpl/template_resources.go
index bb1b11eb3..2b3d7120c 100644
--- a/tpl/template_resources.go
+++ b/tpl/tplimpl/template_resources.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"bytes"
diff --git a/tpl/template_resources_test.go b/tpl/tplimpl/template_resources_test.go
similarity index 99%
rename from tpl/template_resources_test.go
rename to tpl/tplimpl/template_resources_test.go
index 82764977f..8a9f62659 100644
--- a/tpl/template_resources_test.go
+++ b/tpl/tplimpl/template_resources_test.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"bytes"
diff --git a/tpl/template_test.go b/tpl/tplimpl/template_test.go
similarity index 97%
rename from tpl/template_test.go
rename to tpl/tplimpl/template_test.go
index 5bb6d89d3..08bcab1a7 100644
--- a/tpl/template_test.go
+++ b/tpl/tplimpl/template_test.go
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package tpl
+package tplimpl
import (
"bytes"
@@ -27,7 +27,7 @@ import (
"github.com/spf13/afero"
"github.com/spf13/hugo/deps"
- "github.com/spf13/hugo/tplapi"
+ "github.com/spf13/hugo/tpl"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
@@ -74,7 +74,7 @@ html lang=en
d := "DATA"
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
return templ.AddAceTemplate("mytemplate.ace", basePath, innerPath,
[]byte(this.baseContent), []byte(this.innerContent))
}
@@ -144,7 +144,7 @@ func TestAddTemplateFileWithMaster(t *testing.T) {
finalTplName := "tp"
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)
@@ -284,7 +284,7 @@ func TestTplGoFuzzReports(t *testing.T) {
config := newDepsConfig(viper.New())
- config.WithTemplate = func(templ tplapi.Template) error {
+ config.WithTemplate = func(templ tpl.Template) error {
return templ.AddTemplate("fuzz", this.data)
}
diff --git a/tplapi/template.go b/tplapi/template.go
deleted file mode 100644
index 58bc5ecf9..000000000
--- a/tplapi/template.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package tplapi
-
-import (
- "html/template"
- "io"
-)
-
-// TODO(bep) make smaller
-// TODO(bep) consider putting this into /tpl and the implementation in /tpl/tplimpl or something
-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()
-}