mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
6dedb4efc7
This is deliberately very simple, but should not break anything. We need to introduce this in baby steps, but this should allow us to introduce this in the documentation. Note that the `params` section's key/values will be added to `.Params` last. This means that you can have different values for "Hugo's summary" and the custom ".Params.summary" if you want to. Updates #11055
892 lines
21 KiB
Go
892 lines
21 KiB
Go
// Copyright 2019 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 (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gobuffalo/flect"
|
|
"github.com/gohugoio/hugo/identity"
|
|
"github.com/gohugoio/hugo/langs"
|
|
"github.com/gohugoio/hugo/markup/converter"
|
|
xmaps "golang.org/x/exp/maps"
|
|
|
|
"github.com/gohugoio/hugo/related"
|
|
|
|
"github.com/gohugoio/hugo/source"
|
|
|
|
"github.com/gohugoio/hugo/common/hugo"
|
|
"github.com/gohugoio/hugo/common/maps"
|
|
"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}`)
|
|
|
|
type pageMeta struct {
|
|
kind string // Page kind.
|
|
term string // Set for kind == KindTerm.
|
|
singular string // Set for kind == KindTerm and kind == KindTaxonomy.
|
|
|
|
resource.Staler
|
|
pageMetaParams
|
|
|
|
pageMetaFrontMatter
|
|
|
|
// Set for standalone pages, e.g. robotsTXT.
|
|
standaloneOutputFormat output.Format
|
|
|
|
resourcePath string // Set for bundled pages; path relative to its bundle root.
|
|
bundled bool // Set if this page is bundled inside another.
|
|
|
|
pathInfo *paths.Path // Always set. This the canonical path to the Page.
|
|
f *source.File
|
|
|
|
s *Site // The site this page belongs to.
|
|
}
|
|
|
|
// Prepare for a rebuild of the data passed in from front matter.
|
|
func (m *pageMeta) setMetaPostPrepareRebuild() {
|
|
params := xmaps.Clone[map[string]any](m.paramsOriginal)
|
|
m.pageMetaParams.params = params
|
|
m.pageMetaFrontMatter = pageMetaFrontMatter{}
|
|
}
|
|
|
|
type pageMetaParams struct {
|
|
setMetaPostCount int
|
|
setMetaPostCascadeChanged bool
|
|
|
|
params map[string]any // Params contains configuration defined in the params section of page frontmatter.
|
|
cascade map[page.PageMatcher]maps.Params // cascade contains default configuration to be cascaded downwards.
|
|
|
|
// These are only set in watch mode.
|
|
datesOriginal pageMetaDates
|
|
paramsOriginal map[string]any // contains the original params as defined in the front matter.
|
|
cascadeOriginal map[page.PageMatcher]maps.Params // contains the original cascade as defined in the front matter.
|
|
}
|
|
|
|
// From page front matter.
|
|
type pageMetaFrontMatter struct {
|
|
draft bool // Only published when running with -D flag
|
|
title string
|
|
linkTitle string
|
|
summary string
|
|
weight int
|
|
markup string
|
|
contentType string // type in front matter.
|
|
isCJKLanguage bool // whether the content is in a CJK language.
|
|
layout string
|
|
aliases []string
|
|
description string
|
|
keywords []string
|
|
translationKey string // maps to translation(s) of this page.
|
|
|
|
buildConfig pagemeta.BuildConfig
|
|
configuredOutputFormats output.Formats // outputs defiend in front matter.
|
|
pageMetaDates // The 4 front matter dates that Hugo cares about.
|
|
resourcesMetadata []map[string]any // Raw front matter metadata that is going to be assigned to the page resources.
|
|
sitemap config.SitemapConfig // Sitemap overrides from front matter.
|
|
urlPaths pagemeta.URLPath
|
|
}
|
|
|
|
func (m *pageMetaParams) init(preserveOringal bool) {
|
|
if preserveOringal {
|
|
m.paramsOriginal = xmaps.Clone[maps.Params](m.params)
|
|
m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.cascade)
|
|
}
|
|
}
|
|
|
|
func (p *pageMeta) Aliases() []string {
|
|
return p.aliases
|
|
}
|
|
|
|
func (p *pageMeta) Author() page.Author {
|
|
hugo.Deprecate(".Author", "Use taxonomies.", "v0.98.0")
|
|
authors := p.Authors()
|
|
|
|
for _, author := range authors {
|
|
return author
|
|
}
|
|
return page.Author{}
|
|
}
|
|
|
|
func (p *pageMeta) Authors() page.AuthorList {
|
|
hugo.Deprecate(".Author", "Use taxonomies.", "v0.112.0")
|
|
return nil
|
|
}
|
|
|
|
func (p *pageMeta) BundleType() string {
|
|
switch p.pathInfo.BundleType() {
|
|
case paths.PathTypeLeaf:
|
|
return "leaf"
|
|
case paths.PathTypeBranch:
|
|
return "branch"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (p *pageMeta) Description() string {
|
|
return p.description
|
|
}
|
|
|
|
func (p *pageMeta) Lang() string {
|
|
return p.s.Lang()
|
|
}
|
|
|
|
func (p *pageMeta) Draft() bool {
|
|
return p.draft
|
|
}
|
|
|
|
func (p *pageMeta) File() *source.File {
|
|
return p.f
|
|
}
|
|
|
|
func (p *pageMeta) IsHome() bool {
|
|
return p.Kind() == kinds.KindHome
|
|
}
|
|
|
|
func (p *pageMeta) Keywords() []string {
|
|
return p.keywords
|
|
}
|
|
|
|
func (p *pageMeta) Kind() string {
|
|
return p.kind
|
|
}
|
|
|
|
func (p *pageMeta) Layout() string {
|
|
return p.layout
|
|
}
|
|
|
|
func (p *pageMeta) LinkTitle() string {
|
|
if p.linkTitle != "" {
|
|
return p.linkTitle
|
|
}
|
|
|
|
return p.Title()
|
|
}
|
|
|
|
func (p *pageMeta) Name() string {
|
|
if p.resourcePath != "" {
|
|
return p.resourcePath
|
|
}
|
|
if p.kind == kinds.KindTerm {
|
|
return p.pathInfo.Unmormalized().BaseNameNoIdentifier()
|
|
}
|
|
return p.Title()
|
|
}
|
|
|
|
func (p *pageMeta) IsNode() bool {
|
|
return !p.IsPage()
|
|
}
|
|
|
|
func (p *pageMeta) IsPage() bool {
|
|
return p.Kind() == kinds.KindPage
|
|
}
|
|
|
|
// Param is a convenience method to do lookups in Page's and Site's Params map,
|
|
// in that order.
|
|
//
|
|
// This method is also implemented on SiteInfo.
|
|
// TODO(bep) interface
|
|
func (p *pageMeta) Param(key any) (any, error) {
|
|
return resource.Param(p, p.s.Params(), key)
|
|
}
|
|
|
|
func (p *pageMeta) Params() maps.Params {
|
|
return p.params
|
|
}
|
|
|
|
func (p *pageMeta) Path() string {
|
|
return p.pathInfo.Base()
|
|
}
|
|
|
|
func (p *pageMeta) PathInfo() *paths.Path {
|
|
return p.pathInfo
|
|
}
|
|
|
|
// RelatedKeywords implements the related.Document interface needed for fast page searches.
|
|
func (p *pageMeta) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
|
|
v, err := p.Param(cfg.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cfg.ToKeywords(v)
|
|
}
|
|
|
|
func (p *pageMeta) IsSection() bool {
|
|
return p.Kind() == kinds.KindSection
|
|
}
|
|
|
|
func (p *pageMeta) Section() string {
|
|
return p.pathInfo.Section()
|
|
}
|
|
|
|
func (p *pageMeta) Sitemap() config.SitemapConfig {
|
|
return p.sitemap
|
|
}
|
|
|
|
func (p *pageMeta) Title() string {
|
|
return p.title
|
|
}
|
|
|
|
const defaultContentType = "page"
|
|
|
|
func (p *pageMeta) Type() string {
|
|
if p.contentType != "" {
|
|
return p.contentType
|
|
}
|
|
|
|
if sect := p.Section(); sect != "" {
|
|
return sect
|
|
}
|
|
|
|
return defaultContentType
|
|
}
|
|
|
|
func (p *pageMeta) Weight() int {
|
|
return p.weight
|
|
}
|
|
|
|
func (ps *pageState) setMetaPre() error {
|
|
pm := ps.m
|
|
p := ps
|
|
frontmatter := p.content.parseInfo.frontMatter
|
|
watching := p.s.watching()
|
|
|
|
if frontmatter != nil {
|
|
// Needed for case insensitive fetching of params values
|
|
maps.PrepareParams(frontmatter)
|
|
pm.pageMetaParams.params = frontmatter
|
|
if p.IsNode() {
|
|
// Check for any cascade define on itself.
|
|
if cv, found := frontmatter["cascade"]; found {
|
|
var err error
|
|
cascade, err := page.DecodeCascade(cv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pm.pageMetaParams.cascade = cascade
|
|
|
|
}
|
|
}
|
|
} else if pm.pageMetaParams.params == nil {
|
|
pm.pageMetaParams.params = make(maps.Params)
|
|
}
|
|
|
|
pm.pageMetaParams.init(watching)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error {
|
|
ps.m.setMetaPostCount++
|
|
var cascadeHashPre uint64
|
|
if ps.m.setMetaPostCount > 1 {
|
|
cascadeHashPre = identity.HashUint64(ps.m.cascade)
|
|
ps.m.cascade = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
|
|
|
|
}
|
|
|
|
// Apply cascades first so they can be overriden later.
|
|
if cascade != nil {
|
|
if ps.m.cascade != nil {
|
|
for k, v := range cascade {
|
|
vv, found := ps.m.cascade[k]
|
|
if !found {
|
|
ps.m.cascade[k] = v
|
|
} else {
|
|
// Merge
|
|
for ck, cv := range v {
|
|
if _, found := vv[ck]; !found {
|
|
vv[ck] = cv
|
|
}
|
|
}
|
|
}
|
|
}
|
|
cascade = ps.m.cascade
|
|
} else {
|
|
ps.m.cascade = cascade
|
|
}
|
|
}
|
|
|
|
if cascade == nil {
|
|
cascade = ps.m.cascade
|
|
}
|
|
|
|
if ps.m.setMetaPostCount > 1 {
|
|
ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.cascade)
|
|
if !ps.m.setMetaPostCascadeChanged {
|
|
// No changes, restore any value that may be changed by aggregation.
|
|
ps.m.dates = ps.m.datesOriginal.dates
|
|
return nil
|
|
}
|
|
ps.m.setMetaPostPrepareRebuild()
|
|
|
|
}
|
|
|
|
// Cascade is also applied to itself.
|
|
for m, v := range cascade {
|
|
if !m.Matches(ps) {
|
|
continue
|
|
}
|
|
for kk, vv := range v {
|
|
if _, found := ps.m.params[kk]; !found {
|
|
ps.m.params[kk] = vv
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := ps.setMetaPostParams(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ps.m.applyDefaultValues(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Store away any original values that may be changed from aggregation.
|
|
ps.m.datesOriginal = ps.m.pageMetaDates
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *pageState) setMetaPostParams() error {
|
|
pm := p.m
|
|
var mtime time.Time
|
|
var contentBaseName string
|
|
if p.File() != nil {
|
|
contentBaseName = p.File().ContentBaseName()
|
|
if p.File().FileInfo() != nil {
|
|
mtime = p.File().FileInfo().ModTime()
|
|
}
|
|
}
|
|
|
|
var gitAuthorDate time.Time
|
|
if !p.gitInfo.IsZero() {
|
|
gitAuthorDate = p.gitInfo.AuthorDate
|
|
}
|
|
|
|
pm.pageMetaDates = pageMetaDates{}
|
|
pm.urlPaths = pagemeta.URLPath{}
|
|
|
|
descriptor := &pagemeta.FrontMatterDescriptor{
|
|
Params: pm.params,
|
|
Dates: &pm.pageMetaDates.dates,
|
|
PageURLs: &pm.urlPaths,
|
|
BaseFilename: contentBaseName,
|
|
ModTime: mtime,
|
|
GitAuthorDate: gitAuthorDate,
|
|
Location: langs.GetLocation(pm.s.Language()),
|
|
}
|
|
|
|
// 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 := pm.s.frontmatterHandler.HandleDates(descriptor)
|
|
if err != nil {
|
|
p.s.Log.Errorf("Failed to handle dates for page %q: %s", p.pathOrTitle(), err)
|
|
}
|
|
|
|
pm.buildConfig, err = pagemeta.DecodeBuildConfig(pm.params["_build"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var sitemapSet bool
|
|
|
|
var draft, published, isCJKLanguage *bool
|
|
var userParams map[string]any
|
|
for k, v := range pm.params {
|
|
loki := strings.ToLower(k)
|
|
|
|
if loki == "params" {
|
|
vv, err := maps.ToStringMapE(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userParams = vv
|
|
delete(pm.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 pm.s.frontmatterHandler.IsDateKey(loki) {
|
|
continue
|
|
}
|
|
|
|
switch loki {
|
|
case "title":
|
|
pm.title = cast.ToString(v)
|
|
pm.params[loki] = pm.title
|
|
case "linktitle":
|
|
pm.linkTitle = cast.ToString(v)
|
|
pm.params[loki] = pm.linkTitle
|
|
case "summary":
|
|
pm.summary = cast.ToString(v)
|
|
pm.params[loki] = pm.summary
|
|
case "description":
|
|
pm.description = cast.ToString(v)
|
|
pm.params[loki] = pm.description
|
|
case "slug":
|
|
// Don't start or end with a -
|
|
pm.urlPaths.Slug = strings.Trim(cast.ToString(v), "-")
|
|
pm.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, p.pathOrTitle())
|
|
}
|
|
pm.urlPaths.URL = url
|
|
pm.params[loki] = url
|
|
case "type":
|
|
pm.contentType = cast.ToString(v)
|
|
pm.params[loki] = pm.contentType
|
|
case "keywords":
|
|
pm.keywords = cast.ToStringSlice(v)
|
|
pm.params[loki] = pm.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)
|
|
pm.params[loki] = isHeadless
|
|
if p.File().TranslationBaseName() == "index" && isHeadless {
|
|
pm.buildConfig.List = pagemeta.Never
|
|
pm.buildConfig.Render = pagemeta.Never
|
|
}
|
|
case "outputs":
|
|
o := cast.ToStringSlice(v)
|
|
// lower case names:
|
|
for i, s := range o {
|
|
o[i] = strings.ToLower(s)
|
|
}
|
|
if len(o) > 0 {
|
|
// Output formats are explicitly set in front matter, use those.
|
|
outFormats, err := p.s.conf.OutputFormats.Config.GetByNames(o...)
|
|
if err != nil {
|
|
p.s.Log.Errorf("Failed to resolve output formats: %s", err)
|
|
} else {
|
|
pm.configuredOutputFormats = outFormats
|
|
pm.params[loki] = outFormats
|
|
}
|
|
}
|
|
case "draft":
|
|
draft = new(bool)
|
|
*draft = cast.ToBool(v)
|
|
case "layout":
|
|
pm.layout = cast.ToString(v)
|
|
pm.params[loki] = pm.layout
|
|
case "markup":
|
|
pm.markup = cast.ToString(v)
|
|
pm.params[loki] = pm.markup
|
|
case "weight":
|
|
pm.weight = cast.ToInt(v)
|
|
pm.params[loki] = pm.weight
|
|
case "aliases":
|
|
pm.aliases = cast.ToStringSlice(v)
|
|
for i, alias := range pm.aliases {
|
|
if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
|
|
return fmt.Errorf("http* aliases not supported: %q", alias)
|
|
}
|
|
pm.aliases[i] = filepath.ToSlash(alias)
|
|
}
|
|
pm.params[loki] = pm.aliases
|
|
case "sitemap":
|
|
p.m.sitemap, err = config.DecodeSitemap(p.s.conf.Sitemap, maps.ToStringMap(v))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode sitemap config in front matter: %s", err)
|
|
}
|
|
pm.params[loki] = p.m.sitemap
|
|
sitemapSet = true
|
|
case "iscjklanguage":
|
|
isCJKLanguage = new(bool)
|
|
*isCJKLanguage = cast.ToBool(v)
|
|
case "translationkey":
|
|
pm.translationKey = cast.ToString(v)
|
|
pm.params[loki] = pm.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, maps.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, maps.ToStringMap(vvvv))
|
|
case map[string]any:
|
|
resources = append(resources, vvvv)
|
|
}
|
|
}
|
|
default:
|
|
handled = false
|
|
}
|
|
|
|
if handled {
|
|
pm.params[loki] = resources
|
|
pm.resourcesMetadata = 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)
|
|
}
|
|
pm.params[loki] = a
|
|
} else {
|
|
pm.params[loki] = vv
|
|
}
|
|
} else {
|
|
pm.params[loki] = []string{}
|
|
}
|
|
|
|
default:
|
|
pm.params[loki] = vv
|
|
}
|
|
}
|
|
}
|
|
|
|
for k, v := range userParams {
|
|
pm.params[strings.ToLower(k)] = v
|
|
}
|
|
|
|
if !sitemapSet {
|
|
pm.sitemap = p.s.conf.Sitemap
|
|
}
|
|
|
|
pm.markup = p.s.ContentSpec.ResolveMarkup(pm.markup)
|
|
|
|
if draft != nil && published != nil {
|
|
pm.draft = *draft
|
|
p.m.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", p.File().Filename())
|
|
} else if draft != nil {
|
|
pm.draft = *draft
|
|
} else if published != nil {
|
|
pm.draft = !*published
|
|
}
|
|
pm.params["draft"] = pm.draft
|
|
|
|
if isCJKLanguage != nil {
|
|
pm.isCJKLanguage = *isCJKLanguage
|
|
} else if p.s.conf.HasCJKLanguage && p.content.openSource != nil {
|
|
if cjkRe.Match(p.content.mustSource()) {
|
|
pm.isCJKLanguage = true
|
|
} else {
|
|
pm.isCJKLanguage = false
|
|
}
|
|
}
|
|
|
|
pm.params["iscjklanguage"] = p.m.isCJKLanguage
|
|
|
|
return nil
|
|
}
|
|
|
|
// shouldList returns whether this page should be included in the list of pages.
|
|
// glogal indicates site.Pages etc.
|
|
func (p *pageMeta) shouldList(global bool) bool {
|
|
if p.isStandalone() {
|
|
// Never list 404, sitemap and similar.
|
|
return false
|
|
}
|
|
|
|
switch p.buildConfig.List {
|
|
case pagemeta.Always:
|
|
return true
|
|
case pagemeta.Never:
|
|
return false
|
|
case pagemeta.ListLocally:
|
|
return !global
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *pageMeta) shouldListAny() bool {
|
|
return p.shouldList(true) || p.shouldList(false)
|
|
}
|
|
|
|
func (p *pageMeta) isStandalone() bool {
|
|
return !p.standaloneOutputFormat.IsZero()
|
|
}
|
|
|
|
func (p *pageMeta) shouldBeCheckedForMenuDefinitions() bool {
|
|
if !p.shouldList(false) {
|
|
return false
|
|
}
|
|
|
|
return p.kind == kinds.KindHome || p.kind == kinds.KindSection || p.kind == kinds.KindPage
|
|
}
|
|
|
|
func (p *pageMeta) noRender() bool {
|
|
return p.buildConfig.Render != pagemeta.Always
|
|
}
|
|
|
|
func (p *pageMeta) noLink() bool {
|
|
return p.buildConfig.Render == pagemeta.Never
|
|
}
|
|
|
|
func (p *pageMeta) applyDefaultValues() error {
|
|
if p.buildConfig.IsZero() {
|
|
p.buildConfig, _ = pagemeta.DecodeBuildConfig(nil)
|
|
}
|
|
|
|
if !p.s.conf.IsKindEnabled(p.Kind()) {
|
|
(&p.buildConfig).Disable()
|
|
}
|
|
|
|
if p.markup == "" {
|
|
if p.File() != nil {
|
|
// Fall back to file extension
|
|
p.markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
|
|
}
|
|
if p.markup == "" {
|
|
p.markup = "markdown"
|
|
}
|
|
}
|
|
|
|
if p.title == "" && p.f == nil {
|
|
switch p.Kind() {
|
|
case kinds.KindHome:
|
|
p.title = p.s.Title()
|
|
case kinds.KindSection:
|
|
sectionName := p.pathInfo.Unmormalized().BaseNameNoIdentifier()
|
|
if p.s.conf.PluralizeListTitles {
|
|
sectionName = flect.Pluralize(sectionName)
|
|
}
|
|
p.title = p.s.conf.C.CreateTitle(sectionName)
|
|
case kinds.KindTerm:
|
|
if p.term != "" {
|
|
p.title = p.s.conf.C.CreateTitle(p.term)
|
|
} else {
|
|
panic("term not set")
|
|
}
|
|
case kinds.KindTaxonomy:
|
|
p.title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unmormalized().BaseNameNoIdentifier()), "-", " ", -1)
|
|
case kinds.KindStatus404:
|
|
p.title = "404 Page not found"
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.Converter, error) {
|
|
if ps == nil {
|
|
panic("no Page provided")
|
|
}
|
|
cp := p.s.ContentSpec.Converters.Get(markup)
|
|
if cp == nil {
|
|
return converter.NopConverter, fmt.Errorf("no content renderer found for markup %q", markup)
|
|
}
|
|
|
|
var id string
|
|
var filename string
|
|
var path string
|
|
if p.f != nil {
|
|
id = p.f.UniqueID()
|
|
filename = p.f.Filename()
|
|
path = p.f.Path()
|
|
} else {
|
|
path = p.Path()
|
|
}
|
|
|
|
cpp, err := cp.New(
|
|
converter.DocumentContext{
|
|
Document: newPageForRenderHook(ps),
|
|
DocumentID: id,
|
|
DocumentName: path,
|
|
Filename: filename,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return converter.NopConverter, err
|
|
}
|
|
|
|
return cpp, nil
|
|
}
|
|
|
|
// The output formats this page will be rendered to.
|
|
func (m *pageMeta) outputFormats() output.Formats {
|
|
if len(m.configuredOutputFormats) > 0 {
|
|
return m.configuredOutputFormats
|
|
}
|
|
return m.s.conf.C.KindOutputFormats[m.Kind()]
|
|
}
|
|
|
|
func (p *pageMeta) Slug() string {
|
|
return p.urlPaths.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)
|
|
}
|
|
|
|
type pageMetaDates struct {
|
|
dates resource.Dates
|
|
}
|
|
|
|
func (d *pageMetaDates) Date() time.Time {
|
|
return d.dates.Date()
|
|
}
|
|
|
|
func (d *pageMetaDates) Lastmod() time.Time {
|
|
return d.dates.Lastmod()
|
|
}
|
|
|
|
func (d *pageMetaDates) PublishDate() time.Time {
|
|
return d.dates.PublishDate()
|
|
}
|
|
|
|
func (d *pageMetaDates) ExpiryDate() time.Time {
|
|
return d.dates.ExpiryDate()
|
|
}
|
|
|
|
func (ps *pageState) initLazyProviders() error {
|
|
ps.init.Add(func(ctx context.Context) (any, error) {
|
|
pp, err := newPagePaths(ps)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var outputFormatsForPage output.Formats
|
|
var renderFormats output.Formats
|
|
|
|
if ps.m.standaloneOutputFormat.IsZero() {
|
|
outputFormatsForPage = ps.m.outputFormats()
|
|
renderFormats = ps.s.h.renderFormats
|
|
} else {
|
|
// One of the fixed output format pages, e.g. 404.
|
|
outputFormatsForPage = output.Formats{ps.m.standaloneOutputFormat}
|
|
renderFormats = outputFormatsForPage
|
|
}
|
|
|
|
// Prepare output formats for all sites.
|
|
// We do this even if this page does not get rendered on
|
|
// its own. It may be referenced via one of the site collections etc.
|
|
// it will then need an output format.
|
|
ps.pageOutputs = make([]*pageOutput, len(renderFormats))
|
|
created := make(map[string]*pageOutput)
|
|
shouldRenderPage := !ps.m.noRender()
|
|
|
|
for i, f := range renderFormats {
|
|
|
|
if po, found := created[f.Name]; found {
|
|
ps.pageOutputs[i] = po
|
|
continue
|
|
}
|
|
|
|
render := shouldRenderPage
|
|
if render {
|
|
_, render = outputFormatsForPage.GetByName(f.Name)
|
|
}
|
|
|
|
po := newPageOutput(ps, pp, f, render)
|
|
|
|
// Create a content provider for the first,
|
|
// we may be able to reuse it.
|
|
if i == 0 {
|
|
contentProvider, err := newPageContentOutput(po)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
po.setContentProvider(contentProvider)
|
|
}
|
|
|
|
ps.pageOutputs[i] = po
|
|
created[f.Name] = po
|
|
|
|
}
|
|
|
|
if err := ps.initCommonProviders(pp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, nil
|
|
})
|
|
|
|
return nil
|
|
}
|