github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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  	"github.com/pkg/errors"
    38  
    39  	"github.com/gohugoio/hugo/common/maps"
    40  	"github.com/gohugoio/hugo/config"
    41  	"github.com/gohugoio/hugo/helpers"
    42  
    43  	"github.com/gohugoio/hugo/output"
    44  	"github.com/gohugoio/hugo/resources/page"
    45  	"github.com/gohugoio/hugo/resources/page/pagemeta"
    46  	"github.com/gohugoio/hugo/resources/resource"
    47  	"github.com/spf13/cast"
    48  )
    49  
    50  var cjkRe = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
    51  
    52  type pageMeta struct {
    53  	// kind is the discriminator that identifies the different page types
    54  	// in the different page collections. This can, as an example, be used
    55  	// to to filter regular pages, find sections etc.
    56  	// Kind will, for the pages available to the templates, be one of:
    57  	// page, home, section, taxonomy and term.
    58  	// It is of string type to make it easy to reason about in
    59  	// the templates.
    60  	kind string
    61  
    62  	// This is a standalone page not part of any page collection. These
    63  	// include sitemap, robotsTXT and similar. It will have no pageOutputs, but
    64  	// a fixed pageOutput.
    65  	standalone bool
    66  
    67  	draft       bool // Only published when running with -D flag
    68  	buildConfig pagemeta.BuildConfig
    69  
    70  	bundleType files.ContentClass
    71  
    72  	// Params contains configuration defined in the params section of page frontmatter.
    73  	params map[string]interface{}
    74  
    75  	title     string
    76  	linkTitle string
    77  
    78  	summary string
    79  
    80  	resourcePath string
    81  
    82  	weight int
    83  
    84  	markup      string
    85  	contentType string
    86  
    87  	// whether the content is in a CJK language.
    88  	isCJKLanguage bool
    89  
    90  	layout string
    91  
    92  	aliases []string
    93  
    94  	description string
    95  	keywords    []string
    96  
    97  	urlPaths pagemeta.URLPath
    98  
    99  	resource.Dates
   100  
   101  	// Set if this page is bundled inside another.
   102  	bundled bool
   103  
   104  	// A key that maps to translation(s) of this page. This value is fetched
   105  	// from the page front matter.
   106  	translationKey string
   107  
   108  	// From front matter.
   109  	configuredOutputFormats output.Formats
   110  
   111  	// This is the raw front matter metadata that is going to be assigned to
   112  	// the Resources above.
   113  	resourcesMetadata []map[string]interface{}
   114  
   115  	f source.File
   116  
   117  	sections []string
   118  
   119  	// Sitemap overrides from front matter.
   120  	sitemap config.Sitemap
   121  
   122  	s *Site
   123  
   124  	renderingConfigOverrides map[string]interface{}
   125  	contentConverterInit     sync.Once
   126  	contentConverter         converter.Converter
   127  }
   128  
   129  func (p *pageMeta) Aliases() []string {
   130  	return p.aliases
   131  }
   132  
   133  func (p *pageMeta) Author() page.Author {
   134  	authors := p.Authors()
   135  
   136  	for _, author := range authors {
   137  		return author
   138  	}
   139  	return page.Author{}
   140  }
   141  
   142  func (p *pageMeta) Authors() page.AuthorList {
   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 interface{}) (interface{}, 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  		return p.File().Path()
   237  	}
   238  	return p.SectionsPath()
   239  }
   240  
   241  // RelatedKeywords implements the related.Document interface needed for fast page searches.
   242  func (p *pageMeta) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
   243  	v, err := p.Param(cfg.Name)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	return cfg.ToKeywords(v)
   249  }
   250  
   251  func (p *pageMeta) IsSection() bool {
   252  	return p.Kind() == page.KindSection
   253  }
   254  
   255  func (p *pageMeta) Section() string {
   256  	if p.IsHome() {
   257  		return ""
   258  	}
   259  
   260  	if p.IsNode() {
   261  		if len(p.sections) == 0 {
   262  			// May be a sitemap or similar.
   263  			return ""
   264  		}
   265  		return p.sections[0]
   266  	}
   267  
   268  	if !p.File().IsZero() {
   269  		return p.File().Section()
   270  	}
   271  
   272  	panic("invalid page state")
   273  }
   274  
   275  func (p *pageMeta) SectionsEntries() []string {
   276  	return p.sections
   277  }
   278  
   279  func (p *pageMeta) SectionsPath() string {
   280  	return path.Join(p.SectionsEntries()...)
   281  }
   282  
   283  func (p *pageMeta) Sitemap() config.Sitemap {
   284  	return p.sitemap
   285  }
   286  
   287  func (p *pageMeta) Title() string {
   288  	return p.title
   289  }
   290  
   291  const defaultContentType = "page"
   292  
   293  func (p *pageMeta) Type() string {
   294  	if p.contentType != "" {
   295  		return p.contentType
   296  	}
   297  
   298  	if sect := p.Section(); sect != "" {
   299  		return sect
   300  	}
   301  
   302  	return defaultContentType
   303  }
   304  
   305  func (p *pageMeta) Weight() int {
   306  	return p.weight
   307  }
   308  
   309  func (pm *pageMeta) mergeBucketCascades(b1, b2 *pagesMapBucket) {
   310  	if b1.cascade == nil {
   311  		b1.cascade = make(map[page.PageMatcher]maps.Params)
   312  	}
   313  
   314  	if b2 != nil && b2.cascade != nil {
   315  		for k, v := range b2.cascade {
   316  
   317  			vv, found := b1.cascade[k]
   318  			if !found {
   319  				b1.cascade[k] = v
   320  			} else {
   321  				// Merge
   322  				for ck, cv := range v {
   323  					if _, found := vv[ck]; !found {
   324  						vv[ck] = cv
   325  					}
   326  				}
   327  			}
   328  		}
   329  	}
   330  }
   331  
   332  func (pm *pageMeta) setMetadata(parentBucket *pagesMapBucket, p *pageState, frontmatter map[string]interface{}) error {
   333  	pm.params = make(maps.Params)
   334  
   335  	if frontmatter == nil && (parentBucket == nil || parentBucket.cascade == nil) {
   336  		return nil
   337  	}
   338  
   339  	if frontmatter != nil {
   340  		// Needed for case insensitive fetching of params values
   341  		maps.PrepareParams(frontmatter)
   342  		if p.bucket != nil {
   343  			// Check for any cascade define on itself.
   344  			if cv, found := frontmatter["cascade"]; found {
   345  				var err error
   346  				p.bucket.cascade, err = page.DecodeCascade(cv)
   347  				if err != nil {
   348  					return err
   349  				}
   350  			}
   351  		}
   352  	} else {
   353  		frontmatter = make(map[string]interface{})
   354  	}
   355  
   356  	var cascade map[page.PageMatcher]maps.Params
   357  
   358  	if p.bucket != nil {
   359  		if parentBucket != nil {
   360  			// Merge missing keys from parent into this.
   361  			pm.mergeBucketCascades(p.bucket, parentBucket)
   362  		}
   363  		cascade = p.bucket.cascade
   364  	} else if parentBucket != nil {
   365  		cascade = parentBucket.cascade
   366  	}
   367  
   368  	for m, v := range cascade {
   369  		if !m.Matches(p) {
   370  			continue
   371  		}
   372  		for kk, vv := range v {
   373  			if _, found := frontmatter[kk]; !found {
   374  				frontmatter[kk] = vv
   375  			}
   376  		}
   377  	}
   378  
   379  	var mtime time.Time
   380  	var contentBaseName string
   381  	if !p.File().IsZero() {
   382  		contentBaseName = p.File().ContentBaseName()
   383  		if p.File().FileInfo() != nil {
   384  			mtime = p.File().FileInfo().ModTime()
   385  		}
   386  	}
   387  
   388  	var gitAuthorDate time.Time
   389  	if p.gitInfo != nil {
   390  		gitAuthorDate = p.gitInfo.AuthorDate
   391  	}
   392  
   393  	descriptor := &pagemeta.FrontMatterDescriptor{
   394  		Frontmatter:   frontmatter,
   395  		Params:        pm.params,
   396  		Dates:         &pm.Dates,
   397  		PageURLs:      &pm.urlPaths,
   398  		BaseFilename:  contentBaseName,
   399  		ModTime:       mtime,
   400  		GitAuthorDate: gitAuthorDate,
   401  		Location:      langs.GetLocation(pm.s.Language()),
   402  	}
   403  
   404  	// Handle the date separately
   405  	// TODO(bep) we need to "do more" in this area so this can be split up and
   406  	// more easily tested without the Page, but the coupling is strong.
   407  	err := pm.s.frontmatterHandler.HandleDates(descriptor)
   408  	if err != nil {
   409  		p.s.Log.Errorf("Failed to handle dates for page %q: %s", p.pathOrTitle(), err)
   410  	}
   411  
   412  	pm.buildConfig, err = pagemeta.DecodeBuildConfig(frontmatter["_build"])
   413  	if err != nil {
   414  		return err
   415  	}
   416  
   417  	var sitemapSet bool
   418  
   419  	var draft, published, isCJKLanguage *bool
   420  	for k, v := range frontmatter {
   421  		loki := strings.ToLower(k)
   422  
   423  		if loki == "published" { // Intentionally undocumented
   424  			vv, err := cast.ToBoolE(v)
   425  			if err == nil {
   426  				published = &vv
   427  			}
   428  			// published may also be a date
   429  			continue
   430  		}
   431  
   432  		if pm.s.frontmatterHandler.IsDateKey(loki) {
   433  			continue
   434  		}
   435  
   436  		switch loki {
   437  		case "title":
   438  			pm.title = cast.ToString(v)
   439  			pm.params[loki] = pm.title
   440  		case "linktitle":
   441  			pm.linkTitle = cast.ToString(v)
   442  			pm.params[loki] = pm.linkTitle
   443  		case "summary":
   444  			pm.summary = cast.ToString(v)
   445  			pm.params[loki] = pm.summary
   446  		case "description":
   447  			pm.description = cast.ToString(v)
   448  			pm.params[loki] = pm.description
   449  		case "slug":
   450  			// Don't start or end with a -
   451  			pm.urlPaths.Slug = strings.Trim(cast.ToString(v), "-")
   452  			pm.params[loki] = pm.Slug()
   453  		case "url":
   454  			url := cast.ToString(v)
   455  			if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
   456  				return fmt.Errorf("URLs with protocol (http*) not supported: %q. In page %q", url, p.pathOrTitle())
   457  			}
   458  			lang := p.s.GetLanguagePrefix()
   459  			if lang != "" && !strings.HasPrefix(url, "/") && strings.HasPrefix(url, lang+"/") {
   460  				if strings.HasPrefix(hugo.CurrentVersion.String(), "0.55") {
   461  					// We added support for page relative URLs in Hugo 0.55 and
   462  					// this may get its language path added twice.
   463  					// TODO(bep) eventually remove this.
   464  					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)
   465  				}
   466  			}
   467  			pm.urlPaths.URL = url
   468  			pm.params[loki] = url
   469  		case "type":
   470  			pm.contentType = cast.ToString(v)
   471  			pm.params[loki] = pm.contentType
   472  		case "keywords":
   473  			pm.keywords = cast.ToStringSlice(v)
   474  			pm.params[loki] = pm.keywords
   475  		case "headless":
   476  			// Legacy setting for leaf bundles.
   477  			// This is since Hugo 0.63 handled in a more general way for all
   478  			// pages.
   479  			isHeadless := cast.ToBool(v)
   480  			pm.params[loki] = isHeadless
   481  			if p.File().TranslationBaseName() == "index" && isHeadless {
   482  				pm.buildConfig.List = pagemeta.Never
   483  				pm.buildConfig.Render = pagemeta.Never
   484  			}
   485  		case "outputs":
   486  			o := cast.ToStringSlice(v)
   487  			if len(o) > 0 {
   488  				// Output formats are explicitly set in front matter, use those.
   489  				outFormats, err := p.s.outputFormatsConfig.GetByNames(o...)
   490  
   491  				if err != nil {
   492  					p.s.Log.Errorf("Failed to resolve output formats: %s", err)
   493  				} else {
   494  					pm.configuredOutputFormats = outFormats
   495  					pm.params[loki] = outFormats
   496  				}
   497  
   498  			}
   499  		case "draft":
   500  			draft = new(bool)
   501  			*draft = cast.ToBool(v)
   502  		case "layout":
   503  			pm.layout = cast.ToString(v)
   504  			pm.params[loki] = pm.layout
   505  		case "markup":
   506  			pm.markup = cast.ToString(v)
   507  			pm.params[loki] = pm.markup
   508  		case "weight":
   509  			pm.weight = cast.ToInt(v)
   510  			pm.params[loki] = pm.weight
   511  		case "aliases":
   512  			pm.aliases = cast.ToStringSlice(v)
   513  			for i, alias := range pm.aliases {
   514  				if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
   515  					return fmt.Errorf("http* aliases not supported: %q", alias)
   516  				}
   517  				pm.aliases[i] = filepath.ToSlash(alias)
   518  			}
   519  			pm.params[loki] = pm.aliases
   520  		case "sitemap":
   521  			p.m.sitemap = config.DecodeSitemap(p.s.siteCfg.sitemap, maps.ToStringMap(v))
   522  			pm.params[loki] = p.m.sitemap
   523  			sitemapSet = true
   524  		case "iscjklanguage":
   525  			isCJKLanguage = new(bool)
   526  			*isCJKLanguage = cast.ToBool(v)
   527  		case "translationkey":
   528  			pm.translationKey = cast.ToString(v)
   529  			pm.params[loki] = pm.translationKey
   530  		case "resources":
   531  			var resources []map[string]interface{}
   532  			handled := true
   533  
   534  			switch vv := v.(type) {
   535  			case []map[interface{}]interface{}:
   536  				for _, vvv := range vv {
   537  					resources = append(resources, maps.ToStringMap(vvv))
   538  				}
   539  			case []map[string]interface{}:
   540  				resources = append(resources, vv...)
   541  			case []interface{}:
   542  				for _, vvv := range vv {
   543  					switch vvvv := vvv.(type) {
   544  					case map[interface{}]interface{}:
   545  						resources = append(resources, maps.ToStringMap(vvvv))
   546  					case map[string]interface{}:
   547  						resources = append(resources, vvvv)
   548  					}
   549  				}
   550  			default:
   551  				handled = false
   552  			}
   553  
   554  			if handled {
   555  				pm.params[loki] = resources
   556  				pm.resourcesMetadata = resources
   557  				break
   558  			}
   559  			fallthrough
   560  
   561  		default:
   562  			// If not one of the explicit values, store in Params
   563  			switch vv := v.(type) {
   564  			case bool:
   565  				pm.params[loki] = vv
   566  			case string:
   567  				pm.params[loki] = vv
   568  			case int64, int32, int16, int8, int:
   569  				pm.params[loki] = vv
   570  			case float64, float32:
   571  				pm.params[loki] = vv
   572  			case time.Time:
   573  				pm.params[loki] = vv
   574  			default: // handle array of strings as well
   575  				switch vvv := vv.(type) {
   576  				case []interface{}:
   577  					if len(vvv) > 0 {
   578  						switch vvv[0].(type) {
   579  						case map[interface{}]interface{}: // Proper parsing structured array from YAML based FrontMatter
   580  							pm.params[loki] = vvv
   581  						case map[string]interface{}: // Proper parsing structured array from JSON based FrontMatter
   582  							pm.params[loki] = vvv
   583  						case []interface{}:
   584  							pm.params[loki] = vvv
   585  						default:
   586  							a := make([]string, len(vvv))
   587  							for i, u := range vvv {
   588  								a[i] = cast.ToString(u)
   589  							}
   590  
   591  							pm.params[loki] = a
   592  						}
   593  					} else {
   594  						pm.params[loki] = []string{}
   595  					}
   596  				default:
   597  					pm.params[loki] = vv
   598  				}
   599  			}
   600  		}
   601  	}
   602  
   603  	if !sitemapSet {
   604  		pm.sitemap = p.s.siteCfg.sitemap
   605  	}
   606  
   607  	pm.markup = p.s.ContentSpec.ResolveMarkup(pm.markup)
   608  
   609  	if draft != nil && published != nil {
   610  		pm.draft = *draft
   611  		p.m.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", p.File().Filename())
   612  	} else if draft != nil {
   613  		pm.draft = *draft
   614  	} else if published != nil {
   615  		pm.draft = !*published
   616  	}
   617  	pm.params["draft"] = pm.draft
   618  
   619  	if isCJKLanguage != nil {
   620  		pm.isCJKLanguage = *isCJKLanguage
   621  	} else if p.s.siteCfg.hasCJKLanguage && p.source.parsed != nil {
   622  		if cjkRe.Match(p.source.parsed.Input()) {
   623  			pm.isCJKLanguage = true
   624  		} else {
   625  			pm.isCJKLanguage = false
   626  		}
   627  	}
   628  
   629  	pm.params["iscjklanguage"] = p.m.isCJKLanguage
   630  
   631  	return nil
   632  }
   633  
   634  func (p *pageMeta) noListAlways() bool {
   635  	return p.buildConfig.List != pagemeta.Always
   636  }
   637  
   638  func (p *pageMeta) getListFilter(local bool) contentTreeNodeCallback {
   639  	return newContentTreeFilter(func(n *contentNode) bool {
   640  		if n == nil {
   641  			return true
   642  		}
   643  
   644  		var shouldList bool
   645  		switch n.p.m.buildConfig.List {
   646  		case pagemeta.Always:
   647  			shouldList = true
   648  		case pagemeta.Never:
   649  			shouldList = false
   650  		case pagemeta.ListLocally:
   651  			shouldList = local
   652  		}
   653  
   654  		return !shouldList
   655  	})
   656  }
   657  
   658  func (p *pageMeta) noRender() bool {
   659  	return p.buildConfig.Render != pagemeta.Always
   660  }
   661  
   662  func (p *pageMeta) noLink() bool {
   663  	return p.buildConfig.Render == pagemeta.Never
   664  }
   665  
   666  func (p *pageMeta) applyDefaultValues(n *contentNode) error {
   667  	if p.buildConfig.IsZero() {
   668  		p.buildConfig, _ = pagemeta.DecodeBuildConfig(nil)
   669  	}
   670  
   671  	if !p.s.isEnabled(p.Kind()) {
   672  		(&p.buildConfig).Disable()
   673  	}
   674  
   675  	if p.markup == "" {
   676  		if !p.File().IsZero() {
   677  			// Fall back to file extension
   678  			p.markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
   679  		}
   680  		if p.markup == "" {
   681  			p.markup = "markdown"
   682  		}
   683  	}
   684  
   685  	if p.title == "" && p.f.IsZero() {
   686  		switch p.Kind() {
   687  		case page.KindHome:
   688  			p.title = p.s.Info.title
   689  		case page.KindSection:
   690  			var sectionName string
   691  			if n != nil {
   692  				sectionName = n.rootSection()
   693  			} else {
   694  				sectionName = p.sections[0]
   695  			}
   696  
   697  			sectionName = helpers.FirstUpper(sectionName)
   698  			if p.s.Cfg.GetBool("pluralizeListTitles") {
   699  				p.title = flect.Pluralize(sectionName)
   700  			} else {
   701  				p.title = sectionName
   702  			}
   703  		case page.KindTerm:
   704  			// TODO(bep) improve
   705  			key := p.sections[len(p.sections)-1]
   706  			p.title = strings.Replace(p.s.titleFunc(key), "-", " ", -1)
   707  		case page.KindTaxonomy:
   708  			p.title = p.s.titleFunc(p.sections[0])
   709  		case kind404:
   710  			p.title = "404 Page not found"
   711  
   712  		}
   713  	}
   714  
   715  	if p.IsNode() {
   716  		p.bundleType = files.ContentClassBranch
   717  	} else {
   718  		source := p.File()
   719  		if fi, ok := source.(*fileInfo); ok {
   720  			class := fi.FileInfo().Meta().Classifier
   721  			switch class {
   722  			case files.ContentClassBranch, files.ContentClassLeaf:
   723  				p.bundleType = class
   724  			}
   725  		}
   726  	}
   727  
   728  	if !p.f.IsZero() {
   729  		var renderingConfigOverrides map[string]interface{}
   730  		bfParam := getParamToLower(p, "blackfriday")
   731  		if bfParam != nil {
   732  			renderingConfigOverrides = maps.ToStringMap(bfParam)
   733  		}
   734  
   735  		p.renderingConfigOverrides = renderingConfigOverrides
   736  
   737  	}
   738  
   739  	return nil
   740  }
   741  
   742  func (p *pageMeta) newContentConverter(ps *pageState, markup string, renderingConfigOverrides map[string]interface{}) (converter.Converter, error) {
   743  	if ps == nil {
   744  		panic("no Page provided")
   745  	}
   746  	cp := p.s.ContentSpec.Converters.Get(markup)
   747  	if cp == nil {
   748  		return converter.NopConverter, errors.Errorf("no content renderer found for markup %q", p.markup)
   749  	}
   750  
   751  	var id string
   752  	var filename string
   753  	if !p.f.IsZero() {
   754  		id = p.f.UniqueID()
   755  		filename = p.f.Filename()
   756  	}
   757  
   758  	cpp, err := cp.New(
   759  		converter.DocumentContext{
   760  			Document:        newPageForRenderHook(ps),
   761  			DocumentID:      id,
   762  			DocumentName:    p.Path(),
   763  			Filename:        filename,
   764  			ConfigOverrides: renderingConfigOverrides,
   765  		},
   766  	)
   767  	if err != nil {
   768  		return converter.NopConverter, err
   769  	}
   770  
   771  	return cpp, nil
   772  }
   773  
   774  // The output formats this page will be rendered to.
   775  func (m *pageMeta) outputFormats() output.Formats {
   776  	if len(m.configuredOutputFormats) > 0 {
   777  		return m.configuredOutputFormats
   778  	}
   779  
   780  	return m.s.outputFormats[m.Kind()]
   781  }
   782  
   783  func (p *pageMeta) Slug() string {
   784  	return p.urlPaths.Slug
   785  }
   786  
   787  func getParam(m resource.ResourceParamsProvider, key string, stringToLower bool) interface{} {
   788  	v := m.Params()[strings.ToLower(key)]
   789  
   790  	if v == nil {
   791  		return nil
   792  	}
   793  
   794  	switch val := v.(type) {
   795  	case bool:
   796  		return val
   797  	case string:
   798  		if stringToLower {
   799  			return strings.ToLower(val)
   800  		}
   801  		return val
   802  	case int64, int32, int16, int8, int:
   803  		return cast.ToInt(v)
   804  	case float64, float32:
   805  		return cast.ToFloat64(v)
   806  	case time.Time:
   807  		return val
   808  	case []string:
   809  		if stringToLower {
   810  			return helpers.SliceToLower(val)
   811  		}
   812  		return v
   813  	default:
   814  		return v
   815  	}
   816  }
   817  
   818  func getParamToLower(m resource.ResourceParamsProvider, key string) interface{} {
   819  	return getParam(m, key, true)
   820  }