diff options
Diffstat (limited to 'resources')
-rw-r--r-- | resources/page/page_matcher.go | 39 | ||||
-rw-r--r-- | resources/page/page_matcher_test.go | 6 | ||||
-rw-r--r-- | resources/page/pagemeta/page_frontmatter.go | 141 | ||||
-rw-r--r-- | resources/page/pagemeta/page_frontmatter_test.go | 3 |
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) } } |