diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-03-27 20:43:49 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-04-02 23:13:10 +0200 |
commit | 8b5b558bb515e80da640f5e114169874771b61e4 (patch) | |
tree | 5755e77efe3120963d012dccbd91d916180485e0 /tpl | |
parent | 27610ddd011e8172d00e02275f948c3f1ed43e4f (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.go | 111 | ||||
-rw-r--r-- | tpl/tplimpl/ace.go | 51 | ||||
-rw-r--r-- | tpl/tplimpl/amber_compiler.go | 4 | ||||
-rw-r--r-- | tpl/tplimpl/template.go | 771 | ||||
-rw-r--r-- | tpl/tplimpl/templateFuncster.go | 86 | ||||
-rw-r--r-- | tpl/tplimpl/templateProvider.go | 59 | ||||
-rw-r--r-- | tpl/tplimpl/template_ast_transformers.go | 50 | ||||
-rw-r--r-- | tpl/tplimpl/template_ast_transformers_test.go | 12 | ||||
-rw-r--r-- | tpl/tplimpl/template_embedded.go | 57 | ||||
-rw-r--r-- | tpl/tplimpl/template_funcs.go | 33 | ||||
-rw-r--r-- | tpl/tplimpl/template_funcs_test.go | 106 | ||||
-rw-r--r-- | tpl/tplimpl/template_test.go | 232 |
12 files changed, 928 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..68528a775 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,540 @@ 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(c.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(c.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{Template: 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{Template: 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) + } +} + +fu |