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