]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Fix recent regression with cascading of params to content adapters
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 26 May 2025 16:12:32 +0000 (18:12 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 26 May 2025 19:26:19 +0000 (21:26 +0200)
Fixes #13743

config/allconfig/allconfig.go
hugolib/cascade_test.go
hugolib/content_map_page.go
hugolib/page__meta.go
resources/page/page_matcher.go
resources/page/page_matcher_test.go
resources/page/pagemeta/page_frontmatter.go

index d3ee28490c0c022660e1acb1b7ff60ca6269d356..0db0be1d8d50b4d9341c5f39c976967b4366a7f3 100644 (file)
@@ -146,7 +146,7 @@ type Config struct {
 
        // The cascade configuration section contains the top level front matter cascade configuration options,
        // a slice of page matcher and params to apply to those pages.
-       Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, maps.Params]] `mapstructure:"-"`
+       Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]] `mapstructure:"-"`
 
        // The segments defines segments for the site. Used for partial/segmented builds.
        Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
@@ -776,7 +776,7 @@ type Configs struct {
 }
 
 func (c *Configs) Validate(logger loggers.Logger) error {
-       c.Base.Cascade.Config.Range(func(p page.PageMatcher, params maps.Params) bool {
+       c.Base.Cascade.Config.Range(func(p page.PageMatcher, cfg page.PageMatcherParamsConfig) bool {
                page.CheckCascadePattern(logger, p)
                return true
        })
index d0a6730db8730cd0761f332b5d28df33013e7b22..10b47764fcffdb81b8f22831009cd461c8cbaa11 100644 (file)
@@ -917,3 +917,24 @@ title: p2
        b.AssertFileExists("public/sx/index.html", true)    // failing
        b.AssertFileExists("public/sx/p2/index.html", true) // failing
 }
+
+func TestCascadeGotmplIssue13743(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+[cascade.params]
+foo = 'bar'
+[cascade.target]
+path = '/p1'
+-- content/_content.gotmpl --
+{{ .AddPage (dict "title" "p1" "path" "p1") }}
+-- layouts/all.html --
+{{ .Title }}|{{ .Params.foo }}
+`
+
+       b := Test(t, files)
+
+       b.AssertFileContent("public/p1/index.html", "p1|bar") // actual content is "p1|"
+}
index 9e67fbb1b064b93369713fb3334717bdc237a4b4..ef2daca74b76d6cb53b8226098f94479757c59b2 100644 (file)
@@ -1412,7 +1412,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
                }
 
                // Handle cascades first to get any default dates set.
-               var cascade *maps.Ordered[page.PageMatcher, maps.Params]
+               var cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]
                if keyPage == "" {
                        // Home page gets it's cascade from the site config.
                        cascade = sa.conf.Cascade.Config
@@ -1424,7 +1424,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
                } else {
                        _, data := pw.WalkContext.Data().LongestPrefix(paths.Dir(keyPage))
                        if data != nil {
-                               cascade = data.(*maps.Ordered[page.PageMatcher, maps.Params])
+                               cascade = data.(*maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig])
                        }
                }
 
@@ -1506,11 +1506,11 @@ func (sa *sitePagesAssembler) applyAggregates() error {
                                pageResource := rs.r.(*pageState)
                                relPath := pageResource.m.pathInfo.BaseRel(pageBundle.m.pathInfo)
                                pageResource.m.resourcePath = relPath
-                               var cascade *maps.Ordered[page.PageMatcher, maps.Params]
+                               var cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]
                                // Apply cascade (if set) to the page.
                                _, data := pw.WalkContext.Data().LongestPrefix(resourceKey)
                                if data != nil {
-                                       cascade = data.(*maps.Ordered[page.PageMatcher, maps.Params])
+                                       cascade = data.(*maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig])
                                }
                                if err := pageResource.setMetaPost(cascade); err != nil {
                                        return false, err
@@ -1574,10 +1574,10 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
                                const eventName = "dates"
 
                                if p.Kind() == kinds.KindTerm {
-                                       var cascade *maps.Ordered[page.PageMatcher, maps.Params]
+                                       var cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]
                                        _, data := pw.WalkContext.Data().LongestPrefix(s)
                                        if data != nil {
-                                               cascade = data.(*maps.Ordered[page.PageMatcher, maps.Params])
+                                               cascade = data.(*maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig])
                                        }
                                        if err := p.setMetaPost(cascade); err != nil {
                                                return false, err
index e8bce20d16449c160a319e65119d42ae918a8262..0d6d22e9ae567ec39f6187aaed9e40fc2258aaa7 100644 (file)
@@ -83,8 +83,8 @@ type pageMetaParams struct {
 
        // These are only set in watch mode.
        datesOriginal   pagemeta.Dates
-       paramsOriginal  map[string]any                               // contains the original params as defined in the front matter.
-       cascadeOriginal *maps.Ordered[page.PageMatcher, maps.Params] // contains the original cascade as defined in the front matter.
+       paramsOriginal  map[string]any                                                // contains the original params as defined in the front matter.
+       cascadeOriginal *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig] // contains the original cascade as defined in the front matter.
 }
 
 func (m *pageMetaParams) init(preserveOriginal bool) {
@@ -291,7 +291,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf
        return nil
 }
 
-func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Params]) error {
+func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]) error {
        ps.m.setMetaPostCount++
        var cascadeHashPre uint64
        if ps.m.setMetaPostCount > 1 {
@@ -303,15 +303,20 @@ func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Pa
        // Apply cascades first so they can be overridden later.
        if cascade != nil {
                if ps.m.pageConfig.CascadeCompiled != nil {
-                       cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
+                       cascade.Range(func(k page.PageMatcher, v page.PageMatcherParamsConfig) bool {
                                vv, found := ps.m.pageConfig.CascadeCompiled.Get(k)
                                if !found {
                                        ps.m.pageConfig.CascadeCompiled.Set(k, v)
                                } else {
                                        // Merge
-                                       for ck, cv := range v {
-                                               if _, found := vv[ck]; !found {
-                                                       vv[ck] = cv
+                                       for ck, cv := range v.Params {
+                                               if _, found := vv.Params[ck]; !found {
+                                                       vv.Params[ck] = cv
+                                               }
+                                       }
+                                       for ck, cv := range v.Fields {
+                                               if _, found := vv.Fields[ck]; !found {
+                                                       vv.Fields[ck] = cv
                                                }
                                        }
                                }
@@ -341,11 +346,17 @@ func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Pa
 
        // Cascade is also applied to itself.
        var err error
-       cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
+       cascade.Range(func(k page.PageMatcher, v page.PageMatcherParamsConfig) bool {
                if !k.Matches(ps) {
                        return true
                }
-               for kk, vv := range v {
+               for kk, vv := range v.Params {
+                       if _, found := ps.m.pageConfig.Params[kk]; !found {
+                               ps.m.pageConfig.Params[kk] = vv
+                       }
+               }
+
+               for kk, vv := range v.Fields {
                        if ps.m.pageConfig.IsFromContentAdapter {
                                if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
                                        ps.m.pageConfig.ContentAdapterData[kk] = vv
index 27a7c7e9e7ff078c979b1d4a29e6a56b9a04204a..306c2d03c567e3e37ca0a748825f79f2f570fa89 100644 (file)
@@ -105,9 +105,9 @@ func CheckCascadePattern(logger loggers.Logger, m PageMatcher) {
        }
 }
 
-func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
-       buildConfig := func(in any) (*maps.Ordered[PageMatcher, maps.Params], any, error) {
-               cascade := maps.NewOrdered[PageMatcher, maps.Params]()
+func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, PageMatcherParamsConfig]], error) {
+       buildConfig := func(in any) (*maps.Ordered[PageMatcher, PageMatcherParamsConfig], any, error) {
+               cascade := maps.NewOrdered[PageMatcher, PageMatcherParamsConfig]()
                if in == nil {
                        return cascade, []map[string]any{}, nil
                }
@@ -124,11 +124,7 @@ func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any)
                                c   PageMatcherParamsConfig
                                err error
                        )
-                       if handleLegacyFormat {
-                               c, err = mapToPageMatcherParamsConfigLegacy(m)
-                       } else {
-                               c, err = mapToPageMatcherParamsConfig(m)
-                       }
+                       c, err = mapToPageMatcherParamsConfig(m)
                        if err != nil {
                                return nil, nil, err
                        }
@@ -147,23 +143,28 @@ func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any)
                        if found {
                                // Merge
                                for k, v := range cfg.Params {
-                                       if _, found := c[k]; !found {
-                                               c[k] = v
+                                       if _, found := c.Params[k]; !found {
+                                               c.Params[k] = v
+                                       }
+                               }
+                               for k, v := range cfg.Fields {
+                                       if _, found := c.Fields[k]; !found {
+                                               c.Fields[k] = v
                                        }
                                }
                        } else {
-                               cascade.Set(m, cfg.Params)
+                               cascade.Set(m, cfg)
                        }
                }
 
                return cascade, cfgs, nil
        }
 
-       return config.DecodeNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]](in, buildConfig)
+       return config.DecodeNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, PageMatcherParamsConfig]](in, buildConfig)
 }
 
 // DecodeCascade decodes in which could be either a map or a slice of maps.
-func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
+func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, PageMatcherParamsConfig], error) {
        conf, err := DecodeCascadeConfig(logger, handleLegacyFormat, in)
        if err != nil {
                return nil, err
@@ -181,47 +182,22 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
                                return pcfg, err
                        }
                        pcfg.Target = target
-               default:
+               case "params":
                        if pcfg.Params == nil {
                                pcfg.Params = make(maps.Params)
                        }
-                       pcfg.Params[k] = v
-               }
-       }
-       return pcfg, pcfg.init()
-}
-
-func mapToPageMatcherParamsConfigLegacy(m map[string]any) (PageMatcherParamsConfig, error) {
-       var pcfg PageMatcherParamsConfig
-       for k, v := range m {
-               switch strings.ToLower(k) {
-               case "params":
-                       // We simplified the structure of the cascade config in Hugo 0.111.0.
-                       // There is a small chance that someone has used the old structure with the params keyword,
-                       // those values will now be moved to the top level.
-                       // This should be very unlikely as it would lead to constructs like .Params.params.foo,
-                       // and most people see params as an Hugo internal keyword.
                        params := maps.ToStringMap(v)
-                       if pcfg.Params == nil {
-                               pcfg.Params = params
-                       } else {
-                               for k, v := range params {
-                                       if _, found := pcfg.Params[k]; !found {
-                                               pcfg.Params[k] = v
-                                       }
+                       for k, v := range params {
+                               if _, found := pcfg.Params[k]; !found {
+                                       pcfg.Params[k] = v
                                }
                        }
-               case "_target", "target":
-                       var target PageMatcher
-                       if err := decodePageMatcher(v, &target); err != nil {
-                               return pcfg, err
-                       }
-                       pcfg.Target = target
                default:
-                       if pcfg.Params == nil {
-                               pcfg.Params = make(maps.Params)
+                       if pcfg.Fields == nil {
+                               pcfg.Fields = make(maps.Params)
                        }
-                       pcfg.Params[k] = v
+
+                       pcfg.Fields[k] = v
                }
        }
        return pcfg, pcfg.init()
@@ -250,10 +226,14 @@ func decodePageMatcher(m any, v *PageMatcher) error {
 type PageMatcherParamsConfig struct {
        // Apply Params to all Pages matching Target.
        Params maps.Params
+       // Fields holds all fields but Params.
+       Fields maps.Params
+       // Target is the PageMatcher that this config applies to.
        Target PageMatcher
 }
 
 func (p *PageMatcherParamsConfig) init() error {
        maps.PrepareParams(p.Params)
+       maps.PrepareParams(p.Fields)
        return nil
 }
index 2fe6ccc89aee85ee3492daf890569b21dc57fe6e..ad35da43c15d07190598eb2c032822084d6a69ee 100644 (file)
@@ -84,19 +84,17 @@ func TestPageMatcher(t *testing.T) {
 
        c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
                fn := func(m map[string]any) PageMatcherParamsConfig {
-                       v, err := mapToPageMatcherParamsConfigLegacy(m)
+                       v, err := mapToPageMatcherParamsConfig(m)
                        c.Assert(err, qt.IsNil)
                        return v
                }
-               // Legacy.
                c.Assert(fn(map[string]any{"_target": map[string]any{"kind": "page"}, "foo": "bar"}), qt.DeepEquals, PageMatcherParamsConfig{
-                       Params: maps.Params{
+                       Fields: maps.Params{
                                "foo": "bar",
                        },
                        Target: PageMatcher{Path: "", Kind: "page", Lang: "", Environment: ""},
                })
 
-               // Current format.
                c.Assert(fn(map[string]any{"target": map[string]any{"kind": "page"}, "params": map[string]any{"foo": "bar"}}), qt.DeepEquals, PageMatcherParamsConfig{
                        Params: maps.Params{
                                "foo": "bar",
@@ -134,7 +132,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
        c.Assert(err, qt.IsNil)
        c.Assert(got, qt.IsNotNil)
        c.Assert(got.Config.Keys(), qt.DeepEquals, []PageMatcher{{Kind: "page", Environment: "production"}, {Kind: "page"}})
-       c.Assert(got.Config.Values(), qt.DeepEquals, []maps.Params{{"a": string("av")}, {"b": string("bv")}})
+
        c.Assert(got.SourceStructure, qt.DeepEquals, []PageMatcherParamsConfig{
                {
                        Params: maps.Params{"a": string("av")},
index 7dea7ca6b398898800ac7fc4780fc96588671730..fd4f7759b86eef15d7f6d1141b189598e6dbc150 100644 (file)
@@ -125,10 +125,10 @@ type PageConfig struct {
        ContentAdapterData map[string]any `mapstructure:"-" json:"-"`
 
        // Compiled values.
-       CascadeCompiled         *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
-       ContentMediaType        media.Type                                   `mapstructure:"-" json:"-"`
-       ConfiguredOutputFormats output.Formats                               `mapstructure:"-" json:"-"`
-       IsFromContentAdapter    bool                                         `mapstructure:"-" json:"-"`
+       CascadeCompiled         *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig] `mapstructure:"-" json:"-"`
+       ContentMediaType        media.Type                                                    `mapstructure:"-" json:"-"`
+       ConfiguredOutputFormats output.Formats                                                `mapstructure:"-" json:"-"`
+       IsFromContentAdapter    bool                                                          `mapstructure:"-" json:"-"`
 }
 
 func ClonePageConfigForRebuild(p *PageConfig, params map[string]any) *PageConfig {