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 }