// 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:"-"`
}
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
})
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|"
+}
}
// 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
} 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])
}
}
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
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
// 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) {
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 {
// 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
}
}
}
// 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
}
}
-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
}
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
}
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
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()
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
}
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",
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")},
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 {