Skip to content

Commit

Permalink
Add site.Store and hugo.Store and Shortcode.Store
Browse files Browse the repository at this point in the history
Closes #13021
  • Loading branch information
bep committed Nov 13, 2024
1 parent 3477d9f commit a7df536
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 13 deletions.
10 changes: 10 additions & 0 deletions common/hugo/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -185,6 +194,7 @@ func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
Environment: conf.Environment(),
conf: conf,
deps: deps,
store: maps.NewScratch(),
GoVersion: goVersion,
}
}
Expand Down
8 changes: 7 additions & 1 deletion common/maps/scratch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions hugolib/page_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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|
`,
)
}
34 changes: 25 additions & 9 deletions hugolib/shortcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 6 additions & 0 deletions hugolib/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type Site struct {
language *langs.Language
languagei int
pageMap *pageMap
store *maps.Scratch

// The owning container.
h *HugoSites
Expand Down Expand Up @@ -248,6 +249,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
language: language,
languagei: i,
frontmatterHandler: frontmatterHandler,
store: maps.NewScratch(),
}

if i == 0 {
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 1 addition & 3 deletions resources/page/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions resources/page/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -491,6 +497,10 @@ func (s testSite) RSSLink() template.URL {
return ""
}

func (s testSite) Store() *maps.Scratch {
return maps.NewScratch()
}

func (s testSite) CheckReady() {
}

Expand Down

2 comments on commit a7df536

@irkode
Copy link

@irkode irkode commented on a7df536 Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds promising,

sadly no docs upbout - guess will coming, or is this just a technical thing?
site.Store sounds like a global exchange place maybe solving the $noop - page must be rendered thing
hugo.Store even exchange data between sites?

@bep
Copy link
Member Author

@bep bep commented on a7df536 Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs is coming (see gohugoio/hugoDocs#2771), but it's fairly simple:

  • The difference is scope, shortcode, page, site, hugo -- the latter being the global scope.
  • These will only be reset once they're re-created, e.g. site.Store and hugo.Store will only be recreated on full rebuilds (config changes).

Please sign in to comment.