Skip to content

Commit

Permalink
Implement cascading front matter
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Aug 10, 2019
1 parent 7ff0a8e commit c88506e
Show file tree
Hide file tree
Showing 11 changed files with 384 additions and 62 deletions.
173 changes: 173 additions & 0 deletions hugolib/cascade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hugolib

import (
"bytes"
"testing"

"github.com/alecthomas/assert"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
)

func TestCascade(t *testing.T) {
assert := assert.New(t)

p := func(m map[string]interface{}) string {
var yamlStr string

if len(m) > 0 {
var b bytes.Buffer

parser.InterfaceToConfig(m, metadecoders.YAML, &b)
yamlStr = b.String()
}

metaStr := "---\n" + yamlStr + "\n---"

return metaStr

}

b := newTestSitesBuilder(t).WithConfigFile("toml", `
baseURL = "https://example.org"
`)

// TODO(bep) case
b.WithContent(
"_index.md", p(map[string]interface{}{
"title": "Home",
"cascade": map[string]interface{}{
"title": "Cascade Home",
"ICoN": "home.png",
"outputs": []string{"HTML"},
"weight": 42,
},
}),
"p1.md", p(map[string]interface{}{
"title": "p1",
}),
"p2.md", p(map[string]interface{}{}),
"sect1/_index.md", p(map[string]interface{}{
"title": "Sect1",
"type": "stype",
"cascade": map[string]interface{}{
"title": "Cascade Sect1",
"icon": "sect1.png",
"type": "stype",
"categories": []string{"catsect1"},
},
}),
"sect1/s1_2/_index.md", p(map[string]interface{}{
"title": "Sect1_2",
}),
"sect1/s1_2/p1.md", p(map[string]interface{}{
"title": "Sect1_2_p1",
}),
"sect1/s1_2/p2.md", p(map[string]interface{}{
"title": "Sect1_2_p2",
}),
"sect2/_index.md", p(map[string]interface{}{
"title": "Sect2",
}),
"sect2/p1.md", p(map[string]interface{}{
"title": "Sect2_p1",
"categories": []string{"cool", "funny", "sad"},
"tags": []string{"blue", "green"},
}),
"sect2/p2.md", p(map[string]interface{}{}),
"sect3/p1.md", p(map[string]interface{}{}),
"sect4/_index.md", p(map[string]interface{}{
"title": "Sect4",
"cascade": map[string]interface{}{
"weight": 52,
"outputs": []string{"RSS"},
},
}),
"sect4/p1.md", p(map[string]interface{}{}),
"p2.md", p(map[string]interface{}{}),
"bundle1/index.md", p(map[string]interface{}{}),
"bundle1/bp1.md", p(map[string]interface{}{}),
"categories/_index.md", p(map[string]interface{}{
"title": "My Categories",
"cascade": map[string]interface{}{
"title": "Cascade Category",
"icoN": "cat.png",
"weight": 12,
},
}),
"categories/cool/_index.md", p(map[string]interface{}{}),
"categories/sad/_index.md", p(map[string]interface{}{
"cascade": map[string]interface{}{
"icon": "sad.png",
"weight": 32,
},
}),
)

b.WithTemplates("index.html", `
{{ range .Site.Pages }}
{{- .Weight }}|{{ .Kind }}|{{ .Path }}|{{ .Title }}|{{ .Params.icon }}|{{ .Type }}|{{ range .OutputFormats }}{{ .Name }}-{{ end }}|
{{ end }}
`,

"_default/single.html", "default single: {{ .Title }}|{{ .Content }}|Resources: {{ range .Resources }}{{ .Name }}|{{ .Params.icon }}|{{ .Content }}{{ end }}",
"_default/list.html", "default list: {{ .Title }}",
"stype/single.html", "stype single: {{ .Title }}|{{ .Content }}",
"stype/list.html", "stype list: {{ .Title }}",
)

b.Build(BuildCfg{})

b.AssertFileContent("public/index.html", `
12|taxonomy|categories/cool/_index.md|Cascade Category|cat.png|categories|HTML-|
12|taxonomy|categories/catsect1|catsect1|cat.png|categories|HTML-|
12|taxonomy|categories/funny|funny|cat.png|categories|HTML-|
12|taxonomyTerm|categories/_index.md|My Categories|cat.png|categories|HTML-|
32|taxonomy|categories/sad/_index.md|Cascade Category|sad.png|categories|HTML-|
42|taxonomy|tags/blue|blue|home.png|tags|HTML-|
42|section|sect3|Cascade Home|home.png|sect3|HTML-|
42|taxonomyTerm|tags|Cascade Home|home.png|tags|HTML-|
42|page|p2.md|Cascade Home|home.png|page|HTML-|
42|page|sect2/p2.md|Cascade Home|home.png|sect2|HTML-|
42|page|sect3/p1.md|Cascade Home|home.png|sect3|HTML-|
42|taxonomy|tags/green|green|home.png|tags|HTML-|
42|home|_index.md|Home|home.png|page|HTML-|
42|page|p1.md|p1|home.png|page|HTML-|
42|section|sect1/_index.md|Sect1|sect1.png|stype|HTML-|
42|section|sect1/s1_2/_index.md|Sect1_2|sect1.png|stype|HTML-|
42|page|sect1/s1_2/p1.md|Sect1_2_p1|sect1.png|stype|HTML-|
42|page|sect1/s1_2/p2.md|Sect1_2_p2|sect1.png|stype|HTML-|
42|section|sect2/_index.md|Sect2|home.png|sect2|HTML-|
42|page|sect2/p1.md|Sect2_p1|home.png|sect2|HTML-|
52|page|sect4/p1.md|Cascade Home|home.png|sect4|RSS-|
52|section|sect4/_index.md|Sect4|home.png|sect4|RSS-|
`)

// Check that type set in cascade gets the correct layout.
b.AssertFileContent("public/sect1/index.html", `stype list: Sect1`)
b.AssertFileContent("public/sect1/s1_2/p2/index.html", `stype single: Sect1_2_p2`)

// Check output formats set in cascade
b.AssertFileContent("public/sect4/index.xml", `<link>https://example.org/sect4/index.xml</link>`)
b.AssertFileContent("public/sect4/p1/index.xml", `<link>https://example.org/sect4/p1/index.xml</link>`)
assert.False(b.CheckExists("public/sect2/index.xml"))

// Check cascade into bundled page
b.AssertFileContent("public/bundle1/index.html", `Resources: bp1.md|home.png|`)

}
3 changes: 1 addition & 2 deletions hugolib/collections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ tags_weight: %d
b.WithSimpleConfigFile().
WithContent("page1.md", fmt.Sprintf(pageContent, 10), "page2.md", fmt.Sprintf(pageContent, 20)).
WithTemplatesAdded("index.html", `
{{ $p1 := index .Site.RegularPages 0 }}{{ $p2 := index .Site.RegularPages 1 }}
{{ $pages := slice }}
Expand All @@ -205,7 +204,7 @@ tags_weight: %d
b.CreateSites().Build(BuildCfg{})

assert.Equal(1, len(b.H.Sites))
require.Len(t, b.H.Sites[0].RegularPages(), 2)
assert.Len(b.H.Sites[0].RegularPages(), 2)

b.AssertFileContent("public/index.html",
"pages:2:page.Pages:Page(/page2.md)/Page(/page1.md)",
Expand Down
4 changes: 4 additions & 0 deletions hugolib/hugo_sites_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ func (h *HugoSites) assemble(config *BuildCfg) error {
return err
}

if err := s.pagesMap.assemblePageMeta(); err != nil {
return err
}

if err := s.pagesMap.assembleTaxonomies(s); err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions hugolib/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ func (p *pageState) addResources(r ...resource.Resource) {
p.resources = append(p.resources, r...)
}

func (p *pageState) mapContent(meta *pageMeta) error {
func (p *pageState) mapContent(bucket *pagesMapBucket, meta *pageMeta) error {

s := p.shortcodeState

Expand Down Expand Up @@ -563,7 +563,7 @@ Loop:
}
}

if err := meta.setMetadata(p, m); err != nil {
if err := meta.setMetadata(bucket, p, m); err != nil {
return err
}

Expand Down
3 changes: 3 additions & 0 deletions hugolib/page__common.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type pageCommon struct {
// Laziliy initialized dependencies.
init *lazy.Init

metaInit sync.Once
metaInitFn func(bucket *pagesMapBucket) error

// All of these represents the common parts of a page.Page
maps.Scratcher
navigation.PageMenusProvider
Expand Down
48 changes: 40 additions & 8 deletions hugolib/page__meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,19 +306,51 @@ func (p *pageMeta) Weight() int {
return p.weight
}

func (pm *pageMeta) setMetadata(p *pageState, frontmatter map[string]interface{}) error {
if frontmatter == nil {
func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatter map[string]interface{}) error {
if frontmatter == nil && bucket.cascade == nil {
return errors.New("missing frontmatter data")
}

pm.params = make(map[string]interface{})

// Needed for case insensitive fetching of params values
maps.ToLower(frontmatter)
if frontmatter != nil {
// Needed for case insensitive fetching of params values
maps.ToLower(frontmatter)
if p.IsNode() {
// Check for any cascade define on itself.
if cv, found := frontmatter["cascade"]; found {
cvm := cast.ToStringMap(cv)
if bucket.cascade == nil {
bucket.cascade = cvm
} else {
for k, v := range cvm {
bucket.cascade[k] = v
}
}
}
}

if bucket != nil && bucket.cascade != nil {
for k, v := range bucket.cascade {
if _, found := frontmatter[k]; !found {
frontmatter[k] = v
}
}
}
} else {
frontmatter = make(map[string]interface{})
for k, v := range bucket.cascade {
frontmatter[k] = v
}
}

var mtime time.Time
if p.File().FileInfo() != nil {
mtime = p.File().FileInfo().ModTime()
var contentBaseName string
if !p.File().IsZero() {
contentBaseName = p.File().ContentBaseName()
if p.File().FileInfo() != nil {
mtime = p.File().FileInfo().ModTime()
}
}

var gitAuthorDate time.Time
Expand All @@ -331,7 +363,7 @@ func (pm *pageMeta) setMetadata(p *pageState, frontmatter map[string]interface{}
Params: pm.params,
Dates: &pm.Dates,
PageURLs: &pm.urlPaths,
BaseFilename: p.File().ContentBaseName(),
BaseFilename: contentBaseName,
ModTime: mtime,
GitAuthorDate: gitAuthorDate,
}
Expand Down Expand Up @@ -546,7 +578,7 @@ func (pm *pageMeta) setMetadata(p *pageState, frontmatter map[string]interface{}

if isCJKLanguage != nil {
pm.isCJKLanguage = *isCJKLanguage
} else if p.s.siteCfg.hasCJKLanguage {
} else if p.s.siteCfg.hasCJKLanguage && p.source.parsed != nil {
if cjkRe.Match(p.source.parsed.Input()) {
pm.isCJKLanguage = true
} else {
Expand Down
40 changes: 31 additions & 9 deletions hugolib/page__new.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func newPageBase(metaProvider *pageMeta) (*pageState, error) {

}

func newPageFromMeta(metaProvider *pageMeta) (*pageState, error) {
func newPageFromMeta(meta map[string]interface{}, metaProvider *pageMeta) (*pageState, error) {
if metaProvider.f == nil {
metaProvider.f = page.NewZeroFile(metaProvider.s.DistinctWarningLog)
}
Expand All @@ -105,8 +105,26 @@ func newPageFromMeta(metaProvider *pageMeta) (*pageState, error) {
return nil, err
}

if err := metaProvider.applyDefaultValues(); err != nil {
return nil, err
initMeta := func(bucket *pagesMapBucket) error {
if meta != nil || bucket != nil {
if err := metaProvider.setMetadata(bucket, ps, meta); err != nil {
return ps.wrapError(err)
}
}

if err := metaProvider.applyDefaultValues(); err != nil {
return err
}

return nil
}

if metaProvider.standalone {
initMeta(nil)
} else {
// Because of possible cascade keywords, we need to delay this
// until we have the complete page graph.
ps.metaInitFn = initMeta
}

ps.init.Add(func() (interface{}, error) {
Expand Down Expand Up @@ -152,7 +170,7 @@ func newPageFromMeta(metaProvider *pageMeta) (*pageState, error) {
func newPageStandalone(m *pageMeta, f output.Format) (*pageState, error) {
m.configuredOutputFormats = output.Formats{f}
m.standalone = true
p, err := newPageFromMeta(m)
p, err := newPageFromMeta(nil, m)

if err != nil {
return nil, err
Expand Down Expand Up @@ -211,12 +229,16 @@ func newPageWithContent(f *fileInfo, s *Site, bundled bool, content resource.Ope

ps.shortcodeState = newShortcodeHandler(ps, ps.s, nil)

if err := ps.mapContent(metaProvider); err != nil {
return nil, ps.wrapError(err)
}
ps.metaInitFn = func(bucket *pagesMapBucket) error {
if err := ps.mapContent(bucket, metaProvider); err != nil {
return ps.wrapError(err)
}

if err := metaProvider.applyDefaultValues(); err != nil {
return nil, err
if err := metaProvider.applyDefaultValues(); err != nil {
return err
}

return nil
}

ps.init.Add(func() (interface{}, error) {
Expand Down
Loading

0 comments on commit c88506e

Please sign in to comment.