summaryrefslogtreecommitdiffstats
path: root/resources
diff options
context:
space:
mode:
Diffstat (limited to 'resources')
-rw-r--r--resources/images/exif/exif.go7
-rw-r--r--resources/page/page.go10
-rw-r--r--resources/page/page_marshaljson.autogen.go6
-rw-r--r--resources/page/page_nop.go2
-rw-r--r--resources/page/pagemeta/page_frontmatter.go331
-rw-r--r--resources/page/pagemeta/page_frontmatter_test.go31
-rw-r--r--resources/page/pagemeta/pagemeta.go4
-rw-r--r--resources/page/site.go11
-rw-r--r--resources/page/site_integration_test.go44
-rw-r--r--resources/postpub/fields_test.go2
-rw-r--r--resources/resource.go33
-rw-r--r--resources/resource/resourcetypes.go36
-rw-r--r--resources/resource_cache.go6
-rw-r--r--resources/resource_factories/create/create.go74
-rw-r--r--resources/resource_factories/create/create_integration_test.go3
-rw-r--r--resources/resource_factories/create/remote.go364
-rw-r--r--resources/resource_factories/create/remote_test.go18
-rw-r--r--resources/resource_metadata.go29
-rw-r--r--resources/resource_spec.go35
-rw-r--r--resources/resource_transformers/tocss/scss/scss_integration_test.go61
-rw-r--r--resources/transform.go18
21 files changed, 920 insertions, 205 deletions
diff --git a/resources/images/exif/exif.go b/resources/images/exif/exif.go
index af92366ca..0374cdc96 100644
--- a/resources/images/exif/exif.go
+++ b/resources/images/exif/exif.go
@@ -17,6 +17,7 @@ import (
"bytes"
"fmt"
"io"
+ "math"
"math/big"
"regexp"
"strings"
@@ -140,6 +141,12 @@ func (d *Decoder) Decode(r io.Reader) (ex *ExifInfo, err error) {
if !d.noLatLong {
lat, long, _ = x.LatLong()
+ if math.IsNaN(lat) {
+ lat = 0
+ }
+ if math.IsNaN(long) {
+ long = 0
+ }
}
walker := &exifWalker{x: x, vals: make(map[string]any), includeMatcher: d.includeFieldsRe, excludeMatcher: d.excludeFieldsrRe}
diff --git a/resources/page/page.go b/resources/page/page.go
index f995ee641..9647a916b 100644
--- a/resources/page/page.go
+++ b/resources/page/page.go
@@ -225,9 +225,6 @@ type PageMetaProvider interface {
// to the source of this Page. It will be relative to any content root.
Path() string
- // This is for internal use only.
- PathInfo() *paths.Path
-
// The slug, typically defined in front matter.
Slug() string
@@ -253,6 +250,12 @@ type PageMetaProvider interface {
Weight() int
}
+// PageMetaInternalProvider provides internal page metadata.
+type PageMetaInternalProvider interface {
+ // This is for internal use only.
+ PathInfo() *paths.Path
+}
+
// PageRenderProvider provides a way for a Page to render content.
type PageRenderProvider interface {
// Render renders the given layout with this Page as context.
@@ -273,6 +276,7 @@ type PageWithoutContent interface {
RenderShortcodesProvider
resource.Resource
PageMetaProvider
+ PageMetaInternalProvider
resource.LanguageProvider
// For pages backed by a file.
diff --git a/resources/page/page_marshaljson.autogen.go b/resources/page/page_marshaljson.autogen.go
index 18ed2a75d..3b2138801 100644
--- a/resources/page/page_marshaljson.autogen.go
+++ b/resources/page/page_marshaljson.autogen.go
@@ -17,9 +17,8 @@ package page
import (
"encoding/json"
- "time"
-
"github.com/gohugoio/hugo/config"
+ "time"
)
func MarshalPageToJSON(p Page) ([]byte, error) {
@@ -39,7 +38,6 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
isNode := p.IsNode()
isPage := p.IsPage()
path := p.Path()
- pathc := p.Path()
slug := p.Slug()
lang := p.Lang()
isSection := p.IsSection()
@@ -65,7 +63,6 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
IsNode bool
IsPage bool
Path string
- Pathc string
Slug string
Lang string
IsSection bool
@@ -90,7 +87,6 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
IsNode: isNode,
IsPage: isPage,
Path: path,
- Pathc: pathc,
Slug: slug,
Lang: lang,
IsSection: isSection,
diff --git a/resources/page/page_nop.go b/resources/page/page_nop.go
index d3813337d..f745d8622 100644
--- a/resources/page/page_nop.go
+++ b/resources/page/page_nop.go
@@ -57,7 +57,7 @@ var (
// PageNop implements Page, but does nothing.
type nopPage int
-var noOpPathInfo = paths.Parse(files.ComponentFolderContent, "no-op.md")
+var noOpPathInfo = media.DefaultPathParser.Parse(files.ComponentFolderContent, "no-op.md")
func (p *nopPage) Err() resource.ResourceError {
return nil
diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go
index 123dd4b70..c901ab57b 100644
--- a/resources/page/pagemeta/page_frontmatter.go
+++ b/resources/page/pagemeta/page_frontmatter.go
@@ -14,14 +14,24 @@
package pagemeta
import (
+ "errors"
+ "fmt"
+ "path"
"strings"
"time"
+ "github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/htime"
+ "github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/hugofs/files"
+ "github.com/gohugoio/hugo/markup"
+ "github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
+ "github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/helpers"
@@ -29,6 +39,13 @@ import (
"github.com/spf13/cast"
)
+type DatesStrings struct {
+ Date string `json:"date"`
+ Lastmod string `json:"lastMod"`
+ PublishDate string `json:"publishDate"`
+ ExpiryDate string `json:"expiryDate"`
+}
+
type Dates struct {
Date time.Time
Lastmod time.Time
@@ -57,40 +74,231 @@ func (d Dates) IsAllDatesZero() bool {
// 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 four core dates for this page.
+ 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.
- 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.
- 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.
+ 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 // Whether or not the page should be rendered.
+ 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.
- // 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
+ FrontMatterOnlyValues `mapstructure:"-" json:"-"`
+
+ Cascade []map[string]any
+ Sitemap config.SitemapConfig
+ Build BuildConfig
+ Menus []string
// User defined params.
Params maps.Params
+ // Content holds the content for this page.
+ Content Source
+
+ // Compiled values.
+ CascadeCompiled map[page.PageMatcher]maps.Params
+ ContentMediaType media.Type `mapstructure:"-" json:"-"`
+ IsFromContentAdapter bool `mapstructure:"-" json:"-"`
+}
+
+var DefaultPageConfig = PageConfig{
+ Build: DefaultBuildConfig,
+}
+
+func (p *PageConfig) Validate(pagesFromData bool) error {
+ if pagesFromData {
+ if p.Path == "" {
+ return errors.New("path must be set")
+ }
+ if strings.HasPrefix(p.Path, "/") {
+ return fmt.Errorf("path %q must not start with a /", p.Path)
+ }
+ if p.Lang != "" {
+ return errors.New("lang must not be set")
+ }
+
+ if p.Content.Markup != "" {
+ return errors.New("markup must not be set, use mediaType")
+ }
+ }
+
+ if p.Cascade != nil {
+ if !kinds.IsBranch(p.Kind) {
+ return errors.New("cascade is only supported for branch nodes")
+ }
+ }
+
+ 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 {
+ // In content adapters, we always get relative paths.
+ if basePath != "" {
+ p.Path = path.Join(basePath, p.Path)
+ }
+
+ if p.Params == nil {
+ p.Params = make(maps.Params)
+ }
+ maps.PrepareParams(p.Params)
+
+ if p.Content.Markup == "" && p.Content.MediaType == "" {
+ if ext == "" {
+ ext = "md"
+ }
+ p.ContentMediaType = MarkupToMediaType(ext, mediaTypes)
+ if p.ContentMediaType.IsZero() {
+ return fmt.Errorf("failed to resolve media type for suffix %q", ext)
+ }
+ }
+
+ var s string
+ if p.ContentMediaType.IsZero() {
+ if p.Content.MediaType != "" {
+ s = p.Content.MediaType
+ p.ContentMediaType, _ = mediaTypes.GetByType(s)
+ }
+
+ if p.ContentMediaType.IsZero() && p.Content.Markup != "" {
+ s = p.Content.Markup
+ p.ContentMediaType = MarkupToMediaType(s, mediaTypes)
+ }
+ }
+
+ if p.ContentMediaType.IsZero() {
+ return fmt.Errorf("failed to resolve media type for %q", s)
+ }
+
+ if p.Content.Markup == "" {
+ p.Content.Markup = p.ContentMediaType.SubType
+ }
+
+ 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
+}
+
+// MarkupToMediaType converts a markup string to a media type.
+func MarkupToMediaType(s string, mediaTypes media.Types) media.Type {
+ s = strings.ToLower(s)
+ mt, _ := mediaTypes.GetBestMatch(markup.ResolveMarkup(s))
+ return mt
+}
+
+type ResourceConfig struct {
+ Path string
+ Name string
+ Title string
+ Params maps.Params
+ Content Source
+
// Compiled values.
- IsGoldmark bool `json:"-"`
+ PathInfo *paths.Path `mapstructure:"-" json:"-"`
+ ContentMediaType media.Type
+}
+
+func (rc *ResourceConfig) Validate() error {
+ if rc.Path == "" {
+ return errors.New("path must be set")
+ }
+ if rc.Content.Markup != "" {
+ return errors.New("markup must not be set, use mediaType")
+ }
+ return nil
+}
+
+func (rc *ResourceConfig) Compile(basePath string, pathParser *paths.PathParser, mediaTypes media.Types) error {
+ if rc.Params != nil {
+ maps.PrepareParams(rc.Params)
+ }
+
+ // Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
+ // We do that when we create resources 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.
+ rc.Path = paths.NormalizePathStringBasic(path.Join(basePath, rc.Path))
+ rc.PathInfo = pathParser.Parse(files.ComponentFolderContent, rc.Path)
+ if rc.Content.MediaType != "" {
+ var found bool
+ rc.ContentMediaType, found = mediaTypes.GetByType(rc.Content.MediaType)
+ if !found {
+ return fmt.Errorf("media type %q not found", rc.Content.MediaType)
+ }
+ }
+ return nil
+}
+
+type Source struct {
+ // MediaType is the media type of the content.
+ MediaType string
+
+ // The markup used in Value. Only used in front matter.
+ Markup string
+
+ // The content.
+ Value any
+}
+
+func (s Source) IsZero() bool {
+ return !hreflect.IsTruthful(s.Value)
+}
+
+func (s Source) IsResourceValue() bool {
+ _, ok := s.Value.(resource.Resource)
+ return ok
+}
+
+func (s Source) ValueAsString() string {
+ if s.Value == nil {
+ return ""
+ }
+ ss, err := cast.ToStringE(s.Value)
+ if err != nil {
+ panic(fmt.Errorf("content source: failed to convert %T to string: %s", s.Value, err))
+ }
+ return ss
+}
+
+func (s Source) ValueAsOpenReadSeekCloser() hugio.OpenReadSeekCloser {
+ return hugio.NewOpenReadSeekCloser(hugio.NewReadSeekerNoOpCloserFromString(s.ValueAsString()))
+}
+
+// FrontMatterOnlyValues holds values that can only be set via front matter.
+type FrontMatterOnlyValues struct {
+ ResourcesMeta []map[string]any
}
// FrontMatterHandler maps front matter into Page fields and .Params.
@@ -98,6 +306,8 @@ type PageConfig struct {
type FrontMatterHandler struct {
fmConfig FrontmatterConfig
+ contentAdapterDatesHandler func(d *FrontMatterDescriptor) error
+
dateHandler frontMatterFieldHandler
lastModHandler frontMatterFieldHandler
publishDateHandler frontMatterFieldHandler
@@ -144,6 +354,13 @@ func (f FrontMatterHandler) HandleDates(d *FrontMatterDescriptor) error {
panic("missing pageConfig")
}
+ if d.PageConfig.IsFromContentAdapter {
+ if f.contentAdapterDatesHandler == nil {
+ panic("missing content adapter date handler")
+ }
+ return f.contentAdapterDatesHandler(d)
+ }
+
if f.dateHandler == nil {
panic("missing date handler")
}
@@ -352,9 +569,13 @@ func NewFrontmatterHandler(logger loggers.Logger, frontMatterConfig FrontmatterC
func (f *FrontMatterHandler) createHandlers() error {
var err error
+ if f.contentAdapterDatesHandler, err = f.createContentAdapterDatesHandler(f.fmConfig); err != nil {
+ return err
+ }
+
if f.dateHandler, err = f.createDateHandler(f.fmConfig.Date,
func(d *FrontMatterDescriptor, t time.Time) {
- d.PageConfig.Date = t
+ d.PageConfig.Dates.Date = t
setParamIfNotSet(fmDate, t, d)
}); err != nil {
return err
@@ -363,7 +584,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.PageConfig.Lastmod = t
+ d.PageConfig.Dates.Lastmod = t
}); err != nil {
return err
}
@@ -371,7 +592,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.PageConfig.PublishDate = t
+ d.PageConfig.Dates.PublishDate = t
}); err != nil {
return err
}
@@ -379,7 +600,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.PageConfig.ExpiryDate = t
+ d.PageConfig.Dates.ExpiryDate = t
}); err != nil {
return err
}
@@ -394,6 +615,86 @@ func setParamIfNotSet(key string, value any, d *FrontMatterDescriptor) {
d.PageConfig.Params[key] = value
}
+func (f FrontMatterHandler) createContentAdapterDatesHandler(fmcfg FrontmatterConfig) (func(d *FrontMatterDescriptor) error, error) {
+ setTime := func(key string, value time.Time, in *PageConfig) {
+ switch key {
+ case fmDate:
+ in.Dates.Date = value
+ case fmLastmod:
+ in.Dates.Lastmod = value
+ case fmPubDate:
+ in.Dates.PublishDate = value
+ case fmExpiryDate:
+ in.Dates.ExpiryDate = value
+ }
+ }
+
+ getTime := func(key string, in *PageConfig) time.Time {
+ switch key {
+ case fmDate:
+ return in.Dates.Date
+ case fmLastmod:
+ return in.Dates.Lastmod
+ case fmPubDate:
+ return in.Dates.PublishDate
+ case fmExpiryDate:
+ return in.Dates.ExpiryDate
+ }
+ return time.Time{}
+ }
+
+ createSetter := func(identifiers []string, date string) func(pcfg *PageConfig) {
+ var getTimes []func(in *PageConfig) time.Time
+ for _, identifier := range identifiers {
+ if strings.HasPrefix(identifier, ":") {
+ continue
+ }
+ switch identifier {
+ case fmDate:
+ getTimes = append(getTimes, func(in *PageConfig) time.Time {
+ return getTime(fmDate, in)
+ })
+ case fmLastmod:
+ getTimes = append(getTimes, func(in *PageConfig) time.Time {
+ return getTime(fmLastmod, in)
+ })
+ case fmPubDate:
+ getTimes = append(getTimes, func(in *PageConfig) time.Time {
+ return getTime(fmPubDate, in)
+ })
+ case fmExpiryDate:
+ getTimes = append(getTimes, func(in *PageConfig) time.Time {
+ return getTime(fmExpiryDate, in)
+ })
+ }
+ }
+
+ return func(pcfg *PageConfig) {
+ for _, get := range getTimes {
+ if t := get(pcfg); !t.IsZero() {
+ setTime(date, t, pcfg)
+ return
+ }
+ }
+ }
+ }
+
+ setDate := createSetter(fmcfg.Date, fmDate)
+ setLastmod := createSetter(fmcfg.Lastmod, fmLastmod)
+ setPublishDate := createSetter(fmcfg.PublishDate, fmPubDate)
+ setExpiryDate := createSetter(fmcfg.ExpiryDate, fmExpiryDate)
+
+ fn := func(d *FrontMatterDescriptor) error {
+ pcfg := d.PageConfig
+ setDate(pcfg)
+ setLastmod(pcfg)
+ setPublishDate(pcfg)
+ setExpiryDate(pcfg)
+ return nil
+ }
+ return fn, nil
+}
+
func (f FrontMatterHandler) createDateHandler(identifiers []string, setter func(d *FrontMatterDescriptor, t time.Time)) (frontMatterFieldHandler, error) {
var h *frontmatterFieldHandlers
var handlers []frontMatterFieldHandler
diff --git a/resources/page/pagemeta/page_frontmatter_test.go b/resources/page/pagemeta/page_frontmatter_test.go
index 9e1151f22..18f9e5aa1 100644
--- a/resources/page/pagemeta/page_frontmatter_test.go
+++ b/resources/page/pagemeta/page_frontmatter_test.go
@@ -18,8 +18,10 @@ import (
"testing"
"time"
+ "github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
+ "github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/page/pagemeta"
@@ -148,3 +150,32 @@ func TestFrontMatterDatesDefaultKeyword(t *testing.T) {
c.Assert(d.PageConfig.Dates.PublishDate.Day(), qt.Equals, 4)
c.Assert(d.PageConfig.Dates.ExpiryDate.IsZero(), qt.Equals, true)
}
+
+func TestContentMediaTypeFromMarkup(t *testing.T) {
+ c := qt.New(t)
+ logger := loggers.NewDefault()
+
+ for _, test := range []struct {
+ in string
+ expected string
+ }{
+ {"", "text/markdown"},
+ {"md", "text/markdown"},
+ {"markdown", "text/markdown"},
+ {"mdown", "text/markdown"},
+ {"goldmark", "text/markdown"},
+ {"html", "text/html"},
+ {"htm", "text/html"},
+ {"asciidoc", "text/asciidoc"},
+ {"asciidocext", "text/asciidoc"},
+ {"adoc", "text/asciidoc"},
+ {"pandoc", "text/pandoc"},
+ {"pdc", "text/pandoc"},
+ {"rst", "text/rst"},
+ } {
+ var pc pagemeta.PageConfig
+ pc.Content.Markup = test.in
+ c.Assert(pc.Compile("", true, "", logger, media.DefaultTypes), qt.IsNil)
+ c.Assert(pc.ContentMediaType.Type, qt.Equals, test.expected)
+ }
+}
diff --git a/resources/page/pagemeta/pagemeta.go b/resources/page/pagemeta/pagemeta.go
index f5b6380bc..b6b953231 100644
--- a/resources/page/pagemeta/pagemeta.go
+++ b/resources/page/pagemeta/pagemeta.go
@@ -24,7 +24,7 @@ const (
Link = "link"
)
-var defaultBuildConfig = BuildConfig{
+var DefaultBuildConfig = BuildConfig{
List: Always,
Render: Always,
PublishResources: true,
@@ -69,7 +69,7 @@ func (b BuildConfig) IsZero() bool {
}
func DecodeBuildConfig(m any) (BuildConfig, error) {
- b := defaultBuildConfig
+ b := DefaultBuildConfig
if m == nil {
return b, nil
}
diff --git a/resources/page/site.go b/resources/page/site.go
index df33485eb..56f438cb6 100644
--- a/resources/page/site.go
+++ b/resources/page/site.go
@@ -139,8 +139,15 @@ type Site interface {
// Sites represents an ordered list of sites (languages).
type Sites []Site
-// First is a convenience method to get the first Site, i.e. the main language.
+// Deprecated: Use .Sites.Default instead.
func (s Sites) First() Site {
+ hugo.Deprecate(".Sites.First", "Use .Sites.Default instead.", "v0.127.0")
+ return s.Default()
+}
+
+// Default is a convenience method to get the site corresponding to the default
+// content language.
+func (s Sites) Default() Site {
if len(s) == 0 {
return nil
}
@@ -165,7 +172,7 @@ func (s *siteWrapper) Key() string {
return s.s.Language().Lang
}
-// // Deprecated: Use .Site.Params instead.
+// Deprecated: Use .Site.Params instead.
func (s *siteWrapper) Social() map[string]string {
return s.s.Social()
}
diff --git a/resources/page/site_integration_test.go b/resources/page/site_integration_test.go
new file mode 100644
index 000000000..60064df3a
--- /dev/null
+++ b/resources/page/site_integration_test.go
@@ -0,0 +1,44 @@
+// 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.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package page_test
+
+import (
+ "testing"
+
+ "github.com/gohugoio/hugo/hugolib"
+)
+
+// Issue 12513
+func TestPageSiteSitesDefault(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ['page','rss','section','sitemap','taxonomy','term']
+defaultContentLanguage = 'de'
+defaultContentLanguageInSubdir = true
+[languages.en]
+languageName = 'English'
+weight = 1
+[languages.de]
+languageName = 'Deutsch'
+weight = 2
+-- layouts/index.html --
+{{ .Site.Sites.Default.Language.LanguageName }}
+`
+
+ b := hugolib.Test(t, files)
+
+ b.AssertFileContent("public/de/index.html", "Deutsch")
+}
diff --git a/resources/postpub/fields_test.go b/resources/postpub/fields_test.go
index 336da1f0e..53875cb34 100644
--- a/resources/postpub/fields_test.go
+++ b/resources/postpub/fields_test.go
@@ -36,6 +36,8 @@ func TestCreatePlaceholders(t *testing.T) {
"SuffixesCSV": "pre_foo.SuffixesCSV_post",
"Delimiter": "pre_foo.Delimiter_post",
"FirstSuffix": "pre_foo.FirstSuffix_post",
+ "IsHTML": "pre_foo.IsHTML_post",
+ "IsMarkdown": "pre_foo.IsMarkdown_post",
"IsText": "pre_foo.IsText_post",
"String": "pre_foo.String_post",
"Type": "pre_foo.Type_post",
diff --git a/resources/resource.go b/resources/resource.go
index 867b262fb..8fade941a 100644
--- a/resources/resource.go
+++ b/resources/resource.go
@@ -65,6 +65,9 @@ type ResourceSourceDescriptor struct {
// The name of the resource as it was read from the source.
NameOriginal string
+ // The title of the resource.
+ Title string
+
// Any base paths prepended to the target path. This will also typically be the
// language code, but setting it here means that it should not have any effect on
// the permalink.
@@ -79,6 +82,9 @@ type ResourceSourceDescriptor struct {
// The Data to associate with this resource.
Data map[string]any
+ // The Params to associate with this resource.
+ Params maps.Params
+
// Delay publishing until either Permalink or RelPermalink is called. Maybe never.
LazyPublish bool
@@ -107,8 +113,12 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error {
panic(errors.New("RelPath is empty"))
}
+ if fd.Params == nil {
+ fd.Params = make(maps.Params)
+ }
+
if fd.Path == nil {
- fd.Path = paths.Parse("", fd.TargetPath)
+ fd.Path = r.Cfg.PathParser().Parse("", fd.TargetPath)
}
if fd.TargetPath == "" {
@@ -143,6 +153,10 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error {
fd.NameOriginal = fd.NameNormalized
}
+ if fd.Title == "" {
+ fd.Title = fd.NameOriginal
+ }