mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-21 21:35:28 +02:00
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:
@@ -19,23 +19,21 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||
"github.com/gohugoio/hugo/markup/highlight/chromalexers"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
@@ -120,9 +118,9 @@ func (pco *pageContentOutput) Render(ctx context.Context, layout ...string) (tem
|
||||
}
|
||||
|
||||
// Make sure to send the *pageState and not the *pageContentOutput to the template.
|
||||
res, err := executeToString(ctx, pco.po.p.s.Tmpl(), templ, pco.po.p)
|
||||
res, err := executeToString(ctx, pco.po.p.s.GetTemplateStore(), templ, pco.po.p)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Name(), err))
|
||||
return "", pco.po.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Template.Name(), err))
|
||||
}
|
||||
return template.HTML(res), nil
|
||||
}
|
||||
@@ -274,103 +272,100 @@ func (pco *pageContentOutput) initRenderHooks() error {
|
||||
return r
|
||||
}
|
||||
|
||||
layoutDescriptor := pco.po.p.getLayoutDescriptor()
|
||||
layoutDescriptor.RenderingHook = true
|
||||
layoutDescriptor.LayoutOverride = false
|
||||
layoutDescriptor.Layout = ""
|
||||
// Inherit the descriptor from the page/current output format.
|
||||
// This allows for fine-grained control of the template used for
|
||||
// rendering of e.g. links.
|
||||
base, layoutDescriptor := pco.po.p.getTemplateBasePathAndDescriptor()
|
||||
|
||||
switch tp {
|
||||
case hooks.LinkRendererType:
|
||||
layoutDescriptor.Kind = "render-link"
|
||||
layoutDescriptor.Variant1 = "link"
|
||||
case hooks.ImageRendererType:
|
||||
layoutDescriptor.Kind = "render-image"
|
||||
layoutDescriptor.Variant1 = "image"
|
||||
case hooks.HeadingRendererType:
|
||||
layoutDescriptor.Kind = "render-heading"
|
||||
layoutDescriptor.Variant1 = "heading"
|
||||
case hooks.PassthroughRendererType:
|
||||
layoutDescriptor.Kind = "render-passthrough"
|
||||
layoutDescriptor.Variant1 = "passthrough"
|
||||
if id != nil {
|
||||
layoutDescriptor.KindVariants = id.(string)
|
||||
layoutDescriptor.Variant2 = id.(string)
|
||||
}
|
||||
case hooks.BlockquoteRendererType:
|
||||
layoutDescriptor.Kind = "render-blockquote"
|
||||
layoutDescriptor.Variant1 = "blockquote"
|
||||
if id != nil {
|
||||
layoutDescriptor.KindVariants = id.(string)
|
||||
layoutDescriptor.Variant2 = id.(string)
|
||||
}
|
||||
case hooks.TableRendererType:
|
||||
layoutDescriptor.Kind = "render-table"
|
||||
layoutDescriptor.Variant1 = "table"
|
||||
case hooks.CodeBlockRendererType:
|
||||
layoutDescriptor.Kind = "render-codeblock"
|
||||
layoutDescriptor.Variant1 = "codeblock"
|
||||
if id != nil {
|
||||
lang := id.(string)
|
||||
lexer := chromalexers.Get(lang)
|
||||
if lexer != nil {
|
||||
layoutDescriptor.KindVariants = strings.Join(lexer.Config().Aliases, ",")
|
||||
} else {
|
||||
layoutDescriptor.KindVariants = lang
|
||||
}
|
||||
layoutDescriptor.Variant2 = id.(string)
|
||||
}
|
||||
}
|
||||
|
||||
getHookTemplate := func(f output.Format) (tpl.Template, bool) {
|
||||
templ, found, err := pco.po.p.s.Tmpl().LookupLayout(layoutDescriptor, f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if found {
|
||||
if isitp, ok := templ.(tpl.IsInternalTemplateProvider); ok && isitp.IsInternalTemplate() {
|
||||
|
||||
renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks
|
||||
|
||||
switch templ.Name() {
|
||||
case "_default/_markup/render-link.html":
|
||||
if !renderHookConfig.Link.IsEnableDefault() {
|
||||
return nil, false
|
||||
}
|
||||
case "_default/_markup/render-image.html":
|
||||
if !renderHookConfig.Image.IsEnableDefault() {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return templ, found
|
||||
renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks
|
||||
var ignoreInternal bool
|
||||
switch layoutDescriptor.Variant1 {
|
||||
case "link":
|
||||
ignoreInternal = !renderHookConfig.Link.IsEnableDefault()
|
||||
case "image":
|
||||
ignoreInternal = !renderHookConfig.Image.IsEnableDefault()
|
||||
}
|
||||
|
||||
templ, found1 := getHookTemplate(pco.po.f)
|
||||
if !found1 || pco.po.p.reusePageOutputContent() {
|
||||
defaultOutputFormat := pco.po.p.s.conf.C.DefaultOutputFormat
|
||||
|
||||
candidates := pco.po.p.s.renderFormats
|
||||
|
||||
// Some hooks may only be available in HTML, and if
|
||||
// this site is configured to not have HTML output, we need to
|
||||
// make sure we have a fallback. This should be very rare.
|
||||
if pco.po.f.MediaType.FirstSuffix.Suffix != "html" {
|
||||
if _, found := candidates.GetBySuffix("html"); !found {
|
||||
candidates = append(candidates, output.HTMLFormat)
|
||||
}
|
||||
candidates := pco.po.p.s.renderFormats
|
||||
var numCandidatesFound int
|
||||
consider := func(candidate *tplimpl.TemplInfo) bool {
|
||||
if layoutDescriptor.Variant1 != candidate.D.Variant1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if some of the other output formats would give a different template.
|
||||
for _, f := range candidates {
|
||||
if f.Name == pco.po.f.Name {
|
||||
continue
|
||||
}
|
||||
templ2, found2 := getHookTemplate(f)
|
||||
|
||||
if found2 {
|
||||
if !found1 && f.Name == defaultOutputFormat.Name {
|
||||
templ = templ2
|
||||
found1 = true
|
||||
break
|
||||
}
|
||||
|
||||
if templ != templ2 {
|
||||
pco.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if layoutDescriptor.Variant2 != "" && candidate.D.Variant2 != "" && layoutDescriptor.Variant2 != candidate.D.Variant2 {
|
||||
return false
|
||||
}
|
||||
|
||||
if ignoreInternal && candidate.SubCategory == tplimpl.SubCategoryEmbedded {
|
||||
// Don't consider the internal hook templates.
|
||||
return false
|
||||
}
|
||||
|
||||
if pco.po.p.pageOutputTemplateVariationsState.Load() > 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
if candidate.D.OutputFormat == "" {
|
||||
numCandidatesFound++
|
||||
} else if _, found := candidates.GetByName(candidate.D.OutputFormat); found {
|
||||
numCandidatesFound++
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
getHookTemplate := func() (*tplimpl.TemplInfo, bool) {
|
||||
q := tplimpl.TemplateQuery{
|
||||
Path: base,
|
||||
Category: tplimpl.CategoryMarkup,
|
||||
Desc: layoutDescriptor,
|
||||
Consider: consider,
|
||||
}
|
||||
|
||||
v := pco.po.p.s.TemplateStore.LookupPagesLayout(q)
|
||||
return v, v != nil
|
||||
}
|
||||
|
||||
templ, found1 := getHookTemplate()
|
||||
if found1 && templ == nil {
|
||||
panic("found1 is true, but templ is nil")
|
||||
}
|
||||
|
||||
if !found1 && layoutDescriptor.OutputFormat == pco.po.p.s.conf.DefaultOutputFormat {
|
||||
numCandidatesFound++
|
||||
}
|
||||
|
||||
if numCandidatesFound > 1 {
|
||||
// More than one output format candidate found for this hook temoplate,
|
||||
// so we cannot reuse the same rendered content.
|
||||
pco.po.p.incrPageOutputTemplateVariation()
|
||||
}
|
||||
|
||||
if !found1 {
|
||||
@@ -384,7 +379,7 @@ func (pco *pageContentOutput) initRenderHooks() error {
|
||||
}
|
||||
|
||||
r := hookRendererTemplate{
|
||||
templateHandler: pco.po.p.s.Tmpl(),
|
||||
templateHandler: pco.po.p.s.GetTemplateStore(),
|
||||
templ: templ,
|
||||
resolvePosition: resolvePosition,
|
||||
}
|
||||
@@ -488,7 +483,7 @@ func (t targetPathsHolder) targetPaths() page.TargetPaths {
|
||||
return t.paths
|
||||
}
|
||||
|
||||
func executeToString(ctx context.Context, h tpl.TemplateHandler, templ tpl.Template, data any) (string, error) {
|
||||
func executeToString(ctx context.Context, h *tplimpl.TemplateStore, templ *tplimpl.TemplInfo, data any) (string, error) {
|
||||
b := bp.GetBuffer()
|
||||
defer bp.PutBuffer(b)
|
||||
if err := h.ExecuteWithContext(ctx, templ, b, data); err != nil {
|
||||
|
Reference in New Issue
Block a user