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