Reimplement and simplify Hugo's template system

See #13541 for details.

Fixes #13545
Fixes #13515
Closes #7964
Closes #13365
Closes #12988
Closes #4891
This commit is contained in:
Bjørn Erik Pedersen
2025-04-06 19:55:35 +02:00
parent 812ea0b325
commit 83cfdd78ca
138 changed files with 5342 additions and 4396 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@ package tpl
import (
"context"
"io"
"reflect"
"regexp"
"strings"
"sync"
"unicode"
@@ -27,140 +24,18 @@ import (
"github.com/gohugoio/hugo/common/hcontext"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/output/layouts"
"github.com/gohugoio/hugo/output"
htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
)
// TemplateManager manages the collection of templates.
type TemplateManager interface {
TemplateHandler
TemplateFuncGetter
AddTemplate(name, tpl string) error
}
// TemplateVariants describes the possible variants of a template.
// All of these may be empty.
type TemplateVariants struct {
Language string
OutputFormat output.Format
}
// TemplateFinder finds templates.
type TemplateFinder interface {
TemplateLookup
TemplateLookupVariant
}
// UnusedTemplatesProvider lists unused templates if the build is configured to track those.
type UnusedTemplatesProvider interface {
UnusedTemplates() []FileInfo
}
// TemplateHandlers holds the templates needed by Hugo.
type TemplateHandlers struct {
Tmpl TemplateHandler
TxtTmpl TemplateParseFinder
}
type TemplateExecutor interface {
ExecuteWithContext(ctx context.Context, t Template, wr io.Writer, data any) error
}
// TemplateHandler finds and executes templates.
type TemplateHandler interface {
TemplateFinder
TemplateExecutor
LookupLayout(d layouts.LayoutDescriptor, f output.Format) (Template, bool, error)
HasTemplate(name string) bool
GetIdentity(name string) (identity.Identity, bool)
}
type TemplateLookup interface {
Lookup(name string) (Template, bool)
}
type TemplateLookupVariant interface {
// TODO(bep) this currently only works for shortcodes.
// We may unify and expand this variant pattern to the
// other templates, but we need this now for the shortcodes to
// quickly determine if a shortcode has a template for a given
// output format.
// It returns the template, if it was found or not and if there are
// alternative representations (output format, language).
// We are currently only interested in output formats, so we should improve
// this for speed.
LookupVariant(name string, variants TemplateVariants) (Template, bool, bool)
LookupVariants(name string) []Template
}
// Template is the common interface between text/template and html/template.
type Template interface {
Name() string
Prepare() (*texttemplate.Template, error)
}
// AddIdentity checks if t is an identity.Identity and returns it if so.
// Else it wraps it in a templateIdentity using its name as the base.
func AddIdentity(t Template) Template {
if _, ok := t.(identity.IdentityProvider); ok {
return t
}
return templateIdentityProvider{
Template: t,
id: identity.StringIdentity(t.Name()),
}
}
type templateIdentityProvider struct {
Template
id identity.Identity
}
func (t templateIdentityProvider) GetIdentity() identity.Identity {
return t.id
}
// TemplateParser is used to parse ad-hoc templates, e.g. in the Resource chain.
type TemplateParser interface {
Parse(name, tpl string) (Template, error)
}
// TemplateParseFinder provides both parsing and finding.
type TemplateParseFinder interface {
TemplateParser
TemplateFinder
}
// TemplateDebugger prints some debug info to stdout.
type TemplateDebugger interface {
Debug()
}
// TemplatesProvider as implemented by deps.Deps.
type TemplatesProvider interface {
Tmpl() TemplateHandler
TextTmpl() TemplateParseFinder
}
var baseOfRe = regexp.MustCompile("template: (.*?):")
func extractBaseOf(err string) string {
m := baseOfRe.FindStringSubmatch(err)
if len(m) == 2 {
return m[1]
}
return ""
}
// TemplateFuncGetter allows to find a template func by name.
type TemplateFuncGetter interface {
GetFunc(name string) (reflect.Value, bool)
}
// RenderingContext represents the currently rendered site/language.
type RenderingContext struct {
Site site
SiteOutIdx int
@@ -201,7 +76,9 @@ type site interface {
}
const (
// HugoDeferredTemplatePrefix is the prefix for placeholders for deferred templates.
HugoDeferredTemplatePrefix = "__hdeferred/"
// HugoDeferredTemplateSuffix is the suffix for placeholders for deferred templates.
HugoDeferredTemplateSuffix = "__d="
)
@@ -243,10 +120,11 @@ func StripHTML(s string) string {
return s
}
// DeferredExecution holds the template and data for a deferred execution.
type DeferredExecution struct {
Mu sync.Mutex
Ctx context.Context
TemplateName string
TemplatePath string
Data any
Executed bool