github.com/SuCicada/su-hugo@v1.0.0/hugolib/page__meta.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugolib
    15  
    16  import (
    17  	"fmt"
    18  	"path"
    19  	"path/filepath"
    20  	"regexp"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/gohugoio/hugo/langs"
    26  
    27  	"github.com/gobuffalo/flect"
    28  	"github.com/gohugoio/hugo/markup/converter"
    29  
    30  	"github.com/gohugoio/hugo/hugofs/files"
    31  
    32  	"github.com/gohugoio/hugo/common/hugo"
    33  
    34  	"github.com/gohugoio/hugo/related"
    35  
    36  	"github.com/gohugoio/hugo/source"
    37  
    38  	"github.com/gohugoio/hugo/common/maps"
    39  	"github.com/gohugoio/hugo/config"
    40  	"github.com/gohugoio/hugo/helpers"
    41  
    42  	"github.com/gohugoio/hugo/output"
    43  	"github.com/gohugoio/hugo/resources/page"
    44  	"github.com/gohugoio/hugo/resources/page/pagemeta"
    45  	"github.com/gohugoio/hugo/resources/resource"
    46  	"github.com/spf13/cast"
    47  )
    48  
    49  var cjkRe = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
    50  
    51  type pageMeta struct {
    52  	// kind is the discriminator that identifies the different page types
    53  	// in the different page collections. This can, as an example, be used
    54  	// to to filter regular pages, find sections etc.
    55  	// Kind will, for the pages available to the templates, be one of:
    56  	// page, home, section, taxonomy and term.
    57  	// It is of string type to make it easy to reason about in
    58  	// the templates.
    59  	kind string
    60  
    61  	// This is a standalone page not part of any page collection. These
    62  	// include sitemap, robotsTXT and similar. It will have no pageOutputs, but
    63  	// a fixed pageOutput.
    64  	standalone bool
    65  
    66  	draft       bool // Only published when running with -D flag
    67  	buildConfig pagemeta.BuildConfig
    68  
    69  	bundleType files.ContentClass
    70  
    71  	// Params contains configuration defined in the params section of page frontmatter.
    72  	params map[string]any
    73  
    74  	title     string
    75  	linkTitle string
    76  
    77  	summary string
    78  
    79  	resourcePath string
    80  
    81  	weight int
    82  
    83  	markup      string
    84  	contentType string
    85  
    86  	// whether the content is in a CJK language.
    87  	isCJKLanguage bool
    88  
    89  	layout string
    90  
    91  	aliases []string
    92  
    93  	description string
    94  	keywords    []string
    95  
    96  	urlPaths pagemeta.URLPath
    97  
    98  	resource.Dates
    99  
   100  	// Set if this page is bundled inside another.
   101  	bundled bool
   102  
   103  	// A key that maps to translation(s) of this page. This value is fetched
   104  	// from the page front matter.
   105  	translationKey string
   106  
   107  	// From front matter.
   108  	configuredOutputFormats output.Formats
   109  
   110  	// This is the raw front matter metadata that is going to be assigned to
   111  	// the Resources above.
   112  	resourcesMetadata []map[string]any
   113  
   114  	f source.File
   115  
   116  	sections []string
   117  
   118  	// Sitemap overrides from front matter.
   119  	sitemap config.Sitemap
   120  
   121  	s *Site
   122  
   123  	contentConverterInit sync.Once
   124  	contentConverter     converter.Converter
   125  }
   126  
   127  func (p *pageMeta) Aliases() []string {
   128  	return p.aliases
   129  }
   130  
   131  func (p *pageMeta) Author() page.Author {
   132  	helpers.Deprecated(".Author", "Use taxonomies.", false)
   133  	authors := p.Authors()
   134  
   135  	for _, author := range authors {
   136  		return author
   137  	}
   138  	return page.Author{}
   139  }
   140  
   141  func (p *pageMeta) Authors() page.AuthorList {
   142  	helpers.Deprecated(".Authors", "Use taxonomies.", false)
   143  	authorKeys, ok := p.params["authors"]
   144  	if !ok {
   145  		return page.AuthorList{}
   146  	}
   147  	authors := authorKeys.([]string)
   148  	if len(authors) < 1 || len(p.s.Info.Authors) < 1 {
   149  		return page.AuthorList{}
   150  	}
   151  
   152  	al := make(page.AuthorList)
   153  	for _, author := range authors {
   154  		a, ok := p.s.Info.Authors[author]
   155  		if ok {
   156  			al[author] = a
   157  		}
   158  	}
   159  	return al
   160  }
   161  
   162  func (p *pageMeta) BundleType() files.ContentClass {
   163  	return p.bundleType
   164  }
   165  
   166  func (p *pageMeta) Description() string {
   167  	return p.description
   168  }
   169  
   170  func (p *pageMeta) Lang() string {
   171  	return p.s.Lang()
   172  }
   173  
   174  func (p *pageMeta) Draft() bool {
   175  	return p.draft
   176  }
   177  
   178  func (p *pageMeta) File() source.File {
   179  	return p.f
   180  }
   181  
   182  func (p *pageMeta) IsHome() bool {
   183  	return p.Kind() == page.KindHome
   184  }
   185  
   186  func (p *pageMeta) Keywords() []string {
   187  	return p.keywords
   188  }
   189  
   190  func (p *pageMeta) Kind() string {
   191  	return p.kind
   192  }
   193  
   194  func (p *pageMeta) Layout() string {
   195  	return p.layout
   196  }
   197  
   198  func (p *pageMeta) LinkTitle() string {
   199  	if p.linkTitle != "" {
   200  		return p.linkTitle
   201  	}
   202  
   203  	return p.Title()
   204  }
   205  
   206  func (p *pageMeta) Name() string {
   207  	if p.resourcePath != "" {
   208  		return p.resourcePath
   209  	}
   210  	return p.Title()
   211  }
   212  
   213  func (p *pageMeta) IsNode() bool {
   214  	return !p.IsPage()
   215  }
   216  
   217  func (p *pageMeta) IsPage() bool {
   218  	return p.Kind() == page.KindPage
   219  }
   220  
   221  // Param is a convenience method to do lookups in Page's and Site's Params map,
   222  // in that order.
   223  //
   224  // This method is also implemented on SiteInfo.
   225  // TODO(bep) interface
   226  func (p *pageMeta) Param(key any) (any, error) {
   227  	return resource.Param(p, p.s.Info.Params(), key)
   228  }
   229  
   230  func (p *pageMeta) Params() maps.Params {
   231  	return p.params
   232  }
   233  
   234  func (p *pageMeta) Path() string {
   235  	if !p.File().IsZero() {
   236  		const example = `
   237    {{ $path := "" }}
   238    {{ with .File }}
   239  	{{ $path = .Path }}
   240    {{ else }}
   241  	{{ $path = .Path }}
   242    {{ end }}
   243  `
   244  		helpers.Deprecated(".Path when the page is backed by a file", "We plan to use Path for a canonical source path and you probably want to check the source is a file. To get the current behaviour, you can use a construct similar to the one below:\n"+example, false)
   245  
   246  	}
   247  
   248  	return p.Pathc()
   249  }
   250  
   251  // This is just a bridge method, use Path in templates.
   252  func (p *pageMeta) Pathc() string {
   253  	if !p.File().IsZero() {
   254  		return p.File().Path()
   255  	}
   256  	return p.SectionsPath()
   257  }
   258  
   259  // RelatedKeywords implements the related.Document interface needed for fast page searches.
   260  func (p *pageMeta) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
   261  	v, err := p.Param(cfg.Name)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	return cfg.ToKeywords(v)
   267  }
   268  
   269  func (p *pageMeta) IsSection() bool {
   270  	return p.Kind() == page.KindSection
   271  }
   272  
   273  func (p *pageMeta) Section() string {
   274  	if p.IsHome() {
   275  		return ""
   276  	}
   277  
   278  	if p.IsNode() {
   279  		if len(p.sections) == 0 {
   280  			// May be a sitemap or similar.
   281  			return ""
   282  		}
   283  		return p.sections[0]
   284  	}
   285  
   286  	if !p.File().IsZero() {
   287  		return p.File().Section()
   288  	}
   289  
   290  	panic("invalid page state")
   291  }
   292  
   293  func (p *pageMeta) SectionsEntries() []string {
   294  	return p.sections
   295  }
   296  
   297  func (p *pageMeta) SectionsPath() string {
   298  	return path.Join(p.SectionsEntries()...)
   299  }
   300  
   301  func (p *pageMeta) Sitemap() config.Sitemap {
   302  	return p.sitemap
   303  }
   304  
   305  func (p *pageMeta) Title() string {
   306  	return p.title
   307  }
   308  
   309  const defaultContentType = "page"
   310  
   311  func (p *pageMeta) Type() string {
   312  	if p.contentType != "" {
   313  		return p.contentType
   314  	}
   315  
   316  	if sect := p.Section(); sect != "" {
   317  		return sect
   318  	}
   319  
   320  	return defaultContentType
   321  }
   322  
   323  func (p *pageMeta) Weight() int {
   324  	return p.weight
   325  }
   326  
   327  func (pm *pageMeta) mergeBucketCascades(b1, b2 *pagesMapBucket) {
   328  	if b1.cascade == nil {
   329  		b1.cascade = make(map[page.PageMatcher]maps.Params)
   330  	}
   331  
   332  	if b2 != nil && b2.cascade != nil {
   333  		for k, v := range b2.cascade {
   334  
   335  			vv, found := b1.cascade[k]
   336  			if !found {
   337  				b1.cascade[k] = v
   338  			} else {
   339  				// Merge
   340  				for ck, cv := range v {
   341  					if _, found := vv[ck]; !found {
   342  						vv[ck] = cv
   343  					}
   344  				}
   345  			}
   346  		}
   347  	}
   348  }
   349  func (pm *pageMeta) setTagsToMetadata(frontmatter map[string]any) {
   350  	defer func() {
   351  		if r := recover(); r != nil {
   352  			fmt.Println("Recovered from error:", r)
   353  		}
   354  	}()
   355  
   356  	file := pm.File()
   357  	if len(file.Dir()) > 0 {
   358  		var dirTagsPath string
   359  		if strings.HasPrefix(file.Dir(), file.Section()) {
   360  			dirTagsPath = file.Dir()[len(file.Section())+1:]
   361  			var dirTags []interface{}
   362  			for _, s := range strings.Split(dirTagsPath, "/") {
   363  				if len(s) > 0 {
   364  					dirTags = append(dirTags, s)
   365  				}
   366  			}
   367  			if dirTags != nil {
   368  				if tags, ok := frontmatter["tags"]; !ok {
   369  					frontmatter["tags"] = dirTags
   370  				} else {
   371  					switch tags.(type) {
   372  					case []interface{}:
   373  						frontmatter["tags"] = append(tags.([]interface{}), dirTags...)
   374  					case interface{}:
   375  						frontmatter["tags"] = []interface{}{tags, dirTags}
   376  					}
   377  				}
   378  				//fmt.Println("frontmatter:", frontmatter)
   379  			}
   380  		}
   381  	}
   382  }
   383  
   384  func (pm *pageMeta) setTitleToMetadata() {
   385  	if pm.title == "" {
   386  		pm.title = pm.File().ContentBaseName()
   387  	}
   388  }
   389  func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, frontmatter map[string]any) error {
   390  	pm.params = make(maps.Params)
   391  
   392  	if frontmatter == nil && (parentBucket == nil || parentBucket.cascade == nil) {
   393  		return nil
   394  	}
   395  
   396  	if frontmatter != nil {
   397  		// Needed for case insensitive fetching of params values
   398  		maps.PrepareParams(frontmatter)
   399  		if p.bucket != nil {
   400  			// Check for any cascade define on itself.
   401  			if cv, found := frontmatter["cascade"]; found {
   402  				var err error
   403  				p.bucket.cascade, err = page.DecodeCascade(cv)
   404  				if err != nil {
   405  					return err
   406  				}
   407  			}
   408  		}
   409  	} else {
   410  		frontmatter = make(map[string]any)
   411  	}
   412  
   413  	var cascade map[page.PageMatcher]maps.Params
   414  
   415  	if p.bucket != nil {
   416  		if parentBucket != nil {
   417  			// Merge missing keys from parent into this.
   418  			pm.mergeBucketCascades(p.bucket, parentBucket)
   419  		}
   420  		cascade = p.bucket.cascade
   421  	} else if parentBucket != nil {
   422  		cascade = parentBucket.cascade
   423  	}
   424  
   425  	for m, v := range cascade {
   426  		if !m.Matches(p) {
   427  			continue
   428  		}
   429  		for kk, vv := range v {
   430  			if _, found := frontmatter[kk]; !found {
   431  				frontmatter[kk] = vv
   432  			}
   433  		}
   434  	}
   435  
   436  	var mtime time.Time
   437  	var contentBaseName string
   438  	if !p.File().IsZero() {
   439  		contentBaseName = p.File().ContentBaseName()
   440  		if p.File().FileInfo() != nil {
   441  			mtime = p.File().FileInfo().ModTime()
   442  		}
   443  	}
   444  
   445  	var gitAuthorDate time.Time
   446  	if p.gitInfo != nil {
   447  		gitAuthorDate = p.gitInfo.AuthorDate
   448  	}
   449  
   450  	descriptor := &pagemeta.FrontMatterDescriptor{
   451  		Frontmatter:   frontmatter,
   452  		Params:        pm.params,
   453  		Dates:         &pm.Dates,
   454  		PageURLs:      &pm.urlPaths,
   455  		BaseFilename:  contentBaseName,
   456  		ModTime:       mtime,
   457  		GitAuthorDate: gitAuthorDate,
   458  		Location:      langs.GetLocation(pm.s.Language()),
   459  	}
   460  
   461  	// Handle the date separately
   462  	// TODO(bep) we need to "do more" in this area so this can be split up and
   463  	// more easily tested without the Page, but the coupling is strong.
   464  	err := pm.s.frontmatterHandler.HandleDates(descriptor)
   465  	if err != nil {
   466  		p.s.Log.Errorf("Failed to handle dates for page %q: %s", p.pathOrTitle(), err)
   467  	}
   468  
   469  	pm.buildConfig, err = pagemeta.DecodeBuildConfig(frontmatter["_build"])
   470  	if err != nil {
   471  		return err
   472  	}
   473  
   474  	var sitemapSet bool
   475  
   476  	var draft, published, isCJKLanguage *bool
   477  
   478  	pm.setTagsToMetadata(frontmatter)
   479  
   480  	for k, v := range frontmatter {
   481  		loki := strings.ToLower(k)
   482  
   483  		if loki == "published" { // Intentionally undocumented
   484  			vv, err := cast.ToBoolE(v)
   485  			if err == nil {
   486  				published = &vv
   487  			}
   488  			// published may also be a date
   489  			continue
   490  		}
   491  
   492  		if pm.s.frontmatterHandler.IsDateKey(loki) {
   493  			continue
   494  		}
   495  
   496  		switch loki {
   497  		case "title":
   498  			pm.title = cast.ToString(v)
   499  			pm.params[loki] = pm.title
   500  		case "linktitle":
   501  			pm.linkTitle = cast.ToString(v)
   502  			pm.params[loki] = pm.linkTitle
   503  		case "summary":
   504  			pm.summary = cast.ToString(v)
   505  			pm.params[loki] = pm.summary
   506  		case "description":
   507  			pm.description = cast.ToString(v)
   508  			pm.params[loki] = pm.description
   509  		case "slug":
   510  			// Don't start or end with a -
   511  			pm.urlPaths.Slug = strings.Trim(cast.ToString(v), "-")
   512  			pm.params[loki] = pm.Slug()
   513  		case "url":
   514  			url := cast.ToString(v)
   515  			if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
   516  				return fmt.Errorf("URLs with protocol (http*) not supported: %q. In page %q", url, p.pathOrTitle())
   517  			}
   518  			lang := p.s.GetLanguagePrefix()
   519  			if lang != "" && !strings.HasPrefix(url, "/") && strings.HasPrefix(url, lang+"/") {
   520  				if strings.HasPrefix(hugo.CurrentVersion.String(), "0.55") {
   521  					// We added support for page relative URLs in Hugo 0.55 and
   522  					// this may get its language path added twice.
   523  					// TODO(bep) eventually remove this.
   524  					p.s.Log.Warnf(`Front matter in %q with the url %q with no leading / has what looks like the language prefix added. In Hugo 0.55 we added support for page relative URLs in front matter, no language prefix needed. Check the URL and consider to either add a leading / or remove the language prefix.`, p.pathOrTitle(), url)
   525  				}
   526  			}
   527  			pm.urlPaths.URL = url
   528  			pm.params[loki] = url
   529  		case "type":
   530  			pm.contentType = cast.ToString(v)
   531  			pm.params[loki] = pm.contentType
   532  		case "keywords":
   533  			pm.keywords = cast.ToStringSlice(v)
   534  			pm.params[loki] = pm.keywords
   535  		case "headless":
   536  			// Legacy setting for leaf bundles.
   537  			// This is since Hugo 0.63 handled in a more general way for all
   538  			// pages.
   539  			isHeadless := cast.ToBool(v)
   540  			pm.params[loki] = isHeadless
   541  			if p.File().TranslationBaseName() == "index" && isHeadless {
   542  				pm.buildConfig.List = pagemeta.Never
   543  				pm.buildConfig.Render = pagemeta.Never
   544  			}
   545  		case "outputs":
   546  			o := cast.ToStringSlice(v)
   547  			if len(o) > 0 {
   548  				// Output formats are explicitly set in front matter, use those.
   549  				outFormats, err := p.s.outputFormatsConfig.GetByNames(o...)
   550  
   551  				if err != nil {
   552  					p.s.Log.Errorf("Failed to resolve output formats: %s", err)
   553  				} else {
   554  					pm.configuredOutputFormats = outFormats
   555  					pm.params[loki] = outFormats
   556  				}
   557  
   558  			}
   559  		case "draft":
   560  			draft = new(bool)
   561  			*draft = cast.ToBool(v)
   562  		case "layout":
   563  			pm.layout = cast.ToString(v)
   564  			pm.params[loki] = pm.layout
   565  		case "markup":
   566  			pm.markup = cast.ToString(v)
   567  			pm.params[loki] = pm.markup
   568  		case "weight":
   569  			pm.weight = cast.ToInt(v)
   570  			pm.params[loki] = pm.weight
   571  		case "aliases":
   572  			pm.aliases = cast.ToStringSlice(v)
   573  			for i, alias := range pm.aliases {
   574  				if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
   575  					return fmt.Errorf("http* aliases not supported: %q", alias)
   576  				}
   577  				pm.aliases[i] = filepath.ToSlash(alias)
   578  			}
   579  			pm.params[loki] = pm.aliases
   580  		case "sitemap":
   581  			p.m.sitemap = config.DecodeSitemap(p.s.siteCfg.sitemap, maps.ToStringMap(v))
   582  			pm.params[loki] = p.m.sitemap
   583  			sitemapSet = true
   584  		case "iscjklanguage":
   585  			isCJKLanguage = new(bool)
   586  			*isCJKLanguage = cast.ToBool(v)
   587  		case "translationkey":
   588  			pm.translationKey = cast.ToString(v)
   589  			pm.params[loki] = pm.translationKey
   590  		case "resources":
   591  			var resources []map[string]any
   592  			handled := true
   593  
   594  			switch vv := v.(type) {
   595  			case []map[any]any:
   596  				for _, vvv := range vv {
   597  					resources = append(resources, maps.ToStringMap(vvv))
   598  				}
   599  			case []map[string]any:
   600  				resources = append(resources, vv...)
   601  			case []any:
   602  				for _, vvv := range vv {
   603  					switch vvvv := vvv.(type) {
   604  					case map[any]any:
   605  						resources = append(resources, maps.ToStringMap(vvvv))
   606  					case map[string]any:
   607  						resources = append(resources, vvvv)
   608  					}
   609  				}
   610  			default:
   611  				handled = false
   612  			}
   613  
   614  			if handled {
   615  				pm.params[loki] = resources
   616  				pm.resourcesMetadata = resources
   617  				break
   618  			}
   619  			fallthrough
   620  
   621  		default:
   622  			// If not one of the explicit values, store in Params
   623  			switch vv := v.(type) {
   624  			case bool:
   625  				pm.params[loki] = vv
   626  			case string:
   627  				pm.params[loki] = vv
   628  			case int64, int32, int16, int8, int:
   629  				pm.params[loki] = vv
   630  			case float64, float32:
   631  				pm.params[loki] = vv
   632  			case time.Time:
   633  				pm.params[loki] = vv
   634  			default: // handle array of strings as well
   635  				switch vvv := vv.(type) {
   636  				case []any:
   637  					if len(vvv) > 0 {
   638  						switch vvv[0].(type) {
   639  						case map[any]any:
   640  							pm.params[loki] = vvv
   641  						case map[string]any:
   642  							pm.params[loki] = vvv
   643  						case []any:
   644  							pm.params[loki] = vvv
   645  						default:
   646  							a := make([]string, len(vvv))
   647  							for i, u := range vvv {
   648  								a[i] = cast.ToString(u)
   649  							}
   650  
   651  							pm.params[loki] = a
   652  						}
   653  					} else {
   654  						pm.params[loki] = []string{}
   655  					}
   656  				default:
   657  					pm.params[loki] = vv
   658  				}
   659  			}
   660  		}
   661  	}
   662  
   663  	// custom metadata settings
   664  	pm.setTitleToMetadata()
   665  
   666  	fmt.Println("frontmatter", frontmatter)
   667  	if !sitemapSet {
   668  		pm.sitemap = p.s.siteCfg.sitemap
   669  	}
   670  
   671  	pm.markup = p.s.ContentSpec.ResolveMarkup(pm.markup)
   672  
   673  	if draft != nil && published != nil {
   674  		pm.draft = *draft
   675  		p.m.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", p.File().Filename())
   676  	} else if draft != nil {
   677  		pm.draft = *draft
   678  	} else if published != nil {
   679  		pm.draft = !*published
   680  	}
   681  	pm.params["draft"] = pm.draft
   682  
   683  	if isCJKLanguage != nil {
   684  		pm.isCJKLanguage = *isCJKLanguage
   685  	} else if p.s.siteCfg.hasCJKLanguage && p.source.parsed != nil {
   686  		if cjkRe.Match(p.source.parsed.Input()) {
   687  			pm.isCJKLanguage = true
   688  		} else {
   689  			pm.isCJKLanguage = false
   690  		}
   691  	}
   692  
   693  	pm.params["iscjklanguage"] = p.m.isCJKLanguage
   694  
   695  	return nil
   696  }
   697  
   698  func (p *pageMeta) noListAlways() bool {
   699  	return p.buildConfig.List != pagemeta.Always
   700  }
   701  
   702  func (p *pageMeta) getListFilter(local bool) contentTreeNodeCallback {
   703  	return newContentTreeFilter(func(n *contentNode) bool {
   704  		if n == nil {
   705  			return true
   706  		}
   707  
   708  		var shouldList bool
   709  		switch n.p.m.buildConfig.List {
   710  		case pagemeta.Always:
   711  			shouldList = true
   712  		case pagemeta.Never:
   713  			shouldList = false
   714  		case pagemeta.ListLocally:
   715  			shouldList = local
   716  		}
   717  
   718  		return !shouldList
   719  	})
   720  }
   721  
   722  func (p *pageMeta) noRender() bool {
   723  	return p.buildConfig.Render != pagemeta.Always
   724  }
   725  
   726  func (p *pageMeta) noLink() bool {
   727  	return p.buildConfig.Render == pagemeta.Never
   728  }
   729  
   730  func (p *pageMeta) applyDefaultValues(n *contentNode) error {
   731  	if p.buildConfig.IsZero() {
   732  		p.buildConfig, _ = pagemeta.DecodeBuildConfig(nil)
   733  	}
   734  
   735  	if !p.s.isEnabled(p.Kind()) {
   736  		(&p.buildConfig).Disable()
   737  	}
   738  
   739  	if p.markup == "" {
   740  		if !p.File().IsZero() {
   741  			// Fall back to file extension
   742  			p.markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
   743  		}
   744  		if p.markup == "" {
   745  			p.markup = "markdown"
   746  		}
   747  	}
   748  
   749  	if p.title == "" && p.f.IsZero() {
   750  		switch p.Kind() {
   751  		case page.KindHome:
   752  			p.title = p.s.Info.title
   753  		case page.KindSection:
   754  			var sectionName string
   755  			if n != nil {
   756  				sectionName = n.rootSection()
   757  			} else {
   758  				sectionName = p.sections[0]
   759  			}
   760  
   761  			sectionName = helpers.FirstUpper(sectionName)
   762  			if p.s.Cfg.GetBool("pluralizeListTitles") {
   763  				p.title = flect.Pluralize(sectionName)
   764  			} else {
   765  				p.title = sectionName
   766  			}
   767  		case page.KindTerm:
   768  			// TODO(bep) improve
   769  			key := p.sections[len(p.sections)-1]
   770  			p.title = strings.Replace(p.s.titleFunc(key), "-", " ", -1)
   771  		case page.KindTaxonomy:
   772  			p.title = p.s.titleFunc(p.sections[0])
   773  		case kind404:
   774  			p.title = "404 Page not found"
   775  
   776  		}
   777  	}
   778  
   779  	if p.IsNode() {
   780  		p.bundleType = files.ContentClassBranch
   781  	} else {
   782  		source := p.File()
   783  		if fi, ok := source.(*fileInfo); ok {
   784  			class := fi.FileInfo().Meta().Classifier
   785  			switch class {
   786  			case files.ContentClassBranch, files.ContentClassLeaf:
   787  				p.bundleType = class
   788  			}
   789  		}
   790  	}
   791  
   792  	return nil
   793  }
   794  
   795  func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.Converter, error) {
   796  	if ps == nil {
   797  		panic("no Page provided")
   798  	}
   799  	cp := p.s.ContentSpec.Converters.Get(markup)
   800  	if cp == nil {
   801  		return converter.NopConverter, fmt.Errorf("no content renderer found for markup %q", p.markup)
   802  	}
   803  
   804  	var id string
   805  	var filename string
   806  	var path string
   807  	if !p.f.IsZero() {
   808  		id = p.f.UniqueID()
   809  		filename = p.f.Filename()
   810  		path = p.f.Path()
   811  	} else {
   812  		path = p.Pathc()
   813  	}
   814  
   815  	cpp, err := cp.New(
   816  		converter.DocumentContext{
   817  			Document:     newPageForRenderHook(ps),
   818  			DocumentID:   id,
   819  			DocumentName: path,
   820  			Filename:     filename,
   821  		},
   822  	)
   823  	if err != nil {
   824  		return converter.NopConverter, err
   825  	}
   826  
   827  	return cpp, nil
   828  }
   829  
   830  // The output formats this page will be rendered to.
   831  func (m *pageMeta) outputFormats() output.Formats {
   832  	if len(m.configuredOutputFormats) > 0 {
   833  		return m.configuredOutputFormats
   834  	}
   835  
   836  	return m.s.outputFormats[m.Kind()]
   837  }
   838  
   839  func (p *pageMeta) Slug() string {
   840  	return p.urlPaths.Slug
   841  }
   842  
   843  func getParam(m resource.ResourceParamsProvider, key string, stringToLower bool) any {
   844  	v := m.Params()[strings.ToLower(key)]
   845  
   846  	if v == nil {
   847  		return nil
   848  	}
   849  
   850  	switch val := v.(type) {
   851  	case bool:
   852  		return val
   853  	case string:
   854  		if stringToLower {
   855  			return strings.ToLower(val)
   856  		}
   857  		return val
   858  	case int64, int32, int16, int8, int:
   859  		return cast.ToInt(v)
   860  	case float64, float32:
   861  		return cast.ToFloat64(v)
   862  	case time.Time:
   863  		return val
   864  	case []string:
   865  		if stringToLower {
   866  			return helpers.SliceToLower(val)
   867  		}
   868  		return v
   869  	default:
   870  		return v
   871  	}
   872  }
   873  
   874  func getParamToLower(m resource.ResourceParamsProvider, key string) any {
   875  	return getParam(m, key, true)
   876  }