mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-23 21:53:09 +02:00
Fix recent regression with cascading of params to content adapters
Fixes #13743
This commit is contained in:
@@ -146,7 +146,7 @@ type Config struct {
|
|||||||
|
|
||||||
// The cascade configuration section contains the top level front matter cascade configuration options,
|
// 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.
|
// 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.
|
// The segments defines segments for the site. Used for partial/segmented builds.
|
||||||
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
|
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 {
|
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)
|
page.CheckCascadePattern(logger, p)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@@ -917,3 +917,24 @@ title: p2
|
|||||||
b.AssertFileExists("public/sx/index.html", true) // failing
|
b.AssertFileExists("public/sx/index.html", true) // failing
|
||||||
b.AssertFileExists("public/sx/p2/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|"
|
||||||
|
}
|
||||||
|
@@ -1412,7 +1412,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle cascades first to get any default dates set.
|
// 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 == "" {
|
if keyPage == "" {
|
||||||
// Home page gets it's cascade from the site config.
|
// Home page gets it's cascade from the site config.
|
||||||
cascade = sa.conf.Cascade.Config
|
cascade = sa.conf.Cascade.Config
|
||||||
@@ -1424,7 +1424,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
|||||||
} else {
|
} else {
|
||||||
_, data := pw.WalkContext.Data().LongestPrefix(paths.Dir(keyPage))
|
_, data := pw.WalkContext.Data().LongestPrefix(paths.Dir(keyPage))
|
||||||
if data != nil {
|
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)
|
pageResource := rs.r.(*pageState)
|
||||||
relPath := pageResource.m.pathInfo.BaseRel(pageBundle.m.pathInfo)
|
relPath := pageResource.m.pathInfo.BaseRel(pageBundle.m.pathInfo)
|
||||||
pageResource.m.resourcePath = relPath
|
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.
|
// Apply cascade (if set) to the page.
|
||||||
_, data := pw.WalkContext.Data().LongestPrefix(resourceKey)
|
_, data := pw.WalkContext.Data().LongestPrefix(resourceKey)
|
||||||
if data != nil {
|
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 {
|
if err := pageResource.setMetaPost(cascade); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -1574,10 +1574,10 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
|
|||||||
const eventName = "dates"
|
const eventName = "dates"
|
||||||
|
|
||||||
if p.Kind() == kinds.KindTerm {
|
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)
|
_, data := pw.WalkContext.Data().LongestPrefix(s)
|
||||||
if data != nil {
|
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 {
|
if err := p.setMetaPost(cascade); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@@ -84,7 +84,7 @@ type pageMetaParams struct {
|
|||||||
// These are only set in watch mode.
|
// These are only set in watch mode.
|
||||||
datesOriginal pagemeta.Dates
|
datesOriginal pagemeta.Dates
|
||||||
paramsOriginal map[string]any // contains the original params 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, maps.Params] // contains the original cascade 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) {
|
func (m *pageMetaParams) init(preserveOriginal bool) {
|
||||||
@@ -291,7 +291,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf
|
|||||||
return nil
|
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++
|
ps.m.setMetaPostCount++
|
||||||
var cascadeHashPre uint64
|
var cascadeHashPre uint64
|
||||||
if ps.m.setMetaPostCount > 1 {
|
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.
|
// Apply cascades first so they can be overridden later.
|
||||||
if cascade != nil {
|
if cascade != nil {
|
||||||
if ps.m.pageConfig.CascadeCompiled != 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)
|
vv, found := ps.m.pageConfig.CascadeCompiled.Get(k)
|
||||||
if !found {
|
if !found {
|
||||||
ps.m.pageConfig.CascadeCompiled.Set(k, v)
|
ps.m.pageConfig.CascadeCompiled.Set(k, v)
|
||||||
} else {
|
} else {
|
||||||
// Merge
|
// Merge
|
||||||
for ck, cv := range v {
|
for ck, cv := range v.Params {
|
||||||
if _, found := vv[ck]; !found {
|
if _, found := vv.Params[ck]; !found {
|
||||||
vv[ck] = cv
|
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.
|
// Cascade is also applied to itself.
|
||||||
var err error
|
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) {
|
if !k.Matches(ps) {
|
||||||
return true
|
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 ps.m.pageConfig.IsFromContentAdapter {
|
||||||
if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
|
if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
|
||||||
ps.m.pageConfig.ContentAdapterData[kk] = vv
|
ps.m.pageConfig.ContentAdapterData[kk] = vv
|
||||||
|
@@ -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) {
|
func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, PageMatcherParamsConfig]], error) {
|
||||||
buildConfig := func(in any) (*maps.Ordered[PageMatcher, maps.Params], any, error) {
|
buildConfig := func(in any) (*maps.Ordered[PageMatcher, PageMatcherParamsConfig], any, error) {
|
||||||
cascade := maps.NewOrdered[PageMatcher, maps.Params]()
|
cascade := maps.NewOrdered[PageMatcher, PageMatcherParamsConfig]()
|
||||||
if in == nil {
|
if in == nil {
|
||||||
return cascade, []map[string]any{}, nil
|
return cascade, []map[string]any{}, nil
|
||||||
}
|
}
|
||||||
@@ -124,11 +124,7 @@ func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any)
|
|||||||
c PageMatcherParamsConfig
|
c PageMatcherParamsConfig
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if handleLegacyFormat {
|
|
||||||
c, err = mapToPageMatcherParamsConfigLegacy(m)
|
|
||||||
} else {
|
|
||||||
c, err = mapToPageMatcherParamsConfig(m)
|
c, err = mapToPageMatcherParamsConfig(m)
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -147,23 +143,28 @@ func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any)
|
|||||||
if found {
|
if found {
|
||||||
// Merge
|
// Merge
|
||||||
for k, v := range cfg.Params {
|
for k, v := range cfg.Params {
|
||||||
if _, found := c[k]; !found {
|
if _, found := c.Params[k]; !found {
|
||||||
c[k] = v
|
c.Params[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range cfg.Fields {
|
||||||
|
if _, found := c.Fields[k]; !found {
|
||||||
|
c.Fields[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cascade.Set(m, cfg.Params)
|
cascade.Set(m, cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cascade, cfgs, nil
|
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.
|
// 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)
|
conf, err := DecodeCascadeConfig(logger, handleLegacyFormat, in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -181,47 +182,22 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
|
|||||||
return pcfg, err
|
return pcfg, err
|
||||||
}
|
}
|
||||||
pcfg.Target = target
|
pcfg.Target = target
|
||||||
default:
|
case "params":
|
||||||
if pcfg.Params == nil {
|
if pcfg.Params == nil {
|
||||||
pcfg.Params = make(maps.Params)
|
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)
|
params := maps.ToStringMap(v)
|
||||||
if pcfg.Params == nil {
|
|
||||||
pcfg.Params = params
|
|
||||||
} else {
|
|
||||||
for k, v := range params {
|
for k, v := range params {
|
||||||
if _, found := pcfg.Params[k]; !found {
|
if _, found := pcfg.Params[k]; !found {
|
||||||
pcfg.Params[k] = v
|
pcfg.Params[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
case "_target", "target":
|
|
||||||
var target PageMatcher
|
|
||||||
if err := decodePageMatcher(v, &target); err != nil {
|
|
||||||
return pcfg, err
|
|
||||||
}
|
|
||||||
pcfg.Target = target
|
|
||||||
default:
|
default:
|
||||||
if pcfg.Params == nil {
|
if pcfg.Fields == nil {
|
||||||
pcfg.Params = make(maps.Params)
|
pcfg.Fields = make(maps.Params)
|
||||||
}
|
}
|
||||||
pcfg.Params[k] = v
|
|
||||||
|
pcfg.Fields[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pcfg, pcfg.init()
|
return pcfg, pcfg.init()
|
||||||
@@ -250,10 +226,14 @@ func decodePageMatcher(m any, v *PageMatcher) error {
|
|||||||
type PageMatcherParamsConfig struct {
|
type PageMatcherParamsConfig struct {
|
||||||
// Apply Params to all Pages matching Target.
|
// Apply Params to all Pages matching Target.
|
||||||
Params maps.Params
|
Params maps.Params
|
||||||
|
// Fields holds all fields but Params.
|
||||||
|
Fields maps.Params
|
||||||
|
// Target is the PageMatcher that this config applies to.
|
||||||
Target PageMatcher
|
Target PageMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PageMatcherParamsConfig) init() error {
|
func (p *PageMatcherParamsConfig) init() error {
|
||||||
maps.PrepareParams(p.Params)
|
maps.PrepareParams(p.Params)
|
||||||
|
maps.PrepareParams(p.Fields)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -84,19 +84,17 @@ func TestPageMatcher(t *testing.T) {
|
|||||||
|
|
||||||
c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
|
c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
|
||||||
fn := func(m map[string]any) PageMatcherParamsConfig {
|
fn := func(m map[string]any) PageMatcherParamsConfig {
|
||||||
v, err := mapToPageMatcherParamsConfigLegacy(m)
|
v, err := mapToPageMatcherParamsConfig(m)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
// Legacy.
|
|
||||||
c.Assert(fn(map[string]any{"_target": map[string]any{"kind": "page"}, "foo": "bar"}), qt.DeepEquals, PageMatcherParamsConfig{
|
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",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
Target: PageMatcher{Path: "", Kind: "page", Lang: "", Environment: ""},
|
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{
|
c.Assert(fn(map[string]any{"target": map[string]any{"kind": "page"}, "params": map[string]any{"foo": "bar"}}), qt.DeepEquals, PageMatcherParamsConfig{
|
||||||
Params: maps.Params{
|
Params: maps.Params{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
@@ -134,7 +132,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
|
|||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
c.Assert(got, qt.IsNotNil)
|
c.Assert(got, qt.IsNotNil)
|
||||||
c.Assert(got.Config.Keys(), qt.DeepEquals, []PageMatcher{{Kind: "page", Environment: "production"}, {Kind: "page"}})
|
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{
|
c.Assert(got.SourceStructure, qt.DeepEquals, []PageMatcherParamsConfig{
|
||||||
{
|
{
|
||||||
Params: maps.Params{"a": string("av")},
|
Params: maps.Params{"a": string("av")},
|
||||||
|
@@ -125,7 +125,7 @@ type PageConfig struct {
|
|||||||
ContentAdapterData map[string]any `mapstructure:"-" json:"-"`
|
ContentAdapterData map[string]any `mapstructure:"-" json:"-"`
|
||||||
|
|
||||||
// Compiled values.
|
// Compiled values.
|
||||||
CascadeCompiled *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
|
CascadeCompiled *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig] `mapstructure:"-" json:"-"`
|
||||||
ContentMediaType media.Type `mapstructure:"-" json:"-"`
|
ContentMediaType media.Type `mapstructure:"-" json:"-"`
|
||||||
ConfiguredOutputFormats output.Formats `mapstructure:"-" json:"-"`
|
ConfiguredOutputFormats output.Formats `mapstructure:"-" json:"-"`
|
||||||
IsFromContentAdapter bool `mapstructure:"-" json:"-"`
|
IsFromContentAdapter bool `mapstructure:"-" json:"-"`
|
||||||
|
Reference in New Issue
Block a user