From 05b7f0d1a6c1e852851ac9034d78333e424a6ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 1 Aug 2019 14:24:52 +0200 Subject: [PATCH] Implement cascade support in front matter Fixes #6041 Fixes #6153 Fixes #6154 --- hugolib/collections_test.go | 1 + hugolib/disableKinds_test.go | 183 +----------------- hugolib/hugo_sites.go | 122 +++++++++--- hugolib/hugo_sites_build.go | 20 +- hugolib/hugo_sites_build_test.go | 21 +- hugolib/hugo_smoke_test.go | 3 +- hugolib/page.go | 30 +-- hugolib/page__content.go | 4 + hugolib/page__meta.go | 9 + hugolib/pages_metadata_handler.go | 152 +++++++++++++++ hugolib/pages_metadata_handler_test.go | 103 ++++++++++ hugolib/site.go | 9 +- hugolib/site_sections.go | 32 +-- tpl/tplimpl/embedded/templates.autogen.go | 1 + .../embedded/templates/_default/rss.xml | 1 + 15 files changed, 407 insertions(+), 284 deletions(-) create mode 100644 hugolib/pages_metadata_handler.go create mode 100644 hugolib/pages_metadata_handler_test.go diff --git a/hugolib/collections_test.go b/hugolib/collections_test.go index 1a261260ec0..7d525f31b31 100644 --- a/hugolib/collections_test.go +++ b/hugolib/collections_test.go @@ -79,6 +79,7 @@ tags_weight: %d b.CreateSites().Build(BuildCfg{}) assert.Equal(1, len(b.H.Sites)) + require.Len(t, b.H.Sites[0].RegularPages(), 2) b.AssertFileContent("public/index.html", diff --git a/hugolib/disableKinds_test.go b/hugolib/disableKinds_test.go index c191dfef1ef..aff3bd254cc 100644 --- a/hugolib/disableKinds_test.go +++ b/hugolib/disableKinds_test.go @@ -13,190 +13,11 @@ package hugolib import ( - "strings" "testing" - - "fmt" - - "github.com/gohugoio/hugo/resources/page" - - "github.com/gohugoio/hugo/helpers" - "github.com/stretchr/testify/require" ) -func TestDisableKindsNoneDisabled(t *testing.T) { - t.Parallel() - doTestDisableKinds(t) -} - -func TestDisableKindsSomeDisabled(t *testing.T) { - t.Parallel() - doTestDisableKinds(t, page.KindSection, kind404) -} - -func TestDisableKindsOneDisabled(t *testing.T) { - t.Parallel() - for _, kind := range allKinds { - if kind == page.KindPage { - // Turning off regular page generation have some side-effects - // not handled by the assertions below (no sections), so - // skip that for now. - continue - } - doTestDisableKinds(t, kind) - } -} - -func TestDisableKindsAllDisabled(t *testing.T) { +// TODO(bep) cascade add some simpler tests. +func TestDisableKinds(t *testing.T) { t.Parallel() - doTestDisableKinds(t, allKinds...) -} - -func doTestDisableKinds(t *testing.T, disabled ...string) { - siteConfigTemplate := ` -baseURL = "http://example.com/blog" -enableRobotsTXT = true -disableKinds = %s - -paginate = 1 -defaultContentLanguage = "en" - -[Taxonomies] -tag = "tags" -category = "categories" -` - - pageTemplate := `--- -title: "%s" -tags: -%s -categories: -- Hugo ---- -# Doc -` - - disabledStr := "[]" - - if len(disabled) > 0 { - disabledStr = strings.Replace(fmt.Sprintf("%#v", disabled), "[]string{", "[", -1) - disabledStr = strings.Replace(disabledStr, "}", "]", -1) - } - - siteConfig := fmt.Sprintf(siteConfigTemplate, disabledStr) - - b := newTestSitesBuilder(t).WithConfigFile("toml", siteConfig) - - b.WithTemplates( - "index.html", "Home|{{ .Title }}|{{ .Content }}", - "_default/single.html", "Single|{{ .Title }}|{{ .Content }}", - "_default/list.html", "List|{{ .Title }}|{{ .Content }}", - "_default/terms.html", "Terms List|{{ .Title }}|{{ .Content }}", - "layouts/404.html", "Page Not Found", - ) - - b.WithContent( - "sect/p1.md", fmt.Sprintf(pageTemplate, "P1", "- tag1"), - "categories/_index.md", newTestPage("Category Terms", "2017-01-01", 10), - "tags/tag1/_index.md", newTestPage("Tag1 List", "2017-01-01", 10), - ) - - b.Build(BuildCfg{}) - h := b.H - - require.Len(t, h.Sites, 1) - - assertDisabledKinds(b, h.Sites[0], disabled...) - -} - -func assertDisabledKinds(b *sitesBuilder, s *Site, disabled ...string) { - assertDisabledKind(b, - func(isDisabled bool) bool { - if isDisabled { - return len(s.RegularPages()) == 0 - } - return len(s.RegularPages()) > 0 - }, disabled, page.KindPage, "public/sect/p1/index.html", "Single|P1") - assertDisabledKind(b, - func(isDisabled bool) bool { - p := s.getPage(page.KindHome) - if isDisabled { - return p == nil - } - return p != nil - }, disabled, page.KindHome, "public/index.html", "Home") - assertDisabledKind(b, - func(isDisabled bool) bool { - p := s.getPage(page.KindSection, "sect") - if isDisabled { - return p == nil - } - return p != nil - }, disabled, page.KindSection, "public/sect/index.html", "Sects") - assertDisabledKind(b, - func(isDisabled bool) bool { - p := s.getPage(page.KindTaxonomy, "tags", "tag1") - - if isDisabled { - return p == nil - } - return p != nil - - }, disabled, page.KindTaxonomy, "public/tags/tag1/index.html", "Tag1") - assertDisabledKind(b, - func(isDisabled bool) bool { - p := s.getPage(page.KindTaxonomyTerm, "tags") - if isDisabled { - return p == nil - } - return p != nil - - }, disabled, page.KindTaxonomyTerm, "public/tags/index.html", "Tags") - assertDisabledKind(b, - func(isDisabled bool) bool { - p := s.getPage(page.KindTaxonomyTerm, "categories") - - if isDisabled { - return p == nil - } - return p != nil - - }, disabled, page.KindTaxonomyTerm, "public/categories/index.html", "Category Terms") - assertDisabledKind(b, - func(isDisabled bool) bool { - p := s.getPage(page.KindTaxonomy, "categories", "hugo") - if isDisabled { - return p == nil - } - return p != nil - - }, disabled, page.KindTaxonomy, "public/categories/hugo/index.html", "Hugo") - // The below have no page in any collection. - assertDisabledKind(b, func(isDisabled bool) bool { return true }, disabled, kindRSS, "public/index.xml", "") - assertDisabledKind(b, func(isDisabled bool) bool { return true }, disabled, kindSitemap, "public/sitemap.xml", "sitemap") - assertDisabledKind(b, func(isDisabled bool) bool { return true }, disabled, kindRobotsTXT, "public/robots.txt", "User-agent") - assertDisabledKind(b, func(isDisabled bool) bool { return true }, disabled, kind404, "public/404.html", "Page Not Found") -} - -func assertDisabledKind(b *sitesBuilder, kindAssert func(bool) bool, disabled []string, kind, path, matcher string) { - isDisabled := stringSliceContains(kind, disabled...) - require.True(b.T, kindAssert(isDisabled), fmt.Sprintf("%s: %t", kind, isDisabled)) - - if kind == kindRSS && !isDisabled { - // If the home page is also disabled, there is not RSS to look for. - if stringSliceContains(page.KindHome, disabled...) { - isDisabled = true - } - } - - if isDisabled { - // Path should not exist - fileExists, err := helpers.Exists(path, b.Fs.Destination) - require.False(b.T, fileExists) - require.NoError(b.T, err) - } else { - b.AssertFileContent(path, matcher) - } } diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 6ad8715645b..4581b1cab50 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -47,6 +47,7 @@ import ( "github.com/gohugoio/hugo/langs/i18n" "github.com/gohugoio/hugo/resources/page" + "github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/tpl" "github.com/gohugoio/hugo/tpl/tplimpl" ) @@ -623,31 +624,69 @@ func (h *HugoSites) renderCrossSitesArtifacts() error { s.siteCfg.sitemap.Filename, h.toSiteInfos(), smLayouts...) } -// createMissingPages creates home page, taxonomies etc. that isnt't created as an -// effect of having a content file. -func (h *HugoSites) createMissingPages() error { - +// assignMetaData parses any front matter stored away and merges it with any +// cascading data available. +// TODO(bep) cascade workers +func (h *HugoSites) assignMetaData() error { for _, s := range h.Sites { - if s.isEnabled(page.KindHome) { - // home pages - homes := s.findWorkPagesByKind(page.KindHome) - if len(homes) > 1 { - panic("Too many homes") - } - var home *pageState - if len(homes) == 0 { - home = s.newPage(page.KindHome) - s.workAllPages = append(s.workAllPages, home) - } else { - home = homes[0] + section := s.home + if section == nil { + panic("no home set") + } + + ma := &pagesMetadataHandler{dates: &resource.Dates{}} + + if err := ma.handleSection(nil, section); err != nil { + return err + } + + if ma.dates != nil { + section.m.Dates = *ma.dates + } + + // The headless pages currently lives outside the main tree, which + // is unfortunate. + // TODO(bep) improve + for _, p := range s.headlessPages { + if _, err := ma.parseAndAssignMeta(false, nil, p); err != nil { + return err } + } + } + + return nil +} - s.home = home +// Will create sections (including home) with not content page. +func (h *HugoSites) createMissingSectionPages() error { + for _, s := range h.Sites { + // home pages + homes := s.findWorkPagesByKind(page.KindHome) + if len(homes) > 1 { + panic("Too many homes") + } + var home *pageState + if len(homes) == 0 { + home = s.newPage(page.KindHome) + s.workAllPages = append(s.workAllPages, home) + } else { + home = homes[0] } + s.home = home + // Will create content-less root sections. newSections := s.assembleSections() s.workAllPages = append(s.workAllPages, newSections...) + } + + return nil +} + +// Will create taxonomy term/list pages with not content page. +func (h *HugoSites) createMissingTaxonomyPages() error { + + for _, s := range h.Sites { taxonomyTermEnabled := s.isEnabled(page.KindTaxonomyTerm) taxonomyEnabled := s.isEnabled(page.KindTaxonomy) @@ -741,25 +780,46 @@ func (h *HugoSites) removePageByFilename(filename string) { } } -func (h *HugoSites) createPageCollections() error { +func (h *HugoSites) removeNoBuildPages() error { for _, s := range h.Sites { - for _, p := range s.rawAllPages { - if !s.isEnabled(p.Kind()) { - continue - } + s.workAllPages = s.filterNoBuildPages(s.workAllPages) + s.workAllPages, s.headlessPages = s.filterHeadlessPages(s.workAllPages) - shouldBuild := s.shouldBuild(p) - s.buildStats.update(p) - if shouldBuild { - if p.m.headless { - s.headlessPages = append(s.headlessPages, p) - } else { - s.workAllPages = append(s.workAllPages, p) - } - } + } + + return nil +} + +func (s *Site) filterNoBuildPages(pages pageStatePages) pageStatePages { + tmp := pages[:0] + for _, p := range pages { + if s.shouldBuild(p) { + tmp = append(tmp, p) + } + } + return tmp +} + +func (s *Site) filterHeadlessPages(pages pageStatePages) (pageStatePages, pageStatePages) { + tmp := pages[:0] + headless := make(pageStatePages, 0) + for _, p := range pages { + if p.m.headless { + headless = append(headless, p) + } else { + tmp = append(tmp, p) } } + return tmp, headless +} + +func (h *HugoSites) createPageCollections() error { + for _, s := range h.Sites { + s.workAllPages = make(pageStatePages, len(s.rawAllPages)) + copy(s.workAllPages, s.rawAllPages) + } + // TODO(bep) move these allPages := newLazyPagesFactory(func() page.Pages { var pages page.Pages for _, s := range h.Sites { diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index d20932599c3..adcd3c5a4a5 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -239,7 +239,18 @@ func (h *HugoSites) assemble(config *BuildCfg) error { return err } + // Create pages for the section pages etc. without content file. + if err := h.createMissingSectionPages(); err != nil { + return err + } + if config.whatChanged.source { + // TODO(bep) cascade taxo meta? + // TODO(bep) cascade consider empty sections with expired etc. + if err := h.assignMetaData(); err != nil { + return err + } + for _, s := range h.Sites { if err := s.assembleTaxonomies(); err != nil { return err @@ -247,8 +258,13 @@ func (h *HugoSites) assemble(config *BuildCfg) error { } } - // Create pagexs for the section pages etc. without content file. - if err := h.createMissingPages(); err != nil { + // Remove drafts, future posts etc. + if err := h.removeNoBuildPages(); err != nil { + return nil + } + + // Create pages for the taxonomies without a content file. + if err := h.createMissingTaxonomyPages(); err != nil { return err } diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index 876f21cfa6d..3d412f6c136 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -245,10 +245,8 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { require.Equal(t, "en", enSite.language.Lang) - //dumpPages(enSite.RegularPages()...) - assert.Equal(5, len(enSite.RegularPages())) - assert.Equal(32, len(enSite.AllPages())) + assert.Equal(34, len(enSite.AllPages())) // Check 404s b.AssertFileContent("public/en/404.html", "404|en|404 Page not found") @@ -286,7 +284,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { require.Equal(t, "fr", frSite.language.Lang) require.Len(t, frSite.RegularPages(), 4, "should have 3 pages") - require.Len(t, frSite.AllPages(), 32, "should have 32 total pages (including translations and nodes)") + require.Len(t, frSite.AllPages(), 34, "should have 34 total pages (including translations and nodes)") for _, frenchPage := range frSite.RegularPages() { p := frenchPage @@ -470,13 +468,6 @@ func TestMultiSitesRebuild(t *testing.T) { func(t *testing.T) { assert.Len(enSite.RegularPages(), 4, "1 en removed") - // Check build stats - require.Equal(t, 1, enSite.buildStats.draftCount, "Draft") - require.Equal(t, 1, enSite.buildStats.futureCount, "Future") - require.Equal(t, 1, enSite.buildStats.expiredCount, "Expired") - require.Equal(t, 0, frSite.buildStats.draftCount, "Draft") - require.Equal(t, 1, frSite.buildStats.futureCount, "Future") - require.Equal(t, 1, frSite.buildStats.expiredCount, "Expired") }, }, { @@ -492,7 +483,7 @@ func TestMultiSitesRebuild(t *testing.T) { }, func(t *testing.T) { assert.Len(enSite.RegularPages(), 6) - assert.Len(enSite.AllPages(), 34) + assert.Len(enSite.AllPages(), 36) assert.Len(frSite.RegularPages(), 5) require.Equal(t, "new_fr_1", frSite.RegularPages()[3].Title()) require.Equal(t, "new_en_2", enSite.RegularPages()[0].Title()) @@ -545,7 +536,7 @@ func TestMultiSitesRebuild(t *testing.T) { []fsnotify.Event{{Name: filepath.FromSlash("layouts/_default/single.html"), Op: fsnotify.Write}}, func(t *testing.T) { assert.Len(enSite.RegularPages(), 6) - assert.Len(enSite.AllPages(), 34) + assert.Len(enSite.AllPages(), 36) assert.Len(frSite.RegularPages(), 5) doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(doc1, "Template Changed"), doc1) @@ -562,7 +553,7 @@ func TestMultiSitesRebuild(t *testing.T) { []fsnotify.Event{{Name: filepath.FromSlash("i18n/fr.yaml"), Op: fsnotify.Write}}, func(t *testing.T) { assert.Len(enSite.RegularPages(), 6) - assert.Len(enSite.AllPages(), 34) + assert.Len(enSite.AllPages(), 36) assert.Len(frSite.RegularPages(), 5) docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(docEn, "Hello"), "No Hello") @@ -586,7 +577,7 @@ func TestMultiSitesRebuild(t *testing.T) { }, func(t *testing.T) { assert.Len(enSite.RegularPages(), 6) - assert.Len(enSite.AllPages(), 34) + assert.Len(enSite.AllPages(), 36) assert.Len(frSite.RegularPages(), 5) b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut") b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello") diff --git a/hugolib/hugo_smoke_test.go b/hugolib/hugo_smoke_test.go index d5b8861ce6b..3bcda9d2400 100644 --- a/hugolib/hugo_smoke_test.go +++ b/hugolib/hugo_smoke_test.go @@ -30,6 +30,7 @@ func TestSmoke(t *testing.T) { baseURL = "https://example.com" title = "Simple Site" rssLimit = 3 +paginate = 4 defaultContentLanguage = "en" enableRobotsTXT = true @@ -193,7 +194,7 @@ Some **Markdown** in JSON shortcode. b.AssertFileContent("public/index.html", "home|In English", "Site params: Rules", - "Pages: Pages(18)|Data Pages: Pages(18)", + "Pages: Pages(5)|Data Pages: Pages(5)", "Paginator: 1", "First Site: In English", "RelPermalink: /", diff --git a/hugolib/page.go b/hugolib/page.go index 676cba762ac..c321e4c90ac 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -30,7 +30,6 @@ import ( "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/common/herrors" - "github.com/gohugoio/hugo/parser/metadecoders" "github.com/gohugoio/hugo/parser/pageparser" "github.com/pkg/errors" @@ -131,8 +130,7 @@ func (p *pageState) Pages() page.Pages { switch p.Kind() { case page.KindPage: - case page.KindHome: - pages = p.s.RegularPages() + case page.KindHome, page.KindSection: case page.KindTaxonomy: termInfo := p.getTaxonomyNodeInfo() taxonomy := p.s.Taxonomies[termInfo.plural].Get(termInfo.termKey) @@ -360,7 +358,6 @@ func (p *pageState) initPage() error { } func (p *pageState) setPages(pages page.Pages) { - page.SortByDefault(pages) p.pages = pages } @@ -529,19 +526,11 @@ Loop: p.renderable = false rn.AddBytes(it) case it.IsFrontMatter(): - f := metadecoders.FormatFromFrontMatterType(it.Type) - m, err := metadecoders.Default.UnmarshalToMap(it.Val, f) - if err != nil { - if fe, ok := err.(herrors.FileError); ok { - return herrors.ToFileErrorWithOffset(fe, iter.LineNumber()-1) - } else { - return err - } - } - - if err := meta.setMetadata(p, m); err != nil { - return err - } + // To support cascading front matter, we need to wait until + // we have created the section tree until we have the full set + // of metadata, so store the raw front matter away for later. + p.source.frontMatter = it + p.source.frontMatterLineNumber = iter.LineNumber() - 1 next := iter.Peek() if !next.IsDone() { @@ -757,13 +746,6 @@ func (p *pageState) getTaxonomyNodeInfo() *taxonomyNodeInfo { } -func (p *pageState) sortParentSections() { - if p.parent == nil { - return - } - page.SortByDefault(p.parent.subSections) -} - // sourceRef returns the reference used by GetPage and ref/relref shortcodes to refer to // this page. It is prefixed with a "/". // diff --git a/hugolib/page__content.go b/hugolib/page__content.go index 1919fb17154..a07c4ac775f 100644 --- a/hugolib/page__content.go +++ b/hugolib/page__content.go @@ -91,6 +91,10 @@ type rawPageContent struct { // shortcodes, front matter, summary indicators. parsed pageparser.Result + // The raw front matter, if set. + frontMatter pageparser.Item + frontMatterLineNumber int + // Returns the position in bytes after any front matter. posMainContent int diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go index 551f4797744..d16bd1c66dc 100644 --- a/hugolib/page__meta.go +++ b/hugolib/page__meta.go @@ -19,6 +19,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "time" "github.com/gohugoio/hugo/hugofs/files" @@ -124,6 +125,14 @@ type pageMeta struct { s *Site renderingConfig *helpers.BlackFriday + + // Any cascade element set on the section nodes. + // Is used to cascade common metadata downwards. + cascade map[string]interface{} + + // We decode the front matter late, and we may try more than once + // in server mode. + metaInit sync.Once } func (p *pageMeta) Aliases() []string { diff --git a/hugolib/pages_metadata_handler.go b/hugolib/pages_metadata_handler.go new file mode 100644 index 00000000000..133ca26c588 --- /dev/null +++ b/hugolib/pages_metadata_handler.go @@ -0,0 +1,152 @@ +// 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 ( + "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/gohugoio/hugo/resources/page" + "github.com/gohugoio/hugo/resources/resource" +) + +const cascadeKey = "cascade" + +type pagesMetadataHandler struct { + dates *resource.Dates +} + +func (m *pagesMetadataHandler) handleSection( + parentCascade map[string]interface{}, + section *pageState) error { + + var err error + + if parentCascade, err = m.parseAndAssignMeta(true, parentCascade, section); err != nil { + return err + } + + var sectionDates *resource.Dates + if section.IsHome() && resource.IsZeroDates(section) { + // Calculate the home dates from the entire tree. + m.dates = &resource.Dates{} + } else if resource.IsZeroDates(section) { + // Calculate the section dates from the section pages. + sectionDates = &resource.Dates{} + } + + for _, p := range section.pages { + if parentCascade, err = m.parseAndAssignMeta(false, parentCascade, p.(*pageState)); err != nil { + return err + } + + if sectionDates != nil { + sectionDates.UpdateDateAndLastmodIfAfter(p) + } + + if m.dates != nil { + m.dates.UpdateDateAndLastmodIfAfter(p) + } + + // TODO(bep) cascade check sort + for _, rp := range p.Resources().ByType(pageResourceType) { + if _, err := m.parseAndAssignMeta(false, parentCascade, rp.(*pageState)); err != nil { + } + } + } + + if sectionDates != nil { + section.m.Dates = *sectionDates + } + + // Now all metadata is set for the section and we can sort the pages. + page.SortByDefault(section.pages) + + for _, p := range section.subSections { + ps := p.(*pageState) + + if err := m.handleSection(parentCascade, ps); err != nil { + return err + } + + } + + // Now all metadata is set for the sections and we can sort them. + page.SortByDefault(section.subSections) + + return nil + +} + +func (m *pagesMetadataHandler) parseAndAssignMeta(isSection bool, parentCascade map[string]interface{}, p *pageState) (map[string]interface{}, error) { + var initErr error + p.m.metaInit.Do(func() { + var meta map[string]interface{} + + p.m.cascade = parentCascade + + if p.source.frontMatter.Val != nil { + var err error + meta, err = m.parseMeta(p.source) + if err != nil { + initErr = err + return + } + + if isSection { + if c, found := meta[cascadeKey]; found { + // Use this section's cascade for all children. + p.m.cascade = c.(map[string]interface{}) + delete(meta, cascadeKey) + } + } + } else { + meta = make(map[string]interface{}) + } + + if p.m.cascade != nil { + for k, v := range p.m.cascade { + // TODO(bep) cascade case + if k == cascadeKey { + continue + } + if _, found := meta[k]; !found { + meta[k] = v + } + } + } + + if err := p.m.setMetadata(p, meta); err != nil { + initErr = err + return + } + }) + + return p.m.cascade, initErr +} + +func (m *pagesMetadataHandler) parseMeta(pc rawPageContent) (map[string]interface{}, error) { + it := pc.frontMatter + f := metadecoders.FormatFromFrontMatterType(it.Type) + meta, err := metadecoders.Default.UnmarshalToMap(it.Val, f) + if err != nil { + if fe, ok := err.(herrors.FileError); ok { + // TODO(bep) cascade check this in the browser + return nil, herrors.ToFileErrorWithOffset(fe, pc.frontMatterLineNumber) + } else { + return nil, err + } + } + + return meta, nil +} diff --git a/hugolib/pages_metadata_handler_test.go b/hugolib/pages_metadata_handler_test.go new file mode 100644 index 00000000000..3e539e3a570 --- /dev/null +++ b/hugolib/pages_metadata_handler_test.go @@ -0,0 +1,103 @@ +// 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" + "fmt" + "testing" + + "github.com/gohugoio/hugo/parser" + "github.com/gohugoio/hugo/parser/metadecoders" +) + +func TestCascade(t *testing.T) { + + weight := 0 + + 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() + } + + weight += 10 + + metaStr := "---\n" + yamlStr + fmt.Sprintf("\nweight: %d\n---", weight) + + return metaStr + + } + + b := newTestSitesBuilder(t).WithConfigFile("toml", ` +baseURL = "https://example.org" +disableKinds = ["taxonomy", "taxonomyTerm"] +`) + b.WithContent( + "_index.md", p(map[string]interface{}{ + "title": "Home", + "cascade": map[string]interface{}{ + "title": "Cascade Home", + "icon": "home.png", + }, + }), + "p1.md", p(map[string]interface{}{ + "title": "p1", + }), + "p2.md", p(map[string]interface{}{}), + "sect1/_index.md", p(map[string]interface{}{ + "title": "Sect1", + "cascade": map[string]interface{}{ + "title": "Cascade Sect1", + "icon": "sect1.png", + }, + }), + "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", + }), + "sect2/_index.md", p(map[string]interface{}{ + "title": "Sect2", + }), + "sect2/p1.md", p(map[string]interface{}{ + "title": "Sect2_p1", + }), + "sect2/p2.md", p(map[string]interface{}{}), + ) + + b.WithTemplates("index.html", ` + +{{ range .Site.Pages }} +{{- .Weight }}|{{ .Kind }}|{{ .Path }}|{{ .Title }}|{{ .Params.icon }}| +{{ end }} +`) + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", `10|home|_index.md|Home|home.png| +20|page|p1.md|p1|home.png| +30|page|p2.md|Cascade Home|home.png| +40|section|sect1/_index.md|Sect1|sect1.png| +50|section|sect1/s1_2/_index.md|Sect1_2|sect1.png| +60|page|sect1/s1_2/p1.md|Sect1_2_p1|sect1.png| +70|section|sect2/_index.md|Sect2|home.png| +80|page|sect2/p1.md|Sect2_p1|home.png| +90|page|sect2/p2.md|Cascade Home|home.png|`) +} diff --git a/hugolib/site.go b/hugolib/site.go index 882874db947..7042ab5ff9c 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -101,8 +101,6 @@ type Site struct { layoutHandler *output.LayoutHandler - buildStats *buildStats - language *langs.Language siteCfg siteConfigHolder @@ -355,7 +353,6 @@ func (s *Site) reset() *Site { publisher: s.publisher, siteConfigConfig: s.siteConfigConfig, enableInlineShortcodes: s.enableInlineShortcodes, - buildStats: &buildStats{}, init: s.init, PageCollections: newPageCollections(), siteCfg: s.siteCfg, @@ -453,7 +450,6 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { outputFormatsConfig: siteOutputFormatsConfig, mediaTypesConfig: siteMediaTypesConfig, frontmatterHandler: frontMatterHandler, - buildStats: &buildStats{}, enableInlineShortcodes: cfg.Language.GetBool("enableInlineShortcodes"), siteCfg: siteConfig, } @@ -1550,7 +1546,6 @@ func (s *Site) assembleTaxonomies() error { func (s *Site) resetBuildState() { s.relatedDocsHandler = s.relatedDocsHandler.Clone() s.PageCollections = newPageCollectionsFromPages(s.rawAllPages) - s.buildStats = &buildStats{} s.init.Reset() for _, p := range s.rawAllPages { @@ -1806,6 +1801,10 @@ func (s *Site) newPage(kind string, sections ...string) *pageState { } func (s *Site) shouldBuild(p page.Page) bool { + if !s.isEnabled(p.Kind()) { + return false + } + return shouldBuild(s.BuildFuture, s.BuildExpired, s.BuildDrafts, p.Draft(), p.PublishDate(), p.ExpiryDate()) } diff --git a/hugolib/site_sections.go b/hugolib/site_sections.go index 8fce43471fc..f3c18c85a7b 100644 --- a/hugolib/site_sections.go +++ b/hugolib/site_sections.go @@ -19,7 +19,6 @@ import ( "strings" "github.com/gohugoio/hugo/resources/page" - "github.com/gohugoio/hugo/resources/resource" radix "github.com/hashicorp/go-immutable-radix" ) @@ -41,10 +40,6 @@ func (s *SiteInfo) Home() (page.Page, error) { func (s *Site) assembleSections() pageStatePages { var newPages pageStatePages - if !s.isEnabled(page.KindSection) { - return newPages - } - // Maps section kind pages to their path, i.e. "my/section" sectionPages := make(map[string]*pageState) @@ -60,9 +55,10 @@ func (s *Site) assembleSections() pageStatePages { ) var ( - inPages = radix.New().Txn() - inSections = radix.New().Txn() - undecided pageStatePages + inPages = radix.New().Txn() + inSections = radix.New().Txn() + undecided pageStatePages + homeSectionPages page.Pages ) home := s.findFirstWorkPageByKindIn(page.KindHome) @@ -78,6 +74,7 @@ func (s *Site) assembleSections() pageStatePages { if len(sections) == 0 { // Root level pages. These will have the home page as their Parent. p.parent = home + homeSectionPages = append(homeSectionPages, p) continue } @@ -139,7 +136,6 @@ func (s *Site) assembleSections() pageStatePages { var ( currentSection *pageState children page.Pages - dates *resource.Dates rootSections = inSections.Commit().Root() ) @@ -162,18 +158,10 @@ func (s *Site) assembleSections() pageStatePages { if currentSection != nil { // A new section currentSection.setPages(children) - if dates != nil { - currentSection.m.Dates = *dates - } } currentSection = p children = make(page.Pages, 0) - dates = nil - // Use section's dates from front matter if set. - if resource.IsZeroDates(currentSection) { - dates = &resource.Dates{} - } return false @@ -182,18 +170,12 @@ func (s *Site) assembleSections() pageStatePages { // Regular page p.parent = currentSection children = append(children, p) - if dates != nil { - dates.UpdateDateAndLastmodIfAfter(p) - } return false }) if currentSection != nil { currentSection.setPages(children) - if dates != nil { - currentSection.m.Dates = *dates - } } // Build the sections hierarchy @@ -224,8 +206,6 @@ func (s *Site) assembleSections() pageStatePages { mainSections, mainSectionsFound = s.Info.Params()[sectionsParamIdLower] for _, sect := range sectionPages { - sect.sortParentSections() - if !mainSectionsFound { weight := len(sect.Pages()) + (len(sect.Sections()) * 5) if weight >= maxSectionWeight { @@ -235,6 +215,8 @@ func (s *Site) assembleSections() pageStatePages { } } + home.setPages(homeSectionPages) + // Try to make this as backwards compatible as possible. s.Info.Params()[sectionsParamId] = mainSections s.Info.Params()[sectionsParamIdLower] = mainSections diff --git a/tpl/tplimpl/embedded/templates.autogen.go b/tpl/tplimpl/embedded/templates.autogen.go index e2d1d3c39bd..8ef274d0dd9 100644 --- a/tpl/tplimpl/embedded/templates.autogen.go +++ b/tpl/tplimpl/embedded/templates.autogen.go @@ -20,6 +20,7 @@ package embedded var EmbeddedTemplates = [][2]string{ {`_default/robots.txt`, `User-agent: *`}, {`_default/rss.xml`, `{{- $pages := .Data.Pages -}} +{{- if .IsHome }}{{ $pages := .Site.RegularPages }}{{ end -}} {{- $limit := .Site.Config.Services.RSS.Limit -}} {{- if ge $limit 1 -}} {{- $pages = $pages | first $limit -}} diff --git a/tpl/tplimpl/embedded/templates/_default/rss.xml b/tpl/tplimpl/embedded/templates/_default/rss.xml index 675ecd43c3a..5ffa8c27ccf 100644 --- a/tpl/tplimpl/embedded/templates/_default/rss.xml +++ b/tpl/tplimpl/embedded/templates/_default/rss.xml @@ -1,4 +1,5 @@ {{- $pages := .Data.Pages -}} +{{- if .IsHome }}{{ $pages := .Site.RegularPages }}{{ end -}} {{- $limit := .Site.Config.Services.RSS.Limit -}} {{- if ge $limit 1 -}} {{- $pages = $pages | first $limit -}}