Add config.cascade
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 9 Jul 2021 09:52:03 +0000 (11:52 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sat, 10 Jul 2021 09:13:41 +0000 (11:13 +0200)
This commit adds support for using the `cascade` keyword in your configuration file(s), e.g. `config.toml`.

Note that

* Every feature of `cascade` is available, e.g. `_target` to target specific page sets.
* Pages, e.g. the home page, can overwrite the cascade defined in config.

Fixes #8741

config/defaultConfigProvider.go
hugolib/cascade_test.go
hugolib/content_map_page.go
hugolib/page__meta.go
hugolib/site.go
resources/page/page_matcher.go

index 9f1c44ee12a0b51acb16863bf836f9c163a38cba..05c3b9126aafe880889b59a4eedf9030be492b35 100644 (file)
@@ -27,10 +27,10 @@ import (
 var (
 
        // ConfigRootKeysSet contains all of the config map root keys.
-       // TODO(bep) use this for something (docs etc.)
        ConfigRootKeysSet = map[string]bool{
                "build":         true,
                "caches":        true,
+               "cascade":       true,
                "frontmatter":   true,
                "languages":     true,
                "imaging":       true,
index 78409a4b1d57f427c6309932a0f05fc6ddaaea64..000b641e54da81ed019b4a22e4f0bfb7967114bc 100644 (file)
@@ -20,6 +20,8 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/common/maps"
+
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/parser"
        "github.com/gohugoio/hugo/parser/metadecoders"
@@ -50,6 +52,69 @@ func BenchmarkCascade(b *testing.B) {
        }
 }
 
+func TestCascadeConfig(t *testing.T) {
+       c := qt.New(t)
+
+       // Make sure the cascade from config gets applied even if we're not
+       // having a content file for the home page.
+       for _, withHomeContent := range []bool{true, false} {
+               testName := "Home content file"
+               if !withHomeContent {
+                       testName = "No home content file"
+               }
+               c.Run(testName, func(c *qt.C) {
+                       b := newTestSitesBuilder(c)
+
+                       b.WithConfigFile("toml", `
+baseURL="https://example.org"
+
+[cascade]
+img1 = "img1-config.jpg"
+imgconfig = "img-config.jpg"
+
+`)
+
+                       if withHomeContent {
+                               b.WithContent("_index.md", `
+---
+title: "Home"
+cascade:
+  img1: "img1-home.jpg"
+  img2: "img2-home.jpg"
+---
+`)
+                       }
+
+                       b.WithContent("p1.md", ``)
+
+                       b.Build(BuildCfg{})
+
+                       p1 := b.H.Sites[0].getPage("p1")
+
+                       if withHomeContent {
+                               b.Assert(p1.Params(), qt.DeepEquals, maps.Params{
+                                       "imgconfig":     "img-config.jpg",
+                                       "draft":         bool(false),
+                                       "iscjklanguage": bool(false),
+                                       "img1":          "img1-home.jpg",
+                                       "img2":          "img2-home.jpg",
+                               })
+                       } else {
+                               b.Assert(p1.Params(), qt.DeepEquals, maps.Params{
+                                       "img1":          "img1-config.jpg",
+                                       "imgconfig":     "img-config.jpg",
+                                       "draft":         bool(false),
+                                       "iscjklanguage": bool(false),
+                               })
+
+                       }
+
+               })
+
+       }
+
+}
+
 func TestCascade(t *testing.T) {
        allLangs := []string{"en", "nn", "nb", "sv"}
 
index 74dd0e02904a6533b74512491df54f9b672b609d..278d24766d2ccb1ef2c8a8c4a58657122cd9a8a2 100644 (file)
@@ -462,10 +462,13 @@ func (m *pageMap) assembleSections() error {
 
                if parent != nil {
                        parentBucket = parent.p.bucket
+               } else if s == "/" {
+                       parentBucket = m.s.siteBucket
                }
 
                kind := page.KindSection
                if s == "/" {
+
                        kind = page.KindHome
                }
 
index 759fadd2d405b083bfe99f56ddda05cee0c5fbaf..3b31cb29971f0c94cc0b0b143314e7193c30a28c 100644 (file)
@@ -340,34 +340,10 @@ func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, fron
                if p.bucket != nil {
                        // Check for any cascade define on itself.
                        if cv, found := frontmatter["cascade"]; found {
-                               if v, err := maps.ToSliceStringMap(cv); err == nil {
-                                       p.bucket.cascade = make(map[page.PageMatcher]maps.Params)
-
-                                       for _, vv := range v {
-                                               var m page.PageMatcher
-                                               if mv, found := vv["_target"]; found {
-                                                       err := page.DecodePageMatcher(mv, &m)
-                                                       if err != nil {
-                                                               return err
-                                                       }
-                                               }
-                                               c, found := p.bucket.cascade[m]
-                                               if found {
-                                                       // Merge
-                                                       for k, v := range vv {
-                                                               if _, found := c[k]; !found {
-                                                                       c[k] = v
-                                                               }
-                                                       }
-                                               } else {
-                                                       p.bucket.cascade[m] = vv
-                                               }
-
-                                       }
-                               } else {
-                                       p.bucket.cascade = map[page.PageMatcher]maps.Params{
-                                               {}: maps.ToStringMap(cv),
-                                       }
+                               var err error
+                               p.bucket.cascade, err = page.DecodeCascade(cv)
+                               if err != nil {
+                                       return err
                                }
                        }
                }
index 2e7e1d7f9b01f71902a3703dacff0d7df4779ff8..fe7305b918242a6c5d9b51bb450d9f822d49fa89 100644 (file)
@@ -103,7 +103,7 @@ import (
 type Site struct {
 
        // The owning container. When multiple languages, there will be multiple
-       // sites.
+       // sites .
        h *HugoSites
 
        *PageCollections
@@ -113,7 +113,8 @@ type Site struct {
        Sections Taxonomy
        Info     *SiteInfo
 
-       language *langs.Language
+       language   *langs.Language
+       siteBucket *pagesMapBucket
 
        siteCfg siteConfigHolder
 
@@ -388,6 +389,7 @@ func (s *Site) reset() *Site {
                frontmatterHandler:     s.frontmatterHandler,
                mediaTypesConfig:       s.mediaTypesConfig,
                language:               s.language,
+               siteBucket:             s.siteBucket,
                h:                      s.h,
                publisher:              s.publisher,
                siteConfigConfig:       s.siteConfigConfig,
@@ -539,9 +541,23 @@ But this also means that your site configuration may not do what you expect. If
                enableEmoji:      cfg.Language.Cfg.GetBool("enableEmoji"),
        }
 
-       s := &Site{
+       var siteBucket *pagesMapBucket
+       if cfg.Language.IsSet("cascade") {
+               var err error
+               cascade, err := page.DecodeCascade(cfg.Language.Get("cascade"))
+               if err != nil {
+                       return nil, errors.Errorf("failed to decode cascade config: %s", err)
+               }
 
+               siteBucket = &pagesMapBucket{
+                       cascade: cascade,
+               }
+
+       }
+
+       s := &Site{
                language:      cfg.Language,
+               siteBucket:    siteBucket,
                disabledKinds: disabledKinds,
 
                outputFormats:       outputFormats,
index 7d3db373bf32d7c883180746013eba0c0ea91ac7..8e81f810d8413216c992c8655c5cd6e13c10d52c 100644 (file)
@@ -19,6 +19,7 @@ import (
 
        "github.com/pkg/errors"
 
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/hugofs/glob"
        "github.com/mitchellh/mapstructure"
 )
@@ -70,6 +71,42 @@ func (m PageMatcher) Matches(p Page) bool {
        return true
 }
 
+// DecodeCascade decodes in which could be eiter a map or a slice of maps.
+func DecodeCascade(in interface{}) (map[PageMatcher]maps.Params, error) {
+       m, err := maps.ToSliceStringMap(in)
+       if err != nil {
+               return map[PageMatcher]maps.Params{
+                       {}: maps.ToStringMap(in),
+               }, nil
+       }
+
+       cascade := make(map[PageMatcher]maps.Params)
+
+       for _, vv := range m {
+               var m PageMatcher
+               if mv, found := vv["_target"]; found {
+                       err := DecodePageMatcher(mv, &m)
+                       if err != nil {
+                               return nil, err
+                       }
+               }
+               c, found := cascade[m]
+               if found {
+                       // Merge
+                       for k, v := range vv {
+                               if _, found := c[k]; !found {
+                                       c[k] = v
+                               }
+                       }
+               } else {
+                       cascade[m] = vv
+               }
+       }
+
+       return cascade, nil
+
+}
+
 // DecodePageMatcher decodes m into v.
 func DecodePageMatcher(m interface{}, v *PageMatcher) error {
        if err := mapstructure.WeakDecode(m, v); err != nil {