github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/tpl/tplimpl/template.go (about)

     1  // Copyright 2019 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  	"io"
    18  	"os"
    19  	"path/filepath"
    20  	"reflect"
    21  	"regexp"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  	"unicode"
    27  	"unicode/utf8"
    28  
    29  	"github.com/gohugoio/hugo/common/types"
    30  
    31  	"github.com/gohugoio/hugo/helpers"
    32  
    33  	"github.com/gohugoio/hugo/output"
    34  
    35  	"github.com/gohugoio/hugo/deps"
    36  	"github.com/spf13/afero"
    37  
    38  	"github.com/gohugoio/hugo/common/herrors"
    39  	"github.com/gohugoio/hugo/hugofs"
    40  	"github.com/gohugoio/hugo/hugofs/files"
    41  	"github.com/pkg/errors"
    42  
    43  	"github.com/gohugoio/hugo/tpl/tplimpl/embedded"
    44  
    45  	htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
    46  	texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
    47  
    48  	"github.com/gohugoio/hugo/identity"
    49  	"github.com/gohugoio/hugo/tpl"
    50  )
    51  
    52  const (
    53  	textTmplNamePrefix = "_text/"
    54  
    55  	shortcodesPathPrefix = "shortcodes/"
    56  	internalPathPrefix   = "_internal/"
    57  	baseFileBase         = "baseof"
    58  )
    59  
    60  // The identifiers may be truncated in the log, e.g.
    61  // "executing "main" at <$scaled.SRelPermalin...>: can't evaluate field SRelPermalink in type *resource.Image"
    62  var identifiersRe = regexp.MustCompile(`at \<(.*?)(\.{3})?\>:`)
    63  
    64  var embeddedTemplatesAliases = map[string][]string{
    65  	"shortcodes/twitter.html": {"shortcodes/tweet.html"},
    66  }
    67  
    68  var (
    69  	_ tpl.TemplateManager    = (*templateExec)(nil)
    70  	_ tpl.TemplateHandler    = (*templateExec)(nil)
    71  	_ tpl.TemplateFuncGetter = (*templateExec)(nil)
    72  	_ tpl.TemplateFinder     = (*templateExec)(nil)
    73  
    74  	_ tpl.Template = (*templateState)(nil)
    75  	_ tpl.Info     = (*templateState)(nil)
    76  )
    77  
    78  var baseTemplateDefineRe = regexp.MustCompile(`^{{-?\s*define`)
    79  
    80  // needsBaseTemplate returns true if the first non-comment template block is a
    81  // define block.
    82  // If a base template does not exist, we will handle that when it's used.
    83  func needsBaseTemplate(templ string) bool {
    84  	idx := -1
    85  	inComment := false
    86  	for i := 0; i < len(templ); {
    87  		if !inComment && strings.HasPrefix(templ[i:], "{{/*") {
    88  			inComment = true
    89  			i += 4
    90  		} else if inComment && strings.HasPrefix(templ[i:], "*/}}") {
    91  			inComment = false
    92  			i += 4
    93  		} else {
    94  			r, size := utf8.DecodeRuneInString(templ[i:])
    95  			if !inComment {
    96  				if strings.HasPrefix(templ[i:], "{{") {
    97  					idx = i
    98  					break
    99  				} else if !unicode.IsSpace(r) {
   100  					break
   101  				}
   102  			}
   103  			i += size
   104  		}
   105  	}
   106  
   107  	if idx == -1 {
   108  		return false
   109  	}
   110  
   111  	return baseTemplateDefineRe.MatchString(templ[idx:])
   112  }
   113  
   114  func newIdentity(name string) identity.Manager {
   115  	return identity.NewManager(identity.NewPathIdentity(files.ComponentFolderLayouts, name))
   116  }
   117  
   118  func newStandaloneTextTemplate(funcs map[string]interface{}) tpl.TemplateParseFinder {
   119  	return &textTemplateWrapperWithLock{
   120  		RWMutex:  &sync.RWMutex{},
   121  		Template: texttemplate.New("").Funcs(funcs),
   122  	}
   123  }
   124  
   125  func newTemplateExec(d *deps.Deps) (*templateExec, error) {
   126  	exec, funcs := newTemplateExecuter(d)
   127  	funcMap := make(map[string]interface{})
   128  	for k, v := range funcs {
   129  		funcMap[k] = v.Interface()
   130  	}
   131  
   132  	h := &templateHandler{
   133  		nameBaseTemplateName: make(map[string]string),
   134  		transformNotFound:    make(map[string]*templateState),
   135  		identityNotFound:     make(map[string][]identity.Manager),
   136  
   137  		shortcodes:   make(map[string]*shortcodeTemplates),
   138  		templateInfo: make(map[string]tpl.Info),
   139  		baseof:       make(map[string]templateInfo),
   140  		needsBaseof:  make(map[string]templateInfo),
   141  
   142  		main: newTemplateNamespace(funcMap),
   143  
   144  		Deps:                d,
   145  		layoutHandler:       output.NewLayoutHandler(),
   146  		layoutsFs:           d.BaseFs.Layouts.Fs,
   147  		layoutTemplateCache: make(map[layoutCacheKey]tpl.Template),
   148  	}
   149  
   150  	if err := h.loadEmbedded(); err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	if err := h.loadTemplates(); err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	e := &templateExec{
   159  		d:               d,
   160  		executor:        exec,
   161  		funcs:           funcs,
   162  		templateHandler: h,
   163  	}
   164  
   165  	d.SetTmpl(e)
   166  	d.SetTextTmpl(newStandaloneTextTemplate(funcMap))
   167  
   168  	if d.WithTemplate != nil {
   169  		if err := d.WithTemplate(e); err != nil {
   170  			return nil, err
   171  		}
   172  	}
   173  
   174  	return e, nil
   175  }
   176  
   177  func newTemplateNamespace(funcs map[string]interface{}) *templateNamespace {
   178  	return &templateNamespace{
   179  		prototypeHTML: htmltemplate.New("").Funcs(funcs),
   180  		prototypeText: texttemplate.New("").Funcs(funcs),
   181  		templateStateMap: &templateStateMap{
   182  			templates: make(map[string]*templateState),
   183  		},
   184  	}
   185  }
   186  
   187  func newTemplateState(templ tpl.Template, info templateInfo) *templateState {
   188  	return &templateState{
   189  		info:      info,
   190  		typ:       info.resolveType(),
   191  		Template:  templ,
   192  		Manager:   newIdentity(info.name),
   193  		parseInfo: tpl.DefaultParseInfo,
   194  	}
   195  }
   196  
   197  type layoutCacheKey struct {
   198  	d output.LayoutDescriptor
   199  	f string
   200  }
   201  
   202  type templateExec struct {
   203  	d        *deps.Deps
   204  	executor texttemplate.Executer
   205  	funcs    map[string]reflect.Value
   206  
   207  	*templateHandler
   208  }
   209  
   210  func (t templateExec) Clone(d *deps.Deps) *templateExec {
   211  	exec, funcs := newTemplateExecuter(d)
   212  	t.executor = exec
   213  	t.funcs = funcs
   214  	t.d = d
   215  	return &t
   216  }
   217  
   218  func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data interface{}) error {
   219  	if rlocker, ok := templ.(types.RLocker); ok {
   220  		rlocker.RLock()
   221  		defer rlocker.RUnlock()
   222  	}
   223  	if t.Metrics != nil {
   224  		defer t.Metrics.MeasureSince(templ.Name(), time.Now())
   225  	}
   226  
   227  	execErr := t.executor.Execute(templ, wr, data)
   228  	if execErr != nil {
   229  		execErr = t.addFileContext(templ, execErr)
   230  	}
   231  	return execErr
   232  }
   233  
   234  func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
   235  	v, found := t.funcs[name]
   236  	return v, found
   237  }
   238  
   239  func (t *templateExec) MarkReady() error {
   240  	var err error
   241  	t.readyInit.Do(func() {
   242  		// We only need the clones if base templates are in use.
   243  		if len(t.needsBaseof) > 0 {
   244  			err = t.main.createPrototypes()
   245  		}
   246  	})
   247  
   248  	return err
   249  }
   250  
   251  type templateHandler struct {
   252  	main        *templateNamespace
   253  	needsBaseof map[string]templateInfo
   254  	baseof      map[string]templateInfo
   255  
   256  	readyInit sync.Once
   257  
   258  	// This is the filesystem to load the templates from. All the templates are
   259  	// stored in the root of this filesystem.
   260  	layoutsFs afero.Fs
   261  
   262  	layoutHandler *output.LayoutHandler
   263  
   264  	layoutTemplateCache   map[layoutCacheKey]tpl.Template
   265  	layoutTemplateCacheMu sync.RWMutex
   266  
   267  	*deps.Deps
   268  
   269  	// Used to get proper filenames in errors
   270  	nameBaseTemplateName map[string]string
   271  
   272  	// Holds name and source of template definitions not found during the first
   273  	// AST transformation pass.
   274  	transformNotFound map[string]*templateState
   275  
   276  	// Holds identities of templates not found during first pass.
   277  	identityNotFound map[string][]identity.Manager
   278  
   279  	// shortcodes maps shortcode name to template variants
   280  	// (language, output format etc.) of that shortcode.
   281  	shortcodes map[string]*shortcodeTemplates
   282  
   283  	// templateInfo maps template name to some additional information about that template.
   284  	// Note that for shortcodes that same information is embedded in the
   285  	// shortcodeTemplates type.
   286  	templateInfo map[string]tpl.Info
   287  }
   288  
   289  // AddTemplate parses and adds a template to the collection.
   290  // Templates with name prefixed with "_text" will be handled as plain
   291  // text templates.
   292  func (t *templateHandler) AddTemplate(name, tpl string) error {
   293  	templ, err := t.addTemplateTo(t.newTemplateInfo(name, tpl), t.main)
   294  	if err == nil {
   295  		t.applyTemplateTransformers(t.main, templ)
   296  	}
   297  	return err
   298  }
   299  
   300  func (t *templateHandler) Lookup(name string) (tpl.Template, bool) {
   301  	templ, found := t.main.Lookup(name)
   302  	if found {
   303  		return templ, true
   304  	}
   305  
   306  	return nil, false
   307  }
   308  
   309  func (t *templateHandler) LookupLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
   310  	key := layoutCacheKey{d, f.Name}
   311  	t.layoutTemplateCacheMu.RLock()
   312  	if cacheVal, found := t.layoutTemplateCache[key]; found {
   313  		t.layoutTemplateCacheMu.RUnlock()
   314  		return cacheVal, true, nil
   315  	}
   316  	t.layoutTemplateCacheMu.RUnlock()
   317  
   318  	t.layoutTemplateCacheMu.Lock()
   319  	defer t.layoutTemplateCacheMu.Unlock()
   320  
   321  	templ, found, err := t.findLayout(d, f)
   322  	if err == nil && found {
   323  		t.layoutTemplateCache[key] = templ
   324  		return templ, true, nil
   325  	}
   326  
   327  	return nil, false, err
   328  }
   329  
   330  // This currently only applies to shortcodes and what we get here is the
   331  // shortcode name.
   332  func (t *templateHandler) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
   333  	name = templateBaseName(templateShortcode, name)
   334  	s, found := t.shortcodes[name]
   335  	if !found {
   336  		return nil, false, false
   337  	}
   338  
   339  	sv, found := s.fromVariants(variants)
   340  	if !found {
   341  		return nil, false, false
   342  	}
   343  
   344  	more := len(s.variants) > 1
   345  
   346  	return sv.ts, true, more
   347  }
   348  
   349  // LookupVariants returns all variants of name, nil if none found.
   350  func (t *templateHandler) LookupVariants(name string) []tpl.Template {
   351  	name = templateBaseName(templateShortcode, name)
   352  	s, found := t.shortcodes[name]
   353  	if !found {
   354  		return nil
   355  	}
   356  
   357  	variants := make([]tpl.Template, len(s.variants))
   358  	for i := 0; i < len(variants); i++ {
   359  		variants[i] = s.variants[i].ts
   360  	}
   361  
   362  	return variants
   363  }
   364  
   365  func (t *templateHandler) HasTemplate(name string) bool {
   366  	if _, found := t.baseof[name]; found {
   367  		return true
   368  	}
   369  
   370  	if _, found := t.needsBaseof[name]; found {
   371  		return true
   372  	}
   373  
   374  	_, found := t.Lookup(name)
   375  	return found
   376  }
   377  
   378  func (t *templateHandler) findLayout(d output.LayoutDescriptor, f output.Format) (tpl.Template, bool, error) {
   379  	layouts, _ := t.layoutHandler.For(d, f)
   380  	for _, name := range layouts {
   381  		templ, found := t.main.Lookup(name)
   382  		if found {
   383  			return templ, true, nil
   384  		}
   385  
   386  		overlay, found := t.needsBaseof[name]
   387  
   388  		if !found {
   389  			continue
   390  		}
   391  
   392  		d.Baseof = true
   393  		baseLayouts, _ := t.layoutHandler.For(d, f)
   394  		var base templateInfo
   395  		found = false
   396  		for _, l := range baseLayouts {
   397  			base, found = t.baseof[l]
   398  			if found {
   399  				break
   400  			}
   401  		}
   402  
   403  		templ, err := t.applyBaseTemplate(overlay, base)
   404  		if err != nil {
   405  			return nil, false, err
   406  		}
   407  
   408  		ts := newTemplateState(templ, overlay)
   409  
   410  		if found {
   411  			ts.baseInfo = base
   412  
   413  			// Add the base identity to detect changes
   414  			ts.Add(identity.NewPathIdentity(files.ComponentFolderLayouts, base.name))
   415  		}
   416  
   417  		t.applyTemplateTransformers(t.main, ts)
   418  
   419  		if err := t.extractPartials(ts.Template); err != nil {
   420  			return nil, false, err
   421  		}
   422  
   423  		return ts, true, nil
   424  
   425  	}
   426  
   427  	return nil, false, nil
   428  }
   429  
   430  func (t *templateHandler) findTemplate(name string) *templateState {
   431  	if templ, found := t.Lookup(name); found {
   432  		return templ.(*templateState)
   433  	}
   434  	return nil
   435  }
   436  
   437  func (t *templateHandler) newTemplateInfo(name, tpl string) templateInfo {
   438  	var isText bool
   439  	name, isText = t.nameIsText(name)
   440  	return templateInfo{
   441  		name:     name,
   442  		isText:   isText,
   443  		template: tpl,
   444  	}
   445  }
   446  
   447  func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error {
   448  	if strings.HasPrefix(templ.Name(), "_internal") {
   449  		return inerr
   450  	}
   451  
   452  	ts, ok := templ.(*templateState)
   453  	if !ok {
   454  		return inerr
   455  	}
   456  
   457  	//lint:ignore ST1008 the error is the main result
   458  	checkFilename := func(info templateInfo, inErr error) (error, bool) {
   459  		if info.filename == "" {
   460  			return inErr, false
   461  		}
   462  
   463  		lineMatcher := func(m herrors.LineMatcher) bool {
   464  			if m.Position.LineNumber != m.LineNumber {
   465  				return false
   466  			}
   467  
   468  			identifiers := t.extractIdentifiers(m.Error.Error())
   469  
   470  			for _, id := range identifiers {
   471  				if strings.Contains(m.Line, id) {
   472  					return true
   473  				}
   474  			}
   475  			return false
   476  		}
   477  
   478  		f, err := t.layoutsFs.Open(info.filename)
   479  		if err != nil {
   480  			return inErr, false
   481  		}
   482  		defer f.Close()
   483  
   484  		fe, ok := herrors.WithFileContext(inErr, info.realFilename, f, lineMatcher)
   485  		if ok {
   486  			return fe, true
   487  		}
   488  		return inErr, false
   489  	}
   490  
   491  	inerr = errors.Wrap(inerr, "execute of template failed")
   492  
   493  	if err, ok := checkFilename(ts.info, inerr); ok {
   494  		return err
   495  	}
   496  
   497  	err, _ := checkFilename(ts.baseInfo, inerr)
   498  
   499  	return err
   500  }
   501  
   502  func (t *templateHandler) addShortcodeVariant(ts *templateState) {
   503  	name := ts.Name()
   504  	base := templateBaseName(templateShortcode, name)
   505  
   506  	shortcodename, variants := templateNameAndVariants(base)
   507  
   508  	templs, found := t.shortcodes[shortcodename]
   509  	if !found {
   510  		templs = &shortcodeTemplates{}
   511  		t.shortcodes[shortcodename] = templs
   512  	}
   513  
   514  	sv := shortcodeVariant{variants: variants, ts: ts}
   515  
   516  	i := templs.indexOf(variants)
   517  
   518  	if i != -1 {
   519  		// Only replace if it's an override of an internal template.
   520  		if !isInternal(name) {
   521  			templs.variants[i] = sv
   522  		}
   523  	} else {
   524  		templs.variants = append(templs.variants, sv)
   525  	}
   526  }
   527  
   528  func (t *templateHandler) addTemplateFile(name, path string) error {
   529  	getTemplate := func(filename string) (templateInfo, error) {
   530  		fs := t.Layouts.Fs
   531  		b, err := afero.ReadFile(fs, filename)
   532  		if err != nil {
   533  			return templateInfo{filename: filename, fs: fs}, err
   534  		}
   535  
   536  		s := removeLeadingBOM(string(b))
   537  
   538  		realFilename := filename
   539  		if fi, err := fs.Stat(filename); err == nil {
   540  			if fim, ok := fi.(hugofs.FileMetaInfo); ok {
   541  				realFilename = fim.Meta().Filename
   542  			}
   543  		}
   544  
   545  		var isText bool
   546  		name, isText = t.nameIsText(name)
   547  
   548  		return templateInfo{
   549  			name:         name,
   550  			isText:       isText,
   551  			template:     s,
   552  			filename:     filename,
   553  			realFilename: realFilename,
   554  			fs:           fs,
   555  		}, nil
   556  	}
   557  
   558  	tinfo, err := getTemplate(path)
   559  	if err != nil {
   560  		return err
   561  	}
   562  
   563  	if isBaseTemplatePath(name) {
   564  		// Store it for later.
   565  		t.baseof[name] = tinfo
   566  		return nil
   567  	}
   568  
   569  	needsBaseof := !t.noBaseNeeded(name) && needsBaseTemplate(tinfo.template)
   570  	if needsBaseof {
   571  		t.needsBaseof[name] = tinfo
   572  		return nil
   573  	}
   574  
   575  	templ, err := t.addTemplateTo(tinfo, t.main)
   576  	if err != nil {
   577  		return tinfo.errWithFileContext("parse failed", err)
   578  	}
   579  	t.applyTemplateTransformers(t.main, templ)
   580  
   581  	return nil
   582  }
   583  
   584  func (t *templateHandler) addTemplateTo(info templateInfo, to *templateNamespace) (*templateState, error) {
   585  	return to.parse(info)
   586  }
   587  
   588  func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) {
   589  	if overlay.isText {
   590  		var (
   591  			templ = t.main.prototypeTextClone.New(overlay.name)
   592  			err   error
   593  		)
   594  
   595  		if !base.IsZero() {
   596  			templ, err = templ.Parse(base.template)
   597  			if err != nil {
   598  				return nil, base.errWithFileContext("parse failed", err)
   599  			}
   600  		}
   601  
   602  		templ, err = texttemplate.Must(templ.Clone()).Parse(overlay.template)
   603  		if err != nil {
   604  			return nil, overlay.errWithFileContext("parse failed", err)
   605  		}
   606  
   607  		// The extra lookup is a workaround, see
   608  		// * https://github.com/golang/go/issues/16101
   609  		// * https://github.com/gohugoio/hugo/issues/2549
   610  		// templ = templ.Lookup(templ.Name())
   611  
   612  		return templ, nil
   613  	}
   614  
   615  	var (
   616  		templ = t.main.prototypeHTMLClone.New(overlay.name)
   617  		err   error
   618  	)
   619  
   620  	if !base.IsZero() {
   621  		templ, err = templ.Parse(base.template)
   622  		if err != nil {
   623  			return nil, base.errWithFileContext("parse failed", err)
   624  		}
   625  	}
   626  
   627  	templ, err = htmltemplate.Must(templ.Clone()).Parse(overlay.template)
   628  	if err != nil {
   629  		return nil, overlay.errWithFileContext("parse failed", err)
   630  	}
   631  
   632  	// The extra lookup is a workaround, see
   633  	// * https://github.com/golang/go/issues/16101
   634  	// * https://github.com/gohugoio/hugo/issues/2549
   635  	templ = templ.Lookup(templ.Name())
   636  
   637  	return templ, err
   638  }
   639  
   640  func (t *templateHandler) applyTemplateTransformers(ns *templateNamespace, ts *templateState) (*templateContext, error) {
   641  	c, err := applyTemplateTransformers(ts, ns.newTemplateLookup(ts))
   642  	if err != nil {
   643  		return nil, err
   644  	}
   645  
   646  	for k := range c.templateNotFound {
   647  		t.transformNotFound[k] = ts
   648  		t.identityNotFound[k] = append(t.identityNotFound[k], c.t)
   649  	}
   650  
   651  	for k := range c.identityNotFound {
   652  		t.identityNotFound[k] = append(t.identityNotFound[k], c.t)
   653  	}
   654  
   655  	return c, err
   656  }
   657  
   658  func (t *templateHandler) extractIdentifiers(line string) []string {
   659  	m := identifiersRe.FindAllStringSubmatch(line, -1)
   660  	identifiers := make([]string, len(m))
   661  	for i := 0; i < len(m); i++ {
   662  		identifiers[i] = m[i][1]
   663  	}
   664  	return identifiers
   665  }
   666  
   667  func (t *templateHandler) loadEmbedded() error {
   668  	for _, kv := range embedded.EmbeddedTemplates {
   669  		name, templ := kv[0], kv[1]
   670  		if err := t.AddTemplate(internalPathPrefix+name, templ); err != nil {
   671  			return err
   672  		}
   673  		if aliases, found := embeddedTemplatesAliases[name]; found {
   674  			// TODO(bep) avoid reparsing these aliases
   675  			for _, alias := range aliases {
   676  				alias = internalPathPrefix + alias
   677  				if err := t.AddTemplate(alias, templ); err != nil {
   678  					return err
   679  				}
   680  			}
   681  		}
   682  	}
   683  	return nil
   684  }
   685  
   686  func (t *templateHandler) loadTemplates() error {
   687  	walker := func(path string, fi hugofs.FileMetaInfo, err error) error {
   688  		if err != nil || fi.IsDir() {
   689  			return err
   690  		}
   691  
   692  		if isDotFile(path) || isBackupFile(path) {
   693  			return nil
   694  		}
   695  
   696  		name := strings.TrimPrefix(filepath.ToSlash(path), "/")
   697  		filename := filepath.Base(path)
   698  		outputFormat, found := t.OutputFormatsConfig.FromFilename(filename)
   699  
   700  		if found && outputFormat.IsPlainText {
   701  			name = textTmplNamePrefix + name
   702  		}
   703  
   704  		if err := t.addTemplateFile(name, path); err != nil {
   705  			return err
   706  		}
   707  
   708  		return nil
   709  	}
   710  
   711  	if err := helpers.SymbolicWalk(t.Layouts.Fs, "", walker); err != nil {
   712  		if !os.IsNotExist(err) {
   713  			return err
   714  		}
   715  		return nil
   716  	}
   717  
   718  	return nil
   719  }
   720  
   721  func (t *templateHandler) nameIsText(name string) (string, bool) {
   722  	isText := strings.HasPrefix(name, textTmplNamePrefix)
   723  	if isText {
   724  		name = strings.TrimPrefix(name, textTmplNamePrefix)
   725  	}
   726  	return name, isText
   727  }
   728  
   729  func (t *templateHandler) noBaseNeeded(name string) bool {
   730  	if strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/") {
   731  		return true
   732  	}
   733  	return strings.Contains(name, "_markup/")
   734  }
   735  
   736  func (t *templateHandler) extractPartials(templ tpl.Template) error {
   737  	templs := templates(templ)
   738  	for _, templ := range templs {
   739  		if templ.Name() == "" || !strings.HasPrefix(templ.Name(), "partials/") {
   740  			continue
   741  		}
   742  
   743  		ts := newTemplateState(templ, templateInfo{name: templ.Name()})
   744  		ts.typ = templatePartial
   745  
   746  		t.main.mu.RLock()
   747  		_, found := t.main.templates[templ.Name()]
   748  		t.main.mu.RUnlock()
   749  
   750  		if !found {
   751  			t.main.mu.Lock()
   752  			// This is a template defined inline.
   753  			_, err := applyTemplateTransformers(ts, t.main.newTemplateLookup(ts))
   754  			if err != nil {
   755  				t.main.mu.Unlock()
   756  				return err
   757  			}
   758  			t.main.templates[templ.Name()] = ts
   759  			t.main.mu.Unlock()
   760  
   761  		}
   762  	}
   763  
   764  	return nil
   765  }
   766  
   767  func (t *templateHandler) postTransform() error {
   768  	defineCheckedHTML := false
   769  	defineCheckedText := false
   770  
   771  	for _, v := range t.main.templates {
   772  		if v.typ == templateShortcode {
   773  			t.addShortcodeVariant(v)
   774  		}
   775  
   776  		if defineCheckedHTML && defineCheckedText {
   777  			continue
   778  		}
   779  
   780  		isText := isText(v.Template)
   781  		if isText {
   782  			if defineCheckedText {
   783  				continue
   784  			}
   785  			defineCheckedText = true
   786  		} else {
   787  			if defineCheckedHTML {
   788  				continue
   789  			}
   790  			defineCheckedHTML = true
   791  		}
   792  
   793  		if err := t.extractPartials(v.Template); err != nil {
   794  			return err
   795  		}
   796  	}
   797  
   798  	for name, source := range t.transformNotFound {
   799  		lookup := t.main.newTemplateLookup(source)
   800  		templ := lookup(name)
   801  		if templ != nil {
   802  			_, err := applyTemplateTransformers(templ, lookup)
   803  			if err != nil {
   804  				return err
   805  			}
   806  		}
   807  	}
   808  
   809  	for k, v := range t.identityNotFound {
   810  		ts := t.findTemplate(k)
   811  		if ts != nil {
   812  			for _, im := range v {
   813  				im.Add(ts)
   814  			}
   815  		}
   816  	}
   817  
   818  	for _, v := range t.shortcodes {
   819  		sort.Slice(v.variants, func(i, j int) bool {
   820  			v1, v2 := v.variants[i], v.variants[j]
   821  			name1, name2 := v1.ts.Name(), v2.ts.Name()
   822  			isHTMl1, isHTML2 := strings.HasSuffix(name1, "html"), strings.HasSuffix(name2, "html")
   823  
   824  			// There will be a weighted selection later, but make
   825  			// sure these are sorted to get a stable selection for
   826  			// output formats missing specific templates.
   827  			// Prefer HTML.
   828  			if isHTMl1 || isHTML2 && !(isHTMl1 && isHTML2) {
   829  				return isHTMl1
   830  			}
   831  
   832  			return name1 < name2
   833  		})
   834  	}
   835  
   836  	return nil
   837  }
   838  
   839  type templateNamespace struct {
   840  	prototypeText      *texttemplate.Template
   841  	prototypeHTML      *htmltemplate.Template
   842  	prototypeTextClone *texttemplate.Template
   843  	prototypeHTMLClone *htmltemplate.Template
   844  
   845  	*templateStateMap
   846  }
   847  
   848  func (t templateNamespace) Clone() *templateNamespace {
   849  	t.mu.Lock()
   850  	defer t.mu.Unlock()
   851  
   852  	t.templateStateMap = &templateStateMap{
   853  		templates: make(map[string]*templateState),
   854  	}
   855  
   856  	t.prototypeText = texttemplate.Must(t.prototypeText.Clone())
   857  	t.prototypeHTML = htmltemplate.Must(t.prototypeHTML.Clone())
   858  
   859  	return &t
   860  }
   861  
   862  func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
   863  	t.mu.RLock()
   864  	defer t.mu.RUnlock()
   865  
   866  	templ, found := t.templates[name]
   867  	if !found {
   868  		return nil, false
   869  	}
   870  
   871  	return templ, found
   872  }
   873  
   874  func (t *templateNamespace) createPrototypes() error {
   875  	t.prototypeTextClone = texttemplate.Must(t.prototypeText.Clone())
   876  	t.prototypeHTMLClone = htmltemplate.Must(t.prototypeHTML.Clone())
   877  
   878  	return nil
   879  }
   880  
   881  func (t *templateNamespace) newTemplateLookup(in *templateState) func(name string) *templateState {
   882  	return func(name string) *templateState {
   883  		if templ, found := t.templates[name]; found {
   884  			if templ.isText() != in.isText() {
   885  				return nil
   886  			}
   887  			return templ
   888  		}
   889  		if templ, found := findTemplateIn(name, in); found {
   890  			return newTemplateState(templ, templateInfo{name: templ.Name()})
   891  		}
   892  		return nil
   893  	}
   894  }
   895  
   896  func (t *templateNamespace) parse(info templateInfo) (*templateState, error) {
   897  	t.mu.Lock()
   898  	defer t.mu.Unlock()
   899  
   900  	if info.isText {
   901  		prototype := t.prototypeText
   902  
   903  		templ, err := prototype.New(info.name).Parse(info.template)
   904  		if err != nil {
   905  			return nil, err
   906  		}
   907  
   908  		ts := newTemplateState(templ, info)
   909  
   910  		t.templates[info.name] = ts
   911  
   912  		return ts, nil
   913  	}
   914  
   915  	prototype := t.prototypeHTML
   916  
   917  	templ, err := prototype.New(info.name).Parse(info.template)
   918  	if err != nil {
   919  		return nil, err
   920  	}
   921  
   922  	ts := newTemplateState(templ, info)
   923  
   924  	t.templates[info.name] = ts
   925  
   926  	return ts, nil
   927  }
   928  
   929  type templateState struct {
   930  	tpl.Template
   931  
   932  	typ       templateType
   933  	parseInfo tpl.ParseInfo
   934  	identity.Manager
   935  
   936  	info     templateInfo
   937  	baseInfo templateInfo // Set when a base template is used.
   938  }
   939  
   940  func (t *templateState) ParseInfo() tpl.ParseInfo {
   941  	return t.parseInfo
   942  }
   943  
   944  func (t *templateState) isText() bool {
   945  	return isText(t.Template)
   946  }
   947  
   948  func isText(templ tpl.Template) bool {
   949  	_, isText := templ.(*texttemplate.Template)
   950  	return isText
   951  }
   952  
   953  type templateStateMap struct {
   954  	mu        sync.RWMutex
   955  	templates map[string]*templateState
   956  }
   957  
   958  type templateWrapperWithLock struct {
   959  	*sync.RWMutex
   960  	tpl.Template
   961  }
   962  
   963  type textTemplateWrapperWithLock struct {
   964  	*sync.RWMutex
   965  	*texttemplate.Template
   966  }
   967  
   968  func (t *textTemplateWrapperWithLock) Lookup(name string) (tpl.Template, bool) {
   969  	t.RLock()
   970  	templ := t.Template.Lookup(name)
   971  	t.RUnlock()
   972  	if templ == nil {
   973  		return nil, false
   974  	}
   975  	return &textTemplateWrapperWithLock{
   976  		RWMutex:  t.RWMutex,
   977  		Template: templ,
   978  	}, true
   979  }
   980  
   981  func (t *textTemplateWrapperWithLock) LookupVariant(name string, variants tpl.TemplateVariants) (tpl.Template, bool, bool) {
   982  	panic("not supported")
   983  }
   984  
   985  func (t *textTemplateWrapperWithLock) LookupVariants(name string) []tpl.Template {
   986  	panic("not supported")
   987  }
   988  
   989  func (t *textTemplateWrapperWithLock) Parse(name, tpl string) (tpl.Template, error) {
   990  	t.Lock()
   991  	defer t.Unlock()
   992  	return t.Template.New(name).Parse(tpl)
   993  }
   994  
   995  func isBackupFile(path string) bool {
   996  	return path[len(path)-1] == '~'
   997  }
   998  
   999  func isBaseTemplatePath(path string) bool {
  1000  	return strings.Contains(filepath.Base(path), baseFileBase)
  1001  }
  1002  
  1003  func isDotFile(path string) bool {
  1004  	return filepath.Base(path)[0] == '.'
  1005  }
  1006  
  1007  func removeLeadingBOM(s string) string {
  1008  	const bom = '\ufeff'
  1009  
  1010  	for i, r := range s {
  1011  		if i == 0 && r != bom {
  1012  			return s
  1013  		}
  1014  		if i > 0 {
  1015  			return s[i:]
  1016  		}
  1017  	}
  1018  
  1019  	return s
  1020  }
  1021  
  1022  // resolves _internal/shortcodes/param.html => param.html etc.
  1023  func templateBaseName(typ templateType, name string) string {
  1024  	name = strings.TrimPrefix(name, internalPathPrefix)
  1025  	switch typ {
  1026  	case templateShortcode:
  1027  		return strings.TrimPrefix(name, shortcodesPathPrefix)
  1028  	default:
  1029  		panic("not implemented")
  1030  	}
  1031  }
  1032  
  1033  func unwrap(templ tpl.Template) tpl.Template {
  1034  	if ts, ok := templ.(*templateState); ok {
  1035  		return ts.Template
  1036  	}
  1037  	return templ
  1038  }
  1039  
  1040  func templates(in tpl.Template) []tpl.Template {
  1041  	var templs []tpl.Template
  1042  	in = unwrap(in)
  1043  	if textt, ok := in.(*texttemplate.Template); ok {
  1044  		for _, t := range textt.Templates() {
  1045  			templs = append(templs, t)
  1046  		}
  1047  	}
  1048  
  1049  	if htmlt, ok := in.(*htmltemplate.Template); ok {
  1050  		for _, t := range htmlt.Templates() {
  1051  			templs = append(templs, t)
  1052  		}
  1053  	}
  1054  
  1055  	return templs
  1056  }