"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/config/security"
return s
}
+func (s *IntegrationTestBuilder) BuildPartial(urls ...string) *IntegrationTestBuilder {
+ if _, err := s.BuildPartialE(urls...); err != nil {
+ s.Fatal(err)
+ }
+ return s
+}
+
+func (s *IntegrationTestBuilder) BuildPartialE(urls ...string) (*IntegrationTestBuilder, error) {
+ if s.buildCount == 0 {
+ panic("BuildPartial can only be used after a full build")
+ }
+ if !s.Cfg.Running {
+ panic("BuildPartial can only be used in server mode")
+ }
+ visited := types.NewEvictingStringQueue(len(urls))
+ for _, url := range urls {
+ visited.Add(url)
+ }
+ buildCfg := BuildCfg{RecentlyVisited: visited, PartialReRender: true}
+ return s, s.build(buildCfg)
+}
+
func (s *IntegrationTestBuilder) Close() {
s.Helper()
s.Assert(s.H.Close(), qt.IsNil)
s.counters = &buildCounters{}
cfg.testCounters = s.counters
- if s.buildCount > 0 && (len(changeEvents) == 0) {
- return nil
- }
-
s.buildCount++
err := s.H.Build(cfg, changeEvents...)
b.EditFileReplaceAll("assets/other/bar.css", ".bar-edit {", ".bar-edit2 {").Build()
b.AssertFileContent("public/mybundle/reactbatch.css", ".bar-edit2 {")
}
-
-func TestEditBaseofManyTimes(t *testing.T) {
- files := `
--- hugo.toml --
-baseURL = "https://example.com"
-disableLiveReload = true
-disableKinds = ["taxonomy", "term"]
--- layouts/_default/baseof.html --
-Baseof.
-{{ block "main" . }}{{ end }}
-{{ with (templates.Defer (dict "key" "global")) }}
-Now. {{ now }}
-{{ end }}
--- layouts/_default/single.html --
-{{ define "main" }}
-Single.
-{{ end }}
---
--- layouts/_default/list.html --
-{{ define "main" }}
-List.
-{{ end }}
--- content/mybundle/index.md --
----
-title: "My Bundle"
----
--- content/_index.md --
----
-title: "Home"
----
-`
-
- b := hugolib.TestRunning(t, files)
- b.AssertFileContent("public/index.html", "Baseof.")
-
- for i := 0; i < 100; i++ {
- b.EditFileReplaceAll("layouts/_default/baseof.html", "Now", "Now.").Build()
- b.AssertFileContent("public/index.html", "Now..")
- }
-}
"unicode"
"unicode/utf8"
+ "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/output/layouts"
func newTemplateNamespace(funcs map[string]any) *templateNamespace {
return &templateNamespace{
- prototypeHTML: htmltemplate.New("").Funcs(funcs),
- prototypeText: texttemplate.New("").Funcs(funcs),
+ prototypeHTML: htmltemplate.New("").Funcs(funcs),
+ prototypeText: texttemplate.New("").Funcs(funcs),
+ prototypeHTMLCloneCache: maps.NewCache[prototypeCloneID, *htmltemplate.Template](),
+ prototypeTextCloneCache: maps.NewCache[prototypeCloneID, *texttemplate.Template](),
templateStateMap: &templateStateMap{
templates: make(map[string]*templateState),
},
func (t *templateHandler) applyBaseTemplate(overlay, base templateInfo) (tpl.Template, error) {
if overlay.isText {
var (
- templ = t.main.prototypeTextClone.New(overlay.name)
+ templ = t.main.getPrototypeText(prototypeCloneIDBaseof).New(overlay.name)
err error
)
}
var (
- templ = t.main.prototypeHTMLClone.New(overlay.name)
+ templ = t.main.getPrototypeHTML(prototypeCloneIDBaseof).New(overlay.name)
err error
)
return nil
}
+type prototypeCloneID uint16
+
+const (
+ prototypeCloneIDBaseof prototypeCloneID = iota + 1
+ prototypeCloneIDDefer
+)
+
type templateNamespace struct {
- prototypeText *texttemplate.Template
- prototypeHTML *htmltemplate.Template
- prototypeTextClone *texttemplate.Template
- prototypeHTMLClone *htmltemplate.Template
+ prototypeText *texttemplate.Template
+ prototypeHTML *htmltemplate.Template
+
+ prototypeHTMLCloneCache *maps.Cache[prototypeCloneID, *htmltemplate.Template]
+ prototypeTextCloneCache *maps.Cache[prototypeCloneID, *texttemplate.Template]
*templateStateMap
}
-func (t *templateNamespace) getPrototypeText() *texttemplate.Template {
- if t.prototypeTextClone != nil {
- return t.prototypeTextClone
+func (t *templateNamespace) getPrototypeText(id prototypeCloneID) *texttemplate.Template {
+ v, ok := t.prototypeTextCloneCache.Get(id)
+ if !ok {
+ return t.prototypeText
}
- return t.prototypeText
+ return v
}
-func (t *templateNamespace) getPrototypeHTML() *htmltemplate.Template {
- if t.prototypeHTMLClone != nil {
- return t.prototypeHTMLClone
+func (t *templateNamespace) getPrototypeHTML(id prototypeCloneID) *htmltemplate.Template {
+ v, ok := t.prototypeHTMLCloneCache.Get(id)
+ if !ok {
+ return t.prototypeHTML
}
- return t.prototypeHTML
+ return v
}
func (t *templateNamespace) Lookup(name string) (tpl.Template, bool) {
}
func (t *templateNamespace) createPrototypes() error {
- t.prototypeTextClone = texttemplate.Must(t.prototypeText.Clone())
- t.prototypeHTMLClone = htmltemplate.Must(t.prototypeHTML.Clone())
-
+ for _, id := range []prototypeCloneID{prototypeCloneIDBaseof, prototypeCloneIDDefer} {
+ t.prototypeHTMLCloneCache.Set(id, htmltemplate.Must(t.prototypeHTML.Clone()))
+ t.prototypeTextCloneCache.Set(id, texttemplate.Must(t.prototypeText.Clone()))
+ }
return nil
}
var templ tpl.Template
if owner.isText() {
- prototype := t.getPrototypeText()
+ prototype := t.getPrototypeText(prototypeCloneIDDefer)
tt, err := prototype.New(name).Parse("")
if err != nil {
return fmt.Errorf("failed to parse empty text template %q: %w", name, err)
tt.Tree.Root = n
templ = tt
} else {
- prototype := t.getPrototypeHTML()
+ prototype := t.getPrototypeHTML(prototypeCloneIDDefer)
tt, err := prototype.New(name).Parse("")
if err != nil {
return fmt.Errorf("failed to parse empty HTML template %q: %w", name, err)
"<details>\n <summary>Details</summary>\n <p>D: An <em>emphasized</em> word.</p>\n</details>",
)
}
+
+// Issue 12963
+func TestEditBaseofParseAfterExecute(t *testing.T) {
+ files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+disableLiveReload = true
+disableKinds = ["taxonomy", "term", "rss", "404", "sitemap"]
+[internal]
+fastRenderMode = true
+-- layouts/_default/baseof.html --
+Baseof!
+{{ block "main" . }}default{{ end }}
+{{ with (templates.Defer (dict "key" "global")) }}
+Now. {{ now }}
+{{ end }}
+-- layouts/_default/single.html --
+{{ define "main" }}
+Single.
+{{ end }}
+-- layouts/_default/list.html --
+{{ define "main" }}
+List.
+{{ .Content }}
+{{ range .Pages }}{{ .Title }}{{ end }}|
+{{ end }}
+-- content/mybundle1/index.md --
+---
+title: "My Bundle 1"
+---
+-- content/mybundle2/index.md --
+---
+title: "My Bundle 2"
+---
+-- content/_index.md --
+---
+title: "Home"
+---
+Home!
+`
+
+ b := hugolib.TestRunning(t, files)
+ b.AssertFileContent("public/index.html", "Home!")
+ b.EditFileReplaceAll("layouts/_default/baseof.html", "Baseof", "Baseof!").Build()
+ b.BuildPartial("/")
+ b.AssertFileContent("public/index.html", "Baseof!!")
+ b.BuildPartial("/mybundle1/")
+ b.AssertFileContent("public/mybundle1/index.html", "Baseof!!")
+}