Skip to content

Commit c745a3e

Browse files
committed
Fix/implement cascade for content adapters
Fixes #13692
1 parent 9d1d8c8 commit c745a3e

File tree

11 files changed

+272
-78
lines changed

11 files changed

+272
-78
lines changed

config/allconfig/alldecoders.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ var allDecoderSetups = map[string]decodeWeight{
330330
key: "cascade",
331331
decode: func(d decodeWeight, p decodeConfig) error {
332332
var err error
333-
p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
333+
p.c.Cascade, err = page.DecodeCascadeConfig(nil, true, p.p.Get(d.key))
334334
return err
335335
},
336336
},

hugolib/content_map.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ func (m *pageMap) addPagesFromGoTmplFi(fi hugofs.FileMetaInfo, buildConfig *Buil
356356
Watching: s.Conf.Watching(),
357357
HandlePage: func(pt *pagesfromdata.PagesFromTemplate, pc *pagemeta.PageConfig) error {
358358
s := pt.Site.(*Site)
359-
if err := pc.Compile(pt.GoTmplFi.Meta().PathInfo.Base(), true, "", s.Log, s.conf.OutputFormats.Config, s.conf.MediaTypes.Config); err != nil {
359+
if err := pc.CompileForPagesFromDataPre(pt.GoTmplFi.Meta().PathInfo.Base(), m.s.Log, s.conf.MediaTypes.Config); err != nil {
360360
return err
361361
}
362362

hugolib/content_map_page.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1695,7 +1695,9 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
16951695
pathInfo: pi,
16961696
pageMetaParams: &pageMetaParams{
16971697
pageConfig: &pagemeta.PageConfig{
1698-
Kind: kinds.KindTerm,
1698+
PageConfigEarly: pagemeta.PageConfigEarly{
1699+
Kind: kinds.KindTerm,
1700+
},
16991701
},
17001702
},
17011703
}
@@ -1956,7 +1958,9 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
19561958
pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
19571959
pageMetaParams: &pageMetaParams{
19581960
pageConfig: &pagemeta.PageConfig{
1959-
Kind: kind,
1961+
PageConfigEarly: pagemeta.PageConfigEarly{
1962+
Kind: kind,
1963+
},
19601964
},
19611965
},
19621966
standaloneOutputFormat: f,
@@ -2082,7 +2086,9 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
20822086
pathInfo: p,
20832087
pageMetaParams: &pageMetaParams{
20842088
pageConfig: &pagemeta.PageConfig{
2085-
Kind: kinds.KindHome,
2089+
PageConfigEarly: pagemeta.PageConfigEarly{
2090+
Kind: kinds.KindHome,
2091+
},
20862092
},
20872093
},
20882094
}
@@ -2115,7 +2121,9 @@ func (sa *sitePagesAssembler) addMissingTaxonomies() error {
21152121
pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
21162122
pageMetaParams: &pageMetaParams{
21172123
pageConfig: &pagemeta.PageConfig{
2118-
Kind: kinds.KindTaxonomy,
2124+
PageConfigEarly: pagemeta.PageConfigEarly{
2125+
Kind: kinds.KindTaxonomy,
2126+
},
21192127
},
21202128
},
21212129
singular: viewName.singular,

hugolib/page__meta.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,7 @@ type pageMeta struct {
7272
// Prepare for a rebuild of the data passed in from front matter.
7373
func (m *pageMeta) setMetaPostPrepareRebuild() {
7474
params := xmaps.Clone(m.paramsOriginal)
75-
m.pageMetaParams.pageConfig = &pagemeta.PageConfig{
76-
Kind: m.pageConfig.Kind,
77-
Lang: m.pageConfig.Lang,
78-
Path: m.pageConfig.Path,
79-
Params: params,
80-
}
75+
m.pageMetaParams.pageConfig = pagemeta.ClonePageConfigForRebuild(m.pageMetaParams.pageConfig, params)
8176
}
8277

8378
type pageMetaParams struct {
@@ -94,7 +89,11 @@ type pageMetaParams struct {
9489

9590
func (m *pageMetaParams) init(preserveOriginal bool) {
9691
if preserveOriginal {
97-
m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
92+
if m.pageConfig.IsFromContentAdapter {
93+
m.paramsOriginal = xmaps.Clone(m.pageConfig.ContentAdapterData)
94+
} else {
95+
m.paramsOriginal = xmaps.Clone(m.pageConfig.Params)
96+
}
9897
m.cascadeOriginal = m.pageConfig.CascadeCompiled.Clone()
9998
}
10099
}
@@ -254,7 +253,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf
254253
// Check for any cascade define on itself.
255254
if cv, found := frontmatter["cascade"]; found {
256255
var err error
257-
cascade, err := page.DecodeCascade(logger, cv)
256+
cascade, err := page.DecodeCascade(logger, true, cv)
258257
if err != nil {
259258
return err
260259
}
@@ -341,18 +340,29 @@ func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Pa
341340
}
342341

343342
// Cascade is also applied to itself.
343+
var err error
344344
cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
345345
if !k.Matches(ps) {
346346
return true
347347
}
348348
for kk, vv := range v {
349-
if _, found := ps.m.pageConfig.Params[kk]; !found {
350-
ps.m.pageConfig.Params[kk] = vv
349+
if ps.m.pageConfig.IsFromContentAdapter {
350+
if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
351+
ps.m.pageConfig.ContentAdapterData[kk] = vv
352+
}
353+
} else {
354+
if _, found := ps.m.pageConfig.Params[kk]; !found {
355+
ps.m.pageConfig.Params[kk] = vv
356+
}
351357
}
352358
}
353359
return true
354360
})
355361

362+
if err != nil {
363+
return err
364+
}
365+
356366
if err := ps.setMetaPostParams(); err != nil {
357367
return err
358368
}
@@ -398,6 +408,12 @@ func (p *pageState) setMetaPostParams() error {
398408
PathOrTitle: p.pathOrTitle(),
399409
}
400410

411+
if isContentAdapter {
412+
if err := pm.pageConfig.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
413+
return err
414+
}
415+
}
416+
401417
// Handle the date separately
402418
// TODO(bep) we need to "do more" in this area so this can be split up and
403419
// more easily tested without the Page, but the coupling is strong.
@@ -656,7 +672,7 @@ params:
656672
return err
657673
}
658674

659-
if err := pcfg.Compile("", false, ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
675+
if err := pcfg.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
660676
return err
661677
}
662678

hugolib/pagesfromdata/pagesfromgotmpl.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,20 @@ func (p *pagesFromDataTemplateContext) AddPage(v any) (string, error) {
9191

9292
pd := pagemeta.DefaultPageConfig
9393
pd.IsFromContentAdapter = true
94+
pd.ContentAdapterData = m
9495

95-
if err := mapstructure.WeakDecode(m, &pd); err != nil {
96-
return "", fmt.Errorf("failed to decode page map: %w", err)
96+
// The rest will be handled after the cascade is calculated and applied.
97+
if err := mapstructure.WeakDecode(pd.ContentAdapterData, &pd.PageConfigEarly); err != nil {
98+
err = fmt.Errorf("failed to decode page map: %w", err)
99+
return "", err
97100
}
98101

99-
p.p.buildState.NumPagesAdded++
100-
101102
if err := pd.Validate(true); err != nil {
102103
return "", err
103104
}
104105

106+
p.p.buildState.NumPagesAdded++
107+
105108
return "", p.p.HandlePage(p.p, &pd)
106109
}
107110

hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,3 +821,97 @@ outputs:
821821
b.AssertFileExists("public/p4/index.html", true)
822822
b.AssertFileExists("public/p4/index.json", false) // currently returns true
823823
}
824+
825+
func TestContentAdapterOutputsIssue13692(t *testing.T) {
826+
t.Parallel()
827+
828+
files := `
829+
-- hugo.toml --
830+
disableKinds = ['page','home','sitemap','taxonomy','term']
831+
[[cascade]]
832+
outputs = ['html','json']
833+
[cascade.target]
834+
path = '{/s2,/s4}'
835+
-- layouts/section.html --
836+
html: {{ .Title }}
837+
-- layouts/section.json --
838+
json: {{ .Title }}
839+
-- content/s1/_index.md --
840+
---
841+
title: s1
842+
---
843+
-- content/s2/_index.md --
844+
---
845+
title: s2
846+
---
847+
-- content/_content.gotmpl --
848+
{{ $page := dict "path" "s3" "title" "s3" "kind" "section" }}
849+
{{ $.AddPage $page }}
850+
851+
{{ $page := dict "path" "s4" "title" "s4" "kind" "section" }}
852+
{{ $.AddPage $page }}
853+
854+
{{ $page := dict "path" "s5" "title" "s5" "kind" "section" "outputs" (slice "html") }}
855+
{{ $.AddPage $page }}
856+
`
857+
858+
b := hugolib.Test(t, files)
859+
860+
b.AssertFileExists("public/s1/index.html", true)
861+
b.AssertFileExists("public/s1/index.json", false)
862+
b.AssertFileExists("public/s1/index.xml", true)
863+
864+
b.AssertFileExists("public/s2/index.html", true)
865+
b.AssertFileExists("public/s2/index.json", true)
866+
b.AssertFileExists("public/s2/index.xml", false)
867+
868+
b.AssertFileExists("public/s3/index.html", true)
869+
b.AssertFileExists("public/s3/index.json", false)
870+
b.AssertFileExists("public/s3/index.xml", true)
871+
872+
b.AssertFileExists("public/s4/index.html", true)
873+
b.AssertFileExists("public/s4/index.json", true)
874+
b.AssertFileExists("public/s4/index.xml", false)
875+
876+
b.AssertFileExists("public/s5/index.html", true)
877+
b.AssertFileExists("public/s5/index.json", false)
878+
b.AssertFileExists("public/s5/index.xml", false)
879+
}
880+
881+
func TestContentAdapterCascadeBasic(t *testing.T) {
882+
t.Parallel()
883+
884+
files := `
885+
-- hugo.toml --
886+
disableLiveReload = true
887+
-- content/_index.md --
888+
---
889+
cascade:
890+
- title: foo
891+
target:
892+
path: "**"
893+
---
894+
-- layouts/all.html --
895+
Title: {{ .Title }}|Content: {{ .Content }}|
896+
-- content/_content.gotmpl --
897+
{{ $content := dict
898+
"mediaType" "text/markdown"
899+
"value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
900+
}}
901+
902+
{{ $page := dict "path" "s1" "kind" "page" }}
903+
{{ $.AddPage $page }}
904+
{{ $page := dict "path" "s2" "kind" "page" "title" "bar" "content" $content }}
905+
{{ $.AddPage $page }}
906+
907+
`
908+
909+
b := hugolib.TestRunning(t, files)
910+
911+
b.AssertFileContent("public/s1/index.html", "Title: foo|")
912+
b.AssertFileContent("public/s2/index.html", "Title: bar|", "Content: <p>The <em>Hunchback of Notre Dame</em> was written by Victor Hugo.</p>")
913+
914+
b.EditFileReplaceAll("content/_index.md", "foo", "baz").Build()
915+
916+
b.AssertFileContent("public/s1/index.html", "Title: baz|")
917+
}

resources/page/page_matcher.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package page
1616
import (
1717
"fmt"
1818
"path/filepath"
19+
"slices"
1920
"strings"
2021

2122
"github.com/gohugoio/hugo/common/loggers"
@@ -24,7 +25,6 @@ import (
2425
"github.com/gohugoio/hugo/hugofs/glob"
2526
"github.com/gohugoio/hugo/resources/kinds"
2627
"github.com/mitchellh/mapstructure"
27-
"slices"
2828
)
2929

3030
// A PageMatcher can be used to match a Page with Glob patterns.
@@ -105,7 +105,7 @@ func CheckCascadePattern(logger loggers.Logger, m PageMatcher) {
105105
}
106106
}
107107

108-
func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
108+
func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
109109
buildConfig := func(in any) (*maps.Ordered[PageMatcher, maps.Params], any, error) {
110110
cascade := maps.NewOrdered[PageMatcher, maps.Params]()
111111
if in == nil {
@@ -120,7 +120,15 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
120120

121121
for _, m := range ms {
122122
m = maps.CleanConfigStringMap(m)
123-
c, err := mapToPageMatcherParamsConfig(m)
123+
var (
124+
c PageMatcherParamsConfig
125+
err error
126+
)
127+
if handleLegacyFormat {
128+
c, err = mapToPageMatcherParamsConfigLegacy(m)
129+
} else {
130+
c, err = mapToPageMatcherParamsConfig(m)
131+
}
124132
if err != nil {
125133
return nil, nil, err
126134
}
@@ -155,15 +163,35 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
155163
}
156164

157165
// DecodeCascade decodes in which could be either a map or a slice of maps.
158-
func DecodeCascade(logger loggers.Logger, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
159-
conf, err := DecodeCascadeConfig(logger, in)
166+
func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
167+
conf, err := DecodeCascadeConfig(logger, handleLegacyFormat, in)
160168
if err != nil {
161169
return nil, err
162170
}
163171
return conf.Config, nil
164172
}
165173

166174
func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, error) {
175+
var pcfg PageMatcherParamsConfig
176+
for k, v := range m {
177+
switch strings.ToLower(k) {
178+
case "_target", "target":
179+
var target PageMatcher
180+
if err := decodePageMatcher(v, &target); err != nil {
181+
return pcfg, err
182+
}
183+
pcfg.Target = target
184+
default:
185+
if pcfg.Params == nil {
186+
pcfg.Params = make(maps.Params)
187+
}
188+
pcfg.Params[k] = v
189+
}
190+
}
191+
return pcfg, pcfg.init()
192+
}
193+
194+
func mapToPageMatcherParamsConfigLegacy(m map[string]any) (PageMatcherParamsConfig, error) {
167195
var pcfg PageMatcherParamsConfig
168196
for k, v := range m {
169197
switch strings.ToLower(k) {
@@ -190,7 +218,6 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
190218
}
191219
pcfg.Target = target
192220
default:
193-
// Legacy config.
194221
if pcfg.Params == nil {
195222
pcfg.Params = make(maps.Params)
196223
}

resources/page/page_matcher_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func TestPageMatcher(t *testing.T) {
8484

8585
c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
8686
fn := func(m map[string]any) PageMatcherParamsConfig {
87-
v, err := mapToPageMatcherParamsConfig(m)
87+
v, err := mapToPageMatcherParamsConfigLegacy(m)
8888
c.Assert(err, qt.IsNil)
8989
return v
9090
}
@@ -129,7 +129,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
129129
},
130130
}
131131

132-
got, err := DecodeCascadeConfig(loggers.NewDefault(), in)
132+
got, err := DecodeCascadeConfig(loggers.NewDefault(), true, in)
133133

134134
c.Assert(err, qt.IsNil)
135135
c.Assert(got, qt.IsNotNil)
@@ -143,7 +143,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
143143
{Params: maps.Params{"b": string("bv")}, Target: PageMatcher{Kind: "page"}},
144144
})
145145

146-
got, err = DecodeCascadeConfig(loggers.NewDefault(), nil)
146+
got, err = DecodeCascadeConfig(loggers.NewDefault(), true, nil)
147147
c.Assert(err, qt.IsNil)
148148
c.Assert(got, qt.IsNotNil)
149149
}

0 commit comments

Comments
 (0)