mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
4d775cbe95
A partial's rendered output (placeholder included) is cached by partialCached across rebuilds, but BuildState.DeferredExecutions is reset every stage. On a fast-render rebuild the cached string replays the placeholder while doDefer is not called this build, leaving executeDeferredTemplates to panic with "deferred execution with id ... not found". Mark the ctx inside IncludeCached's body execution and have Defer return a clear error if it sees the flag. Catches transitive cases (partialCached -> partial -> Defer) via ctx propagation. Defer in baseof.html and in a plain partial is unaffected. Fixes #13492 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
205 lines
6.0 KiB
Go
205 lines
6.0 KiB
Go
// 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.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package tpl contains template functions and related types.
|
|
package tpl
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"unicode"
|
|
|
|
"github.com/bep/helpers/contexthelpers"
|
|
bp "github.com/gohugoio/hugo/bufferpool"
|
|
"github.com/gohugoio/hugo/common/collections"
|
|
|
|
"github.com/gohugoio/hugo/identity"
|
|
"github.com/gohugoio/hugo/langs"
|
|
|
|
htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
|
|
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
|
)
|
|
|
|
// Template is the common interface between text/template and html/template.
|
|
type Template interface {
|
|
Name() string
|
|
Prepare() (*texttemplate.Template, error)
|
|
}
|
|
|
|
// RenderingContext represents the currently rendered site/language.
|
|
type RenderingContext struct {
|
|
Site site
|
|
SiteOutIdx int
|
|
}
|
|
|
|
type (
|
|
contextKey uint8
|
|
)
|
|
|
|
const (
|
|
contextKeyDependencyManagerScopedProvider contextKey = iota
|
|
contextKeyDependencyScope
|
|
contextKeyPage
|
|
contextKeyIsInGoldmark
|
|
cntextKeyCurrentTemplateInfo
|
|
contextKeyPartialDecoratorIDStack
|
|
contextKeyIsInPartialCached
|
|
)
|
|
|
|
// Context manages values passed in the context to templates.
|
|
var Context = struct {
|
|
DependencyManagerScopedProvider contexthelpers.ContextDispatcher[identity.DependencyManagerScopedProvider]
|
|
GetDependencyManagerInCurrentScope func(context.Context) identity.Manager
|
|
DependencyScope contexthelpers.ContextDispatcher[int]
|
|
Page contexthelpers.ContextDispatcher[page]
|
|
IsInGoldmark contexthelpers.ContextDispatcher[bool]
|
|
CurrentTemplate contexthelpers.ContextDispatcher[*CurrentTemplateInfo]
|
|
PartialDecoratorIDStack contexthelpers.ContextDispatcher[*collections.Stack[*StringBool]]
|
|
IsInPartialCached contexthelpers.ContextDispatcher[bool]
|
|
}{
|
|
DependencyManagerScopedProvider: contexthelpers.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKeyDependencyManagerScopedProvider),
|
|
DependencyScope: contexthelpers.NewContextDispatcher[int](contextKeyDependencyScope),
|
|
Page: contexthelpers.NewContextDispatcher[page](contextKeyPage),
|
|
IsInGoldmark: contexthelpers.NewContextDispatcher[bool](contextKeyIsInGoldmark),
|
|
CurrentTemplate: contexthelpers.NewContextDispatcher[*CurrentTemplateInfo](cntextKeyCurrentTemplateInfo),
|
|
PartialDecoratorIDStack: contexthelpers.NewContextDispatcher[*collections.Stack[*StringBool]](contextKeyPartialDecoratorIDStack),
|
|
IsInPartialCached: contexthelpers.NewContextDispatcher[bool](contextKeyIsInPartialCached),
|
|
}
|
|
|
|
func init() {
|
|
Context.GetDependencyManagerInCurrentScope = func(ctx context.Context) identity.Manager {
|
|
idmsp := Context.DependencyManagerScopedProvider.Get(ctx)
|
|
if idmsp != nil {
|
|
return idmsp.GetDependencyManagerForScope(Context.DependencyScope.Get(ctx))
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// StringBool is a helper struct to hold a string and a bool value.
|
|
type StringBool struct {
|
|
Str string
|
|
Bool bool
|
|
}
|
|
|
|
type page interface {
|
|
IsNode() bool
|
|
}
|
|
|
|
type site interface {
|
|
Language() *langs.Language
|
|
}
|
|
|
|
const (
|
|
// HugoDeferredTemplatePrefix is the prefix for placeholders for deferred templates.
|
|
HugoDeferredTemplatePrefix = "__hdeferred/"
|
|
// HugoDeferredTemplateSuffix is the suffix for placeholders for deferred templates.
|
|
HugoDeferredTemplateSuffix = "__d="
|
|
)
|
|
|
|
const hugoNewLinePlaceholder = "___hugonl_"
|
|
|
|
var stripHTMLReplacerPre = strings.NewReplacer("\n", " ", "</p>", hugoNewLinePlaceholder, "<br>", hugoNewLinePlaceholder, "<br />", hugoNewLinePlaceholder)
|
|
|
|
// StripHTML strips out all HTML tags in s.
|
|
func StripHTML(s string) string {
|
|
// Shortcut strings with no tags in them
|
|
if !strings.ContainsAny(s, "<>") {
|
|
return s
|
|
}
|
|
|
|
pre := stripHTMLReplacerPre.Replace(s)
|
|
preReplaced := pre != s
|
|
|
|
s = htmltemplate.StripTags(pre)
|
|
|
|
if preReplaced {
|
|
s = strings.ReplaceAll(s, hugoNewLinePlaceholder, "\n")
|
|
}
|
|
|
|
var wasSpace bool
|
|
b := bp.GetBuffer()
|
|
defer bp.PutBuffer(b)
|
|
for _, r := range s {
|
|
isSpace := unicode.IsSpace(r)
|
|
if !(isSpace && wasSpace) {
|
|
b.WriteRune(r)
|
|
}
|
|
wasSpace = isSpace
|
|
}
|
|
|
|
if b.Len() > 0 {
|
|
s = b.String()
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// DeferredExecution holds the template and data for a deferred execution.
|
|
type DeferredExecution struct {
|
|
Mu sync.Mutex
|
|
Ctx context.Context
|
|
TemplatePath string
|
|
Data any
|
|
|
|
Executed bool
|
|
Result string
|
|
}
|
|
|
|
type CurrentTemplateInfoOps interface {
|
|
CurrentTemplateInfoCommonOps
|
|
Base() CurrentTemplateInfoCommonOps
|
|
}
|
|
|
|
type CurrentTemplateInfoCommonOps interface {
|
|
// Template name.
|
|
Name() string
|
|
// Template source filename.
|
|
// Will be empty for internal templates.
|
|
Filename() string
|
|
}
|
|
|
|
// CurrentTemplateInfo as returned in templates.Current.
|
|
type CurrentTemplateInfo struct {
|
|
Parent *CurrentTemplateInfo
|
|
Level int
|
|
Key string
|
|
CurrentTemplateInfoOps
|
|
}
|
|
|
|
// CurrentTemplateInfos is a slice of CurrentTemplateInfo.
|
|
type CurrentTemplateInfos []*CurrentTemplateInfo
|
|
|
|
// Reverse creates a copy of the slice and reverses it.
|
|
func (c CurrentTemplateInfos) Reverse() CurrentTemplateInfos {
|
|
if len(c) == 0 {
|
|
return c
|
|
}
|
|
r := make(CurrentTemplateInfos, len(c))
|
|
copy(r, c)
|
|
slices.Reverse(r)
|
|
return r
|
|
}
|
|
|
|
// Ancestors returns the ancestors of the current template.
|
|
func (ti *CurrentTemplateInfo) Ancestors() CurrentTemplateInfos {
|
|
var ancestors []*CurrentTemplateInfo
|
|
for ti.Parent != nil {
|
|
ti = ti.Parent
|
|
ancestors = append(ancestors, ti)
|
|
}
|
|
return ancestors
|
|
}
|