mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-17 21:01:26 +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:
@@ -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.
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
@@ -36,7 +37,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
@@ -205,8 +205,7 @@ type shortcode struct {
|
||||
|
||||
indentation string // indentation from source.
|
||||
|
||||
info tpl.Info // One of the output formats (arbitrary)
|
||||
templs []tpl.Template // All output formats
|
||||
templ *tplimpl.TemplInfo
|
||||
|
||||
// If set, the rendered shortcode is sent as part of the surrounding content
|
||||
// to Goldmark and similar.
|
||||
@@ -230,16 +229,15 @@ func (s shortcode) insertPlaceholder() bool {
|
||||
}
|
||||
|
||||
func (s shortcode) needsInner() bool {
|
||||
return s.info != nil && s.info.ParseInfo().IsInner
|
||||
return s.templ != nil && s.templ.ParseInfo.IsInner
|
||||
}
|
||||
|
||||
func (s shortcode) configVersion() int {
|
||||
if s.info == nil {
|
||||
if s.templ == nil {
|
||||
// Not set for inline shortcodes.
|
||||
return 2
|
||||
}
|
||||
|
||||
return s.info.ParseInfo().Config.Version
|
||||
return s.templ.ParseInfo.Config.Version
|
||||
}
|
||||
|
||||
func (s shortcode) innerString() string {
|
||||
@@ -315,12 +313,12 @@ func prepareShortcode(
|
||||
ctx context.Context,
|
||||
level int,
|
||||
s *Site,
|
||||
tplVariants tpl.TemplateVariants,
|
||||
sc *shortcode,
|
||||
parent *ShortcodeWithPage,
|
||||
p *pageState,
|
||||
po *pageOutput,
|
||||
isRenderString bool,
|
||||
) (shortcodeRenderer, error) {
|
||||
p := po.p
|
||||
toParseErr := func(err error) error {
|
||||
source := p.m.content.mustSource()
|
||||
return p.parseError(fmt.Errorf("failed to render shortcode %q: %w", sc.name, err), source, sc.pos)
|
||||
@@ -333,7 +331,7 @@ func prepareShortcode(
|
||||
// parsed and rendered by Goldmark.
|
||||
ctx = tpl.Context.IsInGoldmark.Set(ctx, true)
|
||||
}
|
||||
r, err := doRenderShortcode(ctx, level, s, tplVariants, sc, parent, p, isRenderString)
|
||||
r, err := doRenderShortcode(ctx, level, s, sc, parent, po, isRenderString)
|
||||
if err != nil {
|
||||
return nil, false, toParseErr(err)
|
||||
}
|
||||
@@ -352,30 +350,29 @@ func doRenderShortcode(
|
||||
ctx context.Context,
|
||||
level int,
|
||||
s *Site,
|
||||
tplVariants tpl.TemplateVariants,
|
||||
sc *shortcode,
|
||||
parent *ShortcodeWithPage,
|
||||
p *pageState,
|
||||
po *pageOutput,
|
||||
isRenderString bool,
|
||||
) (shortcodeRenderer, error) {
|
||||
var tmpl tpl.Template
|
||||
var tmpl *tplimpl.TemplInfo
|
||||
p := po.p
|
||||
|
||||
// Tracks whether this shortcode or any of its children has template variations
|
||||
// in other languages or output formats. We are currently only interested in
|
||||
// the output formats, so we may get some false positives -- we
|
||||
// should improve on that.
|
||||
// the output formats.
|
||||
var hasVariants bool
|
||||
|
||||
if sc.isInline {
|
||||
if !p.s.ExecHelper.Sec().EnableInlineShortcodes {
|
||||
return zeroShortcode, nil
|
||||
}
|
||||
templName := path.Join("_inline_shortcode", p.Path(), sc.name)
|
||||
templatePath := path.Join("_inline_shortcode", p.Path(), sc.name)
|
||||
if sc.isClosing {
|
||||
templStr := sc.innerString()
|
||||
|
||||
var err error
|
||||
tmpl, err = s.TextTmpl().Parse(templName, templStr)
|
||||
tmpl, err = s.TemplateStore.TextParse(templatePath, templStr)
|
||||
if err != nil {
|
||||
if isRenderString {
|
||||
return zeroShortcode, p.wrapError(err)
|
||||
@@ -389,21 +386,32 @@ func doRenderShortcode(
|
||||
|
||||
} else {
|
||||
// Re-use of shortcode defined earlier in the same page.
|
||||
var found bool
|
||||
tmpl, found = s.TextTmpl().Lookup(templName)
|
||||
if !found {
|
||||
tmpl = s.TemplateStore.TextLookup(templatePath)
|
||||
if tmpl == nil {
|
||||
return zeroShortcode, fmt.Errorf("no earlier definition of shortcode %q found", sc.name)
|
||||
}
|
||||
}
|
||||
tmpl = tpl.AddIdentity(tmpl)
|
||||
} else {
|
||||
var found, more bool
|
||||
tmpl, found, more = s.Tmpl().LookupVariant(sc.name, tplVariants)
|
||||
if !found {
|
||||
ofCount := map[string]int{}
|
||||
include := func(match *tplimpl.TemplInfo) bool {
|
||||
ofCount[match.D.OutputFormat]++
|
||||
return true
|
||||
}
|
||||
base, layoutDescriptor := po.getTemplateBasePathAndDescriptor()
|
||||
q := tplimpl.TemplateQuery{
|
||||
Path: base,
|
||||
Name: sc.name,
|
||||
Category: tplimpl.CategoryShortcode,
|
||||
Desc: layoutDescriptor,
|
||||
Consider: include,
|
||||
}
|
||||
v := s.TemplateStore.LookupShortcode(q)
|
||||
if v == nil {
|
||||
s.Log.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path())
|
||||
return zeroShortcode, nil
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
tmpl = v
|
||||
hasVariants = hasVariants || len(ofCount) > 1
|
||||
}
|
||||
|
||||
data := &ShortcodeWithPage{
|
||||
@@ -427,7 +435,7 @@ func doRenderShortcode(
|
||||
case string:
|
||||
inner += innerData
|
||||
case *shortcode:
|
||||
s, err := prepareShortcode(ctx, level+1, s, tplVariants, innerData, data, p, isRenderString)
|
||||
s, err := prepareShortcode(ctx, level+1, s, innerData, data, po, isRenderString)
|
||||
if err != nil {
|
||||
return zeroShortcode, err
|
||||
}
|
||||
@@ -484,7 +492,7 @@ func doRenderShortcode(
|
||||
|
||||
}
|
||||
|
||||
result, err := renderShortcodeWithPage(ctx, s.Tmpl(), tmpl, data)
|
||||
result, err := renderShortcodeWithPage(ctx, s.GetTemplateStore(), tmpl, data)
|
||||
|
||||
if err != nil && sc.isInline {
|
||||
fe := herrors.NewFileErrorFromName(err, p.File().Filename())
|
||||
@@ -534,16 +542,11 @@ func (s *shortcodeHandler) hasName(name string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) prepareShortcodesForPage(ctx context.Context, p *pageState, f output.Format, isRenderString bool) (map[string]shortcodeRenderer, error) {
|
||||
func (s *shortcodeHandler) prepareShortcodesForPage(ctx context.Context, po *pageOutput, isRenderString bool) (map[string]shortcodeRenderer, error) {
|
||||
rendered := make(map[string]shortcodeRenderer)
|
||||
|
||||
tplVariants := tpl.TemplateVariants{
|
||||
Language: p.Language().Lang,
|
||||
OutputFormat: f,
|
||||
}
|
||||
|
||||
for _, v := range s.shortcodes {
|
||||
s, err := prepareShortcode(ctx, 0, s.s, tplVariants, v, nil, p, isRenderString)
|
||||
s, err := prepareShortcode(ctx, 0, s.s, v, nil, po, isRenderString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -636,7 +639,7 @@ Loop:
|
||||
// we trust the template on this:
|
||||
// if there's no inner, we're done
|
||||
if !sc.isInline {
|
||||
if !sc.info.ParseInfo().IsInner {
|
||||
if !sc.templ.ParseInfo.IsInner {
|
||||
return sc, nil
|
||||
}
|
||||
}
|
||||
@@ -672,14 +675,19 @@ Loop:
|
||||
|
||||
sc.name = currItem.ValStr(source)
|
||||
|
||||
// Used to check if the template expects inner content.
|
||||
templs := s.s.Tmpl().LookupVariants(sc.name)
|
||||
if templs == nil {
|
||||
// Used to check if the template expects inner content,
|
||||
// so just pick one arbitrarily with the same name.
|
||||
q := tplimpl.TemplateQuery{
|
||||
Path: "",
|
||||
Name: sc.name,
|
||||
Category: tplimpl.CategoryShortcode,
|
||||
Consider: nil,
|
||||
}
|
||||
templ := s.s.TemplateStore.LookupShortcode(q)
|
||||
if templ == nil {
|
||||
return nil, fmt.Errorf("%s: template for shortcode %q not found", errorPrefix, sc.name)
|
||||
}
|
||||
|
||||
sc.info = templs[0].(tpl.Info)
|
||||
sc.templs = templs
|
||||
sc.templ = templ
|
||||
case currItem.IsInlineShortcodeName():
|
||||
sc.name = currItem.ValStr(source)
|
||||
sc.isInline = true
|
||||
@@ -778,7 +786,7 @@ func expandShortcodeTokens(
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func renderShortcodeWithPage(ctx context.Context, h tpl.TemplateHandler, tmpl tpl.Template, data *ShortcodeWithPage) (string, error) {
|
||||
func renderShortcodeWithPage(ctx context.Context, h *tplimpl.TemplateStore, tmpl *tplimpl.TemplInfo, data *ShortcodeWithPage) (string, error) {
|
||||
buffer := bp.GetBuffer()
|
||||
defer bp.PutBuffer(buffer)
|
||||
|
||||
|
Reference in New Issue
Block a user