github.com/gohugoio/hugo@v0.88.1/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 }