diff --git a/common/hugo/hugo.go b/common/hugo/hugo.go index f21103940..dd43cf7b6 100644 --- a/common/hugo/hugo.go +++ b/common/hugo/hugo.go @@ -33,6 +33,7 @@ import ( "github.com/gohugoio/hugo/common/hcontext" "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/hugofs/files" "github.com/spf13/afero" @@ -55,6 +56,8 @@ var ( vendorInfo string ) +var _ maps.StoreProvider = (*HugoInfo)(nil) + // HugoInfo contains information about the current Hugo environment type HugoInfo struct { CommitHash string @@ -72,6 +75,8 @@ type HugoInfo struct { conf ConfigProvider deps []*Dependency + store *maps.Scratch + // Context gives access to some of the context scoped variables. Context Context } @@ -116,6 +121,10 @@ func (i HugoInfo) Deps() []*Dependency { return i.deps } +func (i HugoInfo) Store() *maps.Scratch { + return i.store +} + // Deprecated: Use hugo.IsMultihost instead. func (i HugoInfo) IsMultiHost() bool { Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0") @@ -185,6 +194,7 @@ func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo { Environment: conf.Environment(), conf: conf, deps: deps, + store: maps.NewScratch(), GoVersion: goVersion, } } diff --git a/common/maps/scratch.go b/common/maps/scratch.go index 638377216..cf5231783 100644 --- a/common/maps/scratch.go +++ b/common/maps/scratch.go @@ -22,7 +22,13 @@ import ( "github.com/gohugoio/hugo/common/math" ) -// Scratch is a writable context used for stateful operations in Page/Node rendering. +type StoreProvider interface { + // Store returns a Scratch that can be used to store temporary state. + // Store is not reset on server rebuilds. + Store() *Scratch +} + +// Scratch is a writable context used for stateful build operations type Scratch struct { values map[string]any mu sync.RWMutex diff --git a/hugolib/page_test.go b/hugolib/page_test.go index bdd1be6f7..39a16d948 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -1893,3 +1893,52 @@ func TestRenderWithoutArgument(t *testing.T) { b.Assert(err, qt.IsNotNil) } + +// Issue #13021 +func TestAllStores(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +disableKinds = ["taxonomy", "term", "page", "section"] +disableLiveReload = true +-- content/_index.md -- +--- +title: "Home" +--- +{{< s >}} +-- layouts/shortcodes/s.html -- +{{ if not (.Store.Get "Shortcode") }}{{ .Store.Set "Shortcode" (printf "sh-%s" $.Page.Title) }}{{ end }} +Shortcode: {{ .Store.Get "Shortcode" }}| +-- layouts/index.html -- +{{ .Content }} +{{ if not (.Store.Get "Page") }}{{ .Store.Set "Page" (printf "p-%s" $.Title) }}{{ end }} +{{ if not (hugo.Store.Get "Hugo") }}{{ hugo.Store.Set "Hugo" (printf "h-%s" $.Title) }}{{ end }} +{{ if not (site.Store.Get "Site") }}{{ site.Store.Set "Site" (printf "s-%s" $.Title) }}{{ end }} +Page: {{ .Store.Get "Page" }}| +Hugo: {{ hugo.Store.Get "Hugo" }}| +Site: {{ site.Store.Get "Site" }}| +` + + b := TestRunning(t, files) + + b.AssertFileContent("public/index.html", + ` +Shortcode: sh-Home| +Page: p-Home| +Site: s-Home| +Hugo: h-Home| +`, + ) + + b.EditFileReplaceAll("content/_index.md", "Home", "Homer").Build() + + b.AssertFileContent("public/index.html", + ` +Shortcode: sh-Homer| +Page: p-Homer| +Site: s-Home| +Hugo: h-Home| +`, + ) +} diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 8a478c9df..69e891adb 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -43,9 +43,10 @@ import ( ) var ( - _ urls.RefLinker = (*ShortcodeWithPage)(nil) - _ types.Unwrapper = (*ShortcodeWithPage)(nil) - _ text.Positioner = (*ShortcodeWithPage)(nil) + _ urls.RefLinker = (*ShortcodeWithPage)(nil) + _ types.Unwrapper = (*ShortcodeWithPage)(nil) + _ text.Positioner = (*ShortcodeWithPage)(nil) + _ maps.StoreProvider = (*ShortcodeWithPage)(nil) ) // ShortcodeWithPage is the "." context in a shortcode template. @@ -72,7 +73,7 @@ type ShortcodeWithPage struct { posOffset int pos text.Position - scratch *maps.Scratch + store *maps.Scratch } // InnerDeindent returns the (potentially de-indented) inner content of the shortcode. @@ -124,13 +125,19 @@ func (scp *ShortcodeWithPage) RelRef(args map[string]any) (string, error) { return scp.Page.RelRefFrom(args, scp) } +// Store returns this shortcode's Store. +func (scp *ShortcodeWithPage) Store() *maps.Scratch { + if scp.store == nil { + scp.store = maps.NewScratch() + } + return scp.store +} + // Scratch returns a scratch-pad scoped for this shortcode. This can be used // as a temporary storage for variables, counters etc. +// Deprecated: Use Store instead. Note that from the templates this should be considered a "soft deprecation". func (scp *ShortcodeWithPage) Scratch() *maps.Scratch { - if scp.scratch == nil { - scp.scratch = maps.NewScratch() - } - return scp.scratch + return scp.Store() } // Get is a convenience method to look up shortcode parameters by its key. @@ -399,7 +406,16 @@ func doRenderShortcode( hasVariants = hasVariants || more } - data := &ShortcodeWithPage{Ordinal: sc.ordinal, posOffset: sc.pos, indentation: sc.indentation, Params: sc.params, Page: newPageForShortcode(p), Parent: parent, Name: sc.name} + data := &ShortcodeWithPage{ + Ordinal: sc.ordinal, + posOffset: sc.pos, + indentation: sc.indentation, + Params: sc.params, + Page: newPageForShortcode(p), + Parent: parent, + Name: sc.name, + } + if sc.params != nil { data.IsNamedParams = reflect.TypeOf(sc.params).Kind() == reflect.Map } diff --git a/hugolib/site.go b/hugolib/site.go index c5a4956e2..c434ff2b4 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -95,6 +95,7 @@ type Site struct { language *langs.Language languagei int pageMap *pageMap + store *maps.Scratch // The owning container. h *HugoSites @@ -248,6 +249,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { language: language, languagei: i, frontmatterHandler: frontmatterHandler, + store: maps.NewScratch(), } if i == 0 { @@ -614,6 +616,10 @@ func (s *Site) AllRegularPages() page.Pages { return s.h.RegularPages() } +func (s *Site) Store() *maps.Scratch { + return s.store +} + func (s *Site) CheckReady() { if s.state != siteStateReady { panic("this method cannot be called before the site is fully initialized") diff --git a/resources/page/page.go b/resources/page/page.go index ea7f4bf1b..acd4ce313 100644 --- a/resources/page/page.go +++ b/resources/page/page.go @@ -331,9 +331,7 @@ type PageWithoutContent interface { // Deprecated: From Hugo v0.138.0 this is just an alias for Store. Scratch() *maps.Scratch - // Store returns a Scratch that can be used to store temporary state. - // In contrast to Scratch(), this Scratch is not reset on server rebuilds. - Store() *maps.Scratch + maps.StoreProvider RelatedKeywordsProvider diff --git a/resources/page/site.go b/resources/page/site.go index 9f7871a02..4a99982fd 100644 --- a/resources/page/site.go +++ b/resources/page/site.go @@ -135,6 +135,8 @@ type Site interface { // Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead. RSSLink() template.URL + maps.StoreProvider + // For internal use only. // This will panic if the site is not fully initialized. // This is typically used to inform the user in the content adapter templates, @@ -327,6 +329,10 @@ func (s *siteWrapper) RSSLink() template.URL { return s.s.RSSLink() } +func (s *siteWrapper) Store() *maps.Scratch { + return s.s.Store() +} + // For internal use only. func (s *siteWrapper) ForEeachIdentityByName(name string, f func(identity.Identity) bool) { s.s.(identity.ForEeachIdentityByNameProvider).ForEeachIdentityByName(name, f) @@ -491,6 +497,10 @@ func (s testSite) RSSLink() template.URL { return "" } +func (s testSite) Store() *maps.Scratch { + return maps.NewScratch() +} + func (s testSite) CheckReady() { }