// 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) }