mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			924 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			924 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 (
 | ||
| 	"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/constants"
 | ||
| 	"github.com/gohugoio/hugo/common/hugo"
 | ||
| 	"github.com/gohugoio/hugo/common/loggers"
 | ||
| 	"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 {
 | ||
| 	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
 | ||
| 
 | ||
| 	content *cachedContent // The source and the parsed page content.
 | ||
| 
 | ||
| 	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.pageConfig.Params = params
 | ||
| 	m.pageMetaFrontMatter = pageMetaFrontMatter{}
 | ||
| }
 | ||
| 
 | ||
| type pageMetaParams struct {
 | ||
| 	setMetaPostCount          int
 | ||
| 	setMetaPostCascadeChanged bool
 | ||
| 
 | ||
| 	pageConfig *pagemeta.PageConfig
 | ||
| 
 | ||
| 	// These are only set in watch mode.
 | ||
| 	datesOriginal   pagemeta.Dates
 | ||
| 	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 {
 | ||
| 	configuredOutputFormats output.Formats // outputs defined in front matter.
 | ||
| }
 | ||
| 
 | ||
| func (m *pageMetaParams) init(preserveOringal bool) {
 | ||
| 	if preserveOringal {
 | ||
| 		m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
 | ||
| 		m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.pageConfig.Cascade)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Aliases() []string {
 | ||
| 	return p.pageConfig.Aliases
 | ||
| }
 | ||
| 
 | ||
| // Deprecated: use taxonomies.
 | ||
| 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{}
 | ||
| }
 | ||
| 
 | ||
| // Deprecated: use taxonomies.
 | ||
| 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) Date() time.Time {
 | ||
| 	return p.pageConfig.Date
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) PublishDate() time.Time {
 | ||
| 	return p.pageConfig.PublishDate
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Lastmod() time.Time {
 | ||
| 	return p.pageConfig.Lastmod
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) ExpiryDate() time.Time {
 | ||
| 	return p.pageConfig.ExpiryDate
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Description() string {
 | ||
| 	return p.pageConfig.Description
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Lang() string {
 | ||
| 	return p.s.Lang()
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Draft() bool {
 | ||
| 	return p.pageConfig.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.pageConfig.Keywords
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Kind() string {
 | ||
| 	return p.pageConfig.Kind
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Layout() string {
 | ||
| 	return p.pageConfig.Layout
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) LinkTitle() string {
 | ||
| 	if p.pageConfig.LinkTitle != "" {
 | ||
| 		return p.pageConfig.LinkTitle
 | ||
| 	}
 | ||
| 
 | ||
| 	return p.Title()
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Name() string {
 | ||
| 	if p.resourcePath != "" {
 | ||
| 		return p.resourcePath
 | ||
| 	}
 | ||
| 	if p.pageConfig.Kind == kinds.KindTerm {
 | ||
| 		return p.pathInfo.Unnormalized().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.pageConfig.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.pageConfig.Sitemap
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Title() string {
 | ||
| 	return p.pageConfig.Title
 | ||
| }
 | ||
| 
 | ||
| const defaultContentType = "page"
 | ||
| 
 | ||
| func (p *pageMeta) Type() string {
 | ||
| 	if p.pageConfig.Type != "" {
 | ||
| 		return p.pageConfig.Type
 | ||
| 	}
 | ||
| 
 | ||
| 	if sect := p.Section(); sect != "" {
 | ||
| 		return sect
 | ||
| 	}
 | ||
| 
 | ||
| 	return defaultContentType
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) Weight() int {
 | ||
| 	return p.pageConfig.Weight
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf config.AllProvider) error {
 | ||
| 	frontmatter := pi.frontMatter
 | ||
| 	if frontmatter != nil {
 | ||
| 		pcfg := p.pageConfig
 | ||
| 		if pcfg == nil {
 | ||
| 			panic("pageConfig not set")
 | ||
| 		}
 | ||
| 		// Needed for case insensitive fetching of params values
 | ||
| 		maps.PrepareParams(frontmatter)
 | ||
| 		pcfg.Params = frontmatter
 | ||
| 		// Check for any cascade define on itself.
 | ||
| 		if cv, found := frontmatter["cascade"]; found {
 | ||
| 			var err error
 | ||
| 			cascade, err := page.DecodeCascade(logger, cv)
 | ||
| 			if err != nil {
 | ||
| 				return err
 | ||
| 			}
 | ||
| 			pcfg.Cascade = cascade
 | ||
| 		}
 | ||
| 
 | ||
| 		// Look for path, lang and kind, all of which values we need early on.
 | ||
| 		if v, found := frontmatter["path"]; found {
 | ||
| 			pcfg.Path = paths.ToSlashPreserveLeading(cast.ToString(v))
 | ||
| 			pcfg.Params["path"] = pcfg.Path
 | ||
| 		}
 | ||
| 		if v, found := frontmatter["lang"]; found {
 | ||
| 			lang := strings.ToLower(cast.ToString(v))
 | ||
| 			if _, ok := conf.PathParser().LanguageIndex[lang]; ok {
 | ||
| 				pcfg.Lang = lang
 | ||
| 				pcfg.Params["lang"] = pcfg.Lang
 | ||
| 			}
 | ||
| 		}
 | ||
| 		if v, found := frontmatter["kind"]; found {
 | ||
| 			s := cast.ToString(v)
 | ||
| 			if s != "" {
 | ||
| 				pcfg.Kind = kinds.GetKindMain(s)
 | ||
| 				if pcfg.Kind == "" {
 | ||
| 					return fmt.Errorf("unknown kind %q in front matter", s)
 | ||
| 				}
 | ||
| 				pcfg.Params["kind"] = pcfg.Kind
 | ||
| 			}
 | ||
| 		}
 | ||
| 	} else if p.pageMetaParams.pageConfig.Params == nil {
 | ||
| 		p.pageConfig.Params = make(maps.Params)
 | ||
| 	}
 | ||
| 
 | ||
| 	p.pageMetaParams.init(conf.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.pageConfig.Cascade)
 | ||
| 		ps.m.pageConfig.Cascade = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
 | ||
| 
 | ||
| 	}
 | ||
| 
 | ||
| 	// Apply cascades first so they can be overridden later.
 | ||
| 	if cascade != nil {
 | ||
| 		if ps.m.pageConfig.Cascade != nil {
 | ||
| 			for k, v := range cascade {
 | ||
| 				vv, found := ps.m.pageConfig.Cascade[k]
 | ||
| 				if !found {
 | ||
| 					ps.m.pageConfig.Cascade[k] = v
 | ||
| 				} else {
 | ||
| 					// Merge
 | ||
| 					for ck, cv := range v {
 | ||
| 						if _, found := vv[ck]; !found {
 | ||
| 							vv[ck] = cv
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 			cascade = ps.m.pageConfig.Cascade
 | ||
| 		} else {
 | ||
| 			ps.m.pageConfig.Cascade = cascade
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	if cascade == nil {
 | ||
| 		cascade = ps.m.pageConfig.Cascade
 | ||
| 	}
 | ||
| 
 | ||
| 	if ps.m.setMetaPostCount > 1 {
 | ||
| 		ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.Cascade)
 | ||
| 		if !ps.m.setMetaPostCascadeChanged {
 | ||
| 			// No changes, restore any value that may be changed by aggregation.
 | ||
| 			ps.m.pageConfig.Dates = ps.m.datesOriginal
 | ||
| 			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.pageConfig.Params[kk]; !found {
 | ||
| 				ps.m.pageConfig.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.pageConfig.Dates
 | ||
| 
 | ||
| 	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
 | ||
| 	}
 | ||
| 
 | ||
| 	descriptor := &pagemeta.FrontMatterDescriptor{
 | ||
| 		PageConfig:    pm.pageConfig,
 | ||
| 		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)
 | ||
| 	}
 | ||
| 
 | ||
| 	var buildConfig any
 | ||
| 	var isNewBuildKeyword bool
 | ||
| 	if v, ok := pm.pageConfig.Params["_build"]; ok {
 | ||
| 		buildConfig = v
 | ||
| 	} else {
 | ||
| 		buildConfig = pm.pageConfig.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 sitemapSet bool
 | ||
| 
 | ||
| 	pcfg := pm.pageConfig
 | ||
| 
 | ||
| 	params := pcfg.Params
 | ||
| 
 | ||
| 	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 := maps.ToStringMapE(v)
 | ||
| 			if err != nil {
 | ||
| 				return 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 pm.s.frontmatterHandler.IsDateKey(loki) {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		switch loki {
 | ||
| 		case "title":
 | ||
| 			pcfg.Title = cast.ToString(v)
 | ||
| 			params[loki] = pcfg.Title
 | ||
| 		case "linktitle":
 | ||
| 			pcfg.LinkTitle = cast.ToString(v)
 | ||
| 			params[loki] = pcfg.LinkTitle
 | ||
| 		case "summary":
 | ||
| 			pcfg.Summary = cast.ToString(v)
 | ||
| 			params[loki] = pcfg.Summary
 | ||
| 		case "description":
 | ||
| 			pcfg.Description = cast.ToString(v)
 | ||
| 			params[loki] = pcfg.Description
 | ||
| 		case "slug":
 | ||
| 			// Don't start or end with a -
 | ||
| 			pcfg.Slug = strings.Trim(cast.ToString(v), "-")
 | ||
| 			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())
 | ||
| 			}
 | ||
| 			pcfg.URL = url
 | ||
| 			params[loki] = url
 | ||
| 		case "type":
 | ||
| 			pcfg.Type = cast.ToString(v)
 | ||
| 			params[loki] = pcfg.Type
 | ||
| 		case "keywords":
 | ||
| 			pcfg.Keywords = cast.ToStringSlice(v)
 | ||
| 			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)
 | ||
| 			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)
 | ||
| 			}
 | ||
| 			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
 | ||
| 					params[loki] = outFormats
 | ||
| 				}
 | ||
| 			}
 | ||
| 		case "draft":
 | ||
| 			draft = new(bool)
 | ||
| 			*draft = cast.ToBool(v)
 | ||
| 		case "layout":
 | ||
| 			pcfg.Layout = cast.ToString(v)
 | ||
| 			params[loki] = pcfg.Layout
 | ||
| 		case "markup":
 | ||
| 			pcfg.Markup = cast.ToString(v)
 | ||
| 			params[loki] = pcfg.Markup
 | ||
| 		case "weight":
 | ||
| 			pcfg.Weight = cast.ToInt(v)
 | ||
| 			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)
 | ||
| 			}
 | ||
| 			params[loki] = pcfg.Aliases
 | ||
| 		case "sitemap":
 | ||
| 			pcfg.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)
 | ||
| 			}
 | ||
| 			sitemapSet = true
 | ||
| 		case "iscjklanguage":
 | ||
| 			isCJKLanguage = new(bool)
 | ||
| 			*isCJKLanguage = cast.ToBool(v)
 | ||
| 		case "translationkey":
 | ||
| 			pcfg.TranslationKey = cast.ToString(v)
 | ||
| 			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, 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 {
 | ||
| 				pcfg.Resources = 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)
 | ||
| 						}
 | ||
| 						params[loki] = a
 | ||
| 					} else {
 | ||
| 						params[loki] = vv
 | ||
| 					}
 | ||
| 				} else {
 | ||
| 					params[loki] = []string{}
 | ||
| 				}
 | ||
| 
 | ||
| 			default:
 | ||
| 				params[loki] = vv
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	for k, v := range userParams {
 | ||
| 		if _, found := params[k]; found {
 | ||
| 			p.s.Log.Warnidf(constants.WarnFrontMatterParamsOverrides, "Hugo front matter key %q is overridden in params section.", k)
 | ||
| 		}
 | ||
| 		params[strings.ToLower(k)] = v
 | ||
| 	}
 | ||
| 
 | ||
| 	if !sitemapSet {
 | ||
| 		pcfg.Sitemap = p.s.conf.Sitemap
 | ||
| 	}
 | ||
| 
 | ||
| 	pcfg.Markup = p.s.ContentSpec.ResolveMarkup(pcfg.Markup)
 | ||
| 
 | ||
| 	if draft != nil && published != nil {
 | ||
| 		pcfg.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 {
 | ||
| 		pcfg.Draft = *draft
 | ||
| 	} else if published != nil {
 | ||
| 		pcfg.Draft = !*published
 | ||
| 	}
 | ||
| 	params["draft"] = pcfg.Draft
 | ||
| 
 | ||
| 	if isCJKLanguage != nil {
 | ||
| 		pcfg.IsCJKLanguage = *isCJKLanguage
 | ||
| 	} else if p.s.conf.HasCJKLanguage && p.m.content.pi.openSource != nil {
 | ||
| 		if cjkRe.Match(p.m.content.mustSource()) {
 | ||
| 			pcfg.IsCJKLanguage = true
 | ||
| 		} else {
 | ||
| 			pcfg.IsCJKLanguage = false
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	params["iscjklanguage"] = pcfg.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.pageConfig.Build.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.pageConfig.Kind == kinds.KindHome || p.pageConfig.Kind == kinds.KindSection || p.pageConfig.Kind == kinds.KindPage
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) noRender() bool {
 | ||
| 	return p.pageConfig.Build.Render != pagemeta.Always
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) noLink() bool {
 | ||
| 	return p.pageConfig.Build.Render == pagemeta.Never
 | ||
| }
 | ||
| 
 | ||
| func (p *pageMeta) applyDefaultValues() error {
 | ||
| 	if p.pageConfig.Build.IsZero() {
 | ||
| 		p.pageConfig.Build, _ = pagemeta.DecodeBuildConfig(nil)
 | ||
| 	}
 | ||
| 
 | ||
| 	if !p.s.conf.IsKindEnabled(p.Kind()) {
 | ||
| 		(&p.pageConfig.Build).Disable()
 | ||
| 	}
 | ||
| 
 | ||
| 	if p.pageConfig.Markup == "" {
 | ||
| 		if p.File() != nil {
 | ||
| 			// Fall back to file extension
 | ||
| 			p.pageConfig.Markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
 | ||
| 		}
 | ||
| 		if p.pageConfig.Markup == "" {
 | ||
| 			p.pageConfig.Markup = "markdown"
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	if p.pageConfig.Title == "" && p.f == nil {
 | ||
| 		switch p.Kind() {
 | ||
| 		case kinds.KindHome:
 | ||
| 			p.pageConfig.Title = p.s.Title()
 | ||
| 		case kinds.KindSection:
 | ||
| 			sectionName := p.pathInfo.Unnormalized().BaseNameNoIdentifier()
 | ||
| 			if p.s.conf.PluralizeListTitles {
 | ||
| 				sectionName = flect.Pluralize(sectionName)
 | ||
| 			}
 | ||
| 			if p.s.conf.CapitalizeListTitles {
 | ||
| 				sectionName = p.s.conf.C.CreateTitle(sectionName)
 | ||
| 			}
 | ||
| 			p.pageConfig.Title = sectionName
 | ||
| 		case kinds.KindTerm:
 | ||
| 			if p.term != "" {
 | ||
| 				if p.s.conf.CapitalizeListTitles {
 | ||
| 					p.pageConfig.Title = p.s.conf.C.CreateTitle(p.term)
 | ||
| 				} else {
 | ||
| 					p.pageConfig.Title = p.term
 | ||
| 				}
 | ||
| 			} else {
 | ||
| 				panic("term not set")
 | ||
| 			}
 | ||
| 		case kinds.KindTaxonomy:
 | ||
| 			if p.s.conf.CapitalizeListTitles {
 | ||
| 				p.pageConfig.Title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unnormalized().BaseNameNoIdentifier()), "-", " ", -1)
 | ||
| 			} else {
 | ||
| 				p.pageConfig.Title = strings.Replace(p.pathInfo.Unnormalized().BaseNameNoIdentifier(), "-", " ", -1)
 | ||
| 			}
 | ||
| 		case kinds.KindStatus404:
 | ||
| 			p.pageConfig.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, page: %s", markup, ps.getPageInfoForError())
 | ||
| 	}
 | ||
| 
 | ||
| 	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.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)
 | ||
| }
 | ||
| 
 | ||
| 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
 | ||
| }
 |