diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go index 413baebd1..7052f0abd 100644 --- a/config/allconfig/allconfig.go +++ b/config/allconfig/allconfig.go @@ -965,7 +965,7 @@ func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.Base }) for _, v := range decoderSetups { - p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg} + p := decodeConfig{p: p, c: target, fs: fs, logger: logger, bcfg: bcfg} if err := v.decode(v, p); err != nil { return fmt.Errorf("failed to decode %q: %w", v.key, err) } diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go index f96c19cfc..5ab76dec5 100644 --- a/config/allconfig/alldecoders.go +++ b/config/allconfig/alldecoders.go @@ -18,6 +18,7 @@ import ( "strings" "github.com/gohugoio/hugo/cache/filecache" + "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/config" @@ -42,10 +43,11 @@ import ( ) type decodeConfig struct { - p config.Provider - c *Config - fs afero.Fs - bcfg config.BaseConfig + p config.Provider + c *Config + fs afero.Fs + logger loggers.Logger + bcfg config.BaseConfig } type decodeWeight struct { @@ -291,7 +293,7 @@ var allDecoderSetups = map[string]decodeWeight{ key: "cascade", decode: func(d decodeWeight, p decodeConfig) error { var err error - p.c.Cascade, err = page.DecodeCascadeConfig(p.p.Get(d.key)) + p.c.Cascade, err = page.DecodeCascadeConfig(p.logger, p.p.Get(d.key)) return err }, }, diff --git a/hugolib/cascade_test.go b/hugolib/cascade_test.go index 194faeb0a..bb328f761 100644 --- a/hugolib/cascade_test.go +++ b/hugolib/cascade_test.go @@ -671,3 +671,32 @@ S1|p1:|p2:p2| `) }) } + +// Issue 11977. +func TestCascadeExtensionInPath(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.org" +[languages] +[languages.en] +weight = 1 +[languages.de] +-- content/_index.de.md -- ++++ +[[cascade]] +[cascade.params] +foo = 'bar' +[cascade._target] +path = '/posts/post-1.de.md' ++++ +-- content/posts/post-1.de.md -- +--- +title: "Post 1" +--- +-- layouts/_default/single.html -- +{{ .Title }}|{{ .Params.foo }}$ +` + b, err := TestE(t, files) + b.Assert(err, qt.IsNotNil) + b.AssertLogContains(`cascade target path "/posts/post-1.de.md" looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see https://gohugo.io/methods/page/path/`) +} diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go index 35b7766b6..7b785abb6 100644 --- a/hugolib/page__meta.go +++ b/hugolib/page__meta.go @@ -33,6 +33,7 @@ import ( "github.com/gohugoio/hugo/common/constants" "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/config" @@ -272,7 +273,7 @@ func (p *pageMeta) Weight() int { return p.pageConfig.Weight } -func (p *pageMeta) setMetaPre(pi *contentParseInfo, conf config.AllProvider) error { +func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf config.AllProvider) error { frontmatter := pi.frontMatter if frontmatter != nil { pcfg := p.pageConfig @@ -285,7 +286,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, conf config.AllProvider) err // Check for any cascade define on itself. if cv, found := frontmatter["cascade"]; found { var err error - cascade, err := page.DecodeCascade(cv) + cascade, err := page.DecodeCascade(logger, cv) if err != nil { return err } @@ -437,7 +438,6 @@ func (p *pageState) setMetaPostParams() error { } pm.pageConfig.Build, err = pagemeta.DecodeBuildConfig(buildConfig) if err != nil { - //lint:ignore ST1005 end user message. var msgDetail string if isNewBuildKeyword { msgDetail = `. We renamed the _build keyword to build in Hugo 0.123.0. We recommend putting user defined params in the params section, e.g.: diff --git a/hugolib/page__new.go b/hugolib/page__new.go index d846fe03c..818cbb5db 100644 --- a/hugolib/page__new.go +++ b/hugolib/page__new.go @@ -56,7 +56,7 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) { return nil, nil, err } - if err := m.setMetaPre(pi, h.Conf); err != nil { + if err := m.setMetaPre(pi, h.Log, h.Conf); err != nil { return nil, nil, m.wrapError(err, h.BaseFs.SourceFs) } pcfg := m.pageConfig diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go index fbdb25d72..466b6fe53 100644 --- a/resources/page/page_matcher.go +++ b/resources/page/page_matcher.go @@ -18,6 +18,7 @@ import ( "path/filepath" "strings" + "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/hugofs/glob" @@ -90,7 +91,14 @@ var disallowedCascadeKeys = map[string]bool{ "lang": true, } -func DecodeCascadeConfig(in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, map[PageMatcher]maps.Params], error) { +// See issue 11977. +func isGlobWithExtension(s string) bool { + pathParts := strings.Split(s, "/") + last := pathParts[len(pathParts)-1] + return strings.Count(last, ".") > 0 +} + +func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, map[PageMatcher]maps.Params], error) { buildConfig := func(in any) (map[PageMatcher]maps.Params, any, error) { cascade := make(map[PageMatcher]maps.Params) if in == nil { @@ -119,6 +127,9 @@ func DecodeCascadeConfig(in any) (*config.ConfigNamespace[[]PageMatcherParamsCon for _, cfg := range cfgs { m := cfg.Target + if isGlobWithExtension(m.Path) { + logger.Erroridf("cascade-pattern-with-extension", "cascade target path %q looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see https://gohugo.io/methods/page/path/", m.Path) + } c, found := cascade[m] if found { // Merge @@ -139,8 +150,8 @@ func DecodeCascadeConfig(in any) (*config.ConfigNamespace[[]PageMatcherParamsCon } // DecodeCascade decodes in which could be either a map or a slice of maps. -func DecodeCascade(in any) (map[PageMatcher]maps.Params, error) { - conf, err := DecodeCascadeConfig(in) +func DecodeCascade(logger loggers.Logger, in any) (map[PageMatcher]maps.Params, error) { + conf, err := DecodeCascadeConfig(logger, in) if err != nil { return nil, err } diff --git a/resources/page/page_matcher_test.go b/resources/page/page_matcher_test.go index c27a2e9b2..e659eb3b5 100644 --- a/resources/page/page_matcher_test.go +++ b/resources/page/page_matcher_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/gohugoio/hugo/common/hugo" + "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/maps" qt "github.com/frankban/quicktest" @@ -128,7 +129,7 @@ func TestDecodeCascadeConfig(t *testing.T) { }, } - got, err := DecodeCascadeConfig(in) + got, err := DecodeCascadeConfig(loggers.NewDefault(), in) c.Assert(err, qt.IsNil) c.Assert(got, qt.IsNotNil) @@ -150,7 +151,7 @@ func TestDecodeCascadeConfig(t *testing.T) { {Params: maps.Params{"b": string("bv")}, Target: PageMatcher{Kind: "page"}}, }) - got, err = DecodeCascadeConfig(nil) + got, err = DecodeCascadeConfig(loggers.NewDefault(), nil) c.Assert(err, qt.IsNil) c.Assert(got, qt.IsNotNil) } @@ -172,3 +173,17 @@ func (c testConfig) Running() bool { func (c testConfig) WorkingDir() string { return c.workingDir } + +func TestIsGlobWithExtension(t *testing.T) { + c := qt.New(t) + + c.Assert(isGlobWithExtension("index.md"), qt.Equals, true) + c.Assert(isGlobWithExtension("foo/index.html"), qt.Equals, true) + c.Assert(isGlobWithExtension("posts/page"), qt.Equals, false) + c.Assert(isGlobWithExtension("pa.th/foo"), qt.Equals, false) + c.Assert(isGlobWithExtension(""), qt.Equals, false) + c.Assert(isGlobWithExtension("*.md?"), qt.Equals, true) + c.Assert(isGlobWithExtension("*.md*"), qt.Equals, true) + c.Assert(isGlobWithExtension("posts/*"), qt.Equals, false) + c.Assert(isGlobWithExtension("*.md"), qt.Equals, true) +}