github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/tpl/tplimpl/template.go (about)

     1  // Copyright 2017-present The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package tplimpl
    15  
    16  import (
    17  	"fmt"
    18  	"html/template"
    19  	"path"
    20  	"strings"
    21  	texttemplate "text/template"
    22  
    23  	"github.com/gohugoio/hugo/tpl/tplimpl/embedded"
    24  
    25  	"github.com/eknkc/amber"
    26  
    27  	"os"
    28  
    29  	"github.com/gohugoio/hugo/output"
    30  
    31  	"path/filepath"
    32  	"sync"
    33  
    34  	"github.com/gohugoio/hugo/deps"
    35  	"github.com/gohugoio/hugo/helpers"
    36  	"github.com/gohugoio/hugo/tpl"
    37  	"github.com/spf13/afero"
    38  )
    39  
    40  const (
    41  	textTmplNamePrefix = "_text/"
    42  )
    43  
    44  var (
    45  	_ tpl.TemplateHandler       = (*templateHandler)(nil)
    46  	_ tpl.TemplateDebugger      = (*templateHandler)(nil)
    47  	_ tpl.TemplateFuncsGetter   = (*templateHandler)(nil)
    48  	_ tpl.TemplateTestMocker    = (*templateHandler)(nil)
    49  	_ tpl.TemplateFinder        = (*htmlTemplates)(nil)
    50  	_ tpl.TemplateFinder        = (*textTemplates)(nil)
    51  	_ templateLoader            = (*htmlTemplates)(nil)
    52  	_ templateLoader            = (*textTemplates)(nil)
    53  	_ templateLoader            = (*templateHandler)(nil)
    54  	_ templateFuncsterTemplater = (*htmlTemplates)(nil)
    55  	_ templateFuncsterTemplater = (*textTemplates)(nil)
    56  )
    57  
    58  // Protecting global map access (Amber)
    59  var amberMu sync.Mutex
    60  
    61  type templateErr struct {
    62  	name string
    63  	err  error
    64  }
    65  
    66  type templateLoader interface {
    67  	handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error
    68  	addTemplate(name, tpl string) error
    69  	addLateTemplate(name, tpl string) error
    70  }
    71  
    72  type templateFuncsterTemplater interface {
    73  	tpl.TemplateFinder
    74  	setFuncs(funcMap map[string]interface{})
    75  	setTemplateFuncster(f *templateFuncster)
    76  }
    77  
    78  // templateHandler holds the templates in play.
    79  // It implements the templateLoader and tpl.TemplateHandler interfaces.
    80  type templateHandler struct {
    81  	// text holds all the pure text templates.
    82  	text *textTemplates
    83  	html *htmlTemplates
    84  
    85  	amberFuncMap template.FuncMap
    86  
    87  	errors []*templateErr
    88  
    89  	// This is the filesystem to load the templates from. All the templates are
    90  	// stored in the root of this filesystem.
    91  	layoutsFs afero.Fs
    92  
    93  	*deps.Deps
    94  }
    95  
    96  func (t *templateHandler) addError(name string, err error) {
    97  	t.errors = append(t.errors, &templateErr{name, err})
    98  }
    99  
   100  func (t *templateHandler) Debug() {
   101  	fmt.Println("HTML templates:\n", t.html.t.DefinedTemplates())
   102  	fmt.Println("\n\nText templates:\n", t.text.t.DefinedTemplates())
   103  }
   104  
   105  // PrintErrors prints the accumulated errors as ERROR to the log.
   106  func (t *templateHandler) PrintErrors() {
   107  	for _, e := range t.errors {
   108  		t.Log.ERROR.Println(e.name, ":", e.err)
   109  	}
   110  }
   111  
   112  // Lookup tries to find a template with the given name in both template
   113  // collections: First HTML, then the plain text template collection.
   114  func (t *templateHandler) Lookup(name string) *tpl.TemplateAdapter {
   115  
   116  	if strings.HasPrefix(name, textTmplNamePrefix) {
   117  		// The caller has explicitly asked for a text template, so only look
   118  		// in the text template collection.
   119  		// The templates are stored without the prefix identificator.
   120  		name = strings.TrimPrefix(name, textTmplNamePrefix)
   121  
   122  		return t.text.Lookup(name)
   123  	}
   124  
   125  	// Look in both
   126  	if te := t.html.Lookup(name); te != nil {
   127  		return te
   128  	}
   129  
   130  	return t.text.Lookup(name)
   131  
   132  }
   133  
   134  func (t *templateHandler) clone(d *deps.Deps) *templateHandler {
   135  	c := &templateHandler{
   136  		Deps:      d,
   137  		layoutsFs: d.BaseFs.Layouts.Fs,
   138  		html:      &htmlTemplates{t: template.Must(t.html.t.Clone()), overlays: make(map[string]*template.Template)},
   139  		text:      &textTemplates{t: texttemplate.Must(t.text.t.Clone()), overlays: make(map[string]*texttemplate.Template)},
   140  		errors:    make([]*templateErr, 0),
   141  	}
   142  
   143  	d.Tmpl = c
   144  
   145  	c.initFuncs()
   146  
   147  	for k, v := range t.html.overlays {
   148  		vc := template.Must(v.Clone())
   149  		// The extra lookup is a workaround, see
   150  		// * https://github.com/golang/go/issues/16101
   151  		// * https://github.com/gohugoio/hugo/issues/2549
   152  		vc = vc.Lookup(vc.Name())
   153  		vc.Funcs(c.html.funcster.funcMap)
   154  		c.html.overlays[k] = vc
   155  	}
   156  
   157  	for k, v := range t.text.overlays {
   158  		vc := texttemplate.Must(v.Clone())
   159  		vc = vc.Lookup(vc.Name())
   160  		vc.Funcs(texttemplate.FuncMap(c.text.funcster.funcMap))
   161  		c.text.overlays[k] = vc
   162  	}
   163  
   164  	return c
   165  
   166  }
   167  
   168  func newTemplateAdapter(deps *deps.Deps) *templateHandler {
   169  	htmlT := &htmlTemplates{
   170  		t:        template.New(""),
   171  		overlays: make(map[string]*template.Template),
   172  	}
   173  	textT := &textTemplates{
   174  		t:        texttemplate.New(""),
   175  		overlays: make(map[string]*texttemplate.Template),
   176  	}
   177  	return &templateHandler{
   178  		Deps:      deps,
   179  		layoutsFs: deps.BaseFs.Layouts.Fs,
   180  		html:      htmlT,
   181  		text:      textT,
   182  		errors:    make([]*templateErr, 0),
   183  	}
   184  
   185  }
   186  
   187  type htmlTemplates struct {
   188  	funcster *templateFuncster
   189  
   190  	t *template.Template
   191  
   192  	// This looks, and is, strange.
   193  	// The clone is used by non-renderable content pages, and these need to be
   194  	// re-parsed on content change, and to avoid the
   195  	// "cannot Parse after Execute" error, we need to re-clone it from the original clone.
   196  	clone      *template.Template
   197  	cloneClone *template.Template
   198  
   199  	// a separate storage for the overlays created from cloned master templates.
   200  	// note: No mutex protection, so we add these in one Go routine, then just read.
   201  	overlays map[string]*template.Template
   202  }
   203  
   204  func (t *htmlTemplates) setTemplateFuncster(f *templateFuncster) {
   205  	t.funcster = f
   206  }
   207  
   208  func (t *htmlTemplates) Lookup(name string) *tpl.TemplateAdapter {
   209  	templ := t.lookup(name)
   210  	if templ == nil {
   211  		return nil
   212  	}
   213  	return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics}
   214  }
   215  
   216  func (t *htmlTemplates) lookup(name string) *template.Template {
   217  
   218  	// Need to check in the overlay registry first as it will also be found below.
   219  	if t.overlays != nil {
   220  		if templ, ok := t.overlays[name]; ok {
   221  			return templ
   222  		}
   223  	}
   224  
   225  	if templ := t.t.Lookup(name); templ != nil {
   226  		return templ
   227  	}
   228  
   229  	if t.clone != nil {
   230  		return t.clone.Lookup(name)
   231  	}
   232  
   233  	return nil
   234  }
   235  
   236  type textTemplates struct {
   237  	funcster *templateFuncster
   238  
   239  	t *texttemplate.Template
   240  
   241  	clone      *texttemplate.Template
   242  	cloneClone *texttemplate.Template
   243  
   244  	overlays map[string]*texttemplate.Template
   245  }
   246  
   247  func (t *textTemplates) setTemplateFuncster(f *templateFuncster) {
   248  	t.funcster = f
   249  }
   250  
   251  func (t *textTemplates) Lookup(name string) *tpl.TemplateAdapter {
   252  	templ := t.lookup(name)
   253  	if templ == nil {
   254  		return nil
   255  	}
   256  	return &tpl.TemplateAdapter{Template: templ, Metrics: t.funcster.Deps.Metrics}
   257  }
   258  
   259  func (t *textTemplates) lookup(name string) *texttemplate.Template {
   260  
   261  	// Need to check in the overlay registry first as it will also be found below.
   262  	if t.overlays != nil {
   263  		if templ, ok := t.overlays[name]; ok {
   264  			return templ
   265  		}
   266  	}
   267  
   268  	if templ := t.t.Lookup(name); templ != nil {
   269  		return templ
   270  	}
   271  
   272  	if t.clone != nil {
   273  		return t.clone.Lookup(name)
   274  	}
   275  
   276  	return nil
   277  }
   278  
   279  func (t *templateHandler) setFuncs(funcMap map[string]interface{}) {
   280  	t.html.setFuncs(funcMap)
   281  	t.text.setFuncs(funcMap)
   282  }
   283  
   284  // SetFuncs replaces the funcs in the func maps with new definitions.
   285  // This is only used in tests.
   286  func (t *templateHandler) SetFuncs(funcMap map[string]interface{}) {
   287  	t.setFuncs(funcMap)
   288  }
   289  
   290  func (t *templateHandler) GetFuncs() map[string]interface{} {
   291  	return t.html.funcster.funcMap
   292  }
   293  
   294  func (t *htmlTemplates) setFuncs(funcMap map[string]interface{}) {
   295  	t.t.Funcs(funcMap)
   296  }
   297  
   298  func (t *textTemplates) setFuncs(funcMap map[string]interface{}) {
   299  	t.t.Funcs(funcMap)
   300  }
   301  
   302  // LoadTemplates loads the templates from the layouts filesystem.
   303  // A prefix can be given to indicate a template namespace to load the templates
   304  // into, i.e. "_internal" etc.
   305  func (t *templateHandler) LoadTemplates(prefix string) {
   306  	t.loadTemplates(prefix)
   307  
   308  }
   309  
   310  func (t *htmlTemplates) addTemplateIn(tt *template.Template, name, tpl string) error {
   311  	templ, err := tt.New(name).Parse(tpl)
   312  	if err != nil {
   313  		return err
   314  	}
   315  
   316  	if err := applyTemplateTransformersToHMLTTemplate(templ); err != nil {
   317  		return err
   318  	}
   319  
   320  	if strings.Contains(name, "shortcodes") {
   321  		// We need to keep track of one ot the output format's shortcode template
   322  		// without knowing the rendering context.
   323  		withoutExt := strings.TrimSuffix(name, path.Ext(name))
   324  		clone := template.Must(templ.Clone())
   325  		tt.AddParseTree(withoutExt, clone.Tree)
   326  	}
   327  
   328  	return nil
   329  }
   330  
   331  func (t *htmlTemplates) addTemplate(name, tpl string) error {
   332  	return t.addTemplateIn(t.t, name, tpl)
   333  }
   334  
   335  func (t *htmlTemplates) addLateTemplate(name, tpl string) error {
   336  	return t.addTemplateIn(t.clone, name, tpl)
   337  }
   338  
   339  func (t *textTemplates) addTemplateIn(tt *texttemplate.Template, name, tpl string) error {
   340  	name = strings.TrimPrefix(name, textTmplNamePrefix)
   341  	templ, err := tt.New(name).Parse(tpl)
   342  	if err != nil {
   343  		return err
   344  	}
   345  
   346  	if err := applyTemplateTransformersToTextTemplate(templ); err != nil {
   347  		return err
   348  	}
   349  
   350  	if strings.Contains(name, "shortcodes") {
   351  		// We need to keep track of one ot the output format's shortcode template
   352  		// without knowing the rendering context.
   353  		withoutExt := strings.TrimSuffix(name, path.Ext(name))
   354  		clone := texttemplate.Must(templ.Clone())
   355  		tt.AddParseTree(withoutExt, clone.Tree)
   356  	}
   357  
   358  	return nil
   359  }
   360  
   361  func (t *textTemplates) addTemplate(name, tpl string) error {
   362  	return t.addTemplateIn(t.t, name, tpl)
   363  }
   364  
   365  func (t *textTemplates) addLateTemplate(name, tpl string) error {
   366  	return t.addTemplateIn(t.clone, name, tpl)
   367  }
   368  
   369  func (t *templateHandler) addTemplate(name, tpl string) error {
   370  	return t.AddTemplate(name, tpl)
   371  }
   372  
   373  func (t *templateHandler) addLateTemplate(name, tpl string) error {
   374  	return t.AddLateTemplate(name, tpl)
   375  }
   376  
   377  // AddLateTemplate is used to add a template late, i.e. after the
   378  // regular templates have started its execution.
   379  func (t *templateHandler) AddLateTemplate(name, tpl string) error {
   380  	h := t.getTemplateHandler(name)
   381  	if err := h.addLateTemplate(name, tpl); err != nil {
   382  		t.addError(name, err)
   383  		return err
   384  	}
   385  	return nil
   386  }
   387  
   388  // AddTemplate parses and adds a template to the collection.
   389  // Templates with name prefixed with "_text" will be handled as plain
   390  // text templates.
   391  func (t *templateHandler) AddTemplate(name, tpl string) error {
   392  	h := t.getTemplateHandler(name)
   393  	if err := h.addTemplate(name, tpl); err != nil {
   394  		t.addError(name, err)
   395  		return err
   396  	}
   397  	return nil
   398  }
   399  
   400  // MarkReady marks the templates as "ready for execution". No changes allowed
   401  // after this is set.
   402  // TODO(bep) if this proves to be resource heavy, we could detect
   403  // earlier if we really need this, or make it lazy.
   404  func (t *templateHandler) MarkReady() {
   405  	if t.html.clone == nil {
   406  		t.html.clone = template.Must(t.html.t.Clone())
   407  		t.html.cloneClone = template.Must(t.html.clone.Clone())
   408  	}
   409  	if t.text.clone == nil {
   410  		t.text.clone = texttemplate.Must(t.text.t.Clone())
   411  		t.text.cloneClone = texttemplate.Must(t.text.clone.Clone())
   412  	}
   413  }
   414  
   415  // RebuildClone rebuilds the cloned templates. Used for live-reloads.
   416  func (t *templateHandler) RebuildClone() {
   417  	t.html.clone = template.Must(t.html.cloneClone.Clone())
   418  	t.text.clone = texttemplate.Must(t.text.cloneClone.Clone())
   419  }
   420  
   421  func (t *templateHandler) loadTemplates(prefix string) {
   422  	walker := func(path string, fi os.FileInfo, err error) error {
   423  		if err != nil || fi.IsDir() {
   424  			return nil
   425  		}
   426  
   427  		if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) {
   428  			return nil
   429  		}
   430  
   431  		workingDir := t.PathSpec.WorkingDir
   432  
   433  		descriptor := output.TemplateLookupDescriptor{
   434  			WorkingDir:    workingDir,
   435  			RelPath:       path,
   436  			Prefix:        prefix,
   437  			OutputFormats: t.OutputFormatsConfig,
   438  			FileExists: func(filename string) (bool, error) {
   439  				return helpers.Exists(filename, t.Layouts.Fs)
   440  			},
   441  			ContainsAny: func(filename string, subslices [][]byte) (bool, error) {
   442  				return helpers.FileContainsAny(filename, subslices, t.Layouts.Fs)
   443  			},
   444  		}
   445  
   446  		tplID, err := output.CreateTemplateNames(descriptor)
   447  		if err != nil {
   448  			t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err)
   449  
   450  			return nil
   451  		}
   452  
   453  		if err := t.addTemplateFile(tplID.Name, tplID.MasterFilename, tplID.OverlayFilename); err != nil {
   454  			t.Log.ERROR.Printf("Failed to add template %q in path %q: %s", tplID.Name, path, err)
   455  		}
   456  
   457  		return nil
   458  	}
   459  
   460  	if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil {
   461  		t.Log.ERROR.Printf("Failed to load templates: %s", err)
   462  	}
   463  
   464  }
   465  
   466  func (t *templateHandler) initFuncs() {
   467  
   468  	// Both template types will get their own funcster instance, which
   469  	// in the current case contains the same set of funcs.
   470  	for _, funcsterHolder := range []templateFuncsterTemplater{t.html, t.text} {
   471  		funcster := newTemplateFuncster(t.Deps)
   472  
   473  		// The URL funcs in the funcMap is somewhat language dependent,
   474  		// so we need to wait until the language and site config is loaded.
   475  		funcster.initFuncMap()
   476  
   477  		funcsterHolder.setTemplateFuncster(funcster)
   478  
   479  	}
   480  
   481  	// Amber is HTML only.
   482  	t.amberFuncMap = template.FuncMap{}
   483  
   484  	amberMu.Lock()
   485  	for k, v := range amber.FuncMap {
   486  		t.amberFuncMap[k] = v
   487  	}
   488  
   489  	for k, v := range t.html.funcster.funcMap {
   490  		t.amberFuncMap[k] = v
   491  		// Hacky, but we need to make sure that the func names are in the global map.
   492  		amber.FuncMap[k] = func() string {
   493  			panic("should never be invoked")
   494  		}
   495  	}
   496  	amberMu.Unlock()
   497  
   498  }
   499  
   500  func (t *templateHandler) getTemplateHandler(name string) templateLoader {
   501  	if strings.HasPrefix(name, textTmplNamePrefix) {
   502  		return t.text
   503  	}
   504  	return t.html
   505  }
   506  
   507  func (t *templateHandler) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
   508  	h := t.getTemplateHandler(name)
   509  	return h.handleMaster(name, overlayFilename, masterFilename, onMissing)
   510  }
   511  
   512  func (t *htmlTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
   513  
   514  	masterTpl := t.lookup(masterFilename)
   515  
   516  	if masterTpl == nil {
   517  		templ, err := onMissing(masterFilename)
   518  		if err != nil {
   519  			return err
   520  		}
   521  
   522  		masterTpl, err = t.t.New(overlayFilename).Parse(templ)
   523  		if err != nil {
   524  			return err
   525  		}
   526  	}
   527  
   528  	templ, err := onMissing(overlayFilename)
   529  	if err != nil {
   530  		return err
   531  	}
   532  
   533  	overlayTpl, err := template.Must(masterTpl.Clone()).Parse(templ)
   534  	if err != nil {
   535  		return err
   536  	}
   537  
   538  	// The extra lookup is a workaround, see
   539  	// * https://github.com/golang/go/issues/16101
   540  	// * https://github.com/gohugoio/hugo/issues/2549
   541  	overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
   542  	if err := applyTemplateTransformersToHMLTTemplate(overlayTpl); err != nil {
   543  		return err
   544  	}
   545  
   546  	t.overlays[name] = overlayTpl
   547  
   548  	return err
   549  
   550  }
   551  
   552  func (t *textTemplates) handleMaster(name, overlayFilename, masterFilename string, onMissing func(filename string) (string, error)) error {
   553  
   554  	name = strings.TrimPrefix(name, textTmplNamePrefix)
   555  	masterTpl := t.lookup(masterFilename)
   556  
   557  	if masterTpl == nil {
   558  		templ, err := onMissing(masterFilename)
   559  		if err != nil {
   560  			return err
   561  		}
   562  
   563  		masterTpl, err = t.t.New(overlayFilename).Parse(templ)
   564  		if err != nil {
   565  			return err
   566  		}
   567  	}
   568  
   569  	templ, err := onMissing(overlayFilename)
   570  	if err != nil {
   571  		return err
   572  	}
   573  
   574  	overlayTpl, err := texttemplate.Must(masterTpl.Clone()).Parse(templ)
   575  	if err != nil {
   576  		return err
   577  	}
   578  
   579  	overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
   580  	if err := applyTemplateTransformersToTextTemplate(overlayTpl); err != nil {
   581  		return err
   582  	}
   583  	t.overlays[name] = overlayTpl
   584  
   585  	return err
   586  
   587  }
   588  
   589  func (t *templateHandler) addTemplateFile(name, baseTemplatePath, path string) error {
   590  	t.checkState()
   591  
   592  	t.Log.DEBUG.Printf("Add template file: name %q, baseTemplatePath %q, path %q", name, baseTemplatePath, path)
   593  
   594  	getTemplate := func(filename string) (string, error) {
   595  		b, err := afero.ReadFile(t.Layouts.Fs, filename)
   596  		if err != nil {
   597  			return "", err
   598  		}
   599  		s := string(b)
   600  
   601  		return s, nil
   602  	}
   603  
   604  	// get the suffix and switch on that
   605  	ext := filepath.Ext(path)
   606  	switch ext {
   607  	case ".amber":
   608  		//	Only HTML support for Amber
   609  		withoutExt := strings.TrimSuffix(name, filepath.Ext(name))
   610  		templateName := withoutExt + ".html"
   611  		b, err := afero.ReadFile(t.Layouts.Fs, path)
   612  
   613  		if err != nil {
   614  			return err
   615  		}
   616  
   617  		amberMu.Lock()
   618  		templ, err := t.compileAmberWithTemplate(b, path, t.html.t.New(templateName))
   619  		amberMu.Unlock()
   620  		if err != nil {
   621  			return err
   622  		}
   623  
   624  		if err := applyTemplateTransformersToHMLTTemplate(templ); err != nil {
   625  			return err
   626  		}
   627  
   628  		if strings.Contains(templateName, "shortcodes") {
   629  			// We need to keep track of one ot the output format's shortcode template
   630  			// without knowing the rendering context.
   631  			clone := template.Must(templ.Clone())
   632  			t.html.t.AddParseTree(withoutExt, clone.Tree)
   633  		}
   634  
   635  		return nil
   636  
   637  	case ".ace":
   638  		//	Only HTML support for Ace
   639  		var innerContent, baseContent []byte
   640  		innerContent, err := afero.ReadFile(t.Layouts.Fs, path)
   641  
   642  		if err != nil {
   643  			return err
   644  		}
   645  
   646  		if baseTemplatePath != "" {
   647  			baseContent, err = afero.ReadFile(t.Layouts.Fs, baseTemplatePath)
   648  			if err != nil {
   649  				return err
   650  			}
   651  		}
   652  
   653  		return t.addAceTemplate(name, baseTemplatePath, path, baseContent, innerContent)
   654  	default:
   655  
   656  		if baseTemplatePath != "" {
   657  			return t.handleMaster(name, path, baseTemplatePath, getTemplate)
   658  		}
   659  
   660  		templ, err := getTemplate(path)
   661  
   662  		if err != nil {
   663  			return err
   664  		}
   665  
   666  		return t.AddTemplate(name, templ)
   667  	}
   668  }
   669  
   670  var embeddedTemplatesAliases = map[string][]string{
   671  	"shortcodes/twitter.html": []string{"shortcodes/tweet.html"},
   672  }
   673  
   674  func (t *templateHandler) loadEmbedded() {
   675  	for _, kv := range embedded.EmbeddedTemplates {
   676  		// TODO(bep) error handling
   677  		name, templ := kv[0], kv[1]
   678  		t.addInternalTemplate(name, templ)
   679  		if aliases, found := embeddedTemplatesAliases[name]; found {
   680  			for _, alias := range aliases {
   681  				t.addInternalTemplate(alias, templ)
   682  			}
   683  
   684  		}
   685  	}
   686  
   687  }
   688  
   689  func (t *templateHandler) addInternalTemplate(name, tpl string) error {
   690  	return t.AddTemplate("_internal/"+name, tpl)
   691  }
   692  
   693  func (t *templateHandler) checkState() {
   694  	if t.html.clone != nil || t.text.clone != nil {
   695  		panic("template is cloned and cannot be modfified")
   696  	}
   697  }
   698  
   699  func isDotFile(path string) bool {
   700  	return filepath.Base(path)[0] == '.'
   701  }
   702  
   703  func isBackupFile(path string) bool {
   704  	return path[len(path)-1] == '~'
   705  }
   706  
   707  const baseFileBase = "baseof"
   708  
   709  func isBaseTemplate(path string) bool {
   710  	return strings.Contains(filepath.Base(path), baseFileBase)
   711  }