page__meta.go - hugo - [fork] hugo port for 9front
HTML git clone https://git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
page__meta.go (23601B)
---
1 // Copyright 2024 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 "context"
18 "fmt"
19 "path/filepath"
20 "regexp"
21 "strings"
22 "time"
23
24 "github.com/bep/logg"
25 "github.com/gobuffalo/flect"
26 "github.com/gohugoio/hugo/langs"
27 "github.com/gohugoio/hugo/markup/converter"
28 xmaps "golang.org/x/exp/maps"
29
30 "github.com/gohugoio/hugo/source"
31
32 "github.com/gohugoio/hugo/common/hashing"
33 "github.com/gohugoio/hugo/common/hugo"
34 "github.com/gohugoio/hugo/common/loggers"
35 "github.com/gohugoio/hugo/common/maps"
36 "github.com/gohugoio/hugo/common/paths"
37 "github.com/gohugoio/hugo/config"
38 "github.com/gohugoio/hugo/helpers"
39
40 "github.com/gohugoio/hugo/output"
41 "github.com/gohugoio/hugo/resources/kinds"
42 "github.com/gohugoio/hugo/resources/page"
43 "github.com/gohugoio/hugo/resources/page/pagemeta"
44 "github.com/gohugoio/hugo/resources/resource"
45 "github.com/spf13/cast"
46 )
47
48 var cjkRe = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
49
50 type pageMeta struct {
51 term string // Set for kind == KindTerm.
52 singular string // Set for kind == KindTerm and kind == KindTaxonomy.
53
54 resource.Staler
55 *pageMetaParams
56
57 // Set for standalone pages, e.g. robotsTXT.
58 standaloneOutputFormat output.Format
59
60 resourcePath string // Set for bundled pages; path relative to its bundle root.
61 bundled bool // Set if this page is bundled inside another.
62
63 pathInfo *paths.Path // Always set. This the canonical path to the Page.
64 f *source.File
65
66 content *cachedContent // The source and the parsed page content.
67
68 s *Site // The site this page belongs to.
69 }
70
71 // Prepare for a rebuild of the data passed in from front matter.
72 func (m *pageMeta) setMetaPostPrepareRebuild() {
73 params := xmaps.Clone(m.paramsOriginal)
74 m.pageMetaParams.pageConfig = pagemeta.ClonePageConfigForRebuild(m.pageMetaParams.pageConfig, params)
75 }
76
77 type pageMetaParams struct {
78 setMetaPostCount int
79 setMetaPostCascadeChanged bool
80
81 pageConfig *pagemeta.PageConfig
82
83 // These are only set in watch mode.
84 datesOriginal pagemeta.Dates
85 paramsOriginal map[string]any // contains the original params as defined in the front matter.
86 cascadeOriginal *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig] // contains the original cascade as defined in the front matter.
87 }
88
89 func (m *pageMetaParams) init(preserveOriginal bool) {
90 if preserveOriginal {
91 if m.pageConfig.IsFromContentAdapter {
92 m.paramsOriginal = xmaps.Clone(m.pageConfig.ContentAdapterData)
93 } else {
94 m.paramsOriginal = xmaps.Clone(m.pageConfig.Params)
95 }
96 m.cascadeOriginal = m.pageConfig.CascadeCompiled.Clone()
97 }
98 }
99
100 func (p *pageMeta) Aliases() []string {
101 return p.pageConfig.Aliases
102 }
103
104 func (p *pageMeta) BundleType() string {
105 switch p.pathInfo.Type() {
106 case paths.TypeLeaf:
107 return "leaf"
108 case paths.TypeBranch:
109 return "branch"
110 default:
111 return ""
112 }
113 }
114
115 func (p *pageMeta) Date() time.Time {
116 return p.pageConfig.Dates.Date
117 }
118
119 func (p *pageMeta) PublishDate() time.Time {
120 return p.pageConfig.Dates.PublishDate
121 }
122
123 func (p *pageMeta) Lastmod() time.Time {
124 return p.pageConfig.Dates.Lastmod
125 }
126
127 func (p *pageMeta) ExpiryDate() time.Time {
128 return p.pageConfig.Dates.ExpiryDate
129 }
130
131 func (p *pageMeta) Description() string {
132 return p.pageConfig.Description
133 }
134
135 func (p *pageMeta) Lang() string {
136 return p.s.Lang()
137 }
138
139 func (p *pageMeta) Draft() bool {
140 return p.pageConfig.Draft
141 }
142
143 func (p *pageMeta) File() *source.File {
144 return p.f
145 }
146
147 func (p *pageMeta) IsHome() bool {
148 return p.Kind() == kinds.KindHome
149 }
150
151 func (p *pageMeta) Keywords() []string {
152 return p.pageConfig.Keywords
153 }
154
155 func (p *pageMeta) Kind() string {
156 return p.pageConfig.Kind
157 }
158
159 func (p *pageMeta) Layout() string {
160 return p.pageConfig.Layout
161 }
162
163 func (p *pageMeta) LinkTitle() string {
164 if p.pageConfig.LinkTitle != "" {
165 return p.pageConfig.LinkTitle
166 }
167
168 return p.Title()
169 }
170
171 func (p *pageMeta) Name() string {
172 if p.resourcePath != "" {
173 return p.resourcePath
174 }
175 if p.pageConfig.Kind == kinds.KindTerm {
176 return p.pathInfo.Unnormalized().BaseNameNoIdentifier()
177 }
178 return p.Title()
179 }
180
181 func (p *pageMeta) IsNode() bool {
182 return !p.IsPage()
183 }
184
185 func (p *pageMeta) IsPage() bool {
186 return p.Kind() == kinds.KindPage
187 }
188
189 // Param is a convenience method to do lookups in Page's and Site's Params map,
190 // in that order.
191 //
192 // This method is also implemented on SiteInfo.
193 // TODO(bep) interface
194 func (p *pageMeta) Param(key any) (any, error) {
195 return resource.Param(p, p.s.Params(), key)
196 }
197
198 func (p *pageMeta) Params() maps.Params {
199 return p.pageConfig.Params
200 }
201
202 func (p *pageMeta) Path() string {
203 return p.pathInfo.Base()
204 }
205
206 func (p *pageMeta) PathInfo() *paths.Path {
207 return p.pathInfo
208 }
209
210 func (p *pageMeta) IsSection() bool {
211 return p.Kind() == kinds.KindSection
212 }
213
214 func (p *pageMeta) Section() string {
215 return p.pathInfo.Section()
216 }
217
218 func (p *pageMeta) Sitemap() config.SitemapConfig {
219 return p.pageConfig.Sitemap
220 }
221
222 func (p *pageMeta) Title() string {
223 return p.pageConfig.Title
224 }
225
226 const defaultContentType = "page"
227
228 func (p *pageMeta) Type() string {
229 if p.pageConfig.Type != "" {
230 return p.pageConfig.Type
231 }
232
233 if sect := p.Section(); sect != "" {
234 return sect
235 }
236
237 return defaultContentType
238 }
239
240 func (p *pageMeta) Weight() int {
241 return p.pageConfig.Weight
242 }
243
244 func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf config.AllProvider) error {
245 frontmatter := pi.frontMatter
246
247 if frontmatter != nil {
248 pcfg := p.pageConfig
249 // Needed for case insensitive fetching of params values
250 maps.PrepareParams(frontmatter)
251 pcfg.Params = frontmatter
252 // Check for any cascade define on itself.
253 if cv, found := frontmatter["cascade"]; found {
254 var err error
255 cascade, err := page.DecodeCascade(logger, true, cv)
256 if err != nil {
257 return err
258 }
259 pcfg.CascadeCompiled = cascade
260 }
261
262 // Look for path, lang and kind, all of which values we need early on.
263 if v, found := frontmatter["path"]; found {
264 pcfg.Path = paths.ToSlashPreserveLeading(cast.ToString(v))
265 pcfg.Params["path"] = pcfg.Path
266 }
267 if v, found := frontmatter["lang"]; found {
268 lang := strings.ToLower(cast.ToString(v))
269 if _, ok := conf.PathParser().LanguageIndex[lang]; ok {
270 pcfg.Lang = lang
271 pcfg.Params["lang"] = pcfg.Lang
272 }
273 }
274 if v, found := frontmatter["kind"]; found {
275 s := cast.ToString(v)
276 if s != "" {
277 pcfg.Kind = kinds.GetKindMain(s)
278 if pcfg.Kind == "" {
279 return fmt.Errorf("unknown kind %q in front matter", s)
280 }
281 pcfg.Params["kind"] = pcfg.Kind
282 }
283 }
284 } else if p.pageMetaParams.pageConfig.Params == nil {
285 p.pageConfig.Params = make(maps.Params)
286 }
287
288 p.pageMetaParams.init(conf.Watching())
289
290 return nil
291 }
292
293 func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]) error {
294 ps.m.setMetaPostCount++
295 var cascadeHashPre uint64
296 if ps.m.setMetaPostCount > 1 {
297 cascadeHashPre = hashing.HashUint64(ps.m.pageConfig.CascadeCompiled)
298 ps.m.pageConfig.CascadeCompiled = ps.m.cascadeOriginal.Clone()
299
300 }
301
302 // Apply cascades first so they can be overridden later.
303 if cascade != nil {
304 if ps.m.pageConfig.CascadeCompiled != nil {
305 cascade.Range(func(k page.PageMatcher, v page.PageMatcherParamsConfig) bool {
306 vv, found := ps.m.pageConfig.CascadeCompiled.Get(k)
307 if !found {
308 ps.m.pageConfig.CascadeCompiled.Set(k, v)
309 } else {
310 // Merge
311 for ck, cv := range v.Params {
312 if _, found := vv.Params[ck]; !found {
313 vv.Params[ck] = cv
314 }
315 }
316 for ck, cv := range v.Fields {
317 if _, found := vv.Fields[ck]; !found {
318 vv.Fields[ck] = cv
319 }
320 }
321 }
322 return true
323 })
324 cascade = ps.m.pageConfig.CascadeCompiled
325 } else {
326 ps.m.pageConfig.CascadeCompiled = cascade
327 }
328 }
329
330 if cascade == nil {
331 cascade = ps.m.pageConfig.CascadeCompiled
332 }
333
334 if ps.m.setMetaPostCount > 1 {
335 ps.m.setMetaPostCascadeChanged = cascadeHashPre != hashing.HashUint64(ps.m.pageConfig.CascadeCompiled)
336 if !ps.m.setMetaPostCascadeChanged {
337
338 // No changes, restore any value that may be changed by aggregation.
339 ps.m.pageConfig.Dates = ps.m.datesOriginal
340 return nil
341 }
342 ps.m.setMetaPostPrepareRebuild()
343
344 }
345
346 // Cascade is also applied to itself.
347 var err error
348 cascade.Range(func(k page.PageMatcher, v page.PageMatcherParamsConfig) bool {
349 if !k.Matches(ps) {
350 return true
351 }
352 for kk, vv := range v.Params {
353 if _, found := ps.m.pageConfig.Params[kk]; !found {
354 ps.m.pageConfig.Params[kk] = vv
355 }
356 }
357
358 for kk, vv := range v.Fields {
359 if ps.m.pageConfig.IsFromContentAdapter {
360 if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
361 ps.m.pageConfig.ContentAdapterData[kk] = vv
362 }
363 } else {
364 if _, found := ps.m.pageConfig.Params[kk]; !found {
365 ps.m.pageConfig.Params[kk] = vv
366 }
367 }
368 }
369 return true
370 })
371
372 if err != nil {
373 return err
374 }
375
376 if err := ps.setMetaPostParams(); err != nil {
377 return err
378 }
379
380 if err := ps.m.applyDefaultValues(); err != nil {
381 return err
382 }
383
384 // Store away any original values that may be changed from aggregation.
385 ps.m.datesOriginal = ps.m.pageConfig.Dates
386
387 return nil
388 }
389
390 func (p *pageState) setMetaPostParams() error {
391 pm := p.m
392 var mtime time.Time
393 var contentBaseName string
394 var ext string
395 var isContentAdapter bool
396 if p.File() != nil {
397 isContentAdapter = p.File().IsContentAdapter()
398 contentBaseName = p.File().ContentBaseName()
399 if p.File().FileInfo() != nil {
400 mtime = p.File().FileInfo().ModTime()
401 }
402 if !isContentAdapter {
403 ext = p.File().Ext()
404 }
405 }
406
407 var gitAuthorDate time.Time
408 if p.gitInfo != nil {
409 gitAuthorDate = p.gitInfo.AuthorDate
410 }
411
412 descriptor := &pagemeta.FrontMatterDescriptor{
413 PageConfig: pm.pageConfig,
414 BaseFilename: contentBaseName,
415 ModTime: mtime,
416 GitAuthorDate: gitAuthorDate,
417 Location: langs.GetLocation(pm.s.Language()),
418 PathOrTitle: p.pathOrTitle(),
419 }
420
421 if isContentAdapter {
422 if err := pm.pageConfig.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
423 return err
424 }
425 }
426
427 // Handle the date separately
428 // TODO(bep) we need to "do more" in this area so this can be split up and
429 // more easily tested without the Page, but the coupling is strong.
430 err := pm.s.frontmatterHandler.HandleDates(descriptor)
431 if err != nil {
432 p.s.Log.Errorf("Failed to handle dates for page %q: %s", p.pathOrTitle(), err)
433 }
434
435 if isContentAdapter {
436 // Done.
437 return nil
438 }
439
440 var buildConfig any
441 var isNewBuildKeyword bool
442 if v, ok := pm.pageConfig.Params["_build"]; ok {
443 hugo.Deprecate("The \"_build\" front matter key", "Use \"build\" instead. See https://gohugo.io/content-management/build-options.", "0.145.0")
444 buildConfig = v
445 } else {
446 buildConfig = pm.pageConfig.Params["build"]
447 isNewBuildKeyword = true
448 }
449 pm.pageConfig.Build, err = pagemeta.DecodeBuildConfig(buildConfig)
450 if err != nil {
451 var msgDetail string
452 if isNewBuildKeyword {
453 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.:
454 ---
455 title: "My Title"
456 params:
457 build: "My Build"
458 ---
459 ยด
460
461 `
462 }
463 return fmt.Errorf("failed to decode build config in front matter: %s%s", err, msgDetail)
464 }
465
466 var sitemapSet bool
467
468 pcfg := pm.pageConfig
469 params := pcfg.Params
470 if params == nil {
471 panic("params not set for " + p.Title())
472 }
473
474 var draft, published, isCJKLanguage *bool
475 var userParams map[string]any
476 for k, v := range pcfg.Params {
477 loki := strings.ToLower(k)
478
479 if loki == "params" {
480 vv, err := maps.ToStringMapE(v)
481 if err != nil {
482 return err
483 }
484 userParams = vv
485 delete(pcfg.Params, k)
486 continue
487 }
488
489 if loki == "published" { // Intentionally undocumented
490 vv, err := cast.ToBoolE(v)
491 if err == nil {
492 published = &vv
493 }
494 // published may also be a date
495 continue
496 }
497
498 if pm.s.frontmatterHandler.IsDateKey(loki) {
499 continue
500 }
501
502 if loki == "path" || loki == "kind" || loki == "lang" {
503 // See issue 12484.
504 hugo.DeprecateLevelMin(loki+" in front matter", "", "v0.144.0", logg.LevelWarn)
505 }
506
507 switch loki {
508 case "title":
509 pcfg.Title = cast.ToString(v)
510 params[loki] = pcfg.Title
511 case "linktitle":
512 pcfg.LinkTitle = cast.ToString(v)
513 params[loki] = pcfg.LinkTitle
514 case "summary":
515 pcfg.Summary = cast.ToString(v)
516 params[loki] = pcfg.Summary
517 case "description":
518 pcfg.Description = cast.ToString(v)
519 params[loki] = pcfg.Description
520 case "slug":
521 // Don't start or end with a -
522 pcfg.Slug = strings.Trim(cast.ToString(v), "-")
523 params[loki] = pm.Slug()
524 case "url":
525 url := cast.ToString(v)
526 if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
527 return fmt.Errorf("URLs with protocol (http*) not supported: %q. In page %q", url, p.pathOrTitle())
528 }
529 pcfg.URL = url
530 params[loki] = url
531 case "type":
532 pcfg.Type = cast.ToString(v)
533 params[loki] = pcfg.Type
534 case "keywords":
535 pcfg.Keywords = cast.ToStringSlice(v)
536 params[loki] = pcfg.Keywords
537 case "headless":
538 // Legacy setting for leaf bundles.
539 // This is since Hugo 0.63 handled in a more general way for all
540 // pages.
541 isHeadless := cast.ToBool(v)
542 params[loki] = isHeadless
543 if isHeadless {
544 pm.pageConfig.Build.List = pagemeta.Never
545 pm.pageConfig.Build.Render = pagemeta.Never
546 }
547 case "outputs":
548 o := cast.ToStringSlice(v)
549 // lower case names:
550 for i, s := range o {
551 o[i] = strings.ToLower(s)
552 }
553 pm.pageConfig.Outputs = o
554 case "draft":
555 draft = new(bool)
556 *draft = cast.ToBool(v)
557 case "layout":
558 pcfg.Layout = cast.ToString(v)
559 params[loki] = pcfg.Layout
560 case "markup":
561 pcfg.Content.Markup = cast.ToString(v)
562 params[loki] = pcfg.Content.Markup
563 case "weight":
564 pcfg.Weight = cast.ToInt(v)
565 params[loki] = pcfg.Weight
566 case "aliases":
567 pcfg.Aliases = cast.ToStringSlice(v)
568 for i, alias := range pcfg.Aliases {
569 if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
570 return fmt.Errorf("http* aliases not supported: %q", alias)
571 }
572 pcfg.Aliases[i] = filepath.ToSlash(alias)
573 }
574 params[loki] = pcfg.Aliases
575 case "sitemap":
576 pcfg.Sitemap, err = config.DecodeSitemap(p.s.conf.Sitemap, maps.ToStringMap(v))
577 if err != nil {
578 return fmt.Errorf("failed to decode sitemap config in front matter: %s", err)
579 }
580 sitemapSet = true
581 case "iscjklanguage":
582 isCJKLanguage = new(bool)
583 *isCJKLanguage = cast.ToBool(v)
584 case "translationkey":
585 pcfg.TranslationKey = cast.ToString(v)
586 params[loki] = pcfg.TranslationKey
587 case "resources":
588 var resources []map[string]any
589 handled := true
590
591 switch vv := v.(type) {
592 case []map[any]any:
593 for _, vvv := range vv {
594 resources = append(resources, maps.ToStringMap(vvv))
595 }
596 case []map[string]any:
597 resources = append(resources, vv...)
598 case []any:
599 for _, vvv := range vv {
600 switch vvvv := vvv.(type) {
601 case map[any]any:
602 resources = append(resources, maps.ToStringMap(vvvv))
603 case map[string]any:
604 resources = append(resources, vvvv)
605 }
606 }
607 default:
608 handled = false
609 }
610
611 if handled {
612 pcfg.ResourcesMeta = resources
613 break
614 }
615 fallthrough
616 default:
617 // If not one of the explicit values, store in Params
618 switch vv := v.(type) {
619 case []any:
620 if len(vv) > 0 {
621 allStrings := true
622 for _, vvv := range vv {
623 if _, ok := vvv.(string); !ok {
624 allStrings = false
625 break
626 }
627 }
628 if allStrings {
629 // We need tags, keywords etc. to be []string, not []interface{}.
630 a := make([]string, len(vv))
631 for i, u := range vv {
632 a[i] = cast.ToString(u)
633 }
634 params[loki] = a
635 } else {
636 params[loki] = vv
637 }
638 } else {
639 params[loki] = []string{}
640 }
641
642 default:
643 params[loki] = vv
644 }
645 }
646 }
647
648 for k, v := range userParams {
649 params[strings.ToLower(k)] = v
650 }
651
652 if !sitemapSet {
653 pcfg.Sitemap = p.s.conf.Sitemap
654 }
655
656 if draft != nil && published != nil {
657 pcfg.Draft = *draft
658 p.m.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", p.File().Filename())
659 } else if draft != nil {
660 pcfg.Draft = *draft
661 } else if published != nil {
662 pcfg.Draft = !*published
663 }
664 params["draft"] = pcfg.Draft
665
666 if isCJKLanguage != nil {
667 pcfg.IsCJKLanguage = *isCJKLanguage
668 } else if p.s.conf.HasCJKLanguage && p.m.content.pi.openSource != nil {
669 if cjkRe.Match(p.m.content.mustSource()) {
670 pcfg.IsCJKLanguage = true
671 } else {
672 pcfg.IsCJKLanguage = false
673 }
674 }
675
676 params["iscjklanguage"] = pcfg.IsCJKLanguage
677
678 if err := pcfg.Init(false); err != nil {
679 return err
680 }
681
682 if err := pcfg.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
683 return err
684 }
685
686 return nil
687 }
688
689 // shouldList returns whether this page should be included in the list of pages.
690 // global indicates site.Pages etc.
691 func (p *pageMeta) shouldList(global bool) bool {
692 if p.isStandalone() {
693 // Never list 404, sitemap and similar.
694 return false
695 }
696
697 switch p.pageConfig.Build.List {
698 case pagemeta.Always:
699 return true
700 case pagemeta.Never:
701 return false
702 case pagemeta.ListLocally:
703 return !global
704 }
705 return false
706 }
707
708 func (p *pageMeta) shouldListAny() bool {
709 return p.shouldList(true) || p.shouldList(false)
710 }
711
712 func (p *pageMeta) isStandalone() bool {
713 return !p.standaloneOutputFormat.IsZero()
714 }
715
716 func (p *pageMeta) shouldBeCheckedForMenuDefinitions() bool {
717 if !p.shouldList(false) {
718 return false
719 }
720
721 return p.pageConfig.Kind == kinds.KindHome || p.pageConfig.Kind == kinds.KindSection || p.pageConfig.Kind == kinds.KindPage
722 }
723
724 func (p *pageMeta) noRender() bool {
725 return p.pageConfig.Build.Render != pagemeta.Always
726 }
727
728 func (p *pageMeta) noLink() bool {
729 return p.pageConfig.Build.Render == pagemeta.Never
730 }
731
732 func (p *pageMeta) applyDefaultValues() error {
733 if p.pageConfig.Build.IsZero() {
734 p.pageConfig.Build, _ = pagemeta.DecodeBuildConfig(nil)
735 }
736
737 if !p.s.conf.IsKindEnabled(p.Kind()) {
738 (&p.pageConfig.Build).Disable()
739 }
740
741 if p.pageConfig.Content.Markup == "" {
742 if p.File() != nil {
743 // Fall back to file extension
744 p.pageConfig.Content.Markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
745 }
746 if p.pageConfig.Content.Markup == "" {
747 p.pageConfig.Content.Markup = "markdown"
748 }
749 }
750
751 if p.pageConfig.Title == "" && p.f == nil {
752 switch p.Kind() {
753 case kinds.KindHome:
754 p.pageConfig.Title = p.s.Title()
755 case kinds.KindSection:
756 sectionName := p.pathInfo.Unnormalized().BaseNameNoIdentifier()
757 if p.s.conf.PluralizeListTitles {
758 sectionName = flect.Pluralize(sectionName)
759 }
760 if p.s.conf.CapitalizeListTitles {
761 sectionName = p.s.conf.C.CreateTitle(sectionName)
762 }
763 p.pageConfig.Title = sectionName
764 case kinds.KindTerm:
765 if p.term != "" {
766 if p.s.conf.CapitalizeListTitles {
767 p.pageConfig.Title = p.s.conf.C.CreateTitle(p.term)
768 } else {
769 p.pageConfig.Title = p.term
770 }
771 } else {
772 panic("term not set")
773 }
774 case kinds.KindTaxonomy:
775 if p.s.conf.CapitalizeListTitles {
776 p.pageConfig.Title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unnormalized().BaseNameNoIdentifier()), "-", " ", -1)
777 } else {
778 p.pageConfig.Title = strings.Replace(p.pathInfo.Unnormalized().BaseNameNoIdentifier(), "-", " ", -1)
779 }
780 case kinds.KindStatus404:
781 p.pageConfig.Title = "404 Page not found"
782 }
783 }
784
785 return nil
786 }
787
788 func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.Converter, error) {
789 if ps == nil {
790 panic("no Page provided")
791 }
792 cp := p.s.ContentSpec.Converters.Get(markup)
793 if cp == nil {
794 return converter.NopConverter, fmt.Errorf("no content renderer found for markup %q, page: %s", markup, ps.getPageInfoForError())
795 }
796
797 var id string
798 var filename string
799 var path string
800 if p.f != nil {
801 id = p.f.UniqueID()
802 filename = p.f.Filename()
803 path = p.f.Path()
804 } else {
805 path = p.Path()
806 }
807
808 doc := newPageForRenderHook(ps)
809
810 documentLookup := func(id uint64) any {
811 if id == ps.pid {
812 // This prevents infinite recursion in some cases.
813 return doc
814 }
815 if v, ok := ps.pageOutput.pco.otherOutputs.Get(id); ok {
816 return v.po.p
817 }
818 return nil
819 }
820
821 cpp, err := cp.New(
822 converter.DocumentContext{
823 Document: doc,
824 DocumentLookup: documentLookup,
825 DocumentID: id,
826 DocumentName: path,
827 Filename: filename,
828 },
829 )
830 if err != nil {
831 return converter.NopConverter, err
832 }
833
834 return cpp, nil
835 }
836
837 // The output formats this page will be rendered to.
838 func (m *pageMeta) outputFormats() output.Formats {
839 if len(m.pageConfig.ConfiguredOutputFormats) > 0 {
840 return m.pageConfig.ConfiguredOutputFormats
841 }
842 return m.s.conf.C.KindOutputFormats[m.Kind()]
843 }
844
845 func (p *pageMeta) Slug() string {
846 return p.pageConfig.Slug
847 }
848
849 func getParam(m resource.ResourceParamsProvider, key string, stringToLower bool) any {
850 v := m.Params()[strings.ToLower(key)]
851
852 if v == nil {
853 return nil
854 }
855
856 switch val := v.(type) {
857 case bool:
858 return val
859 case string:
860 if stringToLower {
861 return strings.ToLower(val)
862 }
863 return val
864 case int64, int32, int16, int8, int:
865 return cast.ToInt(v)
866 case float64, float32:
867 return cast.ToFloat64(v)
868 case time.Time:
869 return val
870 case []string:
871 if stringToLower {
872 return helpers.SliceToLower(val)
873 }
874 return v
875 default:
876 return v
877 }
878 }
879
880 func getParamToLower(m resource.ResourceParamsProvider, key string) any {
881 return getParam(m, key, true)
882 }
883
884 func (ps *pageState) initLazyProviders() error {
885 ps.init.Add(func(ctx context.Context) (any, error) {
886 pp, err := newPagePaths(ps)
887 if err != nil {
888 return nil, err
889 }
890
891 var outputFormatsForPage output.Formats
892 var renderFormats output.Formats
893
894 if ps.m.standaloneOutputFormat.IsZero() {
895 outputFormatsForPage = ps.m.outputFormats()
896 renderFormats = ps.s.h.renderFormats
897 } else {
898 // One of the fixed output format pages, e.g. 404.
899 outputFormatsForPage = output.Formats{ps.m.standaloneOutputFormat}
900 renderFormats = outputFormatsForPage
901 }
902
903 // Prepare output formats for all sites.
904 // We do this even if this page does not get rendered on
905 // its own. It may be referenced via one of the site collections etc.
906 // it will then need an output format.
907 ps.pageOutputs = make([]*pageOutput, len(renderFormats))
908 created := make(map[string]*pageOutput)
909 shouldRenderPage := !ps.m.noRender()
910
911 for i, f := range renderFormats {
912
913 if po, found := created[f.Name]; found {
914 ps.pageOutputs[i] = po
915 continue
916 }
917
918 render := shouldRenderPage
919 if render {
920 _, render = outputFormatsForPage.GetByName(f.Name)
921 }
922
923 po := newPageOutput(ps, pp, f, render)
924
925 // Create a content provider for the first,
926 // we may be able to reuse it.
927 if i == 0 {
928 contentProvider, err := newPageContentOutput(po)
929 if err != nil {
930 return nil, err
931 }
932 po.setContentProvider(contentProvider)
933 }
934
935 ps.pageOutputs[i] = po
936 created[f.Name] = po
937
938 }
939
940 if err := ps.initCommonProviders(pp); err != nil {
941 return nil, err
942 }
943
944 return nil, nil
945 })
946
947 return nil
948 }