summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2024-01-29 10:02:24 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2024-01-30 20:12:03 +0100
commitf31a6db797c9251a362ef9f8ad4c03fb608b5ac0 (patch)
treea47dbb5764cfc96c49882e13a7da279dc43b1eb5
parentec22bb31a89883db5ca95404cda4f74344fd3762 (diff)
Add path, kind and lang to content front matter
Note that none of these can be set via cascade (you will get an error) Fixes #11544
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--hugolib/content_map.go6
-rw-r--r--hugolib/content_map_page.go89
-rw-r--r--hugolib/hugo_sites.go3
-rw-r--r--hugolib/integrationtest_builder.go9
-rw-r--r--hugolib/page.go33
-rw-r--r--hugolib/page__common.go3
-rw-r--r--hugolib/page__content.go180
-rw-r--r--hugolib/page__meta.go352
-rw-r--r--hugolib/page__new.go112
-rw-r--r--hugolib/page__paths.go2
-rw-r--r--hugolib/page__per_output.go27
-rw-r--r--hugolib/params_test.go109
-rw-r--r--hugolib/shortcode.go4
-rw-r--r--hugolib/site_new.go2
-rw-r--r--hugolib/site_render.go2
-rw-r--r--resources/page/page_matcher.go13
-rw-r--r--resources/page/pagemeta/page_frontmatter.go101
-rw-r--r--resources/page/pagemeta/page_frontmatter_test.go37
-rw-r--r--resources/page/pagemeta/pagemeta.go7
-rw-r--r--resources/resource/dates.go41
22 files changed, 707 insertions, 429 deletions
diff --git a/go.mod b/go.mod
index e8beedbfb..c74972d0b 100644
--- 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 3c1cfe7ff..4795d0d68 100644
--- 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=
diff --git a/hugolib/content_map.go b/hugolib/content_map.go
index fefa90bf1..96013c4ed 100644
--- a/hugolib/content_map.go
+++ b/hugolib/content_map.go
@@ -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,
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
index 536f23ccd..9fee74003 100644
--- a/hugolib/content_map_page.go
+++ b/hugolib/content_map_page.go
@@ -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)
}
}
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 80e754453..1b2840617 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -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.
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 3fec04df0..a46ae7275 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -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 {
diff --git a/hugolib/page.go b/hugolib/page.go
index f8ec5e225..822b7c021 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -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
diff --git a/hugolib/page__common.go b/hugolib/page__common.go
index 0881affe7..164776842 100644
--- a/hugolib/page__common.go
+++ b/hugolib/page__common.go
@@ -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
diff --git a/hugolib/page__content.go b/hugolib/page__content.go
index 64ce83f0e..62e78c612 100644
--- a/hugolib/page__content.go
+++ b/hugolib/page__content.go
@@ -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()
+ }