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