项目文件夹

文件
2026-07-13 11:58:46 +08:00

1081 行
27 KiB
Go

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
// 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 hugolib
import (
"fmt"
"maps"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/bep/logg"
"github.com/gobuffalo/flect"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/hiter"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/output"
"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"
"github.com/spf13/cast"
)
var cjkRe = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
func (m *pageMetaSource) GetIdentity() identity.Identity {
return m.pathInfo
}
func (m *pageMetaSource) Path() string {
return m.pathInfo.Base()
}
func (m *pageMetaSource) PathInfo() *paths.Path {
return m.pathInfo
}
func (m *pageMetaSource) forEeachContentNode(f func(v sitesmatrix.Vector, n contentNode) bool) bool {
return f(sitesmatrix.Vector{}, m)
}
type pageMetaSource struct {
pathInfo *paths.Path // Always set. This the canonical path to the Page.
f *source.File
pi *contentParseInfo
contentAdapterSourceEntryHash uint64
sitesMatrixBase sitesmatrix.VectorIterator
sitesMatrixBaseOnly bool
// Set for standalone pages, e.g. robotsTXT.
standaloneOutputFormat output.Format
resources.AtomicStaler
pageConfigSource *pagemeta.PageConfigEarly
cascadeCompiled *page.PageMatcherParamsConfigs
resourcePath string // Set for bundled pages; path relative to its bundle root.
bundled bool // Set if this page is bundled inside another.
noFrontMatter bool
openSource func() (hugio.ReadSeekCloser, error)
}
func (m *pageMetaSource) String() string {
if m.f != nil {
return fmt.Sprintf("pageMetaSource(%s, %s)", m.f.FileInfo().Meta().PathInfo, m.f.FileInfo().Meta().Filename)
}
return fmt.Sprintf("pageMetaSource(%s)", m.pathInfo)
}
func (m *pageMetaSource) nodeCategoryPage() {
// Marker method.
}
func (m *pageMetaSource) nodeCategorySingle() {
// Marker method.
}
func (m *pageMetaSource) nodeSourceEntryID() any {
if m.f != nil && !m.f.IsContentAdapter() {
return m.f.FileInfo().Meta().Filename
}
return m.contentAdapterSourceEntryHash
}
func (m *pageMetaSource) setCascadeFromMap(frontmatter map[string]any, defaultSitesMatrix sitesmatrix.VectorStore, configuredDimensions *sitesmatrix.ConfiguredDimensions, logger loggers.Logger) error {
const (
pageMetaKeyCascade = "cascade"
)
// Check for any cascade define on itself.
if cv, found := frontmatter[pageMetaKeyCascade]; found {
var err error
cascade, err := page.DecodeCascadeConfig(cv)
if err != nil {
return err
}
if err := cascade.InitConfig(logger, defaultSitesMatrix, configuredDimensions); err != nil {
return err
}
m.cascadeCompiled = cascade
}
return nil
}
var _ contentNodeCascadeProvider = (*pageState)(nil)
type pageMeta struct {
// Shared between all dimensions of this page.
*pageMetaSource
pageConfig *pagemeta.PageConfigLate
term string // Set for kind == KindTerm.
singular string // Set for kind == KindTerm and kind == KindTaxonomy.
content *cachedContent // The source and the parsed page content.
datesOriginal pagemeta.Dates // Original dates for rebuilds.
}
func (p *pageState) getCascade() *page.PageMatcherParamsConfigs {
return p.m.cascadeCompiled
}
func (m *pageMetaSource) initEarly(h *HugoSites, cascades *page.PageMatcherParamsConfigs) error {
if err := m.doInitEarly(h, cascades); err != nil {
return m.wrapError(err, h.SourceFs)
}
return nil
}
func (m *pageMetaSource) doInitEarly(h *HugoSites, cascades *page.PageMatcherParamsConfigs) error {
if err := m.initFrontMatter(h); err != nil {
return err
}
if m.pageConfigSource.Kind == "" {
// Resolve page kind.
m.pageConfigSource.Kind = kinds.KindSection
if m.pathInfo.Base() == "" {
m.pageConfigSource.Kind = kinds.KindHome
} else if m.pathInfo.IsBranchBundle() {
// A section, taxonomy or term.
tc := h.getFirstTaxonomyConfig(m.Path())
if !tc.IsZero() {
// Either a taxonomy or a term.
if tc.pluralTreeKey == m.Path() {
m.pageConfigSource.Kind = kinds.KindTaxonomy
} else {
m.pageConfigSource.Kind = kinds.KindTerm
}
}
} else if m.f != nil {
m.pageConfigSource.Kind = kinds.KindPage
}
}
sitesMatrixBase := m.sitesMatrixBase
if sitesMatrixBase == nil && m.f != nil {
sitesMatrixBase = m.f.FileInfo().Meta().SitesMatrix
}
if m.sitesMatrixBaseOnly && sitesMatrixBase == nil {
panic("sitesMatrixBaseOnly set, but no base sites matrix")
}
var fim *hugofs.FileMeta
if m.f != nil {
fim = m.f.FileInfo().Meta()
}
if err := m.pageConfigSource.CompileEarly(m.pathInfo, cascades, h.Conf, fim, sitesMatrixBase, m.sitesMatrixBaseOnly); err != nil {
return err
}
if m.pageConfigSource.Frontmatter != nil {
if err := m.setCascadeFromMap(m.pageConfigSource.Frontmatter, m.pageConfigSource.SitesMatrix, h.Conf.ConfiguredDimensions(), h.Log); err != nil {
return err
}
}
return nil
}
func (m *pageMetaSource) initFrontMatter(h *HugoSites) error {
if m.pathInfo == nil {
if err := m.initPathInfo(h); err != nil {
return err
}
}
if m.pageConfigSource == nil {
m.pageConfigSource = &pagemeta.PageConfigEarly{}
}
// Read front matter.
if err := m.parseFrontMatter(h, pageSourceIDCounter.Add(1)); err != nil {
return err
}
var ext string
if m.f != nil {
if !m.f.IsContentAdapter() {
ext = m.f.Ext()
}
}
if err := m.pageConfigSource.SetMetaPreFromMap(ext, m.pi.frontMatter, h.Log, h.Conf); err != nil {
return err
}
// Gate the content format against the security policy. The body of a
// content file is treated as untrusted; text/html is denied by default
// because Hugo emits it verbatim and that is an XSS sink. This applies
// to pages emitted by content adapters too -- the adapter is trusted
// but the data it pulls in may not be.
if m.f != nil && !m.pageConfigSource.ContentMediaType.IsZero() {
if err := h.Deps.ExecHelper.Sec().CheckAllowedContent(m.pageConfigSource.ContentMediaType.Type); err != nil {
return err
}
}
return nil
}
func (m *pageMetaSource) initPathInfo(h *HugoSites) error {
pcfg := m.pageConfigSource
if pcfg.Path != "" {
s := pcfg.Path
// Paths from content adapters should never have any extension.
if pcfg.IsFromContentAdapter || !paths.HasExt(s) {
var (
isBranch bool
isBranchSet bool
ext string = pcfg.ContentMediaType.FirstSuffix.Suffix
)
if pcfg.Kind != "" {
isBranch = kinds.IsBranch(pcfg.Kind)
isBranchSet = true
}
if !pcfg.IsFromContentAdapter {
if m.pathInfo != nil {
if !isBranchSet {
isBranch = m.pathInfo.IsBranchBundle()
}
if m.pathInfo.Ext() != "" {
ext = m.pathInfo.Ext()
}
} else if m.f != nil {
pi := m.f.FileInfo().Meta().PathInfo
if !isBranchSet {
isBranch = pi.IsBranchBundle()
}
if pi.Ext() != "" {
ext = pi.Ext()
}
}
}
if isBranch {
s += "/_index." + ext
} else {
s += "/index." + ext
}
}
m.pathInfo = h.Conf.PathParser().Parse(files.ComponentFolderContent, s)
} else {
if m.f != nil {
m.pathInfo = m.f.FileInfo().Meta().PathInfo
}
if m.pathInfo == nil {
panic(fmt.Sprintf("missing pathInfo in %v", m))
}
}
return nil
}
func (m *pageMeta) initLate(s *Site) error {
var tc viewName
if m.pageConfigSource.Kind == kinds.KindTerm || m.pageConfigSource.Kind == kinds.KindTaxonomy {
if tc.IsZero() {
tc = s.pageMap.cfg.getTaxonomyConfig(m.Path())
}
if tc.IsZero() {
return fmt.Errorf("no taxonomy configuration found for %q", m.Path())
}
m.singular = tc.singular
if m.pageConfigSource.Kind == kinds.KindTerm {
m.term = paths.TrimLeading(strings.TrimPrefix(m.pathInfo.Unnormalized().Base(), "/"+tc.plural))
}
}
return nil
}
// bookmark1
func (h *HugoSites) newPageMetaSourceFromFile(fi hugofs.FileMetaInfo) (*pageMetaSource, error) {
p, err := func() (*pageMetaSource, error) {
meta := fi.Meta()
openSource := func() (hugio.ReadSeekCloser, error) {
r, err := meta.Open()
if err != nil {
return nil, fmt.Errorf("failed to open file %q: %w", meta.Filename, err)
}
return r, nil
}
p := &pageMetaSource{
f: source.NewFileInfo(fi),
pathInfo: fi.Meta().PathInfo,
openSource: openSource,
}
return p, nil
}()
if err != nil {
return nil, hugofs.AddFileInfoToError(err, fi, h.SourceFs)
}
return p, err
}
func (h *HugoSites) newPageMetaSourceForContentAdapter(fi hugofs.FileMetaInfo, sitesMatrixBase sitesmatrix.VectorIterator, pc *pagemeta.PageConfigEarly) (*pageMetaSource, error) {
p := &pageMetaSource{
f: source.NewFileInfo(fi),
noFrontMatter: true,
pageConfigSource: pc,
contentAdapterSourceEntryHash: pc.SourceEntryHash,
sitesMatrixBase: sitesMatrixBase,
openSource: pc.Content.ValueAsOpenReadSeekCloser(),
}
return p, p.initPathInfo(h)
}
func (s *Site) newPageFromPageMetasource(ms *pageMetaSource, cascades *page.PageMatcherParamsConfigs) (*pageState, error) {
m, err := s.newPageMetaFromPageMetasource(ms)
if err != nil {
return nil, err
}
p, err := s.newPageFromPageMeta(m, cascades)
if err != nil {
return nil, err
}
return p, nil
}
func (s *Site) newPageMetaFromPageMetasource(ms *pageMetaSource) (*pageMeta, error) {
m := &pageMeta{
pageMetaSource: ms,
pageConfig: &pagemeta.PageConfigLate{
Params: make(hmaps.Params),
},
}
return m, nil
}
// Prepare for a rebuild of the data passed in from front matter.
func (m *pageMeta) prepareRebuild() {
// Restore orignal date values so we can repeat aggregation.
m.pageConfig.Dates = m.datesOriginal
}
func (m *pageMeta) BundleType() string {
switch m.pathInfo.Type() {
case paths.TypeLeaf:
return "leaf"
case paths.TypeBranch:
return "branch"
default:
return ""
}
}
func (m *pageMeta) Date() time.Time {
return m.pageConfig.Dates.Date
}
func (m *pageMeta) PublishDate() time.Time {
return m.pageConfig.Dates.PublishDate
}
func (m *pageMeta) Lastmod() time.Time {
return m.pageConfig.Dates.Lastmod
}
func (m *pageMeta) ExpiryDate() time.Time {
return m.pageConfig.Dates.ExpiryDate
}
func (m *pageMeta) Description() string {
return m.pageConfig.Description
}
func (m *pageMeta) Draft() bool {
return m.pageConfig.Draft
}
func (m *pageMeta) File() *source.File {
return m.f
}
func (m *pageMeta) IsHome() bool {
return m.Kind() == kinds.KindHome
}
func (m *pageMeta) Keywords() []string {
return m.pageConfig.Keywords
}
func (m *pageMetaSource) Kind() string {
return m.pageConfigSource.Kind
}
func (m *pageMeta) Layout() string {
return m.pageConfig.Layout
}
func (m *pageMeta) LinkTitle() string {
if m.pageConfig.LinkTitle != "" {
return m.pageConfig.LinkTitle
}
return m.Title()
}
func (m *pageMeta) Name() string {
if m.resourcePath != "" {
return m.resourcePath
}
if m.Kind() == kinds.KindTerm {
return m.pathInfo.Unnormalized().BaseNameNoIdentifier()
}
return m.Title()
}
func (m *pageMeta) IsNode() bool {
hugo.Deprecate(".Page.IsNode", "Use .Page.IsBranch or not .Page.IsPage instead.", "v0.163.0")
return m.IsBranch()
}
func (m *pageMeta) IsBranch() bool {
return kinds.IsBranch(m.Kind())
}
func (m *pageMeta) IsPage() bool {
return m.Kind() == kinds.KindPage
}
func (m *pageMeta) Params() hmaps.Params {
return m.pageConfig.Params
}
func (m *pageMeta) Path() string {
if m.IsHome() {
// Base returns an empty string for the home page,
// which is correct as the key to the page tree.
return "/"
}
return m.pathInfo.Base()
}
func (m *pageMeta) PathInfo() *paths.Path {
return m.pathInfo
}
func (m *pageMeta) IsSection() bool {
return m.Kind() == kinds.KindSection
}
func (m *pageMeta) Section() string {
return m.pathInfo.Section()
}
func (m *pageMeta) Sitemap() config.SitemapConfig {
return m.pageConfig.Sitemap
}
func (m *pageMeta) Title() string {
return m.pageConfig.Title
}
const defaultContentType = "page"
func (m *pageMeta) Type() string {
if m.pageConfig.Type != "" {
return m.pageConfig.Type
}
if sect := m.Section(); sect != "" {
return sect
}
return defaultContentType
}
func (m *pageMeta) Weight() int {
return m.pageConfig.Weight
}
func (ps *pageState) setMetaPost(cascades *page.PageMatcherParamsConfigs) error {
if ps.m.pageConfigSource.Frontmatter != nil {
if ps.m.pageConfigSource.IsFromContentAdapter {
ps.m.pageConfig.ContentAdapterData = maps.Clone(ps.m.pageConfigSource.Frontmatter)
} else {
ps.m.pageConfig.Params = maps.Clone(ps.m.pageConfigSource.Frontmatter)
}
}
if ps.m.pageConfig.Params == nil {
ps.m.pageConfig.Params = make(hmaps.Params)
}
if ps.m.pageConfigSource.IsFromContentAdapter && ps.m.pageConfig.ContentAdapterData == nil {
ps.m.pageConfig.ContentAdapterData = make(hmaps.Params)
}
// Cascade defined on itself has higher priority than inherited ones.
allCascades := hiter.Concat(ps.m.cascadeCompiled.All(), cascades.All())
for v := range allCascades {
if !v.Target.Match(ps.Kind(), ps.Path(), ps.s.Conf.Environment(), ps.s.siteVector) {
continue
}
for kk, vv := range v.Params {
if _, found := ps.m.pageConfig.Params[kk]; !found {
ps.m.pageConfig.Params[kk] = vv
}
}
for kk, vv := range v.Fields {
if ps.m.pageConfigSource.IsFromContentAdapter {
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
}
}
}
}
if err := ps.setMetaPostParams(); err != nil {
return err
}
if err := ps.m.applyDefaultValues(ps.s); err != nil {
return err
}
// Store away any original values that may be changed from aggregation.
ps.m.datesOriginal = ps.m.pageConfig.Dates
return nil
}
func (ps *pageState) setMetaPostParams() error {
pm := ps.m
var mtime time.Time
var contentBaseName string
var isContentAdapter bool
if ps.File() != nil {
isContentAdapter = ps.File().IsContentAdapter()
contentBaseName = ps.File().ContentBaseName()
if ps.File().FileInfo() != nil {
mtime = ps.File().FileInfo().ModTime()
}
}
var gitAuthorDate time.Time
if ps.gitInfo != nil {
gitAuthorDate = ps.gitInfo.AuthorDate
}
descriptor := &pagemeta.FrontMatterDescriptor{
PageConfigEarly: pm.pageConfigSource,
PageConfigLate: pm.pageConfig,
BaseFilename: contentBaseName,
ModTime: mtime,
GitAuthorDate: gitAuthorDate,
Location: langs.GetLocation(ps.s.Language()),
PathOrTitle: ps.pathOrTitle(),
}
if isContentAdapter {
if err := pm.pageConfig.Compile(pm.pageConfigSource, ps.s.Log, ps.s.conf.OutputFormats.Config); err != nil {
return err
}
}
pcfg := pm.pageConfig
// Handle the date separately
// 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.
err := ps.s.frontmatterHandler.HandleDates(descriptor)
if err != nil {
ps.s.Log.Errorf("Failed to handle dates for page %q: %s", ps.pathOrTitle(), err)
}
if isContentAdapter {
// Done.
return nil
}
var sitemapSet bool
var buildConfig any
var isNewBuildKeyword bool
if v, ok := pcfg.Params["_build"]; ok {
hugo.Deprecate("The \"_build\" front matter key", "Use \"build\" instead. See https://gohugo.io/content-management/build-options.", "0.145.0")
buildConfig = v
} else {
buildConfig = pcfg.Params["build"]
isNewBuildKeyword = true
}
pm.pageConfig.Build, err = pagemeta.DecodeBuildConfig(buildConfig)
if err != nil {
var msgDetail string
if isNewBuildKeyword {
msgDetail = `. We renamed the _build keyword to build in Hugo 0.123.0. We recommend putting user defined params in the params section, e.g.:
---
title: "My Title"
params:
build: "My Build"
---
´
`
}
return fmt.Errorf("failed to decode build config in front matter: %s%s", err, msgDetail)
}
var draft, published, isCJKLanguage *bool
var userParams map[string]any
for k, v := range pcfg.Params {
loki := strings.ToLower(k)
if loki == "params" {
vv, err := hmaps.ToParamsAndPrepare(v)
if err != nil {
return fmt.Errorf("front matter field %q must be a map: %w", k, err)
}
userParams = vv
delete(pcfg.Params, k)
continue
}
if loki == "published" { // Intentionally undocumented
vv, err := cast.ToBoolE(v)
if err == nil {
published = &vv
}
// published may also be a date
continue
}
if ps.s.frontmatterHandler.IsDateKey(loki) {
continue
}
if loki == "path" || loki == "kind" || loki == "lang" {
// See issue 12484.
// Only warn when the key was set at the top level of the
// original front matter; cascade.params can legitimately carry
// these names as user params (issue 14848).
if _, ok := pm.pageConfigSource.Frontmatter[loki]; ok {
hugo.DeprecateLevelMin(loki+" in front matter", "", "v0.144.0", logg.LevelWarn)
}
}
switch loki {
case "title":
pcfg.Title = cast.ToString(v)
pcfg.Params[loki] = pcfg.Title
case "linktitle":
pcfg.LinkTitle = cast.ToString(v)
pcfg.Params[loki] = pcfg.LinkTitle
case "summary":
pcfg.Summary = cast.ToString(v)
pcfg.Params[loki] = pcfg.Summary
case "description":
pcfg.Description = cast.ToString(v)
pcfg.Params[loki] = pcfg.Description
case "slug":
// Don't start or end with a -
pcfg.Slug = strings.Trim(cast.ToString(v), "-")
pcfg.Params[loki] = pm.Slug()
case "url":
url := cast.ToString(v)
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
return fmt.Errorf("URLs with protocol (http*) not supported: %q. In page %q", url, ps.pathOrTitle())
}
pcfg.URL = url
pcfg.Params[loki] = url
case "type":
pcfg.Type = cast.ToString(v)
pcfg.Params[loki] = pcfg.Type
case "keywords":
pcfg.Keywords = cast.ToStringSlice(v)
pcfg.Params[loki] = pcfg.Keywords
case "headless":
// Legacy setting for leaf bundles.
// This is since Hugo 0.63 handled in a more general way for all
// pages.
isHeadless := cast.ToBool(v)
pcfg.Params[loki] = isHeadless
if isHeadless {
pm.pageConfig.Build.List = pagemeta.Never
pm.pageConfig.Build.Render = pagemeta.Never
}
case "outputs":
o := cast.ToStringSlice(v)
// lower case names:
for i, s := range o {
o[i] = strings.ToLower(s)
}
pm.pageConfig.Outputs = o
case "draft":
draft = new(bool)
*draft = cast.ToBool(v)
case "layout":
pcfg.Layout = cast.ToString(v)
pcfg.Params[loki] = pcfg.Layout
case "weight":
pcfg.Weight = cast.ToInt(v)
pcfg.Params[loki] = pcfg.Weight
case "aliases":
pcfg.Aliases = cast.ToStringSlice(v)
for i, alias := range pcfg.Aliases {
if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
return fmt.Errorf("http* aliases not supported: %q", alias)
}
pcfg.Aliases[i] = filepath.ToSlash(alias)
}
pcfg.Params[loki] = pcfg.Aliases
case "sitemap":
pcfg.Sitemap, err = config.DecodeSitemap(ps.s.conf.Sitemap, hmaps.ToStringMap(v))
if err != nil {
return fmt.Errorf("failed to decode sitemap config in front matter: %s", err)
}
sitemapSet = true
case "iscjklanguage":
isCJKLanguage = new(bool)
*isCJKLanguage = cast.ToBool(v)
case "translationkey":
pcfg.TranslationKey = cast.ToString(v)
pcfg.Params[loki] = pcfg.TranslationKey
case "resources":
var resources []map[string]any
handled := true
switch vv := v.(type) {
case []map[any]any:
for _, vvv := range vv {
resources = append(resources, hmaps.ToStringMap(vvv))
}
case []map[string]any:
resources = append(resources, vv...)
case []any:
for _, vvv := range vv {
switch vvvv := vvv.(type) {
case map[any]any:
resources = append(resources, hmaps.ToStringMap(vvvv))
case map[string]any:
resources = append(resources, vvvv)
}
}
default:
handled = false
}
if handled {
pcfg.ResourcesMeta = resources
break
}
fallthrough
default:
// If not one of the explicit values, store in Params
switch vv := v.(type) {
case []any:
if len(vv) > 0 {
allStrings := true
for _, vvv := range vv {
if _, ok := vvv.(string); !ok {
allStrings = false
break
}
}
if allStrings {
// We need tags, keywords etc. to be []string, not []interface{}.
a := make([]string, len(vv))
for i, u := range vv {
a[i] = cast.ToString(u)
}
pcfg.Params[loki] = a
} else {
pcfg.Params[loki] = vv
}
} else {
pcfg.Params[loki] = []string{}
}
default:
pcfg.Params[loki] = vv
}
}
}
for k, v := range userParams {
pcfg.Params[strings.ToLower(k)] = v
}
if !sitemapSet {
pcfg.Sitemap = ps.s.conf.Sitemap
}
if draft != nil && published != nil {
pcfg.Draft = *draft
ps.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", ps.File().Filename())
} else if draft != nil {
pcfg.Draft = *draft
} else if published != nil {
pcfg.Draft = !*published
}
pcfg.Params["draft"] = pcfg.Draft
if isCJKLanguage != nil {
pcfg.IsCJKLanguage = *isCJKLanguage
} else if ps.s.conf.HasCJKLanguage && ps.m.content.pi.openSource != nil {
if cjkRe.Match(ps.m.content.mustSource()) {
pcfg.IsCJKLanguage = true
} else {
pcfg.IsCJKLanguage = false
}
}
pcfg.Params["iscjklanguage"] = pcfg.IsCJKLanguage
if err := pm.pageConfigSource.Init(false); err != nil {
return err
}
if err := pcfg.Init(); err != nil {
return err
}
if err := pcfg.Compile(pm.pageConfigSource, ps.s.Log, ps.s.conf.OutputFormats.Config); err != nil {
return err
}
return nil
}
// shouldList returns whether this page should be included in the list of pages.
// global indicates site.Pages etc.
func (m *pageMeta) shouldList(global bool) bool {
if m.isStandalone() {
// Never list 404, sitemap and similar.
return false
}
switch m.pageConfig.Build.List {
case pagemeta.Always:
return true
case pagemeta.Never:
return false
case pagemeta.ListLocally:
return !global
}
return false
}
func (m *pageMeta) shouldListAny() bool {
return m.shouldList(true) || m.shouldList(false)
}
func (m *pageMeta) isStandalone() bool {
return !m.standaloneOutputFormat.IsZero()
}
func (m *pageMeta) shouldBeCheckedForMenuDefinitions() bool {
if !m.shouldList(false) {
return false
}
return m.Kind() == kinds.KindHome || m.Kind() == kinds.KindSection || m.Kind() == kinds.KindPage
}
func (m *pageMeta) noRender() bool {
return m.pageConfig.Build.Render != pagemeta.Always
}
func (m *pageMeta) noLink() bool {
return m.pageConfig.Build.Render == pagemeta.Never
}
func (m *pageMeta) applyDefaultValues(s *Site) error {
if m.pageConfig.Build.IsZero() {
m.pageConfig.Build, _ = pagemeta.DecodeBuildConfig(nil)
}
if !s.conf.IsKindEnabled(m.Kind()) {
(&m.pageConfig.Build).Disable()
}
if m.pageConfigSource.Content.Markup == "" {
if m.File() != nil {
// Fall back to file extension
m.pageConfigSource.Content.Markup = s.ContentSpec.ResolveMarkup(m.File().Ext())
}
if m.pageConfigSource.Content.Markup == "" {
m.pageConfigSource.Content.Markup = "markdown"
}
}
if m.pageConfig.Title == "" && m.f == nil {
switch m.Kind() {
case kinds.KindHome:
m.pageConfig.Title = s.Title()
case kinds.KindSection:
sectionName := m.pathInfo.Unnormalized().BaseNameNoIdentifier()
if s.conf.PluralizeListTitles {
sectionName = flect.Pluralize(sectionName)
}
if s.conf.CapitalizeListTitles {
sectionName = s.conf.C.CreateTitle(sectionName)
}
m.pageConfig.Title = sectionName
case kinds.KindTerm:
if m.term != "" {
if s.conf.CapitalizeListTitles {
m.pageConfig.Title = s.conf.C.CreateTitle(m.term)
} else {
m.pageConfig.Title = m.term
}
} else {
panic("term not set")
}
case kinds.KindTaxonomy:
if s.conf.CapitalizeListTitles {
m.pageConfig.Title = strings.Replace(s.conf.C.CreateTitle(m.pathInfo.Unnormalized().BaseNameNoIdentifier()), "-", " ", -1)
} else {
m.pageConfig.Title = strings.Replace(m.pathInfo.Unnormalized().BaseNameNoIdentifier(), "-", " ", -1)
}
case kinds.KindStatus404:
m.pageConfig.Title = "404 Page not found"
}
}
return nil
}
func (m *pageMeta) newContentConverter(ps *pageState, markup string) (converter.Converter, error) {
if ps == nil {
panic("no Page provided")
}
cp := ps.s.ContentSpec.Converters.Get(markup)
if cp == nil {
return converter.NopConverter, fmt.Errorf("no content renderer found for markup %q, page: %s", markup, ps.getPageInfoForError())
}
var filename string
var path string
if m.f != nil {
filename = m.f.Filename()
path = m.f.Path()
} else {
path = m.Path()
}
doc := newPageForRenderHook(ps)
documentLookup := func(id uint64) any {
if id == ps.pid {
// This prevents infinite recursion in some cases.
return doc
}
if v, ok := ps.pageOutput.pco.otherOutputs.Get(id); ok {
return v.po.p
}
return nil
}
cpp, err := cp.New(
converter.DocumentContext{
Document: doc,
DocumentLookup: documentLookup,
DocumentID: hashing.XxHashFromStringHexEncoded(ps.Path()),
DocumentName: path,
Filename: filename,
},
)
if err != nil {
return converter.NopConverter, err
}
return cpp, nil
}
// The output formats this page will be rendered to.
func (ps *pageState) outputFormats() output.Formats {
if len(ps.m.pageConfig.ConfiguredOutputFormats) > 0 {
return ps.m.pageConfig.ConfiguredOutputFormats
}
return ps.s.conf.C.KindOutputFormats[ps.Kind()]
}
func (m *pageMeta) Slug() string {
return m.pageConfig.Slug
}
func getParam(m resource.ResourceParamsProvider, key string, stringToLower bool) any {
v := m.Params()[strings.ToLower(key)]
if v == nil {
return nil
}
switch val := v.(type) {
case bool:
return val
case string:
if stringToLower {
return strings.ToLower(val)
}
return val
case int64, int32, int16, int8, int:
return cast.ToInt(v)
case float64, float32:
return cast.ToFloat64(v)
case time.Time:
return val
case []string:
if stringToLower {
return helpers.SliceToLower(val)
}
return v
default:
return v
}
}
func getParamToLower(m resource.ResourceParamsProvider, key string) any {
return getParam(m, key, true)
}