Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-02-17 15:30:50 +0300
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-02-17 19:15:26 +0300
commitc507e2717df7dd4b870478033bc5ece0b039a8c4 (patch)
tree873684fe890f1d23749ecbdbe8303712b6d9fafa /tpl/template.go
parent93ca7c9e958e34469a337e4efcc7c75774ec50fd (diff)
tpl: Refactor package
Now: * The template API lives in /tpl * The rest lives in /tpl/tplimpl This is bound te be more improved in the future. Updates #2701
Diffstat (limited to 'tpl/template.go')
-rw-r--r--tpl/template.go586
1 files changed, 19 insertions, 567 deletions
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. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
- // 2. <current-path>/baseof.<suffix>
- // 3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>.
- // 4. _default/baseof.<suffix>
- // 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()
}