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