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