hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

page.go (23544B)

    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 	"bytes"
   18 	"fmt"
   19 	"os"
   20 	"path"
   21 	"path/filepath"
   22 	"sort"
   23 	"strings"
   24 
   25 	"go.uber.org/atomic"
   26 
   27 	"github.com/gohugoio/hugo/identity"
   28 
   29 	"github.com/gohugoio/hugo/markup/converter"
   30 
   31 	"github.com/gohugoio/hugo/tpl"
   32 
   33 	"github.com/gohugoio/hugo/hugofs/files"
   34 
   35 	"github.com/bep/gitmap"
   36 
   37 	"github.com/gohugoio/hugo/helpers"
   38 
   39 	"github.com/gohugoio/hugo/common/herrors"
   40 	"github.com/gohugoio/hugo/parser/metadecoders"
   41 
   42 	"errors"
   43 
   44 	"github.com/gohugoio/hugo/parser/pageparser"
   45 
   46 	"github.com/gohugoio/hugo/output"
   47 
   48 	"github.com/gohugoio/hugo/media"
   49 	"github.com/gohugoio/hugo/source"
   50 
   51 	"github.com/gohugoio/hugo/common/collections"
   52 	"github.com/gohugoio/hugo/common/text"
   53 	"github.com/gohugoio/hugo/resources"
   54 	"github.com/gohugoio/hugo/resources/page"
   55 	"github.com/gohugoio/hugo/resources/resource"
   56 )
   57 
   58 var (
   59 	_ page.Page           = (*pageState)(nil)
   60 	_ collections.Grouper = (*pageState)(nil)
   61 	_ collections.Slicer  = (*pageState)(nil)
   62 )
   63 
   64 var (
   65 	pageTypesProvider = resource.NewResourceTypesProvider(media.OctetType, pageResourceType)
   66 	nopPageOutput     = &pageOutput{
   67 		pagePerOutputProviders:  nopPagePerOutput,
   68 		ContentProvider:         page.NopPage,
   69 		TableOfContentsProvider: page.NopPage,
   70 	}
   71 )
   72 
   73 // pageContext provides contextual information about this page, for error
   74 // logging and similar.
   75 type pageContext interface {
   76 	posOffset(offset int) text.Position
   77 	wrapError(err error) error
   78 	getContentConverter() converter.Converter
   79 	addDependency(dep identity.Provider)
   80 }
   81 
   82 // wrapErr adds some context to the given error if possible.
   83 func wrapErr(err error, ctx any) error {
   84 	if pc, ok := ctx.(pageContext); ok {
   85 		return pc.wrapError(err)
   86 	}
   87 	return err
   88 }
   89 
   90 type pageSiteAdapter struct {
   91 	p page.Page
   92 	s *Site
   93 }
   94 
   95 func (pa pageSiteAdapter) GetPageWithTemplateInfo(info tpl.Info, ref string) (page.Page, error) {
   96 	p, err := pa.GetPage(ref)
   97 	if p != nil {
   98 		// Track pages referenced by templates/shortcodes
   99 		// when in server mode.
  100 		if im, ok := info.(identity.Manager); ok {
  101 			im.Add(p)
  102 		}
  103 	}
  104 	return p, err
  105 }
  106 
  107 func (pa pageSiteAdapter) GetPage(ref string) (page.Page, error) {
  108 	p, err := pa.s.getPageNew(pa.p, ref)
  109 	if p == nil {
  110 		// The nil struct has meaning in some situations, mostly to avoid breaking
  111 		// existing sites doing $nilpage.IsDescendant($p), which will always return
  112 		// false.
  113 		p = page.NilPage
  114 	}
  115 	return p, err
  116 }
  117 
  118 type pageState struct {
  119 	// This slice will be of same length as the number of global slice of output
  120 	// formats (for all sites).
  121 	pageOutputs []*pageOutput
  122 
  123 	// Used to determine if we can reuse content across output formats.
  124 	pageOutputTemplateVariationsState *atomic.Uint32
  125 
  126 	// This will be shifted out when we start to render a new output format.
  127 	*pageOutput
  128 
  129 	// Common for all output formats.
  130 	*pageCommon
  131 }
  132 
  133 func (p *pageState) reusePageOutputContent() bool {
  134 	return p.pageOutputTemplateVariationsState.Load() == 1
  135 }
  136 
  137 func (p *pageState) Err() resource.ResourceError {
  138 	return nil
  139 }
  140 
  141 // Eq returns whether the current page equals the given page.
  142 // This is what's invoked when doing `{{ if eq $page $otherPage }}`
  143 func (p *pageState) Eq(other any) bool {
  144 	pp, err := unwrapPage(other)
  145 	if err != nil {
  146 		return false
  147 	}
  148 
  149 	return p == pp
  150 }
  151 
  152 func (p *pageState) GetIdentity() identity.Identity {
  153 	return identity.NewPathIdentity(files.ComponentFolderContent, filepath.FromSlash(p.Pathc()))
  154 }
  155 
  156 func (p *pageState) GitInfo() *gitmap.GitInfo {
  157 	return p.gitInfo
  158 }
  159 
  160 func (p *pageState) CodeOwners() []string {
  161 	return p.codeowners
  162 }
  163 
  164 // GetTerms gets the terms defined on this page in the given taxonomy.
  165 // The pages returned will be ordered according to the front matter.
  166 func (p *pageState) GetTerms(taxonomy string) page.Pages {
  167 	if p.treeRef == nil {
  168 		return nil
  169 	}
  170 
  171 	m := p.s.pageMap
  172 
  173 	taxonomy = strings.ToLower(taxonomy)
  174 	prefix := cleanSectionTreeKey(taxonomy)
  175 	self := strings.TrimPrefix(p.treeRef.key, "/")
  176 
  177 	var pas page.Pages
  178 
  179 	m.taxonomies.WalkQuery(pageMapQuery{Prefix: prefix}, func(s string, n *contentNode) bool {
  180 		key := s + self
  181 		if tn, found := m.taxonomyEntries.Get(key); found {
  182 			vi := tn.(*contentNode).viewInfo
  183 			pas = append(pas, pageWithOrdinal{pageState: n.p, ordinal: vi.ordinal})
  184 		}
  185 		return false
  186 	})
  187 
  188 	page.SortByDefault(pas)
  189 
  190 	return pas
  191 }
  192 
  193 func (p *pageState) MarshalJSON() ([]byte, error) {
  194 	return page.MarshalPageToJSON(p)
  195 }
  196 
  197 func (p *pageState) getPages() page.Pages {
  198 	b := p.bucket
  199 	if b == nil {
  200 		return nil
  201 	}
  202 	return b.getPages()
  203 }
  204 
  205 func (p *pageState) getPagesRecursive() page.Pages {
  206 	b := p.bucket
  207 	if b == nil {
  208 		return nil
  209 	}
  210 	return b.getPagesRecursive()
  211 }
  212 
  213 func (p *pageState) getPagesAndSections() page.Pages {
  214 	b := p.bucket
  215 	if b == nil {
  216 		return nil
  217 	}
  218 	return b.getPagesAndSections()
  219 }
  220 
  221 func (p *pageState) RegularPagesRecursive() page.Pages {
  222 	p.regularPagesRecursiveInit.Do(func() {
  223 		var pages page.Pages
  224 		switch p.Kind() {
  225 		case page.KindSection:
  226 			pages = p.getPagesRecursive()
  227 		default:
  228 			pages = p.RegularPages()
  229 		}
  230 		p.regularPagesRecursive = pages
  231 	})
  232 	return p.regularPagesRecursive
  233 }
  234 
  235 func (p *pageState) PagesRecursive() page.Pages {
  236 	return nil
  237 }
  238 
  239 func (p *pageState) RegularPages() page.Pages {
  240 	p.regularPagesInit.Do(func() {
  241 		var pages page.Pages
  242 
  243 		switch p.Kind() {
  244 		case page.KindPage:
  245 		case page.KindSection, page.KindHome, page.KindTaxonomy:
  246 			pages = p.getPages()
  247 		case page.KindTerm:
  248 			all := p.Pages()
  249 			for _, p := range all {
  250 				if p.IsPage() {
  251 					pages = append(pages, p)
  252 				}
  253 			}
  254 		default:
  255 			pages = p.s.RegularPages()
  256 		}
  257 
  258 		p.regularPages = pages
  259 	})
  260 
  261 	return p.regularPages
  262 }
  263 
  264 func (p *pageState) Pages() page.Pages {
  265 	p.pagesInit.Do(func() {
  266 		var pages page.Pages
  267 
  268 		switch p.Kind() {
  269 		case page.KindPage:
  270 		case page.KindSection, page.KindHome:
  271 			pages = p.getPagesAndSections()
  272 		case page.KindTerm:
  273 			pages = p.bucket.getTaxonomyEntries()
  274 		case page.KindTaxonomy:
  275 			pages = p.bucket.getTaxonomies()
  276 		default:
  277 			pages = p.s.Pages()
  278 		}
  279 
  280 		p.pages = pages
  281 	})
  282 
  283 	return p.pages
  284 }
  285 
  286 // RawContent returns the un-rendered source content without
  287 // any leading front matter.
  288 func (p *pageState) RawContent() string {
  289 	if p.source.parsed == nil {
  290 		return ""
  291 	}
  292 	start := p.source.posMainContent
  293 	if start == -1 {
  294 		start = 0
  295 	}
  296 	return string(p.source.parsed.Input()[start:])
  297 }
  298 
  299 func (p *pageState) sortResources() {
  300 	sort.SliceStable(p.resources, func(i, j int) bool {
  301 		ri, rj := p.resources[i], p.resources[j]
  302 		if ri.ResourceType() < rj.ResourceType() {
  303 			return true
  304 		}
  305 
  306 		p1, ok1 := ri.(page.Page)
  307 		p2, ok2 := rj.(page.Page)
  308 
  309 		if ok1 != ok2 {
  310 			return ok2
  311 		}
  312 
  313 		if ok1 {
  314 			return page.DefaultPageSort(p1, p2)
  315 		}
  316 
  317 		// Make sure not to use RelPermalink or any of the other methods that
  318 		// trigger lazy publishing.
  319 		return ri.Name() < rj.Name()
  320 	})
  321 }
  322 
  323 func (p *pageState) Resources() resource.Resources {
  324 	p.resourcesInit.Do(func() {
  325 		p.sortResources()
  326 		if len(p.m.resourcesMetadata) > 0 {
  327 			resources.AssignMetadata(p.m.resourcesMetadata, p.resources...)
  328 			p.sortResources()
  329 		}
  330 	})
  331 	return p.resources
  332 }
  333 
  334 func (p *pageState) HasShortcode(name string) bool {
  335 	if p.shortcodeState == nil {
  336 		return false
  337 	}
  338 
  339 	return p.shortcodeState.hasName(name)
  340 }
  341 
  342 func (p *pageState) Site() page.Site {
  343 	return p.s.Info
  344 }
  345 
  346 func (p *pageState) String() string {
  347 	if sourceRef := p.sourceRef(); sourceRef != "" {
  348 		return fmt.Sprintf("Page(%s)", sourceRef)
  349 	}
  350 	return fmt.Sprintf("Page(%q)", p.Title())
  351 }
  352 
  353 // IsTranslated returns whether this content file is translated to
  354 // other language(s).
  355 func (p *pageState) IsTranslated() bool {
  356 	p.s.h.init.translations.Do()
  357 	return len(p.translations) > 0
  358 }
  359 
  360 // TranslationKey returns the key used to map language translations of this page.
  361 // It will use the translationKey set in front matter if set, or the content path and
  362 // filename (excluding any language code and extension), e.g. "about/index".
  363 // The Page Kind is always prepended.
  364 func (p *pageState) TranslationKey() string {
  365 	p.translationKeyInit.Do(func() {
  366 		if p.m.translationKey != "" {
  367 			p.translationKey = p.Kind() + "/" + p.m.translationKey
  368 		} else if p.IsPage() && !p.File().IsZero() {
  369 			p.translationKey = path.Join(p.Kind(), filepath.ToSlash(p.File().Dir()), p.File().TranslationBaseName())
  370 		} else if p.IsNode() {
  371 			p.translationKey = path.Join(p.Kind(), p.SectionsPath())
  372 		}
  373 	})
  374 
  375 	return p.translationKey
  376 }
  377 
  378 // AllTranslations returns all translations, including the current Page.
  379 func (p *pageState) AllTranslations() page.Pages {
  380 	p.s.h.init.translations.Do()
  381 	return p.allTranslations
  382 }
  383 
  384 // Translations returns the translations excluding the current Page.
  385 func (p *pageState) Translations() page.Pages {
  386 	p.s.h.init.translations.Do()
  387 	return p.translations
  388 }
  389 
  390 func (ps *pageState) initCommonProviders(pp pagePaths) error {
  391 	if ps.IsPage() {
  392 		ps.posNextPrev = &nextPrev{init: ps.s.init.prevNext}
  393 		ps.posNextPrevSection = &nextPrev{init: ps.s.init.prevNextInSection}
  394 		ps.InSectionPositioner = newPagePositionInSection(ps.posNextPrevSection)
  395 		ps.Positioner = newPagePosition(ps.posNextPrev)
  396 	}
  397 
  398 	ps.OutputFormatsProvider = pp
  399 	ps.targetPathDescriptor = pp.targetPathDescriptor
  400 	ps.RefProvider = newPageRef(ps)
  401 	ps.SitesProvider = ps.s.Info
  402 
  403 	return nil
  404 }
  405 
  406 func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor {
  407 	p.layoutDescriptorInit.Do(func() {
  408 		var section string
  409 		sections := p.SectionsEntries()
  410 
  411 		switch p.Kind() {
  412 		case page.KindSection:
  413 			if len(sections) > 0 {
  414 				section = sections[0]
  415 			}
  416 		case page.KindTaxonomy, page.KindTerm:
  417 			b := p.getTreeRef().n
  418 			section = b.viewInfo.name.singular
  419 		default:
  420 		}
  421 
  422 		p.layoutDescriptor = output.LayoutDescriptor{
  423 			Kind:    p.Kind(),
  424 			Type:    p.Type(),
  425 			Lang:    p.Language().Lang,
  426 			Layout:  p.Layout(),
  427 			Section: section,
  428 		}
  429 	})
  430 
  431 	return p.layoutDescriptor
  432 }
  433 
  434 func (p *pageState) resolveTemplate(layouts ...string) (tpl.Template, bool, error) {
  435 	f := p.outputFormat()
  436 
  437 	if len(layouts) == 0 {
  438 		selfLayout := p.selfLayoutForOutput(f)
  439 		if selfLayout != "" {
  440 			templ, found := p.s.Tmpl().Lookup(selfLayout)
  441 			return templ, found, nil
  442 		}
  443 	}
  444 
  445 	d := p.getLayoutDescriptor()
  446 
  447 	if len(layouts) > 0 {
  448 		d.Layout = layouts[0]
  449 		d.LayoutOverride = true
  450 	}
  451 
  452 	return p.s.Tmpl().LookupLayout(d, f)
  453 }
  454 
  455 // This is serialized
  456 func (p *pageState) initOutputFormat(isRenderingSite bool, idx int) error {
  457 	if err := p.shiftToOutputFormat(isRenderingSite, idx); err != nil {
  458 		return err
  459 	}
  460 
  461 	return nil
  462 }
  463 
  464 // Must be run after the site section tree etc. is built and ready.
  465 func (p *pageState) initPage() error {
  466 	if _, err := p.init.Do(); err != nil {
  467 		return err
  468 	}
  469 	return nil
  470 }
  471 
  472 func (p *pageState) renderResources() (err error) {
  473 	p.resourcesPublishInit.Do(func() {
  474 		var toBeDeleted []int
  475 
  476 		for i, r := range p.Resources() {
  477 
  478 			if _, ok := r.(page.Page); ok {
  479 				// Pages gets rendered with the owning page but we count them here.
  480 				p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
  481 				continue
  482 			}
  483 
  484 			src, ok := r.(resource.Source)
  485 			if !ok {
  486 				err = fmt.Errorf("Resource %T does not support resource.Source", src)
  487 				return
  488 			}
  489 
  490 			if err := src.Publish(); err != nil {
  491 				if os.IsNotExist(err) {
  492 					// The resource has been deleted from the file system.
  493 					// This should be extremely rare, but can happen on live reload in server
  494 					// mode when the same resource is member of different page bundles.
  495 					toBeDeleted = append(toBeDeleted, i)
  496 				} else {
  497 					p.s.Log.Errorf("Failed to publish Resource for page %q: %s", p.pathOrTitle(), err)
  498 				}
  499 			} else {
  500 				p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files)
  501 			}
  502 		}
  503 
  504 		for _, i := range toBeDeleted {
  505 			p.deleteResource(i)
  506 		}
  507 	})
  508 
  509 	return
  510 }
  511 
  512 func (p *pageState) deleteResource(i int) {
  513 	p.resources = append(p.resources[:i], p.resources[i+1:]...)
  514 }
  515 
  516 func (p *pageState) getTargetPaths() page.TargetPaths {
  517 	return p.targetPaths()
  518 }
  519 
  520 func (p *pageState) setTranslations(pages page.Pages) {
  521 	p.allTranslations = pages
  522 	page.SortByLanguage(p.allTranslations)
  523 	translations := make(page.Pages, 0)
  524 	for _, t := range p.allTranslations {
  525 		if !t.Eq(p) {
  526 			translations = append(translations, t)
  527 		}
  528 	}
  529 	p.translations = translations
  530 }
  531 
  532 func (p *pageState) AlternativeOutputFormats() page.OutputFormats {
  533 	f := p.outputFormat()
  534 	var o page.OutputFormats
  535 	for _, of := range p.OutputFormats() {
  536 		if of.Format.NotAlternative || of.Format.Name == f.Name {
  537 			continue
  538 		}
  539 
  540 		o = append(o, of)
  541 	}
  542 	return o
  543 }
  544 
  545 type renderStringOpts struct {
  546 	Display string
  547 	Markup  string
  548 }
  549 
  550 var defaultRenderStringOpts = renderStringOpts{
  551 	Display: "inline",
  552 	Markup:  "", // Will inherit the page's value when not set.
  553 }
  554 
  555 func (p *pageState) addDependency(dep identity.Provider) {
  556 	if !p.s.running() || p.pageOutput.cp == nil {
  557 		return
  558 	}
  559 	p.pageOutput.cp.dependencyTracker.Add(dep)
  560 }
  561 
  562 // wrapError adds some more context to the given error if possible/needed
  563 func (p *pageState) wrapError(err error) error {
  564 	if err == nil {
  565 		panic("wrapError with nil")
  566 	}
  567 
  568 	if p.File().IsZero() {
  569 		// No more details to add.
  570 		return fmt.Errorf("%q: %w", p.Pathc(), err)
  571 	}
  572 
  573 	filename := p.File().Filename()
  574 
  575 	// Check if it's already added.
  576 	for _, ferr := range herrors.UnwrapFileErrors(err) {
  577 		errfilename := ferr.Position().Filename
  578 		if errfilename == filename {
  579 			if ferr.ErrorContext() == nil {
  580 				f, ioerr := p.s.SourceSpec.Fs.Source.Open(filename)
  581 				if ioerr != nil {
  582 					return err
  583 				}
  584 				defer f.Close()
  585 				ferr.UpdateContent(f, nil)
  586 			}
  587 			return err
  588 		}
  589 	}
  590 
  591 	return herrors.NewFileErrorFromFile(err, filename, p.s.SourceSpec.Fs.Source, herrors.NopLineMatcher)
  592 
  593 }
  594 
  595 func (p *pageState) getContentConverter() converter.Converter {
  596 	var err error
  597 	p.m.contentConverterInit.Do(func() {
  598 		markup := p.m.markup
  599 		if markup == "html" {
  600 			// Only used for shortcode inner content.
  601 			markup = "markdown"
  602 		}
  603 		p.m.contentConverter, err = p.m.newContentConverter(p, markup)
  604 	})
  605 
  606 	if err != nil {
  607 		p.s.Log.Errorln("Failed to create content converter:", err)
  608 	}
  609 	return p.m.contentConverter
  610 }
  611 
  612 func (p *pageState) mapContent(bucket *pagesMapBucket, meta *pageMeta) error {
  613 	p.cmap = &pageContentMap{
  614 		items: make([]any, 0, 20),
  615 	}
  616 
  617 	return p.mapContentForResult(
  618 		p.source.parsed,
  619 		p.shortcodeState,
  620 		p.cmap,
  621 		meta.markup,
  622 		func(m map[string]interface{}) error {
  623 			return meta.setMetadata(bucket, p, m)
  624 		},
  625 	)
  626 }
  627 
  628 func (p *pageState) mapContentForResult(
  629 	result pageparser.Result,
  630 	s *shortcodeHandler,
  631 	rn *pageContentMap,
  632 	markup string,
  633 	withFrontMatter func(map[string]any) error,
  634 ) error {
  635 
  636 	iter := result.Iterator()
  637 
  638 	fail := func(err error, i pageparser.Item) error {
  639 		if fe, ok := err.(herrors.FileError); ok {
  640 			return fe
  641 		}
  642 		return p.parseError(err, iter.Input(), i.Pos)
  643 	}
  644 
  645 	// the parser is guaranteed to return items in proper order or fail, so …
  646 	// … it's safe to keep some "global" state
  647 	var currShortcode shortcode
  648 	var ordinal int
  649 	var frontMatterSet bool
  650 
  651 Loop:
  652 	for {
  653 		it := iter.Next()
  654 
  655 		switch {
  656 		case it.Type == pageparser.TypeIgnore:
  657 		case it.IsFrontMatter():
  658 			f := pageparser.FormatFromFrontMatterType(it.Type)
  659 			m, err := metadecoders.Default.UnmarshalToMap(it.Val, f)
  660 			if err != nil {
  661 				if fe, ok := err.(herrors.FileError); ok {
  662 					pos := fe.Position()
  663 					// Apply the error to the content file.
  664 					pos.Filename = p.File().Filename()
  665 					// Offset the starting position of front matter.
  666 					offset := iter.LineNumber() - 1
  667 					if f == metadecoders.YAML {
  668 						offset -= 1
  669 					}
  670 					pos.LineNumber += offset
  671 
  672 					fe.UpdatePosition(pos)
  673 
  674 					return fe
  675 				} else {
  676 					return err
  677 				}
  678 			}
  679 
  680 			if withFrontMatter != nil {
  681 				if err := withFrontMatter(m); err != nil {
  682 					return err
  683 				}
  684 			}
  685 
  686 			frontMatterSet = true
  687 
  688 			next := iter.Peek()
  689 			if !next.IsDone() {
  690 				p.source.posMainContent = next.Pos
  691 			}
  692 
  693 			if !p.s.shouldBuild(p) {
  694 				// Nothing more to do.
  695 				return nil
  696 			}
  697 
  698 		case it.Type == pageparser.TypeLeadSummaryDivider:
  699 			posBody := -1
  700 			f := func(item pageparser.Item) bool {
  701 				if posBody == -1 && !item.IsDone() {
  702 					posBody = item.Pos
  703 				}
  704 
  705 				if item.IsNonWhitespace() {
  706 					p.truncated = true
  707 
  708 					// Done
  709 					return false
  710 				}
  711 				return true
  712 			}
  713 			iter.PeekWalk(f)
  714 
  715 			p.source.posSummaryEnd = it.Pos
  716 			p.source.posBodyStart = posBody
  717 			p.source.hasSummaryDivider = true
  718 
  719 			if markup != "html" {
  720 				// The content will be rendered by Goldmark or similar,
  721 				// and we need to track the summary.
  722 				rn.AddReplacement(internalSummaryDividerPre, it)
  723 			}
  724 
  725 		// Handle shortcode
  726 		case it.IsLeftShortcodeDelim():
  727 			// let extractShortcode handle left delim (will do so recursively)
  728 			iter.Backup()
  729 
  730 			currShortcode, err := s.extractShortcode(ordinal, 0, iter)
  731 			if err != nil {
  732 				return fail(err, it)
  733 			}
  734 
  735 			currShortcode.pos = it.Pos
  736 			currShortcode.length = iter.Current().Pos - it.Pos
  737 			if currShortcode.placeholder == "" {
  738 				currShortcode.placeholder = createShortcodePlaceholder("s", currShortcode.ordinal)
  739 			}
  740 
  741 			if currShortcode.name != "" {
  742 				s.addName(currShortcode.name)
  743 			}
  744 
  745 			if currShortcode.params == nil {
  746 				var s []string
  747 				currShortcode.params = s
  748 			}
  749 
  750 			currShortcode.placeholder = createShortcodePlaceholder("s", ordinal)
  751 			ordinal++
  752 			s.shortcodes = append(s.shortcodes, currShortcode)
  753 
  754 			rn.AddShortcode(currShortcode)
  755 
  756 		case it.Type == pageparser.TypeEmoji:
  757 			if emoji := helpers.Emoji(it.ValStr()); emoji != nil {
  758 				rn.AddReplacement(emoji, it)
  759 			} else {
  760 				rn.AddBytes(it)
  761 			}
  762 		case it.IsEOF():
  763 			break Loop
  764 		case it.IsError():
  765 			err := fail(errors.New(it.ValStr()), it)
  766 			currShortcode.err = err
  767 			return err
  768 
  769 		default:
  770 			rn.AddBytes(it)
  771 		}
  772 	}
  773 
  774 	if !frontMatterSet && withFrontMatter != nil {
  775 		// Page content without front matter. Assign default front matter from
  776 		// cascades etc.
  777 		if err := withFrontMatter(nil); err != nil {
  778 			return err
  779 		}
  780 	}
  781 
  782 	return nil
  783 }
  784 
  785 func (p *pageState) errorf(err error, format string, a ...any) error {
  786 	if herrors.UnwrapFileError(err) != nil {
  787 		// More isn't always better.
  788 		return err
  789 	}
  790 	args := append([]any{p.Language().Lang, p.pathOrTitle()}, a...)
  791 	args = append(args, err)
  792 	format = "[%s] page %q: " + format + ": %w"
  793 	if err == nil {
  794 		return fmt.Errorf(format, args...)
  795 	}
  796 	return fmt.Errorf(format, args...)
  797 }
  798 
  799 func (p *pageState) outputFormat() (f output.Format) {
  800 	if p.pageOutput == nil {
  801 		panic("no pageOutput")
  802 	}
  803 	return p.pageOutput.f
  804 }
  805 
  806 func (p *pageState) parseError(err error, input []byte, offset int) error {
  807 	pos := p.posFromInput(input, offset)
  808 	return herrors.NewFileErrorFromName(err, p.File().Filename()).UpdatePosition(pos)
  809 }
  810 
  811 func (p *pageState) pathOrTitle() string {
  812 	if !p.File().IsZero() {
  813 		return p.File().Filename()
  814 	}
  815 
  816 	if p.Pathc() != "" {
  817 		return p.Pathc()
  818 	}
  819 
  820 	return p.Title()
  821 }
  822 
  823 func (p *pageState) posFromPage(offset int) text.Position {
  824 	return p.posFromInput(p.source.parsed.Input(), offset)
  825 }
  826 
  827 func (p *pageState) posFromInput(input []byte, offset int) text.Position {
  828 	if offset < 0 {
  829 		return text.Position{
  830 			Filename: p.pathOrTitle(),
  831 		}
  832 	}
  833 	lf := []byte("\n")
  834 	input = input[:offset]
  835 	lineNumber := bytes.Count(input, lf) + 1
  836 	endOfLastLine := bytes.LastIndex(input, lf)
  837 
  838 	return text.Position{
  839 		Filename:     p.pathOrTitle(),
  840 		LineNumber:   lineNumber,
  841 		ColumnNumber: offset - endOfLastLine,
  842 		Offset:       offset,
  843 	}
  844 }
  845 
  846 func (p *pageState) posOffset(offset int) text.Position {
  847 	return p.posFromInput(p.source.parsed.Input(), offset)
  848 }
  849 
  850 // shiftToOutputFormat is serialized. The output format idx refers to the
  851 // full set of output formats for all sites.
  852 func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
  853 	if err := p.initPage(); err != nil {
  854 		return err
  855 	}
  856 
  857 	if len(p.pageOutputs) == 1 {
  858 		idx = 0
  859 	}
  860 
  861 	p.pageOutput = p.pageOutputs[idx]
  862 	if p.pageOutput == nil {
  863 		panic(fmt.Sprintf("pageOutput is nil for output idx %d", idx))
  864 	}
  865 
  866 	// Reset any built paginator. This will trigger when re-rendering pages in
  867 	// server mode.
  868 	if isRenderingSite && p.pageOutput.paginator != nil && p.pageOutput.paginator.current != nil {
  869 		p.pageOutput.paginator.reset()
  870 	}
  871 
  872 	if isRenderingSite {
  873 		cp := p.pageOutput.cp
  874 		if cp == nil && p.reusePageOutputContent() {
  875 			// Look for content to reuse.
  876 			for i := 0; i < len(p.pageOutputs); i++ {
  877 				if i == idx {
  878 					continue
  879 				}
  880 				po := p.pageOutputs[i]
  881 
  882 				if po.cp != nil {
  883 					cp = po.cp
  884 					break
  885 				}
  886 			}
  887 		}
  888 
  889 		if cp == nil {
  890 			var err error
  891 			cp, err = newPageContentOutput(p, p.pageOutput)
  892 			if err != nil {
  893 				return err
  894 			}
  895 		}
  896 		p.pageOutput.initContentProvider(cp)
  897 	} else {
  898 		// We attempt to assign pageContentOutputs while preparing each site
  899 		// for rendering and before rendering each site. This lets us share
  900 		// content between page outputs to conserve resources. But if a template
  901 		// unexpectedly calls a method of a ContentProvider that is not yet
  902 		// initialized, we assign a LazyContentProvider that performs the
  903 		// initialization just in time.
  904 		if lcp, ok := (p.pageOutput.ContentProvider.(*page.LazyContentProvider)); ok {
  905 			lcp.Reset()
  906 		} else {
  907 			lcp = page.NewLazyContentProvider(func() (page.OutputFormatContentProvider, error) {
  908 				cp, err := newPageContentOutput(p, p.pageOutput)
  909 				if err != nil {
  910 					return nil, err
  911 				}
  912 				return cp, nil
  913 			})
  914 			p.pageOutput.ContentProvider = lcp
  915 			p.pageOutput.TableOfContentsProvider = lcp
  916 			p.pageOutput.PageRenderProvider = lcp
  917 		}
  918 	}
  919 
  920 	return nil
  921 }
  922 
  923 // sourceRef returns the reference used by GetPage and ref/relref shortcodes to refer to
  924 // this page. It is prefixed with a "/".
  925 //
  926 // For pages that have a source file, it is returns the path to this file as an
  927 // absolute path rooted in this site's content dir.
  928 // For pages that do not (sections without content page etc.), it returns the
  929 // virtual path, consistent with where you would add a source file.
  930 func (p *pageState) sourceRef() string {
  931 	if !p.File().IsZero() {
  932 		sourcePath := p.File().Path()
  933 		if sourcePath != "" {
  934 			return "/" + filepath.ToSlash(sourcePath)
  935 		}
  936 	}
  937 
  938 	if len(p.SectionsEntries()) > 0 {
  939 		// no backing file, return the virtual source path
  940 		return "/" + p.SectionsPath()
  941 	}
  942 
  943 	return ""
  944 }
  945 
  946 func (s *Site) sectionsFromFile(fi source.File) []string {
  947 	dirname := fi.Dir()
  948 
  949 	dirname = strings.Trim(dirname, helpers.FilePathSeparator)
  950 	if dirname == "" {
  951 		return nil
  952 	}
  953 	parts := strings.Split(dirname, helpers.FilePathSeparator)
  954 
  955 	if fii, ok := fi.(*fileInfo); ok {
  956 		if len(parts) > 0 && fii.FileInfo().Meta().Classifier == files.ContentClassLeaf {
  957 			// my-section/mybundle/index.md => my-section
  958 			return parts[:len(parts)-1]
  959 		}
  960 	}
  961 
  962 	return parts
  963 }
  964 
  965 var (
  966 	_ page.Page         = (*pageWithOrdinal)(nil)
  967 	_ collections.Order = (*pageWithOrdinal)(nil)
  968 	_ pageWrapper       = (*pageWithOrdinal)(nil)
  969 )
  970 
  971 type pageWithOrdinal struct {
  972 	ordinal int
  973 	*pageState
  974 }
  975 
  976 func (p pageWithOrdinal) Ordinal() int {
  977 	return p.ordinal
  978 }
  979 
  980 func (p pageWithOrdinal) page() page.Page {
  981 	return p.pageState
  982 }