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