]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Add path, kind and lang to content front matter
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 29 Jan 2024 09:02:24 +0000 (10:02 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 30 Jan 2024 19:12:03 +0000 (20:12 +0100)
Note that none of these can be set via cascade (you will get an error)

Fixes #11544

22 files changed:
go.mod
go.sum
hugolib/content_map.go
hugolib/content_map_page.go
hugolib/hugo_sites.go
hugolib/integrationtest_builder.go
hugolib/page.go
hugolib/page__common.go
hugolib/page__content.go
hugolib/page__meta.go
hugolib/page__new.go
hugolib/page__paths.go
hugolib/page__per_output.go
hugolib/params_test.go
hugolib/shortcode.go
hugolib/site_new.go
hugolib/site_render.go
resources/page/page_matcher.go
resources/page/pagemeta/page_frontmatter.go
resources/page/pagemeta/page_frontmatter_test.go
resources/page/pagemeta/pagemeta.go
resources/resource/dates.go

diff --git a/go.mod b/go.mod
index e8beedbfb5628363d474cca9592b9e7c8adcfefa..c74972d0bb87bdec9c5d85e0705a83f8fa9b1b04 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -48,7 +48,7 @@ require (
        github.com/marekm4/color-extractor v1.2.1
        github.com/mattn/go-isatty v0.0.20
        github.com/mitchellh/hashstructure v1.1.0
-       github.com/mitchellh/mapstructure v1.5.0
+       github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
        github.com/muesli/smartcrop v0.3.0
        github.com/niklasfasching/go-org v1.7.0
        github.com/olekukonko/tablewriter v0.0.5
diff --git a/go.sum b/go.sum
index 3c1cfe7ff3b84e49391d0ad03da148141b989d87..4795d0d688ac92f8081758a2da9f81117dbcd297 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -359,6 +359,8 @@ github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9km
 github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
+github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
 github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
index fefa90bf1525a35cfca496f4bf122253b097f8ae..96013c4ed8c0aed7e7a1e606c4ef385f951809fe 100644 (file)
@@ -187,7 +187,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
                if pi.IsContent() {
                        // Create the page now as we need it at assemembly time.
                        // The other resources are created if needed.
-                       pageResource, err := m.s.h.newPage(
+                       pageResource, pi, err := m.s.h.newPage(
                                &pageMeta{
                                        f:        source.NewFileInfo(fim),
                                        pathInfo: pi,
@@ -197,6 +197,8 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
                        if err != nil {
                                return err
                        }
+                       key = pi.Base()
+
                        rs = &resourceSource{r: pageResource}
                } else {
                        rs = &resourceSource{path: pi, opener: r, fi: fim}
@@ -226,7 +228,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
                        },
                ))
                // A content file.
-               p, err := m.s.h.newPage(
+               p, pi, err := m.s.h.newPage(
                        &pageMeta{
                                f:        source.NewFileInfo(fi),
                                pathInfo: pi,
index 536f23ccd3fca675e92d98df685ca40484ba7de1..9fee740033007c3bd0e9d5a87f95b21162b28da9 100644 (file)
@@ -43,6 +43,7 @@ import (
 
        "github.com/gohugoio/hugo/resources/kinds"
        "github.com/gohugoio/hugo/resources/page"
+       "github.com/gohugoio/hugo/resources/page/pagemeta"
        "github.com/gohugoio/hugo/resources/resource"
 )
 
@@ -97,7 +98,6 @@ type pageMap struct {
        cacheContentRendered   *dynacache.Partition[string, *resources.StaleValue[contentSummary]]
        cacheContentPlain      *dynacache.Partition[string, *resources.StaleValue[contentPlainPlainWords]]
        contentTableOfContents *dynacache.Partition[string, *resources.StaleValue[contentTableOfContents]]
-       cacheContentSource     *dynacache.Partition[string, *resources.StaleValue[[]byte]]
 
        cfg contentMapConfig
 }
@@ -147,7 +147,6 @@ func (t *pageTrees) collectIdentities(key string) []identity.Identity {
 
 // collectIdentitiesSurrounding collects all identities surrounding the given key.
 func (t *pageTrees) collectIdentitiesSurrounding(key string, maxSamplesPerTree int) []identity.Identity {
-       // TODO1 test language coverage from this.
        ids := t.collectIdentitiesSurroundingIn(key, maxSamplesPerTree, t.treePages)
        ids = append(ids, t.collectIdentitiesSurroundingIn(key, maxSamplesPerTree, t.treeResources)...)
        return ids
@@ -483,7 +482,7 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
                        return nil, err
                }
 
-               if translationKey := ps.m.translationKey; translationKey != "" {
+               if translationKey := ps.m.pageConfig.TranslationKey; translationKey != "" {
                        // This this should not be a very common case.
                        // Merge in resources from the other languages.
                        translatedPages, _ := m.s.h.translationKeyPages.Get(translationKey)
@@ -539,9 +538,9 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
 
                sort.SliceStable(res, lessFunc)
 
-               if len(ps.m.resourcesMetadata) > 0 {
+               if len(ps.m.pageConfig.Resources) > 0 {
                        for i, r := range res {
-                               res[i] = resources.CloneWithMetadataIfNeeded(ps.m.resourcesMetadata, r)
+                               res[i] = resources.CloneWithMetadataIfNeeded(ps.m.pageConfig.Resources, r)
                        }
                        sort.SliceStable(res, lessFunc)
                }
@@ -819,7 +818,6 @@ func newPageMap(i int, s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *
                cacheContentRendered:   dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentSummary]](mcache, fmt.Sprintf("/cont/ren/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
                cacheContentPlain:      dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentPlainPlainWords]](mcache, fmt.Sprintf("/cont/pla/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
                contentTableOfContents: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentTableOfContents]](mcache, fmt.Sprintf("/cont/toc/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
-               cacheContentSource:     dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](mcache, fmt.Sprintf("/cont/src/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
 
                cfg: contentMapConfig{
                        lang:                 s.Lang(),
@@ -1215,7 +1213,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
                        // Home page gets it's cascade from the site config.
                        cascade = sa.conf.Cascade.Config
 
-                       if pageBundle.m.cascade == nil {
+                       if pageBundle.m.pageConfig.Cascade == nil {
                                // Pass the site cascade downwards.
                                pw.WalkContext.Data().Insert(keyPage, cascade)
                        }
@@ -1227,12 +1225,12 @@ func (sa *sitePagesAssembler) applyAggregates() error {
                }
 
                if (pageBundle.IsHome() || pageBundle.IsSection()) && pageBundle.m.setMetaPostCount > 0 {
-                       oldDates := pageBundle.m.dates
+                       oldDates := pageBundle.m.pageConfig.Dates
 
                        // We need to wait until after the walk to determine if any of the dates have changed.
                        pw.WalkContext.AddPostHook(
                                func() error {
-                                       if oldDates != pageBundle.m.dates {
+                                       if oldDates != pageBundle.m.pageConfig.Dates {
                                                sa.assembleChanges.Add(pageBundle)
                                        }
                                        return nil
@@ -1251,11 +1249,12 @@ func (sa *sitePagesAssembler) applyAggregates() error {
 
                const eventName = "dates"
                if n.isContentNodeBranch() {
-                       if pageBundle.m.cascade != nil {
+                       if pageBundle.m.pageConfig.Cascade != nil {
                                // Pass it down.
-                               pw.WalkContext.Data().Insert(keyPage, pageBundle.m.cascade)
+                               pw.WalkContext.Data().Insert(keyPage, pageBundle.m.pageConfig.Cascade)
                        }
-                       wasZeroDates := resource.IsZeroDates(pageBundle.m.dates)
+
+                       wasZeroDates := pageBundle.m.pageConfig.Dates.IsAllDatesZero()
                        if wasZeroDates || pageBundle.IsHome() {
                                pw.WalkContext.AddEventListener(eventName, keyPage, func(e *doctree.Event[contentNodeI]) {
                                        sp, ok := e.Source.(*pageState)
@@ -1264,15 +1263,15 @@ func (sa *sitePagesAssembler) applyAggregates() error {
                                        }
 
                                        if wasZeroDates {
-                                               pageBundle.m.dates.UpdateDateAndLastmodIfAfter(sp.m.dates)
+                                               pageBundle.m.pageConfig.Dates.UpdateDateAndLastmodIfAfter(sp.m.pageConfig.Dates)
                                        }
 
                                        if pageBundle.IsHome() {
-                                               if pageBundle.m.dates.Lastmod().After(pageBundle.s.lastmod) {
-                                                       pageBundle.s.lastmod = pageBundle.m.dates.Lastmod()
+                                               if pageBundle.m.pageConfig.Dates.Lastmod.After(pageBundle.s.lastmod) {
+                                                       pageBundle.s.lastmod = pageBundle.m.pageConfig.Dates.Lastmod
                                                }
-                                               if sp.m.dates.Lastmod().After(pageBundle.s.lastmod) {
-                                                       pageBundle.s.lastmod = sp.m.dates.Lastmod()
+                                               if sp.m.pageConfig.Dates.Lastmod.After(pageBundle.s.lastmod) {
+                                                       pageBundle.s.lastmod = sp.m.pageConfig.Dates.Lastmod
                                                }
                                        }
                                })
@@ -1351,9 +1350,9 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
                                p := n.(*pageState)
                                if p.Kind() != kinds.KindTerm {
                                        // The other kinds were handled in applyAggregates.
-                                       if p.m.cascade != nil {
+                                       if p.m.pageConfig.Cascade != nil {
                                                // Pass it down.
-                                               pw.WalkContext.Data().Insert(s, p.m.cascade)
+                                               pw.WalkContext.Data().Insert(s, p.m.pageConfig.Cascade)
                                        }
                                }
 
@@ -1388,14 +1387,14 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
                                // Send the date info up the tree.
                                pw.WalkContext.SendEvent(&doctree.Event[contentNodeI]{Source: n, Path: s, Name: eventName})
 
-                               if resource.IsZeroDates(p.m.dates) {
+                               if p.m.pageConfig.Dates.IsAllDatesZero() {
                                        pw.WalkContext.AddEventListener(eventName, s, func(e *doctree.Event[contentNodeI]) {
                                                sp, ok := e.Source.(*pageState)
                                                if !ok {
                                                        return
                                                }
 
-                                               p.m.dates.UpdateDateAndLastmodIfAfter(sp.m.dates)
+                                               p.m.pageConfig.Dates.UpdateDateAndLastmodIfAfter(sp.m.pageConfig.Dates)
                                        })
                                }
 
@@ -1443,8 +1442,8 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
                        // This is a little out of place, but is conveniently put here.
                        // Check if translationKey is set by user.
                        // This is to support the manual way of setting the translationKey in front matter.
-                       if ps.m.translationKey != "" {
-                               sa.s.h.translationKeyPages.Append(ps.m.translationKey, ps)
+                       if ps.m.pageConfig.TranslationKey != "" {
+                               sa.s.h.translationKeyPages.Append(ps.m.pageConfig.TranslationKey, ps)
                        }
 
                        if sa.pageMap.cfg.taxonomyTermDisabled {
@@ -1477,9 +1476,13 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
                                                        singular: viewName.singular,
                                                        s:        sa.Site,
                                                        pathInfo: pi,
-                                                       kind:     kinds.KindTerm,
+                                                       pageMetaParams: pageMetaParams{
+                                                               pageConfig: &pagemeta.PageConfig{
+                                                                       Kind: kinds.KindTerm,
+                                                               },
+                                                       },
                                                }
-                                               n, err := sa.h.newPage(m)
+                                               n, pi, err := sa.h.newPage(m)
                                                if err != nil {
                                                        return false, err
                                                }
@@ -1524,7 +1527,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
                        targetPaths := ps.targetPaths()
                        baseTarget := targetPaths.SubResourceBaseTarget
                        duplicateResourceFiles := true
-                       if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.markup) {
+                       if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.pageConfig.Markup) {
                                duplicateResourceFiles = ps.s.ContentSpec.Converters.GetMarkupConfig().Goldmark.DuplicateResourceFiles
                        }
 
@@ -1566,7 +1569,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
                                                BasePathTargetPath:   baseTarget,
                                                Name:                 relPath,
                                                NameOriginal:         relPathOriginal,
-                                               LazyPublish:          !ps.m.buildConfig.PublishResources,
+                                               LazyPublish:          !ps.m.pageConfig.Build.PublishResources,
                                        }
                                        r, err := ps.m.s.ResourceSpec.NewResource(rd)
                                        if err != nil {
@@ -1631,7 +1634,7 @@ func (sa *sitePagesAssembler) removeShouldNotBuild() error {
                                case kinds.KindHome, kinds.KindSection, kinds.KindTaxonomy:
                                        // We need to keep these for the structure, but disable
                                        // them so they don't get listed/rendered.
-                                       (&p.m.buildConfig).Disable()
+                                       (&p.m.pageConfig.Build).Disable()
                                default:
                                        keys = append(keys, key)
                                }
@@ -1673,13 +1676,17 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
                }
 
                m := &pageMeta{
-                       s:                      s,
-                       pathInfo:               s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
-                       kind:                   kind,
+                       s:        s,
+                       pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
+                       pageMetaParams: pageMetaParams{
+                               pageConfig: &pagemeta.PageConfig{
+                                       Kind: kind,
+                               },
+                       },
                        standaloneOutputFormat: f,
                }
 
-               p, _ := s.h.newPage(m)
+               p, _, _ := s.h.newPage(m)
 
                tree.InsertIntoValuesDimension(key, p)
        }
@@ -1756,7 +1763,7 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
                                        pathInfo: pth,
                                }
 
-                               ps, err := sa.h.newPage(m)
+                               ps, pth, err := sa.h.newPage(m)
                                if err != nil {
                                        return false, err
                                }
@@ -1781,9 +1788,13 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
                m := &pageMeta{
                        s:        sa.Site,
                        pathInfo: p,
-                       kind:     kinds.KindHome,
+                       pageMetaParams: pageMetaParams{
+                               pageConfig: &pagemeta.PageConfig{
+                                       Kind: kinds.KindHome,
+                               },
+                       },
                }
-               n, err := sa.h.newPage(m)
+               n, p, err := sa.h.newPage(m)
                if err != nil {
                        return err
                }
@@ -1810,10 +1821,14 @@ func (sa *sitePagesAssembler) addMissingTaxonomies() error {
                        m := &pageMeta{
                                s:        sa.Site,
                                pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
-                               kind:     kinds.KindTaxonomy,
+                               pageMetaParams: pageMetaParams{
+                                       pageConfig: &pagemeta.PageConfig{
+                                               Kind: kinds.KindTaxonomy,
+                                       },
+                               },
                                singular: viewName.singular,
                        }
-                       p, _ := sa.h.newPage(m)
+                       p, _, _ := sa.h.newPage(m)
                        tree.InsertIntoValuesDimension(key, p)
                }
        }
index 80e75445374003e21396357ec61420ec912e18d5..1b2840617344e4539ea5222b46a928626792d63d 100644 (file)
@@ -26,6 +26,7 @@ import (
        "github.com/gohugoio/hugo/config/allconfig"
        "github.com/gohugoio/hugo/hugofs/glob"
        "github.com/gohugoio/hugo/hugolib/doctree"
+       "github.com/gohugoio/hugo/resources"
 
        "github.com/fsnotify/fsnotify"
 
@@ -72,6 +73,8 @@ type HugoSites struct {
 
        // Cache for page listings.
        cachePages *dynacache.Partition[string, page.Pages]
+       // Cache for content sources.
+       cacheContentSource *dynacache.Partition[string, *resources.StaleValue[[]byte]]
 
        // Before Hugo 0.122.0 we managed all translations in a map using a translationKey
        // that could be overridden in front matter.
index 3fec04df0f7b17eb31f17713908e90276502bcb3..a46ae72751cf6449e446c4eec33373640134d34c 100644 (file)
@@ -80,6 +80,15 @@ func Test(t testing.TB, files string, opts ...TestOpt) *IntegrationTestBuilder {
        return NewIntegrationTestBuilder(cfg).Build()
 }
 
+// TestE is the same as Test, but returns an error instead of failing the test.
+func TestE(t testing.TB, files string, opts ...TestOpt) (*IntegrationTestBuilder, error) {
+       cfg := IntegrationTestConfig{T: t, TxtarString: files}
+       for _, o := range opts {
+               o(&cfg)
+       }
+       return NewIntegrationTestBuilder(cfg).BuildE()
+}
+
 // TestRunning is a convenience method to create a new IntegrationTestBuilder from some files with Running set to true and run a build.
 // Deprecated: Use Test with TestOptRunning instead.
 func TestRunning(t testing.TB, files string, opts ...TestOpt) *IntegrationTestBuilder {
index f8ec5e22578c85b83c3f815cb8875945d58b6e7f..822b7c021f4dfe7427d45b6d71fe45f1859a2cca 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 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.
@@ -27,6 +27,7 @@ import (
        "github.com/gohugoio/hugo/output"
        "github.com/gohugoio/hugo/output/layouts"
        "github.com/gohugoio/hugo/related"
+       "github.com/spf13/afero"
 
        "github.com/gohugoio/hugo/markup/converter"
        "github.com/gohugoio/hugo/markup/tableofcontents"
@@ -197,7 +198,7 @@ func (p *pageHeadingsFiltered) page() page.Page {
 
 // For internal use by the related content feature.
 func (p *pageState) ApplyFilterToHeadings(ctx context.Context, fn func(*tableofcontents.Heading) bool) related.Document {
-       r, err := p.content.contentToC(ctx, p.pageOutput.pco)
+       r, err := p.m.content.contentToC(ctx, p.pageOutput.pco)
        if err != nil {
                panic(err)
        }
@@ -313,14 +314,14 @@ func (p *pageState) Pages() page.Pages {
 // RawContent returns the un-rendered source content without
 // any leading front matter.
 func (p *pageState) RawContent() string {
-       if p.content.parseInfo.itemsStep2 == nil {
+       if p.m.content.pi.itemsStep2 == nil {
                return ""
        }
-       start := p.content.parseInfo.posMainContent
+       start := p.m.content.pi.posMainContent
        if start == -1 {
                start = 0
        }
-       source, err := p.content.contentSource()
+       source, err := p.m.content.pi.contentSource(p.m.content)
        if err != nil {
                panic(err)
        }
@@ -332,11 +333,11 @@ func (p *pageState) Resources() resource.Resources {
 }
 
 func (p *pageState) HasShortcode(name string) bool {
-       if p.content.shortcodeState == nil {
+       if p.m.content.shortcodeState == nil {
                return false
        }
 
-       return p.content.shortcodeState.hasName(name)
+       return p.m.content.shortcodeState.hasName(name)
 }
 
 func (p *pageState) Site() page.Site {
@@ -355,8 +356,8 @@ func (p *pageState) IsTranslated() bool {
 
 // TranslationKey returns the key used to identify a translation of this content.
 func (p *pageState) TranslationKey() string {
-       if p.m.translationKey != "" {
-               return p.m.translationKey
+       if p.m.pageConfig.TranslationKey != "" {
+               return p.m.pageConfig.TranslationKey
        }
        return p.Path()
 }
@@ -365,9 +366,9 @@ func (p *pageState) TranslationKey() string {
 func (p *pageState) AllTranslations() page.Pages {
        key := p.Path() + "/" + "translations-all"
        pages, err := p.s.pageMap.getOrCreatePagesFromCache(key, func(string) (page.Pages, error) {
-               if p.m.translationKey != "" {
+               if p.m.pageConfig.TranslationKey != "" {
                        // translationKey set by user.
-                       pas, _ := p.s.h.translationKeyPages.Get(p.m.translationKey)
+                       pas, _ := p.s.h.translationKeyPages.Get(p.m.pageConfig.TranslationKey)
                        pasc := make(page.Pages, len(pas))
                        copy(pasc, pas)
                        page.SortByLanguage(pasc)
@@ -534,7 +535,7 @@ var defaultRenderStringOpts = renderStringOpts{
        Markup:  "", // Will inherit the page's value when not set.
 }
 
-func (p *pageMeta) wrapError(err error) error {
+func (p *pageMeta) wrapError(err error, sourceFs afero.Fs) error {
        if err == nil {
                panic("wrapError with nil")
        }
@@ -544,18 +545,18 @@ func (p *pageMeta) wrapError(err error) error {
                return fmt.Errorf("%q: %w", p.Path(), err)
        }
 
-       return hugofs.AddFileInfoToError(err, p.File().FileInfo(), p.s.SourceSpec.Fs.Source)
+       return hugofs.AddFileInfoToError(err, p.File().FileInfo(), sourceFs)
 }
 
 // wrapError adds some more context to the given error if possible/needed
 func (p *pageState) wrapError(err error) error {
-       return p.m.wrapError(err)
+       return p.m.wrapError(err, p.s.h.SourceFs)
 }
 
 func (p *pageState) getContentConverter() converter.Converter {
        var err error
        p.contentConverterInit.Do(func() {
-               markup := p.m.markup
+               markup := p.m.pageConfig.Markup
                if markup == "html" {
                        // Only used for shortcode inner content.
                        markup = "markdown"
@@ -612,7 +613,7 @@ func (p *pageState) posFromInput(input []byte, offset int) text.Position {
 }
 
 func (p *pageState) posOffset(offset int) text.Position {
-       return p.posFromInput(p.content.mustSource(), offset)
+       return p.posFromInput(p.m.content.mustSource(), offset)
 }
 
 // shiftToOutputFormat is serialized. The output format idx refers to the
index 0881affe7bee216be8439610538a48e0604a813c..164776842e7de2a3ca0cf22424fc523908334901 100644 (file)
@@ -91,9 +91,6 @@ type pageCommon struct {
        layoutDescriptor     layouts.LayoutDescriptor
        layoutDescriptorInit sync.Once
 
-       // The source and the parsed page content.
-       content *cachedContent
-
        // Set if feature enabled and this is in a Git repo.
        gitInfo    source.GitInfo
        codeowners []string
index 64ce83f0ebf888abce8d0e9b5693be68f51d8e90..62e78c61271a80c6a6d5d6f984a78cf6ddac14e0 100644 (file)
@@ -20,6 +20,7 @@ import (
        "fmt"
        "html/template"
        "io"
+       "strconv"
        "strings"
        "unicode/utf8"
 
@@ -53,9 +54,8 @@ type pageContentReplacement struct {
        source pageparser.Item
 }
 
-func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
+func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64, sourceKey string) (*contentParseInfo, error) {
        var openSource hugio.OpenReadSeekCloser
-       var filename string
        if m.f != nil {
                meta := m.f.FileInfo().Meta()
                openSource = func() (hugio.ReadSeekCloser, error) {
@@ -65,6 +65,44 @@ func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
                        }
                        return r, nil
                }
+       }
+
+       if sourceKey == "" {
+               sourceKey = strconv.Itoa(int(pid))
+       }
+
+       pi := &contentParseInfo{
+               h:          h,
+               pid:        pid,
+               sourceKey:  sourceKey,
+               openSource: openSource,
+       }
+
+       source, err := pi.contentSource(m)
+       if err != nil {
+               return nil, err
+       }
+
+       items, err := pageparser.ParseBytes(
+               source,
+               pageparser.Config{},
+       )
+       if err != nil {
+               return nil, err
+       }
+
+       pi.itemsStep1 = items
+
+       if err := pi.mapFrontMatter(source); err != nil {
+               return nil, err
+       }
+
+       return pi, nil
+}
+
+func (m *pageMeta) newCachedContent(h *HugoSites, pi *contentParseInfo) (*cachedContent, error) {
+       var filename string
+       if m.f != nil {
                filename = m.f.Filename()
        }
 
@@ -72,15 +110,11 @@ func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
                pm:             m.s.pageMap,
                StaleInfo:      m,
                shortcodeState: newShortcodeHandler(filename, m.s),
-               parseInfo: &contentParseInfo{
-                       pid: pid,
-               },
-               cacheBaseKey: m.pathInfo.PathNoLang(),
-               openSource:   openSource,
-               enableEmoji:  m.s.conf.EnableEmoji,
+               pi:             pi,
+               enableEmoji:    m.s.conf.EnableEmoji,
        }
 
-       source, err := c.contentSource()
+       source, err := c.pi.contentSource(m)
        if err != nil {
                return nil, err
        }
@@ -95,23 +129,25 @@ func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
 type cachedContent struct {
        pm *pageMap
 
-       cacheBaseKey string
-
-       // The source bytes.
-       openSource hugio.OpenReadSeekCloser
-
        resource.StaleInfo
 
        shortcodeState *shortcodeHandler
 
        // Parsed content.
-       parseInfo *contentParseInfo
+       pi *contentParseInfo
 
        enableEmoji bool
 }
 
 type contentParseInfo struct {
-       pid         uint64
+       h *HugoSites
+
+       pid       uint64
+       sourceKey string
+
+       // The source bytes.
+       openSource hugio.OpenReadSeekCloser
+
        frontMatter map[string]any
 
        // Whether the parsed content contains a summary separator.
@@ -190,25 +226,15 @@ func (pi *contentParseInfo) contentToRender(ctx context.Context, source []byte,
 }
 
 func (c *cachedContent) IsZero() bool {
-       return len(c.parseInfo.itemsStep2) == 0
+       return len(c.pi.itemsStep2) == 0
 }
 
 func (c *cachedContent) parseContentFile(source []byte) error {
-       if source == nil || c.openSource == nil {
+       if source == nil || c.pi.openSource == nil {
                return nil
        }
 
-       items, err := pageparser.ParseBytes(
-               source,
-               pageparser.Config{},
-       )
-       if err != nil {
-               return err
-       }
-
-       c.parseInfo.itemsStep1 = items
-
-       return c.parseInfo.mapItems(source, c.shortcodeState)
+       return c.pi.mapItemsAfterFrontMatter(source, c.shortcodeState)
 }
 
 func (c *contentParseInfo) parseFrontMatter(it pageparser.Item, iter *pageparser.Iterator, source []byte) error {
@@ -242,7 +268,49 @@ func (c *contentParseInfo) parseFrontMatter(it pageparser.Item, iter *pageparser
        return nil
 }
 
-func (rn *contentParseInfo) mapItems(
+func (rn *contentParseInfo) failMap(source []byte, err error, i pageparser.Item) error {
+       if fe, ok := err.(herrors.FileError); ok {
+               return fe
+       }
+
+       pos := posFromInput("", source, i.Pos())
+
+       return herrors.NewFileErrorFromPos(err, pos)
+}
+
+func (rn *contentParseInfo) mapFrontMatter(source []byte) error {
+       if len(rn.itemsStep1) == 0 {
+               return nil
+       }
+       iter := pageparser.NewIterator(rn.itemsStep1)
+
+Loop:
+       for {
+               it := iter.Next()
+               switch {
+               case it.IsFrontMatter():
+                       if err := rn.parseFrontMatter(it, iter, source); err != nil {
+                               return err
+                       }
+                       next := iter.Peek()
+                       if !next.IsDone() {
+                               rn.posMainContent = next.Pos()
+                       }
+                       // Done.
+                       break Loop
+               case it.IsEOF():
+                       break Loop
+               case it.IsError():
+                       return rn.failMap(source, it.Err, it)
+               default:
+
+               }
+       }
+
+       return nil
+}
+
+func (rn *contentParseInfo) mapItemsAfterFrontMatter(
        source []byte,
        s *shortcodeHandler,
 ) error {
@@ -273,13 +341,7 @@ Loop:
                switch {
                case it.Type == pageparser.TypeIgnore:
                case it.IsFrontMatter():
-                       if err := rn.parseFrontMatter(it, iter, source); err != nil {
-                               return err
-                       }
-                       next := iter.Peek()
-                       if !next.IsDone() {
-                               rn.posMainContent = next.Pos()
-                       }
+                       // Ignore.
                case it.Type == pageparser.TypeLeadSummaryDivider:
                        posBody := -1
                        f := func(item pageparser.Item) bool {
@@ -347,16 +409,16 @@ Loop:
 }
 
 func (c *cachedContent) mustSource() []byte {
-       source, err := c.contentSource()
+       source, err := c.pi.contentSource(c)
        if err != nil {
                panic(err)
        }
        return source
 }
 
-func (c *cachedContent) contentSource() ([]byte, error) {
-       key := c.cacheBaseKey
-       v, err := c.pm.cacheContentSource.GetOrCreate(key, func(string) (*resources.StaleValue[[]byte], error) {
+func (c *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) {
+       key := c.sourceKey
+       v, err := c.h.cacheContentSource.GetOrCreate(key, func(string) (*resources.StaleValue[[]byte], error) {
                b, err := c.readSourceAll()
                if err != nil {
                        return nil, err
@@ -365,7 +427,7 @@ func (c *cachedContent) contentSource() ([]byte, error) {
                return &resources.StaleValue[[]byte]{
                        Value: b,
                        IsStaleFunc: func() bool {
-                               return c.IsStale()
+                               return s.IsStale()
                        },
                }, nil
        })
@@ -376,7 +438,7 @@ func (c *cachedContent) contentSource() ([]byte, error) {
        return v.Value, nil
 }
 
-func (c *cachedContent) readSourceAll() ([]byte, error) {
+func (c *contentParseInfo) readSourceAll() ([]byte, error) {
        if c.openSource == nil {
                return []byte{}, nil
        }
@@ -424,7 +486,7 @@ type contentPlainPlainWords struct {
 
 func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutput) (contentSummary, error) {
        ctx = tpl.Context.DependencyScope.Set(ctx, pageDependencyScopeGlobal)
-       key := c.cacheBaseKey + "/" + cp.po.f.Name
+       key := c.pi.sourceKey + "/" + cp.po.f.Name
        versionv := cp.contentRenderedVersion
 
        v, err := c.pm.cacheContentRendered.GetOrCreate(key, func(string) (*resources.StaleValue[contentSummary], error) {
@@ -447,7 +509,7 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
                        },
                }
 
-               if len(c.parseInfo.itemsStep2) == 0 {
+               if len(c.pi.itemsStep2) == 0 {
                        // Nothing to do.
                        return rs, nil
                }
@@ -501,8 +563,8 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
 
                var result contentSummary // hasVariants bool
 
-               if c.parseInfo.hasSummaryDivider {
-                       isHTML := cp.po.p.m.markup == "html"
+               if c.pi.hasSummaryDivider {
+                       isHTML := cp.po.p.m.pageConfig.Markup == "html"
                        if isHTML {
                                // Use the summary sections as provided by the user.
                                i := bytes.Index(b, internalSummaryDividerPre)
@@ -510,7 +572,7 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
                                b = b[i+len(internalSummaryDividerPre):]
 
                        } else {
-                               summary, content, err := splitUserDefinedSummaryAndContent(cp.po.p.m.markup, b)
+                               summary, content, err := splitUserDefinedSummaryAndContent(cp.po.p.m.pageConfig.Markup, b)
                                if err != nil {
                                        cp.po.p.s.Log.Errorf("Failed to set user defined summary for page %q: %s", cp.po.p.pathOrTitle(), err)
                                } else {
@@ -518,7 +580,7 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
                                        result.summary = helpers.BytesToHTML(summary)
                                }
                        }
-                       result.summaryTruncated = c.parseInfo.summaryTruncated
+                       result.summaryTruncated = c.pi.summaryTruncated
                }
                result.content = helpers.BytesToHTML(b)
                rs.Value = result
@@ -543,11 +605,11 @@ func (c *cachedContent) mustContentToC(ctx context.Context, cp *pageContentOutpu
 var setGetContentCallbackInContext = hcontext.NewContextDispatcher[func(*pageContentOutput, contentTableOfContents)]("contentCallback")
 
 func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (contentTableOfContents, error) {
-       key := c.cacheBaseKey + "/" + cp.po.f.Name
+       key := c.pi.sourceKey + "/" + cp.po.f.Name
        versionv := cp.contentRenderedVersion
 
        v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
-               source, err := c.contentSource()
+               source, err := c.pi.contentSource(c)
                if err != nil {
                        return nil, err
                }
@@ -572,7 +634,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
                        }
 
                        if p.s.conf.Internal.Watch {
-                               for _, s := range cp2.po.p.content.shortcodeState.shortcodes {
+                               for _, s := range cp2.po.p.m.content.shortcodeState.shortcodes {
                                        for _, templ := range s.templs {
                                                cp.trackDependency(templ.(identity.IdentityProvider))
                                        }
@@ -580,7 +642,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
                        }
 
                        // Transfer shortcode names so HasShortcode works for shortcodes from included pages.
-                       cp.po.p.content.shortcodeState.transferNames(cp2.po.p.content.shortcodeState)
+                       cp.po.p.m.content.shortcodeState.transferNames(cp2.po.p.m.content.shortcodeState)
                        if cp2.po.p.pageOutputTemplateVariationsState.Load() > 0 {
                                cp.po.p.pageOutputTemplateVariationsState.Add(1)
                        }
@@ -589,7 +651,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
                ctx = setGetContentCallbackInContext.Set(ctx, ctxCallback)
 
                var hasVariants bool
-               ct.contentToRender, hasVariants, err = c.parseInfo.contentToRender(ctx, source, ct.contentPlaceholders)
+               ct.contentToRender, hasVariants, err = c.pi.contentToRender(ctx, source, ct.contentPlaceholders)
                if err != nil {
                        return nil, err
                }
@@ -598,7 +660,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
                        p.pageOutputTemplateVariationsState.Add(1)
                }
 
-               isHTML := cp.po.p.m.markup == "html"
+               isHTML := cp.po.p.m.pageConfig.Markup == "html"
 
                if !isHTML {
                        createAndSetToC := func(tocProvider converter.TableOfContentsProvider) {
@@ -661,7 +723,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
 }
 
 func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput) (contentPlainPlainWords, error) {
-       key := c.cacheBaseKey + "/" + cp.po.f.Name
+       key := c.pi.sourceKey + "/" + cp.po.f.Name
 
        versionv := cp.contentRenderedVersion
 
@@ -681,7 +743,7 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
                result.plain = tpl.StripHTML(string(rendered.content))
                result.plainWords = strings.Fields(result.plain)
 
-               isCJKLanguage := cp.po.p.m.isCJKLanguage
+               isCJKLanguage := cp.po.p.m.pageConfig.IsCJKLanguage
 
                if isCJKLanguage {
                        result.wordCount = 0
@@ -711,8 +773,8 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
                if rendered.summary != "" {
                        result.summary = rendered.summary
                        result.summaryTruncated = rendered.summaryTruncated
-               } else if cp.po.p.m.summary != "" {
-                       b, err := cp.po.contentRenderer.ParseAndRenderContent(ctx, []byte(cp.po.p.m.summary), false)
+               } else if cp.po.p.m.pageConfig.Summary != "" {
+                       b, err := cp.po.contentRenderer.ParseAndRenderContent(ctx, []byte(cp.po.p.m.pageConfig.Summary), false)
                        if err != nil {
                                return nil, err
                        }
index a549356fd93eb341c716b04b0032595730f6bc67..179622d9b733e7c5e56b1e77ae83a6c3fc308861 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 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.
@@ -48,13 +48,11 @@ import (
 var cjkRe = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
 
 type pageMeta struct {
-       kind     string // Page kind.
        term     string // Set for kind == KindTerm.
        singular string // Set for kind == KindTerm and kind == KindTaxonomy.
 
        resource.Staler
        pageMetaParams
-
        pageMetaFrontMatter
 
        // Set for standalone pages, e.g. robotsTXT.
@@ -66,13 +64,15 @@ type pageMeta struct {
        pathInfo *paths.Path // Always set. This the canonical path to the Page.
        f        *source.File
 
+       content *cachedContent // The source and the parsed page content.
+
        s *Site // The site this page belongs to.
 }
 
 // Prepare for a rebuild of the data passed in from front matter.
 func (m *pageMeta) setMetaPostPrepareRebuild() {
        params := xmaps.Clone[map[string]any](m.paramsOriginal)
-       m.pageMetaParams.params = params
+       m.pageMetaParams.pageConfig.Params = params
        m.pageMetaFrontMatter = pageMetaFrontMatter{}
 }
 
@@ -80,48 +80,28 @@ type pageMetaParams struct {
        setMetaPostCount          int
        setMetaPostCascadeChanged bool
 
-       params  map[string]any                   // Params contains configuration defined in the params section of page frontmatter.
-       cascade map[page.PageMatcher]maps.Params // cascade contains default configuration to be cascaded downwards.
+       pageConfig *pagemeta.PageConfig
 
        // These are only set in watch mode.
-       datesOriginal   pageMetaDates
+       datesOriginal   pagemeta.Dates
        paramsOriginal  map[string]any                   // contains the original params as defined in the front matter.
        cascadeOriginal map[page.PageMatcher]maps.Params // contains the original cascade as defined in the front matter.
 }
 
 // From page front matter.
 type pageMetaFrontMatter struct {
-       draft          bool // Only published when running with -D flag
-       title          string
-       linkTitle      string
-       summary        string
-       weight         int
-       markup         string
-       contentType    string // type in front matter.
-       isCJKLanguage  bool   // whether the content is in a CJK language.
-       layout         string
-       aliases        []string
-       description    string
-       keywords       []string
-       translationKey string // maps to translation(s) of this page.
-
-       buildConfig             pagemeta.BuildConfig
-       configuredOutputFormats output.Formats       // outputs defiend in front matter.
-       pageMetaDates                                // The 4 front matter dates that Hugo cares about.
-       resourcesMetadata       []map[string]any     // Raw front matter metadata that is going to be assigned to the page resources.
-       sitemap                 config.SitemapConfig // Sitemap overrides from front matter.
-       urlPaths                pagemeta.URLPath
+       configuredOutputFormats output.Formats // outputs defiend in front matter.
 }
 
 func (m *pageMetaParams) init(preserveOringal bool) {
        if preserveOringal {
-               m.paramsOriginal = xmaps.Clone[maps.Params](m.params)
-               m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.cascade)
+               m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
+               m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.pageConfig.Cascade)
        }
 }
 
 func (p *pageMeta) Aliases() []string {
-       return p.aliases
+       return p.pageConfig.Aliases
 }
 
 func (p *pageMeta) Author() page.Author {
@@ -150,8 +130,24 @@ func (p *pageMeta) BundleType() string {
        }
 }
 
+func (p *pageMeta) Date() time.Time {
+       return p.pageConfig.Date
+}
+
+func (p *pageMeta) PublishDate() time.Time {
+       return p.pageConfig.PublishDate
+}
+
+func (p *pageMeta) Lastmod() time.Time {
+       return p.pageConfig.Lastmod
+}
+
+func (p *pageMeta) ExpiryDate() time.Time {
+       return p.pageConfig.ExpiryDate
+}
+
 func (p *pageMeta) Description() string {
-       return p.description
+       return p.pageConfig.Description
 }
 
 func (p *pageMeta) Lang() string {
@@ -159,7 +155,7 @@ func (p *pageMeta) Lang() string {
 }
 
 func (p *pageMeta) Draft() bool {
-       return p.draft
+       return p.pageConfig.Draft
 }
 
 func (p *pageMeta) File() *source.File {
@@ -171,20 +167,20 @@ func (p *pageMeta) IsHome() bool {
 }
 
 func (p *pageMeta) Keywords() []string {
-       return p.keywords
+       return p.pageConfig.Keywords
 }
 
 func (p *pageMeta) Kind() string {
-       return p.kind
+       return p.pageConfig.Kind
 }
 
 func (p *pageMeta) Layout() string {
-       return p.layout
+       return p.pageConfig.Layout
 }
 
 func (p *pageMeta) LinkTitle() string {
-       if p.linkTitle != "" {
-               return p.linkTitle
+       if p.pageConfig.LinkTitle != "" {
+               return p.pageConfig.LinkTitle
        }
 
        return p.Title()
@@ -194,7 +190,7 @@ func (p *pageMeta) Name() string {
        if p.resourcePath != "" {
                return p.resourcePath
        }
-       if p.kind == kinds.KindTerm {
+       if p.pageConfig.Kind == kinds.KindTerm {
                return p.pathInfo.Unmormalized().BaseNameNoIdentifier()
        }
        return p.Title()
@@ -218,7 +214,7 @@ func (p *pageMeta) Param(key any) (any, error) {
 }
 
 func (p *pageMeta) Params() maps.Params {
-       return p.params
+       return p.pageConfig.Params
 }
 
 func (p *pageMeta) Path() string {
@@ -248,18 +244,18 @@ func (p *pageMeta) Section() string {
 }
 
 func (p *pageMeta) Sitemap() config.SitemapConfig {
-       return p.sitemap
+       return p.pageConfig.Sitemap
 }
 
 func (p *pageMeta) Title() string {
-       return p.title
+       return p.pageConfig.Title
 }
 
 const defaultContentType = "page"
 
 func (p *pageMeta) Type() string {
-       if p.contentType != "" {
-               return p.contentType
+       if p.pageConfig.Type != "" {
+               return p.pageConfig.Type
        }
 
        if sect := p.Section(); sect != "" {
@@ -270,36 +266,56 @@ func (p *pageMeta) Type() string {
 }
 
 func (p *pageMeta) Weight() int {
-       return p.weight
+       return p.pageConfig.Weight
 }
 
-func (ps *pageState) setMetaPre() error {
-       pm := ps.m
-       p := ps
-       frontmatter := p.content.parseInfo.frontMatter
-       watching := p.s.watching()
-
+func (p *pageMeta) setMetaPre(pi *contentParseInfo, conf config.AllProvider) error {
+       frontmatter := pi.frontMatter
        if frontmatter != nil {
+               pcfg := p.pageConfig
+               if pcfg == nil {
+                       panic("pageConfig not set")
+               }
                // Needed for case insensitive fetching of params values
                maps.PrepareParams(frontmatter)
-               pm.pageMetaParams.params = frontmatter
-               if p.IsNode() {
-                       // Check for any cascade define on itself.
-                       if cv, found := frontmatter["cascade"]; found {
-                               var err error
-                               cascade, err := page.DecodeCascade(cv)
-                               if err != nil {
-                                       return err
-                               }
-                               pm.pageMetaParams.cascade = cascade
+               pcfg.Params = frontmatter
+               // Check for any cascade define on itself.
+               if cv, found := frontmatter["cascade"]; found {
+                       var err error
+                       cascade, err := page.DecodeCascade(cv)
+                       if err != nil {
+                               return err
+                       }
+                       pcfg.Cascade = cascade
+               }
 
+               // Look for path, lang and kind, all of which values we need early on.
+               if v, found := frontmatter["path"]; found {
+                       pcfg.Path = paths.ToSlashPreserveLeading(cast.ToString(v))
+                       pcfg.Params["path"] = pcfg.Path
+               }
+               if v, found := frontmatter["lang"]; found {
+                       lang := strings.ToLower(cast.ToString(v))
+                       if _, ok := conf.PathParser().LanguageIndex[lang]; ok {
+                               pcfg.Lang = lang
+                               pcfg.Params["lang"] = pcfg.Lang
                        }
                }
-       } else if pm.pageMetaParams.params == nil {
-               pm.pageMetaParams.params = make(maps.Params)
+               if v, found := frontmatter["kind"]; found {
+                       s := cast.ToString(v)
+                       if s != "" {
+                               pcfg.Kind = kinds.GetKindMain(s)
+                               if pcfg.Kind == "" {
+                                       return fmt.Errorf("unknown kind %q in front matter", s)
+                               }
+                               pcfg.Params["kind"] = pcfg.Kind
+                       }
+               }
+       } else if p.pageMetaParams.pageConfig.Params == nil {
+               p.pageConfig.Params = make(maps.Params)
        }
 
-       pm.pageMetaParams.init(watching)
+       p.pageMetaParams.init(conf.Watching())
 
        return nil
 }
@@ -308,18 +324,18 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
        ps.m.setMetaPostCount++
        var cascadeHashPre uint64
        if ps.m.setMetaPostCount > 1 {
-               cascadeHashPre = identity.HashUint64(ps.m.cascade)
-               ps.m.cascade = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
+               cascadeHashPre = identity.HashUint64(ps.m.pageConfig.Cascade)
+               ps.m.pageConfig.Cascade = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
 
        }
 
        // Apply cascades first so they can be overriden later.
        if cascade != nil {
-               if ps.m.cascade != nil {
+               if ps.m.pageConfig.Cascade != nil {
                        for k, v := range cascade {
-                               vv, found := ps.m.cascade[k]
+                               vv, found := ps.m.pageConfig.Cascade[k]
                                if !found {
-                                       ps.m.cascade[k] = v
+                                       ps.m.pageConfig.Cascade[k] = v
                                } else {
                                        // Merge
                                        for ck, cv := range v {
@@ -329,21 +345,21 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
                                        }
                                }
                        }
-                       cascade = ps.m.cascade
+                       cascade = ps.m.pageConfig.Cascade
                } else {
-                       ps.m.cascade = cascade
+                       ps.m.pageConfig.Cascade = cascade
                }
        }
 
        if cascade == nil {
-               cascade = ps.m.cascade
+               cascade = ps.m.pageConfig.Cascade
        }
 
        if ps.m.setMetaPostCount > 1 {
-               ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.cascade)
+               ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.Cascade)
                if !ps.m.setMetaPostCascadeChanged {
                        // No changes, restore any value that may be changed by aggregation.
-                       ps.m.dates = ps.m.datesOriginal.dates
+                       ps.m.pageConfig.Dates = ps.m.datesOriginal
                        return nil
                }
                ps.m.setMetaPostPrepareRebuild()
@@ -356,8 +372,8 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
                        continue
                }
                for kk, vv := range v {
-                       if _, found := ps.m.params[kk]; !found {
-                               ps.m.params[kk] = vv
+                       if _, found := ps.m.pageConfig.Params[kk]; !found {
+                               ps.m.pageConfig.Params[kk] = vv
                        }
                }
        }
@@ -371,7 +387,7 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
        }
 
        // Store away any original values that may be changed from aggregation.
-       ps.m.datesOriginal = ps.m.pageMetaDates
+       ps.m.datesOriginal = ps.m.pageConfig.Dates
 
        return nil
 }
@@ -392,13 +408,8 @@ func (p *pageState) setMetaPostParams() error {
                gitAuthorDate = p.gitInfo.AuthorDate
        }
 
-       pm.pageMetaDates = pageMetaDates{}
-       pm.urlPaths = pagemeta.URLPath{}
-
        descriptor := &pagemeta.FrontMatterDescriptor{
-               Params:        pm.params,
-               Dates:         &pm.pageMetaDates.dates,
-               PageURLs:      &pm.urlPaths,
+               PageConfig:    pm.pageConfig,
                BaseFilename:  contentBaseName,
                ModTime:       mtime,
                GitAuthorDate: gitAuthorDate,
@@ -413,16 +424,27 @@ func (p *pageState) setMetaPostParams() error {
                p.s.Log.Errorf("Failed to handle dates for page %q: %s", p.pathOrTitle(), err)
        }
 
-       pm.buildConfig, err = pagemeta.DecodeBuildConfig(pm.params["_build"])
+       var buildConfig any
+       if v, ok := pm.pageConfig.Params["_build"]; ok {
+               buildConfig = v
+       } else {
+               buildConfig = pm.pageConfig.Params["build"]
+       }
+
+       pm.pageConfig.Build, err = pagemeta.DecodeBuildConfig(buildConfig)
        if err != nil {
                return err
        }
 
        var sitemapSet bool
 
+       pcfg := pm.pageConfig
+
+       params := pcfg.Params
+
        var draft, published, isCJKLanguage *bool
        var userParams map[string]any
-       for k, v := range pm.params {
+       for k, v := range pcfg.Params {
                loki := strings.ToLower(k)
 
                if loki == "params" {
@@ -431,7 +453,7 @@ func (p *pageState) setMetaPostParams() error {
                                return err
                        }
                        userParams = vv
-                       delete(pm.params, k)
+                       delete(pcfg.Params, k)
                        continue
                }
 
@@ -450,43 +472,43 @@ func (p *pageState) setMetaPostParams() error {
 
                switch loki {
                case "title":
-                       pm.title = cast.ToString(v)
-                       pm.params[loki] = pm.title
+                       pcfg.Title = cast.ToString(v)
+                       params[loki] = pcfg.Title
                case "linktitle":
-                       pm.linkTitle = cast.ToString(v)
-                       pm.params[loki] = pm.linkTitle
+                       pcfg.LinkTitle = cast.ToString(v)
+                       params[loki] = pcfg.LinkTitle
                case "summary":
-                       pm.summary = cast.ToString(v)
-                       pm.params[loki] = pm.summary
+                       pcfg.Summary = cast.ToString(v)
+                       params[loki] = pcfg.Summary
                case "description":
-                       pm.description = cast.ToString(v)
-                       pm.params[loki] = pm.description
+                       pcfg.Description = cast.ToString(v)
+                       params[loki] = pcfg.Description
                case "slug":
                        // Don't start or end with a -
-                       pm.urlPaths.Slug = strings.Trim(cast.ToString(v), "-")
-                       pm.params[loki] = pm.Slug()
+                       pcfg.Slug = strings.Trim(cast.ToString(v), "-")
+                       params[loki] = pm.Slug()
                case "url":
                        url := cast.ToString(v)
                        if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
                                return fmt.Errorf("URLs with protocol (http*) not supported: %q. In page %q", url, p.pathOrTitle())
                        }
-                       pm.urlPaths.URL = url
-                       pm.params[loki] = url
+                       pcfg.URL = url
+                       params[loki] = url
                case "type":
-                       pm.contentType = cast.ToString(v)
-                       pm.params[loki] = pm.contentType
+                       pcfg.Type = cast.ToString(v)
+                       params[loki] = pcfg.Type
                case "keywords":
-                       pm.keywords = cast.ToStringSlice(v)
-                       pm.params[loki] = pm.keywords
+                       pcfg.Keywords = cast.ToStringSlice(v)
+                       params[loki] = pcfg.Keywords
                case "headless":
                        // Legacy setting for leaf bundles.
                        // This is since Hugo 0.63 handled in a more general way for all
                        // pages.
                        isHeadless := cast.ToBool(v)
-                       pm.params[loki] = isHeadless
+                       params[loki] = isHeadless
                        if p.File().TranslationBaseName() == "index" && isHeadless {
-                               pm.buildConfig.List = pagemeta.Never
-                               pm.buildConfig.Render = pagemeta.Never
+                               pm.pageConfig.Build.List = pagemeta.Never
+                               pm.pageConfig.Build.Render = pagemeta.Never
                        }
                case "outputs":
                        o := cast.ToStringSlice(v)
@@ -501,43 +523,42 @@ func (p *pageState) setMetaPostParams() error {
                                        p.s.Log.Errorf("Failed to resolve output formats: %s", err)
                                } else {
                                        pm.configuredOutputFormats = outFormats
-                                       pm.params[loki] = outFormats
+                                       params[loki] = outFormats
                                }
                        }
                case "draft":
                        draft = new(bool)
                        *draft = cast.ToBool(v)
                case "layout":
-                       pm.layout = cast.ToString(v)
-                       pm.params[loki] = pm.layout
+                       pcfg.Layout = cast.ToString(v)
+                       params[loki] = pcfg.Layout
                case "markup":
-                       pm.markup = cast.ToString(v)
-                       pm.params[loki] = pm.markup
+                       pcfg.Markup = cast.ToString(v)
+                       params[loki] = pcfg.Markup
                case "weight":
-                       pm.weight = cast.ToInt(v)
-                       pm.params[loki] = pm.weight
+                       pcfg.Weight = cast.ToInt(v)
+                       params[loki] = pcfg.Weight
                case "aliases":
-                       pm.aliases = cast.ToStringSlice(v)
-                       for i, alias := range pm.aliases {
+                       pcfg.Aliases = cast.ToStringSlice(v)
+                       for i, alias := range pcfg.Aliases {
                                if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
                                        return fmt.Errorf("http* aliases not supported: %q", alias)
                                }
-                               pm.aliases[i] = filepath.ToSlash(alias)
+                               pcfg.Aliases[i] = filepath.ToSlash(alias)
                        }
-                       pm.params[loki] = pm.aliases
+                       params[loki] = pcfg.Aliases
                case "sitemap":
-                       p.m.sitemap, err = config.DecodeSitemap(p.s.conf.Sitemap, maps.ToStringMap(v))
+                       pcfg.Sitemap, err = config.DecodeSitemap(p.s.conf.Sitemap, maps.ToStringMap(v))
                        if err != nil {
                                return fmt.Errorf("failed to decode sitemap config in front matter: %s", err)
                        }
-                       pm.params[loki] = p.m.sitemap
                        sitemapSet = true
                case "iscjklanguage":
                        isCJKLanguage = new(bool)
                        *isCJKLanguage = cast.ToBool(v)
                case "translationkey":
-                       pm.translationKey = cast.ToString(v)
-                       pm.params[loki] = pm.translationKey
+                       pcfg.TranslationKey = cast.ToString(v)
+                       params[loki] = pcfg.TranslationKey
                case "resources":
                        var resources []map[string]any
                        handled := true
@@ -563,8 +584,7 @@ func (p *pageState) setMetaPostParams() error {
                        }
 
                        if handled {
-                               pm.params[loki] = resources
-                               pm.resourcesMetadata = resources
+                               pcfg.Resources = resources
                                break
                        }
                        fallthrough
@@ -586,51 +606,51 @@ func (p *pageState) setMetaPostParams() error {
                                                for i, u := range vv {
                                                        a[i] = cast.ToString(u)
                                                }
-                                               pm.params[loki] = a
+                                               params[loki] = a
                                        } else {
-                                               pm.params[loki] = vv
+                                               params[loki] = vv
                                        }
                                } else {
-                                       pm.params[loki] = []string{}
+                                       params[loki] = []string{}
                                }
 
                        default:
-                               pm.params[loki] = vv
+                               params[loki] = vv
                        }
                }
        }
 
        for k, v := range userParams {
-               pm.params[strings.ToLower(k)] = v
+               params[strings.ToLower(k)] = v
        }
 
        if !sitemapSet {
-               pm.sitemap = p.s.conf.Sitemap
+               pcfg.Sitemap = p.s.conf.Sitemap
        }
 
-       pm.markup = p.s.ContentSpec.ResolveMarkup(pm.markup)
+       pcfg.Markup = p.s.ContentSpec.ResolveMarkup(pcfg.Markup)
 
        if draft != nil && published != nil {
-               pm.draft = *draft
+               pcfg.Draft = *draft
                p.m.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", p.File().Filename())
        } else if draft != nil {
-               pm.draft = *draft
+               pcfg.Draft = *draft
        } else if published != nil {
-               pm.draft = !*published
+               pcfg.Draft = !*published
        }
-       pm.params["draft"] = pm.draft
+       params["draft"] = pcfg.Draft
 
        if isCJKLanguage != nil {
-               pm.isCJKLanguage = *isCJKLanguage
-       } else if p.s.conf.HasCJKLanguage && p.content.openSource != nil {
-               if cjkRe.Match(p.content.mustSource()) {
-                       pm.isCJKLanguage = true
+               pcfg.IsCJKLanguage = *isCJKLanguage
+       } else if p.s.conf.HasCJKLanguage && p.m.content.pi.openSource != nil {
+               if cjkRe.Match(p.m.content.mustSource()) {
+                       pcfg.IsCJKLanguage = true
                } else {
-                       pm.isCJKLanguage = false
+                       pcfg.IsCJKLanguage = false
                }
        }
 
-       pm.params["iscjklanguage"] = p.m.isCJKLanguage
+       params["iscjklanguage"] = pcfg.IsCJKLanguage
 
        return nil
 }
@@ -643,7 +663,7 @@ func (p *pageMeta) shouldList(global bool) bool {
                return false
        }
 
-       switch p.buildConfig.List {
+       switch p.pageConfig.Build.List {
        case pagemeta.Always:
                return true
        case pagemeta.Never:
@@ -667,56 +687,56 @@ func (p *pageMeta) shouldBeCheckedForMenuDefinitions() bool {
                return false
        }
 
-       return p.kind == kinds.KindHome || p.kind == kinds.KindSection || p.kind == kinds.KindPage
+       return p.pageConfig.Kind == kinds.KindHome || p.pageConfig.Kind == kinds.KindSection || p.pageConfig.Kind == kinds.KindPage
 }
 
 func (p *pageMeta) noRender() bool {
-       return p.buildConfig.Render != pagemeta.Always
+       return p.pageConfig.Build.Render != pagemeta.Always
 }
 
 func (p *pageMeta) noLink() bool {
-       return p.buildConfig.Render == pagemeta.Never
+       return p.pageConfig.Build.Render == pagemeta.Never
 }
 
 func (p *pageMeta) applyDefaultValues() error {
-       if p.buildConfig.IsZero() {
-               p.buildConfig, _ = pagemeta.DecodeBuildConfig(nil)
+       if p.pageConfig.Build.IsZero() {
+               p.pageConfig.Build, _ = pagemeta.DecodeBuildConfig(nil)
        }
 
        if !p.s.conf.IsKindEnabled(p.Kind()) {
-               (&p.buildConfig).Disable()
+               (&p.pageConfig.Build).Disable()
        }
 
-       if p.markup == "" {
+       if p.pageConfig.Markup == "" {
                if p.File() != nil {
                        // Fall back to file extension
-                       p.markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
+                       p.pageConfig.Markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
                }
-               if p.markup == "" {
-                       p.markup = "markdown"
+               if p.pageConfig.Markup == "" {
+                       p.pageConfig.Markup = "markdown"
                }
        }
 
-       if p.title == "" && p.f == nil {
+       if p.pageConfig.Title == "" && p.f == nil {
                switch p.Kind() {
                case kinds.KindHome:
-                       p.title = p.s.Title()
+                       p.pageConfig.Title = p.s.Title()
                case kinds.KindSection:
                        sectionName := p.pathInfo.Unmormalized().BaseNameNoIdentifier()
                        if p.s.conf.PluralizeListTitles {
                                sectionName = flect.Pluralize(sectionName)
                        }
-                       p.title = p.s.conf.C.CreateTitle(sectionName)
+                       p.pageConfig.Title = p.s.conf.C.CreateTitle(sectionName)
                case kinds.KindTerm:
                        if p.term != "" {
-                               p.title = p.s.conf.C.CreateTitle(p.term)
+                               p.pageConfig.Title = p.s.conf.C.CreateTitle(p.term)
                        } else {
                                panic("term not set")
                        }
                case kinds.KindTaxonomy:
-                       p.title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unmormalized().BaseNameNoIdentifier()), "-", " ", -1)
+                       p.pageConfig.Title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unmormalized().BaseNameNoIdentifier()), "-", " ", -1)
                case kinds.KindStatus404:
-                       p.title = "404 Page not found"
+                       p.pageConfig.Title = "404 Page not found"
                }
        }
 
@@ -767,7 +787,7 @@ func (m *pageMeta) outputFormats() output.Formats {
 }
 
 func (p *pageMeta) Slug() string {
-       return p.urlPaths.Slug
+       return p.pageConfig.Slug
 }
 
 func getParam(m resource.ResourceParamsProvider, key string, stringToLower bool) any {
@@ -805,26 +825,6 @@ func getParamToLower(m resource.ResourceParamsProvider, key string) any {
        return getParam(m, key, true)
 }
 
-type pageMetaDates struct {
-       dates resource.Dates
-}
-
-func (d *pageMetaDates) Date() time.Time {
-       return d.dates.Date()
-}
-
-func (d *pageMetaDates) Lastmod() time.Time {
-       return d.dates.Lastmod()
-}
-
-func (d *pageMetaDates) PublishDate() time.Time {
-       return d.dates.PublishDate()
-}
-
-func (d *pageMetaDates) ExpiryDate() time.Time {
-       return d.dates.ExpiryDate()
-}
-
 func (ps *pageState) initLazyProviders() error {
        ps.init.Add(func(ctx context.Context) (any, error) {
                pp, err := newPagePaths(ps)
index 89eeb2e0e3720c0f9368e8d71ac1f01ff920f6d6..3bef55f43fdf97d786a00d288f5d5b8639b07a9f 100644 (file)
@@ -15,45 +15,106 @@ package hugolib
 
 import (
        "fmt"
+       "path/filepath"
        "sync"
        "sync/atomic"
 
+       "github.com/gohugoio/hugo/hugofs/files"
        "github.com/gohugoio/hugo/identity"
        "github.com/gohugoio/hugo/resources"
 
        "github.com/gohugoio/hugo/common/maps"
+       "github.com/gohugoio/hugo/common/paths"
 
        "github.com/gohugoio/hugo/lazy"
 
        "github.com/gohugoio/hugo/resources/kinds"
        "github.com/gohugoio/hugo/resources/page"
+       "github.com/gohugoio/hugo/resources/page/pagemeta"
 )
 
 var pageIDCounter atomic.Uint64
 
-func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
-       if m.pathInfo == nil {
+func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
+       m.Staler = &resources.AtomicStaler{}
+       if m.pageConfig == nil {
+               m.pageMetaParams = pageMetaParams{
+                       pageConfig: &pagemeta.PageConfig{
+                               Params: maps.Params{},
+                       },
+               }
+       }
+
+       var sourceKey string
+       if m.f != nil {
+               sourceKey = filepath.ToSlash(m.f.Filename())
+       }
+
+       pid := pageIDCounter.Add(1)
+       pi, err := m.parseFrontMatter(h, pid, sourceKey)
+       if err != nil {
+               return nil, nil, err
+       }
+
+       if err := m.setMetaPre(pi, h.Conf); err != nil {
+               return nil, nil, m.wrapError(err, h.BaseFs.SourceFs)
+       }
+       pcfg := m.pageConfig
+
+       if pcfg.Path != "" {
+               s := m.pageConfig.Path
+               if !paths.HasExt(s) {
+                       var (
+                               isBranch bool
+                               ext      string = "md"
+                       )
+                       if pcfg.Kind != "" {
+                               isBranch = kinds.IsBranch(pcfg.Kind)
+                       } else if m.pathInfo != nil {
+                               isBranch = m.pathInfo.IsBranchBundle()
+                               if m.pathInfo.Ext() != "" {
+                                       ext = m.pathInfo.Ext()
+                               }
+                       } else if m.f != nil {
+                               pi := m.f.FileInfo().Meta().PathInfo
+                               isBranch = pi.IsBranchBundle()
+                               if pi.Ext() != "" {
+                                       ext = pi.Ext()
+                               }
+                       }
+                       if isBranch {
+                               s += "/_index." + ext
+                       } else {
+                               s += "/index." + ext
+                       }
+               }
+               m.pathInfo = h.Conf.PathParser().Parse(files.ComponentFolderContent, s)
+       } else if m.pathInfo == nil {
                if m.f != nil {
                        m.pathInfo = m.f.FileInfo().Meta().PathInfo
                }
+
                if m.pathInfo == nil {
                        panic(fmt.Sprintf("missing pathInfo in %v", m))
                }
        }
 
-       m.Staler = &resources.AtomicStaler{}
-
        ps, err := func() (*pageState, error) {
                if m.s == nil {
                        // Identify the Site/language to associate this Page with.
                        var lang string
-                       if m.f != nil {
+                       if pcfg.Lang != "" {
+                               lang = pcfg.Lang
+                       } else if m.f != nil {
                                meta := m.f.FileInfo().Meta()
                                lang = meta.Lang
                                m.s = h.Sites[meta.LangIndex]
                        } else {
                                lang = m.pathInfo.Lang()
                        }
+                       if lang == "" {
+                               lang = h.Conf.DefaultContentLanguage()
+                       }
                        var found bool
                        for _, ss := range h.Sites {
                                if ss.Lang() == lang {
@@ -62,51 +123,49 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
                                        break
                                }
                        }
+
                        if !found {
                                return nil, fmt.Errorf("no site found for language %q", lang)
                        }
-
                }
 
                // Identify Page Kind.
-               if m.kind == "" {
-                       m.kind = kinds.KindSection
+               if m.pageConfig.Kind == "" {
+                       m.pageConfig.Kind = kinds.KindSection
                        if m.pathInfo.Base() == "/" {
-                               m.kind = kinds.KindHome
+                               m.pageConfig.Kind = kinds.KindHome
                        } else if m.pathInfo.IsBranchBundle() {
                                // A section, taxonomy or term.
                                tc := m.s.pageMap.cfg.getTaxonomyConfig(m.Path())
                                if !tc.IsZero() {
                                        // Either a taxonomy or a term.
                                        if tc.pluralTreeKey == m.Path() {
-                                               m.kind = kinds.KindTaxonomy
+                                               m.pageConfig.Kind = kinds.KindTaxonomy
                                        } else {
-                                               m.kind = kinds.KindTerm
+                                               m.pageConfig.Kind = kinds.KindTerm
                                        }
                                }
                        } else if m.f != nil {
-                               m.kind = kinds.KindPage
+                               m.pageConfig.Kind = kinds.KindPage
                        }
                }
 
-               if m.kind == kinds.KindPage && !m.s.conf.IsKindEnabled(m.kind) {
+               if m.pageConfig.Kind == kinds.KindPage && !m.s.conf.IsKindEnabled(m.pageConfig.Kind) {
                        return nil, nil
                }
 
-               pid := pageIDCounter.Add(1)
-
-               // Parse page content.
-               cachedContent, err := newCachedContent(m, pid)
-               if err != nil {
-                       return nil, m.wrapError(err)
-               }
-
                var dependencyManager identity.Manager = identity.NopManager
 
                if m.s.conf.Internal.Watch {
                        dependencyManager = identity.NewManager(m.Path())
                }
 
+               // Parse the rest of the page content.
+               m.content, err = m.newCachedContent(h, pi)
+               if err != nil {
+                       return nil, m.wrapError(err, h.SourceFs)
+               }
+
                ps := &pageState{
                        pid:                               pid,
                        pageOutput:                        nopPageOutput,
@@ -115,7 +174,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
                        Staler:                            m,
                        dependencyManager:                 dependencyManager,
                        pageCommon: &pageCommon{
-                               content:                   cachedContent,
                                FileProvider:              m,
                                AuthorProvider:            m,
                                Scratcher:                 maps.NewScratcher(),
@@ -168,10 +226,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
                ps.ShortcodeInfoProvider = ps
                ps.AlternativeOutputFormatsProvider = ps
 
-               if err := ps.setMetaPre(); err != nil {
-                       return nil, ps.wrapError(err)
-               }
-
                if err := ps.initLazyProviders(); err != nil {
                        return nil, ps.wrapError(err)
                }
@@ -182,5 +236,9 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
                m.MarkStale()
        }
 
-       return ps, err
+       if ps == nil {
+               return nil, nil, err
+       }
+
+       return ps, ps.PathInfo(), err
 }
index 6e7980a6d295d4b1389fcd786633e19270b44c56..b0d2009e4325c7c45a1a2f6b9c8b76ff332f8674 100644 (file)
@@ -127,7 +127,7 @@ func createTargetPathDescriptor(p *pageState) (page.TargetPathDescriptor, error)
                Section:     pageInfoCurrentSection,
                UglyURLs:    s.h.Conf.IsUglyURLs(p.Section()),
                ForcePrefix: s.h.Conf.IsMultihost() || alwaysInSubDir,
-               URL:         pm.urlPaths.URL,
+               URL:         pm.pageConfig.URL,
        }
 
        if pm.Slug() != "" {
index 3d86cdece90c6876ccd608534a3eb91710859319..e0026ed1fbda71497a0bf3add2f3f0ed2bbcbe70 100644 (file)
@@ -104,12 +104,12 @@ func (pco *pageContentOutput) Reset() {
 }
 
 func (pco *pageContentOutput) Fragments(ctx context.Context) *tableofcontents.Fragments {
-       return pco.po.p.content.mustContentToC(ctx, pco).tableOfContents
+       return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContents
 }
 
 func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HTML, error) {
-       content := pco.po.p.content
-       source, err := content.contentSource()
+       content := pco.po.p.m.content
+       source, err := content.pi.contentSource(content)
        if err != nil {
                return "", err
        }
@@ -125,7 +125,7 @@ func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HT
                insertPlaceholders = true
        }
        c := make([]byte, 0, len(source)+(len(source)/10))
-       for _, it := range content.parseInfo.itemsStep2 {
+       for _, it := range content.pi.itemsStep2 {
                switch v := it.(type) {
                case pageparser.Item:
                        c = append(c, source[v.Pos():v.Pos()+len(v.Val(source))]...)
@@ -169,12 +169,12 @@ func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HT
 }
 
 func (pco *pageContentOutput) Content(ctx context.Context) (any, error) {
-       r, err := pco.po.p.content.contentRendered(ctx, pco)
+       r, err := pco.po.p.m.content.contentRendered(ctx, pco)
        return r.content, err
 }
 
 func (pco *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
-       return pco.po.p.content.mustContentToC(ctx, pco).tableOfContentsHTML
+       return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContentsHTML
 }
 
 func (p *pageContentOutput) Len(ctx context.Context) int {
@@ -182,7 +182,7 @@ func (p *pageContentOutput) Len(ctx context.Context) int {
 }
 
 func (pco *pageContentOutput) mustContentRendered(ctx context.Context) contentSummary {
-       r, err := pco.po.p.content.contentRendered(ctx, pco)
+       r, err := pco.po.p.m.content.contentRendered(ctx, pco)
        if err != nil {
                pco.fail(err)
        }
@@ -190,7 +190,7 @@ func (pco *pageContentOutput) mustContentRendered(ctx context.Context) contentSu
 }
 
 func (pco *pageContentOutput) mustContentPlain(ctx context.Context) contentPlainPlainWords {
-       r, err := pco.po.p.content.contentPlain(ctx, pco)
+       r, err := pco.po.p.m.content.contentPlain(ctx, pco)
        if err != nil {
                pco.fail(err)
        }
@@ -270,7 +270,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
        }
 
        conv := pco.po.p.getContentConverter()
-       if opts.Markup != "" && opts.Markup != pco.po.p.m.markup {
+       if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfig.Markup {
                var err error
                conv, err = pco.po.p.m.newContentConverter(pco.po.p, opts.Markup)
                if err != nil {
@@ -281,6 +281,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
        var rendered []byte
 
        parseInfo := &contentParseInfo{
+               h:   pco.po.p.s.h,
                pid: pco.po.p.pid,
        }
 
@@ -293,7 +294,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
                }
 
                s := newShortcodeHandler(pco.po.p.pathOrTitle(), pco.po.p.s)
-               if err := parseInfo.mapItems(contentToRenderb, s); err != nil {
+               if err := parseInfo.mapItemsAfterFrontMatter(contentToRenderb, s); err != nil {
                        return "", err
                }
 
@@ -320,7 +321,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
 
                        tokenHandler := func(ctx context.Context, token string) ([]byte, error) {
                                if token == tocShortcodePlaceholder {
-                                       toc, err := pco.po.p.content.contentToC(ctx, pco)
+                                       toc, err := pco.po.p.m.content.contentToC(ctx, pco)
                                        if err != nil {
                                                return nil, err
                                        }
@@ -350,7 +351,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
                }
 
                // We need a consolidated view in $page.HasShortcode
-               pco.po.p.content.shortcodeState.transferNames(s)
+               pco.po.p.m.content.shortcodeState.transferNames(s)
 
        } else {
                c, err := pco.renderContentWithConverter(ctx, conv, []byte(contentToRender), false)
@@ -411,7 +412,7 @@ func (pco *pageContentOutput) initRenderHooks() error {
                var renderCacheMu sync.Mutex
 
                resolvePosition := func(ctx any) text.Position {
-                       source := pco.po.p.content.mustSource()
+                       source := pco.po.p.m.content.mustSource()
                        var offset int
 
                        switch v := ctx.(type) {
index 32b4bd7c33956041b588e9d1d7a6073a62783c88..f80f14035fa763dc48113928f8ec7652397a8c87 100644 (file)
 
 package hugolib
 
-import "testing"
+import (
+       "testing"
+
+       qt "github.com/frankban/quicktest"
+)
 
 func TestFrontMatterParamsInItsOwnSection(t *testing.T) {
        t.Parallel()
@@ -52,3 +56,106 @@ Summary: {{ .Summary }}|
                "Summary: frontmatter.summary|",
        )
 }
+
+func TestFrontMatterParamsKindPath(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+baseURL = "https://example.org/"
+disableKinds = ["taxonomy", "term"]
+
+-- content/p1.md --
+---
+title: "P1"
+date: 2019-08-07
+path: "/a/b/c"
+slug: "s1"
+---
+-- content/mysection.md --
+---
+title: "My Section"
+kind: "section"
+date: 2022-08-07
+path: "/a/b"
+---
+-- layouts/index.html --
+RegularPages: {{ range site.RegularPages }}{{ .Path }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Date.Format "2006-02-01" }}| Slug: {{ .Params.slug }}|{{ end }}$
+Sections: {{ range site.Sections }}{{ .Path }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Date.Format "2006-02-01" }}| Slug: {{ .Params.slug }}|{{ end }}$
+{{ $ab := site.GetPage "a/b" }}
+a/b pages: {{ range $ab.RegularPages }}{{ .Path }}|{{ .RelPermalink }}|{{ end }}$
+`
+
+       b := Test(t, files)
+
+       b.AssertFileContent("public/index.html",
+               "RegularPages: /a/b/c|/a/b/s1/|P1|2019-07-08| Slug: s1|$",
+               "Sections: /a|/a/|As",
+               "a/b pages: /a/b/c|/a/b/s1/|$",
+       )
+}
+
+func TestFrontMatterParamsLang(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+baseURL = "https://example.org/"
+disableKinds = ["taxonomy", "term"]
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+[languages.nn]
+weight = 2
+-- content/p1.md --
+---
+title: "P1 nn"
+lang: "nn"
+---
+-- content/p2.md --
+---
+title: "P2"
+---
+-- layouts/index.html --
+RegularPages: {{ range site.RegularPages }}{{ .Path }}|{{ .RelPermalink }}|{{ .Title }}|{{ end }}$
+
+`
+
+       b := Test(t, files)
+
+       b.AssertFileContent("public/en/index.html",
+               "RegularPages: /p2|/en/p2/|P2|$",
+       )
+       b.AssertFileContent("public/nn/index.html",
+               "RegularPages: /p1|/nn/p1/|P1 nn|$",
+       )
+}
+
+func TestFrontMatterParamsLangNoCascade(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+baseURL = "https://example.org/"
+disableKinds = ["taxonomy", "term"]
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+[languages.nn]
+weight = 2
+-- content/_index.md --
++++
+[[cascade]]
+background = 'yosemite.jpg'
+lang = 'nn'
++++
+
+`
+
+       b, err := TestE(t, files)
+       b.Assert(err, qt.IsNotNil)
+}
index c5125f717d01f8e3870d31bcbd2ccf2cc9d8144a..0ec5d5ce462c8aca9e67349d4e9fdd1e5744717e 100644 (file)
@@ -315,7 +315,7 @@ func prepareShortcode(
        isRenderString bool,
 ) (shortcodeRenderer, error) {
        toParseErr := func(err error) error {
-               source := p.content.mustSource()
+               source := p.m.content.mustSource()
                return p.parseError(fmt.Errorf("failed to render shortcode %q: %w", sc.name, err), source, sc.pos)
        }
 
@@ -443,7 +443,7 @@ func doRenderShortcode(
                        //     unchanged.
                        // 2   If inner does not have a newline, strip the wrapping <p> block and
                        //     the newline.
-                       switch p.m.markup {
+                       switch p.m.pageConfig.Markup {
                        case "", "markdown":
                                if match, _ := regexp.MatchString(innerNewlineRegexp, inner); !match {
                                        cleaner, err := regexp.Compile(innerCleanupRegexp)
index ddf45c2869677cb1d5fe00d2832b9068b203c4aa..0cab713520bf120960cc6decdc4dc70e4edfe3d8 100644 (file)
@@ -40,6 +40,7 @@ import (
        "github.com/gohugoio/hugo/navigation"
        "github.com/gohugoio/hugo/output"
        "github.com/gohugoio/hugo/publisher"
+       "github.com/gohugoio/hugo/resources"
        "github.com/gohugoio/hugo/resources/page"
        "github.com/gohugoio/hugo/resources/page/pagemeta"
        "github.com/gohugoio/hugo/resources/page/siteidentities"
@@ -281,6 +282,7 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
                        page.Pages](d.MemCache, "/pags/all",
                        dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
                ),
+               cacheContentSource:      dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
                translationKeyPages:     maps.NewSliceCache[page.Page](),
                currentSite:             sites[0],
                skipRebuildForFilenames: make(map[string]bool),
index 379dd6e867be1b68836da08a6b0bc66604fb4bc8..138530680aa21885741190fb181d1e2789826a3c 100644 (file)
@@ -129,7 +129,7 @@ func pageRenderer(
                        continue
                }
 
-               if p.m.buildConfig.PublishResources {
+               if p.m.pageConfig.Build.PublishResources {
                        if err := p.renderResources(); err != nil {
                                s.SendError(p.errorf(err, "failed to render page resources"))
                                continue
index 8529b0e2810c4b7f7b36241801dccb647bb3c8bc..fbdb25d72475c85ef5c04ba36af6448b33d8ef04 100644 (file)
@@ -82,6 +82,14 @@ func (m PageMatcher) Matches(p Page) bool {
        return true
 }
 
+var disallowedCascadeKeys = map[string]bool{
+       // These define the structure of the page tree and cannot
+       // currently be set in the cascade.
+       "kind": true,
+       "path": true,
+       "lang": true,
+}
+
 func DecodeCascadeConfig(in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, map[PageMatcher]maps.Params], error) {
        buildConfig := func(in any) (map[PageMatcher]maps.Params, any, error) {
                cascade := make(map[PageMatcher]maps.Params)
@@ -101,6 +109,11 @@ func DecodeCascadeConfig(in any) (*config.ConfigNamespace[[]PageMatcherParamsCon
                        if err != nil {
                                return nil, nil, err
                        }
+                       for k := range m {
+                               if disallowedCascadeKeys[k] {
+                                       return nil, nil, fmt.Errorf("key %q not allowed in cascade config", k)
+                               }
+                       }
                        cfgs = append(cfgs, c)
                }
 
index d804f27a7118b95705d211689a499cff5757a7d2..17859d846cd4c52d4e00c05b7c4adfe0dca25dae 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 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.
@@ -19,15 +19,76 @@ import (
 
        "github.com/gohugoio/hugo/common/htime"
        "github.com/gohugoio/hugo/common/loggers"
+       "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/common/paths"
+       "github.com/gohugoio/hugo/resources/page"
 
        "github.com/gohugoio/hugo/helpers"
-       "github.com/gohugoio/hugo/resources/resource"
 
        "github.com/gohugoio/hugo/config"
        "github.com/spf13/cast"
 )
 
+type Dates struct {
+       Date        time.Time
+       Lastmod     time.Time
+       PublishDate time.Time
+       ExpiryDate  time.Time
+}
+
+func (d Dates) IsDateOrLastModAfter(in Dates) bool {
+       return d.Date.After(in.Date) || d.Lastmod.After(in.Lastmod)
+}
+
+func (d *Dates) UpdateDateAndLastmodIfAfter(in Dates) {
+       if in.Date.After(d.Date) {
+               d.Date = in.Date
+       }
+       if in.Lastmod.After(d.Lastmod) {
+               d.Lastmod = in.Lastmod
+       }
+}
+
+func (d Dates) IsAllDatesZero() bool {
+       return d.Date.IsZero() && d.Lastmod.IsZero() && d.PublishDate.IsZero() && d.ExpiryDate.IsZero()
+}
+
+// PageConfig configures a Page, typically from front matter.
+// Note that all the top level fields are reserved Hugo keywords.
+// Any custom configuration needs to be set in the Params map.
+type PageConfig struct {
+       Dates                   // Dates holds the fource core dates for this page.
+       Title          string   // The title of the page.
+       LinkTitle      string   // The link title of the page.
+       Type           string   // The content type of the page.
+       Layout         string   // The layout to use for to render this page.
+       Markup         string   // The markup used in the content file.
+       Weight         int      // The weight of the page, used in sorting if set to a non-zero value.
+       Kind           string   // The kind of page, e.g. "page", "section", "home" etc. This is usually derived from the content path.
+       Path           string   // The canonical path to the page, e.g. /sect/mypage. Note: Leading slash, no trailing slash, no extensions or language identifiers.
+       Lang           string   // The language code for this page. This is usually derived from the module mount or filename.
+       Slug           string   // The slug for this page.
+       Description    string   // The description for this page.
+       Summary        string   // The summary for this page.
+       Draft          bool     // Whether or not the content is a draft.
+       Headless       bool     // Whether or not the page should be rendered.
+       IsCJKLanguage  bool     // Whether or not the content is in a CJK language.
+       TranslationKey string   // The translation key for this page.
+       Keywords       []string // The keywords for this page.
+       Aliases        []string // The aliases for this page.
+       Outputs        []string // The output formats to render this page in. If not set, the site's configured output formats for this page kind will be used.
+
+       // These build options are set in the front matter,
+       // but not passed on to .Params.
+       Resources []map[string]any
+       Cascade   map[page.PageMatcher]maps.Params // Only relevant for branch nodes.
+       Sitemap   config.SitemapConfig
+       Build     BuildConfig
+
+       // User defined params.
+       Params maps.Params
+}
+
 // FrontMatterHandler maps front matter into Page fields and .Params.
 // Note that we currently have only extracted the date logic.
 type FrontMatterHandler struct {
@@ -47,9 +108,6 @@ type FrontMatterHandler struct {
 // FrontMatterDescriptor describes how to handle front matter for a given Page.
 // It has pointers to values in the receiving page which gets updated.
 type FrontMatterDescriptor struct {
-       // This is the Page's params.
-       Params map[string]any
-
        // This is the Page's base filename (BaseFilename), e.g. page.md., or
        // if page is a leaf bundle, the bundle folder name (ContentBaseName).
        BaseFilename string
@@ -60,13 +118,8 @@ type FrontMatterDescriptor struct {
        // May be set from the author date in Git.
        GitAuthorDate time.Time
 
-       // The below are pointers to values on Page and will be modified.
-
-       // This is the Page's dates.
-       Dates *resource.Dates
-
-       // This is the Page's Slug etc.
-       PageURLs *URLPath
+       // The below will be modified.
+       PageConfig *PageConfig
 
        // The Location to use to parse dates without time zone info.
        Location *time.Location
@@ -83,8 +136,8 @@ var dateFieldAliases = map[string][]string{
 // supplied front matter params. Note that this requires all lower-case keys
 // in the params map.
 func (f FrontMatterHandler) HandleDates(d *FrontMatterDescriptor) error {
-       if d.Dates == nil {
-               panic("missing dates")
+       if d.PageConfig == nil {
+               panic("missing pageConfig")
        }
 
        if f.dateHandler == nil {
@@ -297,7 +350,7 @@ func (f *FrontMatterHandler) createHandlers() error {
 
        if f.dateHandler, err = f.createDateHandler(f.fmConfig.Date,
                func(d *FrontMatterDescriptor, t time.Time) {
-                       d.Dates.FDate = t
+                       d.PageConfig.Date = t
                        setParamIfNotSet(fmDate, t, d)
                }); err != nil {
                return err
@@ -306,7 +359,7 @@ func (f *FrontMatterHandler) createHandlers() error {
        if f.lastModHandler, err = f.createDateHandler(f.fmConfig.Lastmod,
                func(d *FrontMatterDescriptor, t time.Time) {
                        setParamIfNotSet(fmLastmod, t, d)
-                       d.Dates.FLastmod = t
+                       d.PageConfig.Lastmod = t
                }); err != nil {
                return err
        }
@@ -314,7 +367,7 @@ func (f *FrontMatterHandler) createHandlers() error {
        if f.publishDateHandler, err = f.createDateHandler(f.fmConfig.PublishDate,
                func(d *FrontMatterDescriptor, t time.Time) {
                        setParamIfNotSet(fmPubDate, t, d)
-                       d.Dates.FPublishDate = t
+                       d.PageConfig.PublishDate = t
                }); err != nil {
                return err
        }
@@ -322,7 +375,7 @@ func (f *FrontMatterHandler) createHandlers() error {
        if f.expiryDateHandler, err = f.createDateHandler(f.fmConfig.ExpiryDate,
                func(d *FrontMatterDescriptor, t time.Time) {
                        setParamIfNotSet(fmExpiryDate, t, d)
-                       d.Dates.FExpiryDate = t
+                       d.PageConfig.ExpiryDate = t
                }); err != nil {
                return err
        }
@@ -331,10 +384,10 @@ func (f *FrontMatterHandler) createHandlers() error {
 }
 
 func setParamIfNotSet(key string, value any, d *FrontMatterDescriptor) {
-       if _, found := d.Params[key]; found {
+       if _, found := d.PageConfig.Params[key]; found {
                return
        }
-       d.Params[key] = value
+       d.PageConfig.Params[key] = value
 }
 
 func (f FrontMatterHandler) createDateHandler(identifiers []string, setter func(d *FrontMatterDescriptor, t time.Time)) (frontMatterFieldHandler, error) {
@@ -361,7 +414,7 @@ type frontmatterFieldHandlers int
 
 func (f *frontmatterFieldHandlers) newDateFieldHandler(key string, setter func(d *FrontMatterDescriptor, t time.Time)) frontMatterFieldHandler {
        return func(d *FrontMatterDescriptor) (bool, error) {
-               v, found := d.Params[key]
+               v, found := d.PageConfig.Params[key]
 
                if !found {
                        return false, nil
@@ -377,7 +430,7 @@ func (f *frontmatterFieldHandlers) newDateFieldHandler(key string, setter func(d
                setter(d, date)
 
                // This is the params key as set in front matter.
-               d.Params[key] = date
+               d.PageConfig.Params[key] = date
 
                return true, nil
        }
@@ -392,9 +445,9 @@ func (f *frontmatterFieldHandlers) newDateFilenameHandler(setter func(d *FrontMa
 
                setter(d, date)
 
-               if _, found := d.Params["slug"]; !found {
+               if _, found := d.PageConfig.Params["slug"]; !found {
                        // Use slug from filename
-                       d.PageURLs.Slug = slug
+                       d.PageConfig.Slug = slug
                }
 
                return true, nil
index 1aff8b511a53ebbc8afafb8ec4fe535482704425..9e1151f22c2d54b09f2017d905f4ffb774cca49e 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 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.
@@ -22,16 +22,15 @@ import (
        "github.com/gohugoio/hugo/config/testconfig"
 
        "github.com/gohugoio/hugo/resources/page/pagemeta"
-       "github.com/gohugoio/hugo/resources/resource"
 
        qt "github.com/frankban/quicktest"
 )
 
 func newTestFd() *pagemeta.FrontMatterDescriptor {
        return &pagemeta.FrontMatterDescriptor{
-               Params:   make(map[string]any),
-               Dates:    &resource.Dates{},
-               PageURLs: &pagemeta.URLPath{},
+               PageConfig: &pagemeta.PageConfig{
+                       Params: make(map[string]interface{}),
+               },
                Location: time.UTC,
        }
 }
@@ -105,16 +104,16 @@ func TestFrontMatterDatesHandlers(t *testing.T) {
                case ":git":
                        d.GitAuthorDate = d1
                }
-               d.Params["date"] = d2
+               d.PageConfig.Params["date"] = d2
                c.Assert(handler.HandleDates(d), qt.IsNil)
-               c.Assert(d.Dates.FDate, qt.Equals, d1)
-               c.Assert(d.Params["date"], qt.Equals, d2)
+               c.Assert(d.PageConfig.Dates.Date, qt.Equals, d1)
+               c.Assert(d.PageConfig.Params["date"], qt.Equals, d2)
 
                d = newTestFd()
-               d.Params["date"] = d2
+               d.PageConfig.Params["date"] = d2
                c.Assert(handler.HandleDates(d), qt.IsNil)
-               c.Assert(d.Dates.FDate, qt.Equals, d2)
-               c.Assert(d.Params["date"], qt.Equals, d2)
+               c.Assert(d.PageConfig.Dates.Date, qt.Equals, d2)
+               c.Assert(d.PageConfig.Params["date"], qt.Equals, d2)
 
        }
 }
@@ -137,15 +136,15 @@ func TestFrontMatterDatesDefaultKeyword(t *testing.T) {
 
        testDate, _ := time.Parse("2006-01-02", "2018-02-01")
        d := newTestFd()
-       d.Params["mydate"] = testDate
-       d.Params["date"] = testDate.Add(1 * 24 * time.Hour)
-       d.Params["mypubdate"] = testDate.Add(2 * 24 * time.Hour)
-       d.Params["publishdate"] = testDate.Add(3 * 24 * time.Hour)
+       d.PageConfig.Params["mydate"] = testDate
+       d.PageConfig.Params["date"] = testDate.Add(1 * 24 * time.Hour)
+       d.PageConfig.Params["mypubdate"] = testDate.Add(2 * 24 * time.Hour)
+       d.PageConfig.Params["publishdate"] = testDate.Add(3 * 24 * time.Hour)
 
        c.Assert(handler.HandleDates(d), qt.IsNil)
 
-       c.Assert(d.Dates.FDate.Day(), qt.Equals, 1)
-       c.Assert(d.Dates.FLastmod.Day(), qt.Equals, 2)
-       c.Assert(d.Dates.FPublishDate.Day(), qt.Equals, 4)
-       c.Assert(d.Dates.FExpiryDate.IsZero(), qt.Equals, true)
+       c.Assert(d.PageConfig.Dates.Date.Day(), qt.Equals, 1)
+       c.Assert(d.PageConfig.Dates.Lastmod.Day(), qt.Equals, 2)
+       c.Assert(d.PageConfig.Dates.PublishDate.Day(), qt.Equals, 4)
+       c.Assert(d.PageConfig.Dates.ExpiryDate.IsZero(), qt.Equals, true)
 }
index 94c6b00aaef1f7c25cb816a5ec1c65ce7cd3a12c..f5b6380bc20e24445d79ea4533e55f53ecc1443b 100644 (file)
@@ -17,13 +17,6 @@ import (
        "github.com/mitchellh/mapstructure"
 )
 
-type URLPath struct {
-       URL       string
-       Permalink string
-       Slug      string
-       Section   string
-}
-
 const (
        Never       = "never"
        Always      = "always"
index 88968750def84c0fd41b10f125dc5d237b2c360b..d84e26d57e4e38d570dbf8bfc4cb421921810570 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 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.
@@ -19,8 +19,6 @@ import (
        "github.com/gohugoio/hugo/common/htime"
 )
 
-var _ Dated = Dates{}
-
 // Dated wraps a "dated resource". These are the 4 dates that makes
 // the date logic in Hugo.
 type Dated interface {
@@ -37,27 +35,6 @@ type Dated interface {
        ExpiryDate() time.Time
 }
 
-// Dates holds the 4 Hugo dates.
-type Dates struct {
-       FDate        time.Time
-       FLastmod     time.Time
-       FPublishDate time.Time
-       FExpiryDate  time.Time
-}
-
-func (d *Dates) IsDateOrLastModAfter(in Dated) bool {
-       return d.Date().After(in.Date()) || d.Lastmod().After(in.Lastmod())
-}
-
-func (d *Dates) UpdateDateAndLastmodIfAfter(in Dated) {
-       if in.Date().After(d.Date()) {
-               d.FDate = in.Date()
-       }
-       if in.Lastmod().After(d.Lastmod()) {
-               d.FLastmod = in.Lastmod()
-       }
-}
-
 // IsFuture returns whether the argument represents the future.
 func IsFuture(d Dated) bool {
        if d.PublishDate().IsZero() {
@@ -79,19 +56,3 @@ func IsExpired(d Dated) bool {
 func IsZeroDates(d Dated) bool {
        return d.Date().IsZero() && d.Lastmod().IsZero() && d.ExpiryDate().IsZero() && d.PublishDate().IsZero()
 }
-
-func (p Dates) Date() time.Time {
-       return p.FDate
-}
-
-func (p Dates) Lastmod() time.Time {
-       return p.FLastmod
-}
-
-func (p Dates) PublishDate() time.Time {
-       return p.FPublishDate
-}
-
-func (p Dates) ExpiryDate() time.Time {
-       return p.FExpiryDate
-}