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