summaryrefslogtreecommitdiffstats
path: root/tpl
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-03-27 20:43:49 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-04-02 11:37:30 +0200
commit5c5efa03d2512749950b0d05a7d4bde35ecbdc37 (patch)
tree9f3e31a7e30c51fab5ed3f7c323393fcafadc5e8 /tpl
parent73c1c7b69d8302000fa5c5b804ad3eeac36da12f (diff)
tpl: Rework to handle both text and HTML templates
Before this commit, Hugo used `html/template` for all Go templates. While this is a fine choice for HTML and maybe also RSS feeds, it is painful for plain text formats such as CSV, JSON etc. This commit fixes that by using the `IsPlainText` attribute on the output format to decide what to use. A couple of notes: * The above requires a nonambiguous template name to type mapping. I.e. `/layouts/_default/list.json` will only work if there is only one JSON output format, `/layouts/_default/list.mytype.json` will always work. * Ambiguous types will fall back to HTML. * Partials inherits the text vs HTML identificator of the container template. This also means that plain text templates can only include plain text partials. * Shortcode templates are, by definition, currently HTML templates only. Fixes #3221
Diffstat (limited to 'tpl')
-rw-r--r--tpl/template.go111
-rw-r--r--tpl/tplimpl/ace.go51
-rw-r--r--tpl/tplimpl/amber_compiler.go4
-rw-r--r--tpl/tplimpl/template.go770
-rw-r--r--tpl/tplimpl/templateFuncster.go86
-rw-r--r--tpl/tplimpl/templateProvider.go59
-rw-r--r--tpl/tplimpl/template_ast_transformers.go50
-rw-r--r--tpl/tplimpl/template_ast_transformers_test.go12
-rw-r--r--tpl/tplimpl/template_embedded.go57
-rw-r--r--tpl/tplimpl/template_funcs.go33
-rw-r--r--tpl/tplimpl/template_funcs_test.go66
-rw-r--r--tpl/tplimpl/template_test.go232
12 files changed, 887 insertions, 644 deletions
diff --git a/tpl/template.go b/tpl/template.go
index b94fc3242..617aa84ec 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -1,28 +1,103 @@
+// 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 (
- "html/template"
"io"
+
+ "text/template/parse"
+
+ "html/template"
+ texttemplate "text/template"
+
+ bp "github.com/spf13/hugo/bufferpool"
)
-// 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
- RebuildClone() *template.Template
- LoadTemplates(absPath string)
- LoadTemplatesWithPrefix(absPath, prefix string)
+var (
+ _ TemplateExecutor = (*TemplateAdapter)(nil)
+)
+
+// TemplateHandler manages the collection of templates.
+type TemplateHandler interface {
+ TemplateFinder
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
+ AddLateTemplate(name, tpl string) error
+ LoadTemplates(absPath, prefix string)
PrintErrors()
- Funcs(funcMap template.FuncMap)
+
MarkReady()
+ RebuildClone()
+}
+
+// TemplateFinder finds templates.
+type TemplateFinder interface {
+ Lookup(name string) *TemplateAdapter
+}
+
+// Template is the common interface between text/template and html/template.
+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{})
}
diff --git a/tpl/tplimpl/ace.go b/tpl/tplimpl/ace.go
new file mode 100644
index 000000000..fc3a1e1b1
--- /dev/null
+++ b/tpl/tplimpl/ace.go
@@ -0,0 +1,51 @@
+// 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 252c39ffb..10ed0443c 100644
--- a/tpl/tplimpl/amber_compiler.go
+++ b/tpl/tplimpl/amber_compiler.go
@@ -19,7 +19,7 @@ import (
"github.com/eknkc/amber"
)
-func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *template.Template) (*template.Template, error) {
+func (t *templateHandler) compileAmberWithTemplate(b []byte, path string, templ *template.Template) (*template.Template, error) {
c := amber.New()
if err := c.ParseData(b, path); err != nil {
@@ -32,7 +32,7 @@ func (gt *GoHTMLTemplate) CompileAmberWithTemplate(b []byte, path string, t *tem
return nil, err
}
- tpl, err := t.Funcs(gt.amberFuncMap).Parse(data)
+ tpl, err := templ.Funcs(t.amberFuncMap).Parse(data)
if err != nil {
return nil, err
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index ea12cde7a..49be4a769 100644
--- a/tpl/tplimpl/template.go
+++ b/tpl/tplimpl/template.go
@@ -1,4 +1,4 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
+// 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.
@@ -15,23 +15,39 @@ package tplimpl
import (
"html/template"
- "io"
- "os"
- "path/filepath"
"strings"
+ texttemplate "text/template"
+
+ "github.com/eknkc/amber"
+
+ "os"
+ "github.com/spf13/hugo/output"
+
+ "path/filepath"
"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/output"
- "github.com/yosssi/ace"
+ "github.com/spf13/hugo/tpl"
+)
+
+const (
+ textTmplNamePrefix = "_text/"
)
-// TODO(bep) globals get rid of the rest of the jww.ERR etc.
+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)
+)
// Protecting global map access (Amber)
var amberMu sync.Mutex
@@ -41,340 +57,539 @@ type templateErr struct {
err error
}
-type GoHTMLTemplate struct {
- *template.Template
-
- // This looks, and is, strange.
- // The clone is used by non-renderable content pages, and these need to be
- // re-parsed on content change, and to avoid the
- // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
- clone *template.Template
- cloneClone *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
+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
+}
- errors []*templateErr
+type templateFuncsterTemplater interface {
+ tpl.TemplateFinder
+ setFuncs(funcMap map[string]interface{})
+ setTemplateFuncster(f *templateFuncster)
+}
- funcster *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
}
-type TemplateProvider struct{}
-
-var DefaultTemplateProvider *TemplateProvider
+func (t *templateHandler) addError(name string, err error) {
+ t.errors = append(t.errors, &templateErr{name, err})
+}
-// 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,
+// 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)
}
+}
- deps.Tmpl = tmpl
-
- tmpl.initFuncs(deps)
-
- tmpl.LoadEmbedded()
+// 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
- if deps.WithTemplate != nil {
- err := deps.WithTemplate(tmpl)
- if err != nil {
- tmpl.errors = append(tmpl.errors, &templateErr{"init", err})
- }
+ 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)
}
- tmpl.MarkReady()
-
- return nil
+ if te == nil {
+ return nil
+ }
+ return te
}
-// 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,
+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 = tmpl
- tmpl.initFuncs(d)
+ d.Tmpl = c
- for k, v := range t.overlays {
+ 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(tmpl.funcster.funcMap)
- tmpl.overlays[k] = vc
+ 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
}
- tmpl.MarkReady()
+ return c
- return nil
}
-func (t *GoHTMLTemplate) initFuncs(d *deps.Deps) {
+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),
+ }
- 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()
+type htmlTemplates struct {
+ funcster *templateFuncster
- t.amberFuncMap = template.FuncMap{}
+ t *template.Template
- amberMu.Lock()
- for k, v := range amber.FuncMap {
- t.amberFuncMap[k] = v
- }
+ // This looks, and is, strange.
+ // The clone is used by non-renderable content pages, and these need to be
+ // re-parsed on content change, and to avoid the
+ // "cannot Parse after Execute" error, we need to re-clone it from the original clone.
+ clone *template.Template
+ cloneClone *template.Template
- 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()
+ // 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
}
-func (t *GoHTMLTemplate) Funcs(funcMap template.FuncMap) {
- t.Template.Funcs(funcMap)
+func (t *htmlTemplates) Lookup(name string) *tpl.TemplateAdapter {
+ templ := t.lookup(name)
+ if templ == nil {
+ return nil
+ }
+ return &tpl.TemplateAdapter{templ}
}
-func (t *GoHTMLTemplate) Partial(name string, contextList ...interface{}) template.HTML {
- if strings.HasPrefix("partials/", name) {
- name = name[8:]
+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
+ }
}
- var context interface{}
- if len(contextList) == 0 {
- context = nil
- } else {
- context = contextList[0]
+ if t.clone != nil {
+ return t.clone.Lookup(name)
}
- return t.ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name)
+
+ return nil
}
-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)
- }
+type textTemplates struct {
+ funcster *templateFuncster
- 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)
- }
+ t *texttemplate.Template
+
+ clone *texttemplate.Template
+ cloneClone *texttemplate.Template
+
+ overlays map[string]*texttemplate.Template
}
-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 *textTemplates) setTemplateFuncster(f *templateFuncster) {
+ t.funcster = f
}
-func (t *GoHTMLTemplate) Lookup(name string) *template.Template {
+func (t *textTemplates) Lookup(name string) *tpl.TemplateAdapter {
+ templ := t.lookup(name)
+ if templ == nil {
+ return nil
+ }
+ return &tpl.TemplateAdapter{templ}
+}
- if templ := t.Template.Lookup(name); templ != nil {
+func (t *textTemplates) lookup(name string) *texttemplate.Template {
+ if templ := t.t.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 t.clone.Lookup(name)
}
return nil
+}
+func (t *templateHandler) setFuncs(funcMap map[string]interface{}) {
+ t.html.setFuncs(funcMap)
+ t.text.setFuncs(funcMap)
}
-func (t *GoHTMLTemplate) GetClone() *template.Template {
- return t.clone
+// 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 *GoHTMLTemplate) RebuildClone() *template.Template {
- t.clone = template.Must(t.cloneClone.Clone())
- return t.clone
+func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) {
+ t.t.Funcs(funcMap)
}
-func (t *GoHTMLTemplate) LoadEmbedded() {
- t.EmbedShortcodes()
- t.EmbedTemplates()
+func (t *textTemplates) setFuncs(funcMap map[string]interface{}) {
+ t.t.Funcs(funcMap)
}
-// 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 *GoHTMLTemplate) MarkReady() {
- if t.clone == nil {
- t.clone = template.Must(t.Template.Clone())
- t.cloneClone = template.Must(t.clone.Clone())
- }
+// 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 *GoHTMLTemplate) checkState() {
- if t.clone != nil {
- panic("template is cloned and cannot be modfified")
+func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) error {
+ templ, err := tt.New(name).Parse(tpl)
+ if err != nil {
+ return err
}
-}
-func (t *GoHTMLTemplate) AddInternalTemplate(prefix, name, tpl string) error {
- if prefix != "" {
- return t.AddTemplate("_internal/"+prefix+"/"+name, tpl)
+ if err := applyTemplateTransformersToHMLTTemplate(templ); err != nil {
+ return err
}
- return t.AddTemplate("_internal/"+name, tpl)
+
+ return nil
}
-func (t *GoHTMLTemplate) AddInternalShortcode(name, content string) error {
- return t.AddInternalTemplate("shortcodes", name, content)
+func (t *htmlTemplates) addTemplate(name, tpl string) error {
+ return t.addTemplateIn(t.t, name, tpl)
}
-func (t *GoHTMLTemplate) AddTemplate(name, tpl string) error {
- t.checkState()
- templ, err := t.New(name).Parse(tpl)
+func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
+ return t.addTemplateIn(t.clone, name, tpl)
+}
+
+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 {
- t.errors = append(t.errors, &templateErr{name: name, err: err})
return err
}
- if err := applyTemplateTransformers(templ); err != nil {
+
+ 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
}
-func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error {
+// MarkReady marks the templates 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())
+ }
+}
+
+// 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 *templateHandler) loadTemplates(absPath string, prefix string, formats output.Formats) {
+ 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
+ }
+
+ var (
+ workingDir = t.PathSpec.WorkingDir()
+ themeDir = t.PathSpec.GetThemeDir()
+ layoutDir = t.PathSpec.LayoutDir()
+ )
+
+ if themeDir != "" && strings.HasPrefix(absPath, themeDir) {
+ workingDir = themeDir
+ layoutDir = "layouts"
+ }
+
+ li := strings.LastIndex(path, layoutDir) + len(layoutDir) + 1
+ relPath := path[li:]
+
+ descriptor := output.TemplateLookupDescriptor{
+ WorkingDir: workingDir,
+ LayoutDir: layoutDir,
+ RelPath: relPath,
+ Prefix: prefix,
+ Theme: t.PathSpec.Theme(),
+ OutputFormats: formats,
+ FileExists: func(filename string) (bool, error) {
+ return helpers.Exists(filename, t.Fs.Source)
+ },
+ ContainsAny: func(filename string, subslices [][]byte) (bool, error) {
+ return helpers.FileContainsAny(filename, subslices, t.Fs.Source)
+ },
+ }
+
+ tplID, err := output.CreateTemplateNames(descriptor)
+ if err != nil {
+ t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
+
+ return 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)
+ }
+
+ }
+ return nil
+ }
+ if err := helpers.SymbolicWalk(t.Fs.Source, absPath, walker); err != nil {
+ t.Log.ERROR.Printf("Failed to load templates: %s", err)
+ }
+}
+
+func (t *templateHandler