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