summaryrefslogtreecommitdiffstats
path: root/resources
diff options
context:
space:
mode:
Diffstat (limited to 'resources')
-rw-r--r--resources/page/page_matcher.go39
-rw-r--r--resources/page/page_matcher_test.go6
-rw-r--r--resources/page/pagemeta/page_frontmatter.go141
-rw-r--r--resources/page/pagemeta/page_frontmatter_test.go3
4 files changed, 137 insertions, 52 deletions
diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go
index 1e98b0836..27a7c7e9e 100644
--- a/resources/page/page_matcher.go
+++ b/resources/page/page_matcher.go
@@ -16,6 +16,7 @@ package page
import (
"fmt"
"path/filepath"
+ "slices"
"strings"
"github.com/gohugoio/hugo/common/loggers"
@@ -24,7 +25,6 @@ import (
"github.com/gohugoio/hugo/hugofs/glob"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/mitchellh/mapstructure"
- "slices"
)
// A PageMatcher can be used to match a Page with Glob patterns.
@@ -105,7 +105,7 @@ func CheckCascadePattern(logger loggers.Logger, m PageMatcher) {
}
}
-func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
+func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
buildConfig := func(in any) (*maps.Ordered[PageMatcher, maps.Params], any, error) {
cascade := maps.NewOrdered[PageMatcher, maps.Params]()
if in == nil {
@@ -120,7 +120,15 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
for _, m := range ms {
m = maps.CleanConfigStringMap(m)
- c, err := mapToPageMatcherParamsConfig(m)
+ var (
+ c PageMatcherParamsConfig
+ err error
+ )
+ if handleLegacyFormat {
+ c, err = mapToPageMatcherParamsConfigLegacy(m)
+ } else {
+ c, err = mapToPageMatcherParamsConfig(m)
+ }
if err != nil {
return nil, nil, err
}
@@ -155,8 +163,8 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
}
// DecodeCascade decodes in which could be either a map or a slice of maps.
-func DecodeCascade(logger loggers.Logger, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
- conf, err := DecodeCascadeConfig(logger, in)
+func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
+ conf, err := DecodeCascadeConfig(logger, handleLegacyFormat, in)
if err != nil {
return nil, err
}
@@ -167,6 +175,26 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
var pcfg PageMatcherParamsConfig
for k, v := range m {
switch strings.ToLower(k) {
+ case "_target", "target":
+ var target PageMatcher
+ if err := decodePageMatcher(v, &target); err != nil {
+ return pcfg, err
+ }
+ pcfg.Target = target
+ default:
+ if pcfg.Params == nil {
+ pcfg.Params = make(maps.Params)
+ }
+ pcfg.Params[k] = v
+ }
+ }
+ return pcfg, pcfg.init()
+}
+
+func mapToPageMatcherParamsConfigLegacy(m map[string]any) (PageMatcherParamsConfig, error) {
+ var pcfg PageMatcherParamsConfig
+ for k, v := range m {
+ switch strings.ToLower(k) {
case "params":
// We simplified the structure of the cascade config in Hugo 0.111.0.
// There is a small chance that someone has used the old structure with the params keyword,
@@ -190,7 +218,6 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
}
pcfg.Target = target
default:
- // Legacy config.
if pcfg.Params == nil {
pcfg.Params = make(maps.Params)
}
diff --git a/resources/page/page_matcher_test.go b/resources/page/page_matcher_test.go
index bc072ce15..2fe6ccc89 100644
--- a/resources/page/page_matcher_test.go
+++ b/resources/page/page_matcher_test.go
@@ -84,7 +84,7 @@ func TestPageMatcher(t *testing.T) {
c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
fn := func(m map[string]any) PageMatcherParamsConfig {
- v, err := mapToPageMatcherParamsConfig(m)
+ v, err := mapToPageMatcherParamsConfigLegacy(m)
c.Assert(err, qt.IsNil)
return v
}
@@ -129,7 +129,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
},
}
- got, err := DecodeCascadeConfig(loggers.NewDefault(), in)
+ got, err := DecodeCascadeConfig(loggers.NewDefault(), true, in)
c.Assert(err, qt.IsNil)
c.Assert(got, qt.IsNotNil)
@@ -143,7 +143,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
{Params: maps.Params{"b": string("bv")}, Target: PageMatcher{Kind: "page"}},
})
- got, err = DecodeCascadeConfig(loggers.NewDefault(), nil)
+ got, err = DecodeCascadeConfig(loggers.NewDefault(), true, nil)
c.Assert(err, qt.IsNil)
c.Assert(got, qt.IsNotNil)
}
diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go
index 7f98b1b88..7dea7ca6b 100644
--- a/resources/page/pagemeta/page_frontmatter.go
+++ b/resources/page/pagemeta/page_frontmatter.go
@@ -29,9 +29,11 @@ import (
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/markup"
"github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
+ "github.com/mitchellh/mapstructure"
"github.com/gohugoio/hugo/helpers"
@@ -74,35 +76,43 @@ func (d Dates) IsAllDatesZero() bool {
return d.Date.IsZero() && d.Lastmod.IsZero() && d.PublishDate.IsZero() && d.ExpiryDate.IsZero()
}
+// Page config that needs to be set early. These cannot be modified by cascade.
+type PageConfigEarly struct {
+ 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.
+ Cascade []map[string]any
+
+ // Content holds the content for this page.
+ Content Source
+}
+
// 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 `json:"-"` // Dates holds the four core dates for this page.
DatesStrings
- 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.
- 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.
- URL string // The URL to the rendered page, e.g. /sect/mypage.html.
- 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 `json:"-"` // 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.
+ PageConfigEarly `mapstructure:",squash"`
+ 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.
+ Weight int // The weight of the page, used in sorting if set to a non-zero value.
+ URL string // The URL to the rendered page, e.g. /sect/mypage.html.
+ 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 `json:"-"` // 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.
FrontMatterOnlyValues `mapstructure:"-" json:"-"`
- Cascade []map[string]any
Sitemap config.SitemapConfig
Build BuildConfig
Menus any // Can be a string, []string or map[string]any.
@@ -110,13 +120,29 @@ type PageConfig struct {
// User defined params.
Params maps.Params
- // Content holds the content for this page.
- Content Source
+ // The raw data from the content adapter.
+ // TODO(bep) clean up the ContentAdapterData vs Params.
+ ContentAdapterData map[string]any `mapstructure:"-" json:"-"`
// Compiled values.
- CascadeCompiled *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
- ContentMediaType media.Type `mapstructure:"-" json:"-"`
- IsFromContentAdapter bool `mapstructure:"-" json:"-"`
+ CascadeCompiled *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
+ ContentMediaType media.Type `mapstructure:"-" json:"-"`
+ ConfiguredOutputFormats output.Formats `mapstructure:"-" json:"-"`
+ IsFromContentAdapter bool `mapstructure:"-" json:"-"`
+}
+
+func ClonePageConfigForRebuild(p *PageConfig, params map[string]any) *PageConfig {
+ pp := &PageConfig{
+ PageConfigEarly: p.PageConfigEarly,
+ IsFromContentAdapter: p.IsFromContentAdapter,
+ }
+ if pp.IsFromContentAdapter {
+ pp.ContentAdapterData = params
+ } else {
+ pp.Params = params
+ }
+
+ return pp
}
var DefaultPageConfig = PageConfig{
@@ -149,8 +175,7 @@ func (p *PageConfig) Validate(pagesFromData bool) error {
return nil
}
-// Compile sets up the page configuration after all fields have been set.
-func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, logger loggers.Logger, mediaTypes media.Types) error {
+func (p *PageConfig) CompileForPagesFromDataPre(basePath string, logger loggers.Logger, mediaTypes media.Types) error {
// In content adapters, we always get relative paths.
if basePath != "" {
p.Path = path.Join(basePath, p.Path)
@@ -158,12 +183,32 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
if p.Params == nil {
p.Params = make(maps.Params)
- } else if pagesFromData {
- p.Params = maps.PrepareParamsClone(p.Params)
} else {
- maps.PrepareParams(p.Params)
+ p.Params = maps.PrepareParamsClone(p.Params)
+ }
+
+ if p.Kind == "" {
+ p.Kind = kinds.KindPage
+ }
+
+ if p.Cascade != nil {
+ cascade, err := page.DecodeCascade(logger, false, p.Cascade)
+ if err != nil {
+ return fmt.Errorf("failed to decode cascade: %w", err)
+ }
+ p.CascadeCompiled = cascade
}
+ // Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
+ // We do that when we create pages from the file system; mostly for backward compatibility,
+ // but also because people tend to use use the filename to name their resources (with spaces and all),
+ // and this isn't relevant when creating resources from an API where it's easy to add textual meta data.
+ p.Path = paths.NormalizePathStringBasic(p.Path)
+
+ return p.compilePrePost("", mediaTypes)
+}
+
+func (p *PageConfig) compilePrePost(ext string, mediaTypes media.Types) error {
if p.Content.Markup == "" && p.Content.MediaType == "" {
if ext == "" {
ext = "md"
@@ -194,25 +239,37 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
if p.Content.Markup == "" {
p.Content.Markup = p.ContentMediaType.SubType
}
+ return nil
+}
- if pagesFromData {
- if p.Kind == "" {
- p.Kind = kinds.KindPage
+// Compile sets up the page configuration after all fields have been set.
+func (p *PageConfig) Compile(ext string, logger loggers.Logger, outputFormats output.Formats, mediaTypes media.Types) error {
+ if p.IsFromContentAdapter {
+ if err := mapstructure.WeakDecode(p.ContentAdapterData, p); err != nil {
+ err = fmt.Errorf("failed to decode page map: %w", err)
+ return err
}
+ // Not needed anymore.
+ p.ContentAdapterData = nil
+ }
- // Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
- // We do that when we create pages from the file system; mostly for backward compatibility,
- // but also because people tend to use use the filename to name their resources (with spaces and all),
- // and this isn't relevant when creating resources from an API where it's easy to add textual meta data.
- p.Path = paths.NormalizePathStringBasic(p.Path)
+ if p.Params == nil {
+ p.Params = make(maps.Params)
+ } else {
+ maps.PrepareParams(p.Params)
}
- if p.Cascade != nil {
- cascade, err := page.DecodeCascade(logger, p.Cascade)
+ if err := p.compilePrePost(ext, mediaTypes); err != nil {
+ return err
+ }
+
+ if len(p.Outputs) > 0 {
+ outFormats, err := outputFormats.GetByNames(p.Outputs...)
if err != nil {
- return fmt.Errorf("failed to decode cascade: %w", err)
+ return fmt.Errorf("failed to resolve output formats %v: %w", p.Outputs, err)
+ } else {
+ p.ConfiguredOutputFormats = outFormats
}
- p.CascadeCompiled = cascade
}
return nil
diff --git a/resources/page/pagemeta/page_frontmatter_test.go b/resources/page/pagemeta/page_frontmatter_test.go
index fe9d3d99c..8d50f9b57 100644
--- a/resources/page/pagemeta/page_frontmatter_test.go
+++ b/resources/page/pagemeta/page_frontmatter_test.go
@@ -22,6 +22,7 @@ import (
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources/page/pagemeta"
@@ -175,7 +176,7 @@ func TestContentMediaTypeFromMarkup(t *testing.T) {
} {
var pc pagemeta.PageConfig
pc.Content.Markup = test.in
- c.Assert(pc.Compile("", true, "", logger, media.DefaultTypes), qt.IsNil)
+ c.Assert(pc.Compile("", logger, output.DefaultFormats, media.DefaultTypes), qt.IsNil)
c.Assert(pc.ContentMediaType.Type, qt.Equals, test.expected)
}
}