hugo

Unnamed repository; edit this file 'description' to name the repository.

git clone git://git.shimmy1996.com/hugo.git
commit 5cb52c23150032b3fdb211a095745c512369b463
parent 30eea3915b67f72611a3b2f4547146d4c6a96864
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date:   Fri,  9 Jul 2021 11:52:03 +0200

Add config.cascade

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

Diffstat:
Mconfig/defaultConfigProvider.go | 2+-
Mhugolib/cascade_test.go | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhugolib/content_map_page.go | 3+++
Mhugolib/page__meta.go | 32++++----------------------------
Mhugolib/site.go | 22+++++++++++++++++++---
Mresources/page/page_matcher.go | 37+++++++++++++++++++++++++++++++++++++
6 files changed, 129 insertions(+), 32 deletions(-)
diff --git a/config/defaultConfigProvider.go b/config/defaultConfigProvider.go
@@ -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,
diff --git a/hugolib/cascade_test.go b/hugolib/cascade_test.go
@@ -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"}
 
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
@@ -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
 		}
 
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
@@ -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
 				}
 			}
 		}
diff --git a/hugolib/site.go b/hugolib/site.go
@@ -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,
diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go
@@ -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 {