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