mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-21 21:35:28 +02:00
@@ -330,7 +330,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
|||||||
key: "cascade",
|
key: "cascade",
|
||||||
decode: func(d decodeWeight, p decodeConfig) error {
|
decode: func(d decodeWeight, p decodeConfig) error {
|
||||||
var err error
|
var err error
|
||||||
p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
|
p.c.Cascade, err = page.DecodeCascadeConfig(nil, true, p.p.Get(d.key))
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -356,7 +356,7 @@ func (m *pageMap) addPagesFromGoTmplFi(fi hugofs.FileMetaInfo, buildConfig *Buil
|
|||||||
Watching: s.Conf.Watching(),
|
Watching: s.Conf.Watching(),
|
||||||
HandlePage: func(pt *pagesfromdata.PagesFromTemplate, pc *pagemeta.PageConfig) error {
|
HandlePage: func(pt *pagesfromdata.PagesFromTemplate, pc *pagemeta.PageConfig) error {
|
||||||
s := pt.Site.(*Site)
|
s := pt.Site.(*Site)
|
||||||
if err := pc.Compile(pt.GoTmplFi.Meta().PathInfo.Base(), true, "", s.Log, s.conf.OutputFormats.Config, s.conf.MediaTypes.Config); err != nil {
|
if err := pc.CompileForPagesFromDataPre(pt.GoTmplFi.Meta().PathInfo.Base(), m.s.Log, s.conf.MediaTypes.Config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1695,7 +1695,9 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
|
|||||||
pathInfo: pi,
|
pathInfo: pi,
|
||||||
pageMetaParams: &pageMetaParams{
|
pageMetaParams: &pageMetaParams{
|
||||||
pageConfig: &pagemeta.PageConfig{
|
pageConfig: &pagemeta.PageConfig{
|
||||||
Kind: kinds.KindTerm,
|
PageConfigEarly: pagemeta.PageConfigEarly{
|
||||||
|
Kind: kinds.KindTerm,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1956,7 +1958,9 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
|||||||
pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
|
pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
|
||||||
pageMetaParams: &pageMetaParams{
|
pageMetaParams: &pageMetaParams{
|
||||||
pageConfig: &pagemeta.PageConfig{
|
pageConfig: &pagemeta.PageConfig{
|
||||||
Kind: kind,
|
PageConfigEarly: pagemeta.PageConfigEarly{
|
||||||
|
Kind: kind,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
standaloneOutputFormat: f,
|
standaloneOutputFormat: f,
|
||||||
@@ -2082,7 +2086,9 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
|
|||||||
pathInfo: p,
|
pathInfo: p,
|
||||||
pageMetaParams: &pageMetaParams{
|
pageMetaParams: &pageMetaParams{
|
||||||
pageConfig: &pagemeta.PageConfig{
|
pageConfig: &pagemeta.PageConfig{
|
||||||
Kind: kinds.KindHome,
|
PageConfigEarly: pagemeta.PageConfigEarly{
|
||||||
|
Kind: kinds.KindHome,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -2115,7 +2121,9 @@ func (sa *sitePagesAssembler) addMissingTaxonomies() error {
|
|||||||
pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
|
pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
|
||||||
pageMetaParams: &pageMetaParams{
|
pageMetaParams: &pageMetaParams{
|
||||||
pageConfig: &pagemeta.PageConfig{
|
pageConfig: &pagemeta.PageConfig{
|
||||||
Kind: kinds.KindTaxonomy,
|
PageConfigEarly: pagemeta.PageConfigEarly{
|
||||||
|
Kind: kinds.KindTaxonomy,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
singular: viewName.singular,
|
singular: viewName.singular,
|
||||||
|
@@ -72,12 +72,7 @@ type pageMeta struct {
|
|||||||
// Prepare for a rebuild of the data passed in from front matter.
|
// Prepare for a rebuild of the data passed in from front matter.
|
||||||
func (m *pageMeta) setMetaPostPrepareRebuild() {
|
func (m *pageMeta) setMetaPostPrepareRebuild() {
|
||||||
params := xmaps.Clone(m.paramsOriginal)
|
params := xmaps.Clone(m.paramsOriginal)
|
||||||
m.pageMetaParams.pageConfig = &pagemeta.PageConfig{
|
m.pageMetaParams.pageConfig = pagemeta.ClonePageConfigForRebuild(m.pageMetaParams.pageConfig, params)
|
||||||
Kind: m.pageConfig.Kind,
|
|
||||||
Lang: m.pageConfig.Lang,
|
|
||||||
Path: m.pageConfig.Path,
|
|
||||||
Params: params,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pageMetaParams struct {
|
type pageMetaParams struct {
|
||||||
@@ -94,7 +89,11 @@ type pageMetaParams struct {
|
|||||||
|
|
||||||
func (m *pageMetaParams) init(preserveOriginal bool) {
|
func (m *pageMetaParams) init(preserveOriginal bool) {
|
||||||
if preserveOriginal {
|
if preserveOriginal {
|
||||||
m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
|
if m.pageConfig.IsFromContentAdapter {
|
||||||
|
m.paramsOriginal = xmaps.Clone(m.pageConfig.ContentAdapterData)
|
||||||
|
} else {
|
||||||
|
m.paramsOriginal = xmaps.Clone(m.pageConfig.Params)
|
||||||
|
}
|
||||||
m.cascadeOriginal = m.pageConfig.CascadeCompiled.Clone()
|
m.cascadeOriginal = m.pageConfig.CascadeCompiled.Clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +253,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf
|
|||||||
// Check for any cascade define on itself.
|
// Check for any cascade define on itself.
|
||||||
if cv, found := frontmatter["cascade"]; found {
|
if cv, found := frontmatter["cascade"]; found {
|
||||||
var err error
|
var err error
|
||||||
cascade, err := page.DecodeCascade(logger, cv)
|
cascade, err := page.DecodeCascade(logger, true, cv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -341,18 +340,29 @@ func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cascade is also applied to itself.
|
// Cascade is also applied to itself.
|
||||||
|
var err error
|
||||||
cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
|
cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
|
||||||
if !k.Matches(ps) {
|
if !k.Matches(ps) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for kk, vv := range v {
|
for kk, vv := range v {
|
||||||
if _, found := ps.m.pageConfig.Params[kk]; !found {
|
if ps.m.pageConfig.IsFromContentAdapter {
|
||||||
ps.m.pageConfig.Params[kk] = vv
|
if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
|
||||||
|
ps.m.pageConfig.ContentAdapterData[kk] = vv
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, found := ps.m.pageConfig.Params[kk]; !found {
|
||||||
|
ps.m.pageConfig.Params[kk] = vv
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := ps.setMetaPostParams(); err != nil {
|
if err := ps.setMetaPostParams(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -398,6 +408,12 @@ func (p *pageState) setMetaPostParams() error {
|
|||||||
PathOrTitle: p.pathOrTitle(),
|
PathOrTitle: p.pathOrTitle(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isContentAdapter {
|
||||||
|
if err := pm.pageConfig.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the date separately
|
// Handle the date separately
|
||||||
// TODO(bep) we need to "do more" in this area so this can be split up and
|
// TODO(bep) we need to "do more" in this area so this can be split up and
|
||||||
// more easily tested without the Page, but the coupling is strong.
|
// more easily tested without the Page, but the coupling is strong.
|
||||||
@@ -656,7 +672,7 @@ params:
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pcfg.Compile("", false, ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
|
if err := pcfg.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,17 +91,20 @@ func (p *pagesFromDataTemplateContext) AddPage(v any) (string, error) {
|
|||||||
|
|
||||||
pd := pagemeta.DefaultPageConfig
|
pd := pagemeta.DefaultPageConfig
|
||||||
pd.IsFromContentAdapter = true
|
pd.IsFromContentAdapter = true
|
||||||
|
pd.ContentAdapterData = m
|
||||||
|
|
||||||
if err := mapstructure.WeakDecode(m, &pd); err != nil {
|
// The rest will be handled after the cascade is calculated and applied.
|
||||||
return "", fmt.Errorf("failed to decode page map: %w", err)
|
if err := mapstructure.WeakDecode(pd.ContentAdapterData, &pd.PageConfigEarly); err != nil {
|
||||||
|
err = fmt.Errorf("failed to decode page map: %w", err)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.p.buildState.NumPagesAdded++
|
|
||||||
|
|
||||||
if err := pd.Validate(true); err != nil {
|
if err := pd.Validate(true); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.p.buildState.NumPagesAdded++
|
||||||
|
|
||||||
return "", p.p.HandlePage(p.p, &pd)
|
return "", p.p.HandlePage(p.p, &pd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -821,3 +821,97 @@ outputs:
|
|||||||
b.AssertFileExists("public/p4/index.html", true)
|
b.AssertFileExists("public/p4/index.html", true)
|
||||||
b.AssertFileExists("public/p4/index.json", false) // currently returns true
|
b.AssertFileExists("public/p4/index.json", false) // currently returns true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContentAdapterOutputsIssue13692(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- hugo.toml --
|
||||||
|
disableKinds = ['page','home','sitemap','taxonomy','term']
|
||||||
|
[[cascade]]
|
||||||
|
outputs = ['html','json']
|
||||||
|
[cascade.target]
|
||||||
|
path = '{/s2,/s4}'
|
||||||
|
-- layouts/section.html --
|
||||||
|
html: {{ .Title }}
|
||||||
|
-- layouts/section.json --
|
||||||
|
json: {{ .Title }}
|
||||||
|
-- content/s1/_index.md --
|
||||||
|
---
|
||||||
|
title: s1
|
||||||
|
---
|
||||||
|
-- content/s2/_index.md --
|
||||||
|
---
|
||||||
|
title: s2
|
||||||
|
---
|
||||||
|
-- content/_content.gotmpl --
|
||||||
|
{{ $page := dict "path" "s3" "title" "s3" "kind" "section" }}
|
||||||
|
{{ $.AddPage $page }}
|
||||||
|
|
||||||
|
{{ $page := dict "path" "s4" "title" "s4" "kind" "section" }}
|
||||||
|
{{ $.AddPage $page }}
|
||||||
|
|
||||||
|
{{ $page := dict "path" "s5" "title" "s5" "kind" "section" "outputs" (slice "html") }}
|
||||||
|
{{ $.AddPage $page }}
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.Test(t, files)
|
||||||
|
|
||||||
|
b.AssertFileExists("public/s1/index.html", true)
|
||||||
|
b.AssertFileExists("public/s1/index.json", false)
|
||||||
|
b.AssertFileExists("public/s1/index.xml", true)
|
||||||
|
|
||||||
|
b.AssertFileExists("public/s2/index.html", true)
|
||||||
|
b.AssertFileExists("public/s2/index.json", true)
|
||||||
|
b.AssertFileExists("public/s2/index.xml", false)
|
||||||
|
|
||||||
|
b.AssertFileExists("public/s3/index.html", true)
|
||||||
|
b.AssertFileExists("public/s3/index.json", false)
|
||||||
|
b.AssertFileExists("public/s3/index.xml", true)
|
||||||
|
|
||||||
|
b.AssertFileExists("public/s4/index.html", true)
|
||||||
|
b.AssertFileExists("public/s4/index.json", true)
|
||||||
|
b.AssertFileExists("public/s4/index.xml", false)
|
||||||
|
|
||||||
|
b.AssertFileExists("public/s5/index.html", true)
|
||||||
|
b.AssertFileExists("public/s5/index.json", false)
|
||||||
|
b.AssertFileExists("public/s5/index.xml", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContentAdapterCascadeBasic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- hugo.toml --
|
||||||
|
disableLiveReload = true
|
||||||
|
-- content/_index.md --
|
||||||
|
---
|
||||||
|
cascade:
|
||||||
|
- title: foo
|
||||||
|
target:
|
||||||
|
path: "**"
|
||||||
|
---
|
||||||
|
-- layouts/all.html --
|
||||||
|
Title: {{ .Title }}|Content: {{ .Content }}|
|
||||||
|
-- content/_content.gotmpl --
|
||||||
|
{{ $content := dict
|
||||||
|
"mediaType" "text/markdown"
|
||||||
|
"value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
|
||||||
|
}}
|
||||||
|
|
||||||
|
{{ $page := dict "path" "s1" "kind" "page" }}
|
||||||
|
{{ $.AddPage $page }}
|
||||||
|
{{ $page := dict "path" "s2" "kind" "page" "title" "bar" "content" $content }}
|
||||||
|
{{ $.AddPage $page }}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.TestRunning(t, files)
|
||||||
|
|
||||||
|
b.AssertFileContent("public/s1/index.html", "Title: foo|")
|
||||||
|
b.AssertFileContent("public/s2/index.html", "Title: bar|", "Content: <p>The <em>Hunchback of Notre Dame</em> was written by Victor Hugo.</p>")
|
||||||
|
|
||||||
|
b.EditFileReplaceAll("content/_index.md", "foo", "baz").Build()
|
||||||
|
|
||||||
|
b.AssertFileContent("public/s1/index.html", "Title: baz|")
|
||||||
|
}
|
||||||
|
@@ -16,6 +16,7 @@ package page
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
@@ -24,7 +25,6 @@ import (
|
|||||||
"github.com/gohugoio/hugo/hugofs/glob"
|
"github.com/gohugoio/hugo/hugofs/glob"
|
||||||
"github.com/gohugoio/hugo/resources/kinds"
|
"github.com/gohugoio/hugo/resources/kinds"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A PageMatcher can be used to match a Page with Glob patterns.
|
// 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) {
|
buildConfig := func(in any) (*maps.Ordered[PageMatcher, maps.Params], any, error) {
|
||||||
cascade := maps.NewOrdered[PageMatcher, maps.Params]()
|
cascade := maps.NewOrdered[PageMatcher, maps.Params]()
|
||||||
if in == nil {
|
if in == nil {
|
||||||
@@ -120,7 +120,15 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
|
|||||||
|
|
||||||
for _, m := range ms {
|
for _, m := range ms {
|
||||||
m = maps.CleanConfigStringMap(m)
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
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.
|
// 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) {
|
func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
|
||||||
conf, err := DecodeCascadeConfig(logger, in)
|
conf, err := DecodeCascadeConfig(logger, handleLegacyFormat, in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -164,6 +172,26 @@ func DecodeCascade(logger loggers.Logger, in any) (*maps.Ordered[PageMatcher, ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, error) {
|
func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, error) {
|
||||||
|
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
|
var pcfg PageMatcherParamsConfig
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
switch strings.ToLower(k) {
|
switch strings.ToLower(k) {
|
||||||
@@ -190,7 +218,6 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
|
|||||||
}
|
}
|
||||||
pcfg.Target = target
|
pcfg.Target = target
|
||||||
default:
|
default:
|
||||||
// Legacy config.
|
|
||||||
if pcfg.Params == nil {
|
if pcfg.Params == nil {
|
||||||
pcfg.Params = make(maps.Params)
|
pcfg.Params = make(maps.Params)
|
||||||
}
|
}
|
||||||
|
@@ -84,7 +84,7 @@ func TestPageMatcher(t *testing.T) {
|
|||||||
|
|
||||||
c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
|
c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
|
||||||
fn := func(m map[string]any) PageMatcherParamsConfig {
|
fn := func(m map[string]any) PageMatcherParamsConfig {
|
||||||
v, err := mapToPageMatcherParamsConfig(m)
|
v, err := mapToPageMatcherParamsConfigLegacy(m)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
return v
|
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(err, qt.IsNil)
|
||||||
c.Assert(got, qt.IsNotNil)
|
c.Assert(got, qt.IsNotNil)
|
||||||
@@ -143,7 +143,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
|
|||||||
{Params: maps.Params{"b": string("bv")}, Target: PageMatcher{Kind: "page"}},
|
{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(err, qt.IsNil)
|
||||||
c.Assert(got, qt.IsNotNil)
|
c.Assert(got, qt.IsNotNil)
|
||||||
}
|
}
|
||||||
|
@@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/gohugoio/hugo/resources/kinds"
|
"github.com/gohugoio/hugo/resources/kinds"
|
||||||
"github.com/gohugoio/hugo/resources/page"
|
"github.com/gohugoio/hugo/resources/page"
|
||||||
"github.com/gohugoio/hugo/resources/resource"
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
|
|
||||||
@@ -75,35 +76,43 @@ func (d Dates) IsAllDatesZero() bool {
|
|||||||
return d.Date.IsZero() && d.Lastmod.IsZero() && d.PublishDate.IsZero() && d.ExpiryDate.IsZero()
|
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.
|
// PageConfig configures a Page, typically from front matter.
|
||||||
// Note that all the top level fields are reserved Hugo keywords.
|
// Note that all the top level fields are reserved Hugo keywords.
|
||||||
// Any custom configuration needs to be set in the Params map.
|
// Any custom configuration needs to be set in the Params map.
|
||||||
type PageConfig struct {
|
type PageConfig struct {
|
||||||
Dates Dates `json:"-"` // Dates holds the four core dates for this page.
|
Dates Dates `json:"-"` // Dates holds the four core dates for this page.
|
||||||
DatesStrings
|
DatesStrings
|
||||||
Title string // The title of the page.
|
PageConfigEarly `mapstructure:",squash"`
|
||||||
LinkTitle string // The link title of the page.
|
Title string // The title of the page.
|
||||||
Type string // The content type of the page.
|
LinkTitle string // The link title of the page.
|
||||||
Layout string // The layout to use for to render this page.
|
Type string // The content type of the page.
|
||||||
Weight int // The weight of the page, used in sorting if set to a non-zero value.
|
Layout string // The layout to use for to render this page.
|
||||||
Kind string // The kind of page, e.g. "page", "section", "home" etc. This is usually derived from the content path.
|
Weight int // The weight of the page, used in sorting if set to a non-zero value.
|
||||||
Path string // The canonical path to the page, e.g. /sect/mypage. Note: Leading slash, no trailing slash, no extensions or language identifiers.
|
URL string // The URL to the rendered page, e.g. /sect/mypage.html.
|
||||||
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.
|
||||||
URL string // The URL to the rendered page, e.g. /sect/mypage.html.
|
Description string // The description for this page.
|
||||||
Slug string // The slug for this page.
|
Summary string // The summary for this page.
|
||||||
Description string // The description for this page.
|
Draft bool // Whether or not the content is a draft.
|
||||||
Summary string // The summary for this page.
|
Headless bool `json:"-"` // Whether or not the page should be rendered.
|
||||||
Draft bool // Whether or not the content is a draft.
|
IsCJKLanguage bool // Whether or not the content is in a CJK language.
|
||||||
Headless bool `json:"-"` // Whether or not the page should be rendered.
|
TranslationKey string // The translation key for this page.
|
||||||
IsCJKLanguage bool // Whether or not the content is in a CJK language.
|
Keywords []string // The keywords for this page.
|
||||||
TranslationKey string // The translation key for this page.
|
Aliases []string // The aliases for this page.
|
||||||
Keywords []string // The keywords 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.
|
||||||
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:"-"`
|
FrontMatterOnlyValues `mapstructure:"-" json:"-"`
|
||||||
|
|
||||||
Cascade []map[string]any
|
|
||||||
Sitemap config.SitemapConfig
|
Sitemap config.SitemapConfig
|
||||||
Build BuildConfig
|
Build BuildConfig
|
||||||
Menus any // Can be a string, []string or map[string]any.
|
Menus any // Can be a string, []string or map[string]any.
|
||||||
@@ -111,8 +120,9 @@ type PageConfig struct {
|
|||||||
// User defined params.
|
// User defined params.
|
||||||
Params maps.Params
|
Params maps.Params
|
||||||
|
|
||||||
// Content holds the content for this page.
|
// The raw data from the content adapter.
|
||||||
Content Source
|
// TODO(bep) clean up the ContentAdapterData vs Params.
|
||||||
|
ContentAdapterData map[string]any `mapstructure:"-" json:"-"`
|
||||||
|
|
||||||
// Compiled values.
|
// Compiled values.
|
||||||
CascadeCompiled *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
|
CascadeCompiled *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
|
||||||
@@ -121,6 +131,20 @@ type PageConfig struct {
|
|||||||
IsFromContentAdapter bool `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{
|
var DefaultPageConfig = PageConfig{
|
||||||
Build: DefaultBuildConfig,
|
Build: DefaultBuildConfig,
|
||||||
}
|
}
|
||||||
@@ -151,8 +175,7 @@ func (p *PageConfig) Validate(pagesFromData bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile sets up the page configuration after all fields have been set.
|
func (p *PageConfig) CompileForPagesFromDataPre(basePath string, logger loggers.Logger, mediaTypes media.Types) error {
|
||||||
func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, logger loggers.Logger, outputFormats output.Formats, mediaTypes media.Types) error {
|
|
||||||
// In content adapters, we always get relative paths.
|
// In content adapters, we always get relative paths.
|
||||||
if basePath != "" {
|
if basePath != "" {
|
||||||
p.Path = path.Join(basePath, p.Path)
|
p.Path = path.Join(basePath, p.Path)
|
||||||
@@ -160,12 +183,32 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
|
|||||||
|
|
||||||
if p.Params == nil {
|
if p.Params == nil {
|
||||||
p.Params = make(maps.Params)
|
p.Params = make(maps.Params)
|
||||||
} else if pagesFromData {
|
|
||||||
p.Params = maps.PrepareParamsClone(p.Params)
|
|
||||||
} else {
|
} 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 p.Content.Markup == "" && p.Content.MediaType == "" {
|
||||||
if ext == "" {
|
if ext == "" {
|
||||||
ext = "md"
|
ext = "md"
|
||||||
@@ -196,6 +239,29 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
|
|||||||
if p.Content.Markup == "" {
|
if p.Content.Markup == "" {
|
||||||
p.Content.Markup = p.ContentMediaType.SubType
|
p.Content.Markup = p.ContentMediaType.SubType
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Params == nil {
|
||||||
|
p.Params = make(maps.Params)
|
||||||
|
} else {
|
||||||
|
maps.PrepareParams(p.Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.compilePrePost(ext, mediaTypes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(p.Outputs) > 0 {
|
if len(p.Outputs) > 0 {
|
||||||
outFormats, err := outputFormats.GetByNames(p.Outputs...)
|
outFormats, err := outputFormats.GetByNames(p.Outputs...)
|
||||||
@@ -206,27 +272,6 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pagesFromData {
|
|
||||||
if p.Kind == "" {
|
|
||||||
p.Kind = kinds.KindPage
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.Cascade != nil {
|
|
||||||
cascade, err := page.DecodeCascade(logger, p.Cascade)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode cascade: %w", err)
|
|
||||||
}
|
|
||||||
p.CascadeCompiled = cascade
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -176,7 +176,7 @@ func TestContentMediaTypeFromMarkup(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
var pc pagemeta.PageConfig
|
var pc pagemeta.PageConfig
|
||||||
pc.Content.Markup = test.in
|
pc.Content.Markup = test.in
|
||||||
c.Assert(pc.Compile("", true, "", logger, output.DefaultFormats, media.DefaultTypes), qt.IsNil)
|
c.Assert(pc.Compile("", logger, output.DefaultFormats, media.DefaultTypes), qt.IsNil)
|
||||||
c.Assert(pc.ContentMediaType.Type, qt.Equals, test.expected)
|
c.Assert(pc.ContentMediaType.Type, qt.Equals, test.expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -460,6 +460,7 @@ title: p1 (de)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestVimeoShortcode(t *testing.T) {
|
func TestVimeoShortcode(t *testing.T) {
|
||||||
|
t.Skip("Fix me: Upstream API changes")
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
files := `
|
files := `
|
||||||
|
Reference in New Issue
Block a user