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
This commit is contained in:
parent
30eea3915b
commit
5cb52c2315
6 changed files with 129 additions and 32 deletions
|
@ -27,10 +27,10 @@ import (
|
||||||
var (
|
var (
|
||||||
|
|
||||||
// ConfigRootKeysSet contains all of the config map root keys.
|
// ConfigRootKeysSet contains all of the config map root keys.
|
||||||
// TODO(bep) use this for something (docs etc.)
|
|
||||||
ConfigRootKeysSet = map[string]bool{
|
ConfigRootKeysSet = map[string]bool{
|
||||||
"build": true,
|
"build": true,
|
||||||
"caches": true,
|
"caches": true,
|
||||||
|
"cascade": true,
|
||||||
"frontmatter": true,
|
"frontmatter": true,
|
||||||
"languages": true,
|
"languages": true,
|
||||||
"imaging": true,
|
"imaging": true,
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
"github.com/gohugoio/hugo/parser"
|
"github.com/gohugoio/hugo/parser"
|
||||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
"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) {
|
func TestCascade(t *testing.T) {
|
||||||
allLangs := []string{"en", "nn", "nb", "sv"}
|
allLangs := []string{"en", "nn", "nb", "sv"}
|
||||||
|
|
||||||
|
|
|
@ -462,10 +462,13 @@ func (m *pageMap) assembleSections() error {
|
||||||
|
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
parentBucket = parent.p.bucket
|
parentBucket = parent.p.bucket
|
||||||
|
} else if s == "/" {
|
||||||
|
parentBucket = m.s.siteBucket
|
||||||
}
|
}
|
||||||
|
|
||||||
kind := page.KindSection
|
kind := page.KindSection
|
||||||
if s == "/" {
|
if s == "/" {
|
||||||
|
|
||||||
kind = page.KindHome
|
kind = page.KindHome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -340,34 +340,10 @@ func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, fron
|
||||||
if p.bucket != nil {
|
if p.bucket != nil {
|
||||||
// Check for any cascade define on itself.
|
// Check for any cascade define on itself.
|
||||||
if cv, found := frontmatter["cascade"]; found {
|
if cv, found := frontmatter["cascade"]; found {
|
||||||
if v, err := maps.ToSliceStringMap(cv); err == nil {
|
var err error
|
||||||
p.bucket.cascade = make(map[page.PageMatcher]maps.Params)
|
p.bucket.cascade, err = page.DecodeCascade(cv)
|
||||||
|
if err != nil {
|
||||||
for _, vv := range v {
|
return err
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ import (
|
||||||
type Site struct {
|
type Site struct {
|
||||||
|
|
||||||
// The owning container. When multiple languages, there will be multiple
|
// The owning container. When multiple languages, there will be multiple
|
||||||
// sites.
|
// sites .
|
||||||
h *HugoSites
|
h *HugoSites
|
||||||
|
|
||||||
*PageCollections
|
*PageCollections
|
||||||
|
@ -113,7 +113,8 @@ type Site struct {
|
||||||
Sections Taxonomy
|
Sections Taxonomy
|
||||||
Info *SiteInfo
|
Info *SiteInfo
|
||||||
|
|
||||||
language *langs.Language
|
language *langs.Language
|
||||||
|
siteBucket *pagesMapBucket
|
||||||
|
|
||||||
siteCfg siteConfigHolder
|
siteCfg siteConfigHolder
|
||||||
|
|
||||||
|
@ -388,6 +389,7 @@ func (s *Site) reset() *Site {
|
||||||
frontmatterHandler: s.frontmatterHandler,
|
frontmatterHandler: s.frontmatterHandler,
|
||||||
mediaTypesConfig: s.mediaTypesConfig,
|
mediaTypesConfig: s.mediaTypesConfig,
|
||||||
language: s.language,
|
language: s.language,
|
||||||
|
siteBucket: s.siteBucket,
|
||||||
h: s.h,
|
h: s.h,
|
||||||
publisher: s.publisher,
|
publisher: s.publisher,
|
||||||
siteConfigConfig: s.siteConfigConfig,
|
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"),
|
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,
|
language: cfg.Language,
|
||||||
|
siteBucket: siteBucket,
|
||||||
disabledKinds: disabledKinds,
|
disabledKinds: disabledKinds,
|
||||||
|
|
||||||
outputFormats: outputFormats,
|
outputFormats: outputFormats,
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
"github.com/gohugoio/hugo/hugofs/glob"
|
"github.com/gohugoio/hugo/hugofs/glob"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
@ -70,6 +71,42 @@ func (m PageMatcher) Matches(p Page) bool {
|
||||||
return true
|
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.
|
// DecodePageMatcher decodes m into v.
|
||||||
func DecodePageMatcher(m interface{}, v *PageMatcher) error {
|
func DecodePageMatcher(m interface{}, v *PageMatcher) error {
|
||||||
if err := mapstructure.WeakDecode(m, v); err != nil {
|
if err := mapstructure.WeakDecode(m, v); err != nil {
|
||||||
|
|
Loading…
Add table
Reference in a new issue