github.com/olliephillips/hugo@v0.42.2/hugolib/page.go (about) 1 // Copyright 2018 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 "errors" 20 "fmt" 21 "reflect" 22 "unicode" 23 24 "github.com/gohugoio/hugo/common/maps" 25 26 "github.com/gohugoio/hugo/langs" 27 28 "github.com/gohugoio/hugo/related" 29 30 "github.com/bep/gitmap" 31 32 "github.com/gohugoio/hugo/helpers" 33 "github.com/gohugoio/hugo/hugolib/pagemeta" 34 "github.com/gohugoio/hugo/resource" 35 36 "github.com/gohugoio/hugo/output" 37 "github.com/gohugoio/hugo/parser" 38 "github.com/mitchellh/mapstructure" 39 40 "html/template" 41 "io" 42 "path" 43 "path/filepath" 44 "regexp" 45 "runtime" 46 "strings" 47 "sync" 48 "time" 49 "unicode/utf8" 50 51 bp "github.com/gohugoio/hugo/bufferpool" 52 "github.com/gohugoio/hugo/compare" 53 "github.com/gohugoio/hugo/source" 54 "github.com/spf13/cast" 55 ) 56 57 var ( 58 cjk = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`) 59 60 // This is all the kinds we can expect to find in .Site.Pages. 61 allKindsInPages = []string{KindPage, KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm} 62 63 allKinds = append(allKindsInPages, []string{kindRSS, kindSitemap, kindRobotsTXT, kind404}...) 64 65 // Assert that it implements the Eqer interface. 66 _ compare.Eqer = (*Page)(nil) 67 _ compare.Eqer = (*PageOutput)(nil) 68 69 // Assert that it implements the interface needed for related searches. 70 _ related.Document = (*Page)(nil) 71 ) 72 73 const ( 74 KindPage = "page" 75 76 // The rest are node types; home page, sections etc. 77 78 KindHome = "home" 79 KindSection = "section" 80 KindTaxonomy = "taxonomy" 81 KindTaxonomyTerm = "taxonomyTerm" 82 83 // Temporary state. 84 kindUnknown = "unknown" 85 86 // The following are (currently) temporary nodes, 87 // i.e. nodes we create just to render in isolation. 88 kindRSS = "RSS" 89 kindSitemap = "sitemap" 90 kindRobotsTXT = "robotsTXT" 91 kind404 = "404" 92 93 pageResourceType = "page" 94 ) 95 96 type Page struct { 97 *pageInit 98 *pageContentInit 99 100 // Kind is the discriminator that identifies the different page types 101 // in the different page collections. This can, as an example, be used 102 // to to filter regular pages, find sections etc. 103 // Kind will, for the pages available to the templates, be one of: 104 // page, home, section, taxonomy and taxonomyTerm. 105 // It is of string type to make it easy to reason about in 106 // the templates. 107 Kind string 108 109 // Since Hugo 0.18 we got rid of the Node type. So now all pages are ... 110 // pages (regular pages, home page, sections etc.). 111 // Sections etc. will have child pages. These were earlier placed in .Data.Pages, 112 // but can now be more intuitively also be fetched directly from .Pages. 113 // This collection will be nil for regular pages. 114 Pages Pages 115 116 // Since Hugo 0.32, a Page can have resources such as images and CSS associated 117 // with itself. The resource will typically be placed relative to the Page, 118 // but templates should use the links (Permalink and RelPermalink) 119 // provided by the Resource object. 120 Resources resource.Resources 121 122 // This is the raw front matter metadata that is going to be assigned to 123 // the Resources above. 124 resourcesMetadata []map[string]interface{} 125 126 // translations will contain references to this page in other language 127 // if available. 128 translations Pages 129 130 // A key that maps to translation(s) of this page. This value is fetched 131 // from the page front matter. 132 translationKey string 133 134 // Params contains configuration defined in the params section of page frontmatter. 135 params map[string]interface{} 136 137 // Content sections 138 contentv template.HTML 139 summary template.HTML 140 TableOfContents template.HTML 141 // Passed to the shortcodes 142 pageWithoutContent *PageWithoutContent 143 144 Aliases []string 145 146 Images []Image 147 Videos []Video 148 149 truncated bool 150 Draft bool 151 Status string 152 153 // PageMeta contains page stats such as word count etc. 154 PageMeta 155 156 // Markup contains the markup type for the content. 157 Markup string 158 159 extension string 160 contentType string 161 renderable bool 162 163 Layout string 164 165 // For npn-renderable pages (see IsRenderable), the content itself 166 // is used as template and the template name is stored here. 167 selfLayout string 168 169 linkTitle string 170 171 frontmatter []byte 172 173 // rawContent is the raw content read from the content file. 174 rawContent []byte 175 176 // workContent is a copy of rawContent that may be mutated during site build. 177 workContent []byte 178 179 // whether the content is in a CJK language. 180 isCJKLanguage bool 181 182 shortcodeState *shortcodeHandler 183 184 // the content stripped for HTML 185 plain string // TODO should be []byte 186 plainWords []string 187 188 // rendering configuration 189 renderingConfig *helpers.BlackFriday 190 191 // menus 192 pageMenus PageMenus 193 194 Source 195 196 Position `json:"-"` 197 198 GitInfo *gitmap.GitInfo 199 200 // This was added as part of getting the Nodes (taxonomies etc.) to work as 201 // Pages in Hugo 0.18. 202 // It is deliberately named similar to Section, but not exported (for now). 203 // We currently have only one level of section in Hugo, but the page can live 204 // any number of levels down the file path. 205 // To support taxonomies like /categories/hugo etc. we will need to keep track 206 // of that information in a general way. 207 // So, sections represents the path to the content, i.e. a content file or a 208 // virtual content file in the situations where a taxonomy or a section etc. 209 // isn't accomanied by one. 210 sections []string 211 212 // Will only be set for sections and regular pages. 213 parent *Page 214 215 // When we create paginator pages, we create a copy of the original, 216 // but keep track of it here. 217 origOnCopy *Page 218 219 // Will only be set for section pages and the home page. 220 subSections Pages 221 222 s *Site 223 224 // Pulled over from old Node. TODO(bep) reorg and group (embed) 225 226 Site *SiteInfo `json:"-"` 227 228 title string 229 Description string 230 Keywords []string 231 Data map[string]interface{} 232 233 pagemeta.PageDates 234 235 Sitemap Sitemap 236 pagemeta.URLPath 237 frontMatterURL string 238 239 permalink string 240 relPermalink string 241 242 // relative target path without extension and any base path element from the baseURL. 243 // This is used to construct paths in the page resources. 244 relTargetPathBase string 245 // Is set to a forward slashed path if this is a Page resources living in a folder below its owner. 246 resourcePath string 247 248 // This is enabled if it is a leaf bundle (the "index.md" type) and it is marked as headless in front matter. 249 // Being headless means that 250 // 1. The page itself is not rendered to disk 251 // 2. It is not available in .Site.Pages etc. 252 // 3. But you can get it via .Site.GetPage 253 headless bool 254 255 layoutDescriptor output.LayoutDescriptor 256 257 scratch *Scratch 258 259 // It would be tempting to use the language set on the Site, but in they way we do 260 // multi-site processing, these values may differ during the initial page processing. 261 language *langs.Language 262 263 lang string 264 265 // The output formats this page will be rendered to. 266 outputFormats output.Formats 267 268 // This is the PageOutput that represents the first item in outputFormats. 269 // Use with care, as there are potential for inifinite loops. 270 mainPageOutput *PageOutput 271 272 targetPathDescriptorPrototype *targetPathDescriptor 273 } 274 275 func stackTrace() string { 276 trace := make([]byte, 2000) 277 runtime.Stack(trace, true) 278 return string(trace) 279 } 280 281 func (p *Page) initContent() { 282 283 p.contentInit.Do(func() { 284 // This careful dance is here to protect against circular loops in shortcode/content 285 // constructs. 286 // TODO(bep) context vs the remote shortcodes 287 ctx, cancel := context.WithTimeout(context.Background(), p.s.Timeout) 288 defer cancel() 289 c := make(chan error, 1) 290 291 go func() { 292 var err error 293 p.contentInitMu.Lock() 294 defer p.contentInitMu.Unlock() 295 296 err = p.prepareForRender() 297 if err != nil { 298 p.s.Log.ERROR.Printf("Failed to prepare page %q for render: %s", p.Path(), err) 299 return 300 } 301 302 if len(p.summary) == 0 { 303 if err = p.setAutoSummary(); err != nil { 304 err = fmt.Errorf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err) 305 } 306 } 307 c <- err 308 }() 309 310 select { 311 case <-ctx.Done(): 312 p.s.Log.WARN.Printf("WARNING: Timed out creating content for page %q (.Content will be empty). This is most likely a circular shortcode content loop that should be fixed. If this is just a shortcode calling a slow remote service, try to set \"timeout=20000\" (or higher, value is in milliseconds) in config.toml.\n", p.pathOrTitle()) 313 case err := <-c: 314 if err != nil { 315 p.s.Log.ERROR.Println(err) 316 } 317 } 318 }) 319 320 } 321 322 // This is sent to the shortcodes for this page. Not doing that will create an infinite regress. So, 323 // shortcodes can access .Page.TableOfContents, but not .Page.Content etc. 324 func (p *Page) withoutContent() *PageWithoutContent { 325 p.pageInit.withoutContentInit.Do(func() { 326 p.pageWithoutContent = &PageWithoutContent{Page: p} 327 }) 328 return p.pageWithoutContent 329 } 330 331 func (p *Page) Content() (interface{}, error) { 332 return p.content(), nil 333 } 334 335 func (p *Page) Truncated() bool { 336 p.initContent() 337 return p.truncated 338 } 339 340 func (p *Page) content() template.HTML { 341 p.initContent() 342 return p.contentv 343 } 344 345 func (p *Page) Summary() template.HTML { 346 p.initContent() 347 return p.summary 348 } 349 350 // Sites is a convenience method to get all the Hugo sites/languages configured. 351 func (p *Page) Sites() SiteInfos { 352 infos := make(SiteInfos, len(p.s.owner.Sites)) 353 for i, site := range p.s.owner.Sites { 354 infos[i] = &site.Info 355 } 356 357 return infos 358 } 359 360 // SearchKeywords implements the related.Document interface needed for fast page searches. 361 func (p *Page) SearchKeywords(cfg related.IndexConfig) ([]related.Keyword, error) { 362 363 v, err := p.Param(cfg.Name) 364 if err != nil { 365 return nil, err 366 } 367 368 return cfg.ToKeywords(v) 369 } 370 371 // PubDate is when this page was or will be published. 372 // NOTE: This is currently used for search only and is not meant to be used 373 // directly in templates. We need to consolidate the dates in this struct. 374 // TODO(bep) see https://github.com/gohugoio/hugo/issues/3854 375 func (p *Page) PubDate() time.Time { 376 if !p.PublishDate.IsZero() { 377 return p.PublishDate 378 } 379 return p.Date 380 } 381 382 func (*Page) ResourceType() string { 383 return pageResourceType 384 } 385 386 func (p *Page) RSSLink() template.URL { 387 f, found := p.outputFormats.GetByName(output.RSSFormat.Name) 388 if !found { 389 return "" 390 } 391 return template.URL(newOutputFormat(p, f).Permalink()) 392 } 393 394 func (p *Page) createLayoutDescriptor() output.LayoutDescriptor { 395 var section string 396 397 switch p.Kind { 398 case KindSection: 399 // In Hugo 0.22 we introduce nested sections, but we still only 400 // use the first level to pick the correct template. This may change in 401 // the future. 402 section = p.sections[0] 403 case KindTaxonomy, KindTaxonomyTerm: 404 section = p.s.taxonomiesPluralSingular[p.sections[0]] 405 default: 406 } 407 408 return output.LayoutDescriptor{ 409 Kind: p.Kind, 410 Type: p.Type(), 411 Lang: p.Lang(), 412 Layout: p.Layout, 413 Section: section, 414 } 415 } 416 417 // pageInit lazy initializes different parts of the page. It is extracted 418 // into its own type so we can easily create a copy of a given page. 419 type pageInit struct { 420 languageInit sync.Once 421 pageMenusInit sync.Once 422 pageMetaInit sync.Once 423 renderingConfigInit sync.Once 424 withoutContentInit sync.Once 425 } 426 427 type pageContentInit struct { 428 contentInitMu sync.Mutex 429 contentInit sync.Once 430 plainInit sync.Once 431 plainWordsInit sync.Once 432 } 433 434 func (p *Page) resetContent() { 435 p.pageContentInit = &pageContentInit{} 436 } 437 438 // IsNode returns whether this is an item of one of the list types in Hugo, 439 // i.e. not a regular content page. 440 func (p *Page) IsNode() bool { 441 return p.Kind != KindPage 442 } 443 444 // IsHome returns whether this is the home page. 445 func (p *Page) IsHome() bool { 446 return p.Kind == KindHome 447 } 448 449 // IsSection returns whether this is a section page. 450 func (p *Page) IsSection() bool { 451 return p.Kind == KindSection 452 } 453 454 // IsPage returns whether this is a regular content page. 455 func (p *Page) IsPage() bool { 456 return p.Kind == KindPage 457 } 458 459 // BundleType returns the bundle type: "leaf", "branch" or an empty string if it is none. 460 // See https://gohugo.io/content-management/page-bundles/ 461 func (p *Page) BundleType() string { 462 if p.IsNode() { 463 return "branch" 464 } 465 466 var source interface{} = p.Source.File 467 if fi, ok := source.(*fileInfo); ok { 468 switch fi.bundleTp { 469 case bundleBranch: 470 return "branch" 471 case bundleLeaf: 472 return "leaf" 473 } 474 } 475 476 return "" 477 } 478 479 type Source struct { 480 Frontmatter []byte 481 Content []byte 482 source.File 483 } 484 type PageMeta struct { 485 wordCount int 486 fuzzyWordCount int 487 readingTime int 488 Weight int 489 } 490 491 type Position struct { 492 Prev *Page 493 Next *Page 494 PrevInSection *Page 495 NextInSection *Page 496 } 497 498 type Pages []*Page 499 500 func (ps Pages) String() string { 501 return fmt.Sprintf("Pages(%d)", len(ps)) 502 } 503 504 func (ps Pages) findPagePosByFilename(filename string) int { 505 for i, x := range ps { 506 if x.Source.Filename() == filename { 507 return i 508 } 509 } 510 return -1 511 } 512 513 func (ps Pages) removeFirstIfFound(p *Page) Pages { 514 ii := -1 515 for i, pp := range ps { 516 if pp == p { 517 ii = i 518 break 519 } 520 } 521 522 if ii != -1 { 523 ps = append(ps[:ii], ps[ii+1:]...) 524 } 525 return ps 526 } 527 528 func (ps Pages) findPagePosByFilnamePrefix(prefix string) int { 529 if prefix == "" { 530 return -1 531 } 532 533 lenDiff := -1 534 currPos := -1 535 prefixLen := len(prefix) 536 537 // Find the closest match 538 for i, x := range ps { 539 if strings.HasPrefix(x.Source.Filename(), prefix) { 540 diff := len(x.Source.Filename()) - prefixLen 541 if lenDiff == -1 || diff < lenDiff { 542 lenDiff = diff 543 currPos = i 544 } 545 } 546 } 547 return currPos 548 } 549 550 // findPagePos Given a page, it will find the position in Pages 551 // will return -1 if not found 552 func (ps Pages) findPagePos(page *Page) int { 553 for i, x := range ps { 554 if x.Source.Filename() == page.Source.Filename() { 555 return i 556 } 557 } 558 return -1 559 } 560 561 func (p *Page) createWorkContentCopy() { 562 p.workContent = make([]byte, len(p.rawContent)) 563 copy(p.workContent, p.rawContent) 564 } 565 566 func (p *Page) Plain() string { 567 p.initContent() 568 p.initPlain(true) 569 return p.plain 570 } 571 572 func (p *Page) initPlain(lock bool) { 573 p.plainInit.Do(func() { 574 if lock { 575 p.contentInitMu.Lock() 576 defer p.contentInitMu.Unlock() 577 } 578 p.plain = helpers.StripHTML(string(p.contentv)) 579 }) 580 } 581 582 func (p *Page) PlainWords() []string { 583 p.initContent() 584 p.initPlainWords(true) 585 return p.plainWords 586 } 587 588 func (p *Page) initPlainWords(lock bool) { 589 p.plainWordsInit.Do(func() { 590 if lock { 591 p.contentInitMu.Lock() 592 defer p.contentInitMu.Unlock() 593 } 594 p.plainWords = strings.Fields(p.plain) 595 }) 596 } 597 598 // Param is a convenience method to do lookups in Page's and Site's Params map, 599 // in that order. 600 // 601 // This method is also implemented on Node and SiteInfo. 602 func (p *Page) Param(key interface{}) (interface{}, error) { 603 keyStr, err := cast.ToStringE(key) 604 if err != nil { 605 return nil, err 606 } 607 608 keyStr = strings.ToLower(keyStr) 609 result, _ := p.traverseDirect(keyStr) 610 if result != nil { 611 return result, nil 612 } 613 614 keySegments := strings.Split(keyStr, ".") 615 if len(keySegments) == 1 { 616 return nil, nil 617 } 618 619 return p.traverseNested(keySegments) 620 } 621 622 func (p *Page) traverseDirect(key string) (interface{}, error) { 623 keyStr := strings.ToLower(key) 624 if val, ok := p.params[keyStr]; ok { 625 return val, nil 626 } 627 628 return p.Site.Params[keyStr], nil 629 } 630 631 func (p *Page) traverseNested(keySegments []string) (interface{}, error) { 632 result := traverse(keySegments, p.params) 633 if result != nil { 634 return result, nil 635 } 636 637 result = traverse(keySegments, p.Site.Params) 638 if result != nil { 639 return result, nil 640 } 641 642 // Didn't find anything, but also no problems. 643 return nil, nil 644 } 645 646 func traverse(keys []string, m map[string]interface{}) interface{} { 647 // Shift first element off. 648 firstKey, rest := keys[0], keys[1:] 649 result := m[firstKey] 650 651 // No point in continuing here. 652 if result == nil { 653 return result 654 } 655 656 if len(rest) == 0 { 657 // That was the last key. 658 return result 659 } 660 661 // That was not the last key. 662 return traverse(rest, cast.ToStringMap(result)) 663 } 664 665 func (p *Page) Author() Author { 666 authors := p.Authors() 667 668 for _, author := range authors { 669 return author 670 } 671 return Author{} 672 } 673 674 func (p *Page) Authors() AuthorList { 675 authorKeys, ok := p.params["authors"] 676 if !ok { 677 return AuthorList{} 678 } 679 authors := authorKeys.([]string) 680 if len(authors) < 1 || len(p.Site.Authors) < 1 { 681 return AuthorList{} 682 } 683 684 al := make(AuthorList) 685 for _, author := range authors { 686 a, ok := p.Site.Authors[author] 687 if ok { 688 al[author] = a 689 } 690 } 691 return al 692 } 693 694 func (p *Page) UniqueID() string { 695 return p.Source.UniqueID() 696 } 697 698 // for logging 699 func (p *Page) lineNumRawContentStart() int { 700 return bytes.Count(p.frontmatter, []byte("\n")) + 1 701 } 702 703 var ( 704 internalSummaryDivider = []byte("HUGOMORE42") 705 ) 706 707 // replaceDivider replaces the <!--more--> with an internal value and returns 708 // whether the contentis truncated or not. 709 // Note: The content slice will be modified if needed. 710 func replaceDivider(content, from, to []byte) ([]byte, bool) { 711 dividerIdx := bytes.Index(content, from) 712 if dividerIdx == -1 { 713 return content, false 714 } 715 716 afterSummary := content[dividerIdx+len(from):] 717 718 // If the raw content has nothing but whitespace after the summary 719 // marker then the page shouldn't be marked as truncated. This check 720 // is simplest against the raw content because different markup engines 721 // (rst and asciidoc in particular) add div and p elements after the 722 // summary marker. 723 truncated := bytes.IndexFunc(afterSummary, func(r rune) bool { return !unicode.IsSpace(r) }) != -1 724 725 content = append(content[:dividerIdx], append(to, afterSummary...)...) 726 727 return content, truncated 728 729 } 730 731 // We have to replace the <!--more--> with something that survives all the 732 // rendering engines. 733 func (p *Page) replaceDivider(content []byte) []byte { 734 summaryDivider := helpers.SummaryDivider 735 if p.Markup == "org" { 736 summaryDivider = []byte("# more") 737 } 738 739 replaced, truncated := replaceDivider(content, summaryDivider, internalSummaryDivider) 740 741 p.truncated = truncated 742 743 return replaced 744 } 745 746 // Returns the page as summary and main if a user defined split is provided. 747 func (p *Page) setUserDefinedSummaryIfProvided(rawContentCopy []byte) (*summaryContent, error) { 748 749 sc, err := splitUserDefinedSummaryAndContent(p.Markup, rawContentCopy) 750 751 if err != nil { 752 return nil, err 753 } 754 755 if sc == nil { 756 // No divider found 757 return nil, nil 758 } 759 760 p.summary = helpers.BytesToHTML(sc.summary) 761 762 return sc, nil 763 } 764 765 // Make this explicit so there is no doubt about what is what. 766 type summaryContent struct { 767 summary []byte 768 content []byte 769 } 770 771 func splitUserDefinedSummaryAndContent(markup string, c []byte) (sc *summaryContent, err error) { 772 defer func() { 773 if r := recover(); r != nil { 774 err = fmt.Errorf("summary split failed: %s", r) 775 } 776 }() 777 778 c = bytes.TrimSpace(c) 779 startDivider := bytes.Index(c, internalSummaryDivider) 780 781 if startDivider == -1 { 782 return 783 } 784 785 endDivider := startDivider + len(internalSummaryDivider) 786 endSummary := startDivider 787 788 var ( 789 startMarkup []byte 790 endMarkup []byte 791 addDiv bool 792 ) 793 794 switch markup { 795 default: 796 startMarkup = []byte("<p>") 797 endMarkup = []byte("</p>") 798 case "asciidoc": 799 startMarkup = []byte("<div class=\"paragraph\">") 800 endMarkup = []byte("</div>") 801 case "rst": 802 startMarkup = []byte("<p>") 803 endMarkup = []byte("</p>") 804 addDiv = true 805 } 806 807 // Find the closest end/start markup string to the divider 808 fromStart := -1 809 fromIdx := bytes.LastIndex(c[:startDivider], startMarkup) 810 if fromIdx != -1 { 811 fromStart = startDivider - fromIdx - len(startMarkup) 812 } 813 fromEnd := bytes.Index(c[endDivider:], endMarkup) 814 815 if fromEnd != -1 && fromEnd <= fromStart { 816 endSummary = startDivider + fromEnd + len(endMarkup) 817 } else if fromStart != -1 && fromEnd != -1 { 818 endSummary = startDivider - fromStart - len(startMarkup) 819 } 820 821 withoutDivider := bytes.TrimSpace(append(c[:startDivider], c[endDivider:]...)) 822 var ( 823 summary []byte 824 ) 825 826 if len(withoutDivider) > 0 { 827 summary = bytes.TrimSpace(withoutDivider[:endSummary]) 828 } 829 830 if addDiv { 831 // For the rst 832 summary = append(append([]byte(nil), summary...), []byte("</div>")...) 833 } 834 835 if err != nil { 836 return 837 } 838 839 sc = &summaryContent{ 840 summary: summary, 841 content: withoutDivider, 842 } 843 844 return 845 } 846 847 func (p *Page) setAutoSummary() error { 848 var summary string 849 var truncated bool 850 // This careful init dance could probably be refined, but it is purely for performance 851 // reasons. These "plain" methods are expensive if the plain content is never actually 852 // used. 853 p.initPlain(false) 854 if p.isCJKLanguage { 855 p.initPlainWords(false) 856 summary, truncated = p.s.ContentSpec.TruncateWordsByRune(p.plainWords) 857 } else { 858 summary, truncated = p.s.ContentSpec.TruncateWordsToWholeSentence(p.plain) 859 } 860 p.summary = template.HTML(summary) 861 p.truncated = truncated 862 863 return nil 864 865 } 866 867 func (p *Page) renderContent(content []byte) []byte { 868 return p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{ 869 Content: content, RenderTOC: true, PageFmt: p.Markup, 870 Cfg: p.Language(), 871 DocumentID: p.UniqueID(), DocumentName: p.Path(), 872 Config: p.getRenderingConfig()}) 873 } 874 875 func (p *Page) getRenderingConfig() *helpers.BlackFriday { 876 p.renderingConfigInit.Do(func() { 877 bfParam := p.getParamToLower("blackfriday") 878 if bfParam == nil { 879 p.renderingConfig = p.s.ContentSpec.BlackFriday 880 return 881 } 882 // Create a copy so we can modify it. 883 bf := *p.s.ContentSpec.BlackFriday 884 p.renderingConfig = &bf 885 886 if p.Language() == nil { 887 panic(fmt.Sprintf("nil language for %s with source lang %s", p.BaseFileName(), p.lang)) 888 } 889 890 pageParam := cast.ToStringMap(bfParam) 891 if err := mapstructure.Decode(pageParam, &p.renderingConfig); err != nil { 892 p.s.Log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error()) 893 } 894 895 }) 896 897 return p.renderingConfig 898 } 899 900 func (s *Site) newPage(filename string) *Page { 901 fi := newFileInfo( 902 s.SourceSpec, 903 s.absContentDir(), 904 filename, 905 nil, 906 bundleNot, 907 ) 908 return s.newPageFromFile(fi) 909 } 910 911 func (s *Site) newPageFromFile(fi *fileInfo) *Page { 912 return &Page{ 913 pageInit: &pageInit{}, 914 pageContentInit: &pageContentInit{}, 915 Kind: kindFromFileInfo(fi), 916 contentType: "", 917 Source: Source{File: fi}, 918 Keywords: []string{}, Sitemap: Sitemap{Priority: -1}, 919 params: make(map[string]interface{}), 920 translations: make(Pages, 0), 921 sections: sectionsFromFile(fi), 922 Site: &s.Info, 923 s: s, 924 } 925 } 926 927 func (p *Page) IsRenderable() bool { 928 return p.renderable 929 } 930 931 func (p *Page) Type() string { 932 if p.contentType != "" { 933 return p.contentType 934 } 935 936 if x := p.Section(); x != "" { 937 return x 938 } 939 940 return "page" 941 } 942 943 // Section returns the first path element below the content root. Note that 944 // since Hugo 0.22 we support nested sections, but this will always be the first 945 // element of any nested path. 946 func (p *Page) Section() string { 947 if p.Kind == KindSection || p.Kind == KindTaxonomy || p.Kind == KindTaxonomyTerm { 948 return p.sections[0] 949 } 950 return p.Source.Section() 951 } 952 953 func (s *Site) NewPageFrom(buf io.Reader, name string) (*Page, error) { 954 p, err := s.NewPage(name) 955 if err != nil { 956 return p, err 957 } 958 _, err = p.ReadFrom(buf) 959 960 return p, err 961 } 962 963 func (s *Site) NewPage(name string) (*Page, error) { 964 if len(name) == 0 { 965 return nil, errors.New("Zero length page name") 966 } 967 968 // Create new page 969 p := s.newPage(name) 970 p.s = s 971 p.Site = &s.Info 972 973 return p, nil 974 } 975 976 func (p *Page) ReadFrom(buf io.Reader) (int64, error) { 977 // Parse for metadata & body 978 if err := p.parse(buf); err != nil { 979 p.s.Log.ERROR.Printf("%s for %s", err, p.File.Path()) 980 return 0, err 981 } 982 983 return int64(len(p.rawContent)), nil 984 } 985 986 func (p *Page) WordCount() int { 987 p.initContentPlainAndMeta() 988 return p.wordCount 989 } 990 991 func (p *Page) ReadingTime() int { 992 p.initContentPlainAndMeta() 993 return p.readingTime 994 } 995 996 func (p *Page) FuzzyWordCount() int { 997 p.initContentPlainAndMeta() 998 return p.fuzzyWordCount 999 } 1000 1001 func (p *Page) initContentPlainAndMeta() { 1002 p.initContent() 1003 p.initPlain(true) 1004 p.initPlainWords(true) 1005 p.initMeta() 1006 } 1007 1008 func (p *Page) initContentAndMeta() { 1009 p.initContent() 1010 p.initMeta() 1011 } 1012 1013 func (p *Page) initMeta() { 1014 p.pageMetaInit.Do(func() { 1015 if p.isCJKLanguage { 1016 p.wordCount = 0 1017 for _, word := range p.plainWords { 1018 runeCount := utf8.RuneCountInString(word) 1019 if len(word) == runeCount { 1020 p.wordCount++ 1021 } else { 1022 p.wordCount += runeCount 1023 } 1024 } 1025 } else { 1026 p.wordCount = helpers.TotalWords(p.plain) 1027 } 1028 1029 // TODO(bep) is set in a test. Fix that. 1030 if p.fuzzyWordCount == 0 { 1031 p.fuzzyWordCount = (p.wordCount + 100) / 100 * 100 1032 } 1033 1034 if p.isCJKLanguage { 1035 p.readingTime = (p.wordCount + 500) / 501 1036 } else { 1037 p.readingTime = (p.wordCount + 212) / 213 1038 } 1039 }) 1040 } 1041 1042 // HasShortcode return whether the page has a shortcode with the given name. 1043 // This method is mainly motivated with the Hugo Docs site's need for a list 1044 // of pages with the `todo` shortcode in it. 1045 func (p *Page) HasShortcode(name string) bool { 1046 if p.shortcodeState == nil { 1047 return false 1048 } 1049 1050 return p.shortcodeState.nameSet[name] 1051 } 1052 1053 // AllTranslations returns all translations, including the current Page. 1054 func (p *Page) AllTranslations() Pages { 1055 return p.translations 1056 } 1057 1058 // IsTranslated returns whether this content file is translated to 1059 // other language(s). 1060 func (p *Page) IsTranslated() bool { 1061 return len(p.translations) > 1 1062 } 1063 1064 // Translations returns the translations excluding the current Page. 1065 func (p *Page) Translations() Pages { 1066 translations := make(Pages, 0) 1067 for _, t := range p.translations { 1068 if t.Lang() != p.Lang() { 1069 translations = append(translations, t) 1070 } 1071 } 1072 return translations 1073 } 1074 1075 // TranslationKey returns the key used to map language translations of this page. 1076 // It will use the translationKey set in front matter if set, or the content path and 1077 // filename (excluding any language code and extension), e.g. "about/index". 1078 // The Page Kind is always prepended. 1079 func (p *Page) TranslationKey() string { 1080 if p.translationKey != "" { 1081 return p.Kind + "/" + p.translationKey 1082 } 1083 1084 if p.IsNode() { 1085 return path.Join(p.Kind, path.Join(p.sections...), p.TranslationBaseName()) 1086 } 1087 1088 return path.Join(p.Kind, filepath.ToSlash(p.Dir()), p.TranslationBaseName()) 1089 } 1090 1091 func (p *Page) LinkTitle() string { 1092 if len(p.linkTitle) > 0 { 1093 return p.linkTitle 1094 } 1095 return p.title 1096 } 1097 1098 func (p *Page) shouldBuild() bool { 1099 return shouldBuild(p.s.BuildFuture, p.s.BuildExpired, 1100 p.s.BuildDrafts, p.Draft, p.PublishDate, p.ExpiryDate) 1101 } 1102 1103 func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool, 1104 publishDate time.Time, expiryDate time.Time) bool { 1105 if !(buildDrafts || !Draft) { 1106 return false 1107 } 1108 if !buildFuture && !publishDate.IsZero() && publishDate.After(time.Now()) { 1109 return false 1110 } 1111 if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(time.Now()) { 1112 return false 1113 } 1114 return true 1115 } 1116 1117 func (p *Page) IsDraft() bool { 1118 return p.Draft 1119 } 1120 1121 func (p *Page) IsFuture() bool { 1122 if p.PublishDate.IsZero() { 1123 return false 1124 } 1125 return p.PublishDate.After(time.Now()) 1126 } 1127 1128 func (p *Page) IsExpired() bool { 1129 if p.ExpiryDate.IsZero() { 1130 return false 1131 } 1132 return p.ExpiryDate.Before(time.Now()) 1133 } 1134 1135 func (p *Page) URL() string { 1136 1137 if p.IsPage() && p.URLPath.URL != "" { 1138 // This is the url set in front matter 1139 return p.URLPath.URL 1140 } 1141 // Fall back to the relative permalink. 1142 u := p.RelPermalink() 1143 return u 1144 } 1145 1146 // Permalink returns the absolute URL to this Page. 1147 func (p *Page) Permalink() string { 1148 if p.headless { 1149 return "" 1150 } 1151 return p.permalink 1152 } 1153 1154 // RelPermalink gets a URL to the resource relative to the host. 1155 func (p *Page) RelPermalink() string { 1156 if p.headless { 1157 return "" 1158 } 1159 return p.relPermalink 1160 } 1161 1162 // See resource.Resource 1163 // This value is used, by default, in Resources.ByPrefix etc. 1164 func (p *Page) Name() string { 1165 if p.resourcePath != "" { 1166 return p.resourcePath 1167 } 1168 return p.title 1169 } 1170 1171 func (p *Page) Title() string { 1172 return p.title 1173 } 1174 1175 func (p *Page) Params() map[string]interface{} { 1176 return p.params 1177 } 1178 1179 func (p *Page) subResourceTargetPathFactory(base string) string { 1180 return path.Join(p.relTargetPathBase, base) 1181 } 1182 1183 func (p *Page) initMainOutputFormat() error { 1184 if p.mainPageOutput != nil { 1185 return nil 1186 } 1187 1188 outFormat := p.outputFormats[0] 1189 pageOutput, err := newPageOutput(p, false, false, outFormat) 1190 1191 if err != nil { 1192 return fmt.Errorf("Failed to create output page for type %q for page %q: %s", outFormat.Name, p.pathOrTitle(), err) 1193 } 1194 1195 p.mainPageOutput = pageOutput 1196 1197 return nil 1198 1199 } 1200 1201 func (p *Page) setContentInit(start bool) error { 1202 1203 if start { 1204 // This is a new language. 1205 p.shortcodeState.clearDelta() 1206 } 1207 updated := true 1208 if p.shortcodeState != nil { 1209 updated = p.shortcodeState.updateDelta() 1210 } 1211 1212 if updated { 1213 p.resetContent() 1214 } 1215 1216 for _, r := range p.Resources.ByType(pageResourceType) { 1217 p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages) 1218 bp := r.(*Page) 1219 if start { 1220 bp.shortcodeState.clearDelta() 1221 } 1222 if bp.shortcodeState != nil { 1223 updated = bp.shortcodeState.updateDelta() 1224 } 1225 if updated { 1226 bp.resetContent() 1227 } 1228 } 1229 1230 return nil 1231 1232 } 1233 1234 func (p *Page) prepareForRender() error { 1235 s := p.s 1236 1237 // If we got this far it means that this is either a new Page pointer 1238 // or a template or similar has changed so wee need to do a rerendering 1239 // of the shortcodes etc. 1240 1241 // If in watch mode or if we have multiple output formats, 1242 // we need to keep the original so we can 1243 // potentially repeat this process on rebuild. 1244 needsACopy := s.running() || len(p.outputFormats) > 1 1245 var workContentCopy []byte 1246 if needsACopy { 1247 workContentCopy = make([]byte, len(p.workContent)) 1248 copy(workContentCopy, p.workContent) 1249 } else { 1250 // Just reuse the same slice. 1251 workContentCopy = p.workContent 1252 } 1253 1254 var err error 1255 // Note: The shortcodes in a page cannot access the page content it lives in, 1256 // hence the withoutContent(). 1257 if workContentCopy, err = handleShortcodes(p.withoutContent(), workContentCopy); err != nil { 1258 s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err) 1259 } 1260 1261 if p.Markup != "html" { 1262 1263 // Now we know enough to create a summary of the page and count some words 1264 summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy) 1265 1266 if err != nil { 1267 s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err) 1268 } else if summaryContent != nil { 1269 workContentCopy = summaryContent.content 1270 } 1271 1272 p.contentv = helpers.BytesToHTML(workContentCopy) 1273 1274 } else { 1275 p.contentv = helpers.BytesToHTML(workContentCopy) 1276 } 1277 1278 return nil 1279 } 1280 1281 var ErrHasDraftAndPublished = errors.New("both draft and published parameters were found in page's frontmatter") 1282 1283 func (p *Page) update(frontmatter map[string]interface{}) error { 1284 if frontmatter == nil { 1285 return errors.New("missing frontmatter data") 1286 } 1287 // Needed for case insensitive fetching of params values 1288 maps.ToLower(frontmatter) 1289 1290 var mtime time.Time 1291 if p.Source.FileInfo() != nil { 1292 mtime = p.Source.FileInfo().ModTime() 1293 } 1294 1295 var gitAuthorDate time.Time 1296 if p.GitInfo != nil { 1297 gitAuthorDate = p.GitInfo.AuthorDate 1298 } 1299 1300 descriptor := &pagemeta.FrontMatterDescriptor{ 1301 Frontmatter: frontmatter, 1302 Params: p.params, 1303 Dates: &p.PageDates, 1304 PageURLs: &p.URLPath, 1305 BaseFilename: p.BaseFileName(), 1306 ModTime: mtime, 1307 GitAuthorDate: gitAuthorDate, 1308 } 1309 1310 // Handle the date separately 1311 // TODO(bep) we need to "do more" in this area so this can be split up and 1312 // more easily tested without the Page, but the coupling is strong. 1313 err := p.s.frontmatterHandler.HandleDates(descriptor) 1314 if err != nil { 1315 p.s.Log.ERROR.Printf("Failed to handle dates for page %q: %s", p.Path(), err) 1316 } 1317 1318 var draft, published, isCJKLanguage *bool 1319 for k, v := range frontmatter { 1320 loki := strings.ToLower(k) 1321 1322 if loki == "published" { // Intentionally undocumented 1323 vv, err := cast.ToBoolE(v) 1324 if err == nil { 1325 published = &vv 1326 } 1327 // published may also be a date 1328 continue 1329 } 1330 1331 if p.s.frontmatterHandler.IsDateKey(loki) { 1332 continue 1333 } 1334 1335 switch loki { 1336 case "title": 1337 p.title = cast.ToString(v) 1338 p.params[loki] = p.title 1339 case "linktitle": 1340 p.linkTitle = cast.ToString(v) 1341 p.params[loki] = p.linkTitle 1342 case "description": 1343 p.Description = cast.ToString(v) 1344 p.params[loki] = p.Description 1345 case "slug": 1346 p.Slug = cast.ToString(v) 1347 p.params[loki] = p.Slug 1348 case "url": 1349 if url := cast.ToString(v); strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { 1350 return fmt.Errorf("Only relative URLs are supported, %v provided", url) 1351 } 1352 p.URLPath.URL = cast.ToString(v) 1353 p.frontMatterURL = p.URLPath.URL 1354 p.params[loki] = p.URLPath.URL 1355 case "type": 1356 p.contentType = cast.ToString(v) 1357 p.params[loki] = p.contentType 1358 case "extension", "ext": 1359 p.extension = cast.ToString(v) 1360 p.params[loki] = p.extension 1361 case "keywords": 1362 p.Keywords = cast.ToStringSlice(v) 1363 p.params[loki] = p.Keywords 1364 case "headless": 1365 // For now, only the leaf bundles ("index.md") can be headless (i.e. produce no output). 1366 // We may expand on this in the future, but that gets more complex pretty fast. 1367 if p.TranslationBaseName() == "index" { 1368 p.headless = cast.ToBool(v) 1369 } 1370 p.params[loki] = p.headless 1371 case "outputs": 1372 o := cast.ToStringSlice(v) 1373 if len(o) > 0 { 1374 // Output formats are exlicitly set in front matter, use those. 1375 outFormats, err := p.s.outputFormatsConfig.GetByNames(o...) 1376 1377 if err != nil { 1378 p.s.Log.ERROR.Printf("Failed to resolve output formats: %s", err) 1379 } else { 1380 p.outputFormats = outFormats 1381 p.params[loki] = outFormats 1382 } 1383 1384 } 1385 case "draft": 1386 draft = new(bool) 1387 *draft = cast.ToBool(v) 1388 case "layout": 1389 p.Layout = cast.ToString(v) 1390 p.params[loki] = p.Layout 1391 case "markup": 1392 p.Markup = cast.ToString(v) 1393 p.params[loki] = p.Markup 1394 case "weight": 1395 p.Weight = cast.ToInt(v) 1396 p.params[loki] = p.Weight 1397 case "aliases": 1398 p.Aliases = cast.ToStringSlice(v) 1399 for _, alias := range p.Aliases { 1400 if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") { 1401 return fmt.Errorf("Only relative aliases are supported, %v provided", alias) 1402 } 1403 } 1404 p.params[loki] = p.Aliases 1405 case "status": 1406 p.Status = cast.ToString(v) 1407 p.params[loki] = p.Status 1408 case "sitemap": 1409 p.Sitemap = parseSitemap(cast.ToStringMap(v)) 1410 p.params[loki] = p.Sitemap 1411 case "iscjklanguage": 1412 isCJKLanguage = new(bool) 1413 *isCJKLanguage = cast.ToBool(v) 1414 case "translationkey": 1415 p.translationKey = cast.ToString(v) 1416 p.params[loki] = p.translationKey 1417 case "resources": 1418 var resources []map[string]interface{} 1419 handled := true 1420 1421 switch vv := v.(type) { 1422 case []map[interface{}]interface{}: 1423 for _, vvv := range vv { 1424 resources = append(resources, cast.ToStringMap(vvv)) 1425 } 1426 case []map[string]interface{}: 1427 for _, vvv := range vv { 1428 resources = append(resources, vvv) 1429 } 1430 case []interface{}: 1431 for _, vvv := range vv { 1432 switch vvvv := vvv.(type) { 1433 case map[interface{}]interface{}: 1434 resources = append(resources, cast.ToStringMap(vvvv)) 1435 case map[string]interface{}: 1436 resources = append(resources, vvvv) 1437 } 1438 } 1439 default: 1440 handled = false 1441 } 1442 1443 if handled { 1444 p.params[loki] = resources 1445 p.resourcesMetadata = resources 1446 break 1447 } 1448 fallthrough 1449 1450 default: 1451 // If not one of the explicit values, store in Params 1452 switch vv := v.(type) { 1453 case bool: 1454 p.params[loki] = vv 1455 case string: 1456 p.params[loki] = vv 1457 case int64, int32, int16, int8, int: 1458 p.params[loki] = vv 1459 case float64, float32: 1460 p.params[loki] = vv 1461 case time.Time: 1462 p.params[loki] = vv 1463 default: // handle array of strings as well 1464 switch vvv := vv.(type) { 1465 case []interface{}: 1466 if len(vvv) > 0 { 1467 switch vvv[0].(type) { 1468 case map[interface{}]interface{}: // Proper parsing structured array from YAML based FrontMatter 1469 p.params[loki] = vvv 1470 case map[string]interface{}: // Proper parsing structured array from JSON based FrontMatter 1471 p.params[loki] = vvv 1472 case []interface{}: 1473 p.params[loki] = vvv 1474 default: 1475 a := make([]string, len(vvv)) 1476 for i, u := range vvv { 1477 a[i] = cast.ToString(u) 1478 } 1479 1480 p.params[loki] = a 1481 } 1482 } else { 1483 p.params[loki] = []string{} 1484 } 1485 default: 1486 p.params[loki] = vv 1487 } 1488 } 1489 } 1490 } 1491 1492 // Try markup explicitly set in the frontmatter 1493 p.Markup = helpers.GuessType(p.Markup) 1494 if p.Markup == "unknown" { 1495 // Fall back to file extension (might also return "unknown") 1496 p.Markup = helpers.GuessType(p.Source.Ext()) 1497 } 1498 1499 if draft != nil && published != nil { 1500 p.Draft = *draft 1501 p.s.Log.ERROR.Printf("page %s has both draft and published settings in its frontmatter. Using draft.", p.File.Path()) 1502 return ErrHasDraftAndPublished 1503 } else if draft != nil { 1504 p.Draft = *draft 1505 } else if published != nil { 1506 p.Draft = !*published 1507 } 1508 p.params["draft"] = p.Draft 1509 1510 if isCJKLanguage != nil { 1511 p.isCJKLanguage = *isCJKLanguage 1512 } else if p.s.Cfg.GetBool("hasCJKLanguage") { 1513 if cjk.Match(p.rawContent) { 1514 p.isCJKLanguage = true 1515 } else { 1516 p.isCJKLanguage = false 1517 } 1518 } 1519 p.params["iscjklanguage"] = p.isCJKLanguage 1520 1521 return nil 1522 } 1523 1524 func (p *Page) GetParam(key string) interface{} { 1525 return p.getParam(key, false) 1526 } 1527 1528 func (p *Page) getParamToLower(key string) interface{} { 1529 return p.getParam(key, true) 1530 } 1531 1532 func (p *Page) getParam(key string, stringToLower bool) interface{} { 1533 v := p.params[strings.ToLower(key)] 1534 1535 if v == nil { 1536 return nil 1537 } 1538 1539 switch val := v.(type) { 1540 case bool: 1541 return val 1542 case string: 1543 if stringToLower { 1544 return strings.ToLower(val) 1545 } 1546 return val 1547 case int64, int32, int16, int8, int: 1548 return cast.ToInt(v) 1549 case float64, float32: 1550 return cast.ToFloat64(v) 1551 case time.Time: 1552 return val 1553 case []string: 1554 if stringToLower { 1555 return helpers.SliceToLower(val) 1556 } 1557 return v 1558 case map[string]interface{}: // JSON and TOML 1559 return v 1560 case map[interface{}]interface{}: // YAML 1561 return v 1562 } 1563 1564 p.s.Log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v)) 1565 return nil 1566 } 1567 1568 func (p *Page) HasMenuCurrent(menuID string, me *MenuEntry) bool { 1569 1570 sectionPagesMenu := p.Site.sectionPagesMenu 1571 1572 // page is labeled as "shadow-member" of the menu with the same identifier as the section 1573 if sectionPagesMenu != "" { 1574 section := p.Section() 1575 1576 if section != "" && sectionPagesMenu == menuID && section == me.Identifier { 1577 return true 1578 } 1579 } 1580 1581 if !me.HasChildren() { 1582 return false 1583 } 1584 1585 menus := p.Menus() 1586 1587 if m, ok := menus[menuID]; ok { 1588 1589 for _, child := range me.Children { 1590 if child.IsEqual(m) { 1591 return true 1592 } 1593 if p.HasMenuCurrent(menuID, child) { 1594 return true 1595 } 1596 } 1597 1598 } 1599 1600 if p.IsPage() { 1601 return false 1602 } 1603 1604 // The following logic is kept from back when Hugo had both Page and Node types. 1605 // TODO(bep) consolidate / clean 1606 nme := MenuEntry{Page: p, Name: p.title, URL: p.URL()} 1607 1608 for _, child := range me.Children { 1609 if nme.IsSameResource(child) { 1610 return true 1611 } 1612 if p.HasMenuCurrent(menuID, child) { 1613 return true 1614 } 1615 } 1616 1617 return false 1618 1619 } 1620 1621 func (p *Page) IsMenuCurrent(menuID string, inme *MenuEntry) bool { 1622 1623 menus := p.Menus() 1624 1625 if me, ok := menus[menuID]; ok { 1626 if me.IsEqual(inme) { 1627 return true 1628 } 1629 } 1630 1631 if p.IsPage() { 1632 return false 1633 } 1634 1635 // The following logic is kept from back when Hugo had both Page and Node types. 1636 // TODO(bep) consolidate / clean 1637 me := MenuEntry{Page: p, Name: p.title, URL: p.URL()} 1638 1639 if !me.IsSameResource(inme) { 1640 return false 1641 } 1642 1643 // this resource may be included in several menus 1644 // search for it to make sure that it is in the menu with the given menuId 1645 if menu, ok := (*p.Site.Menus)[menuID]; ok { 1646 for _, menuEntry := range *menu { 1647 if menuEntry.IsSameResource(inme) { 1648 return true 1649 } 1650 1651 descendantFound := p.isSameAsDescendantMenu(inme, menuEntry) 1652 if descendantFound { 1653 return descendantFound 1654 } 1655 1656 } 1657 } 1658 1659 return false 1660 } 1661 1662 func (p *Page) isSameAsDescendantMenu(inme *MenuEntry, parent *MenuEntry) bool { 1663 if parent.HasChildren() { 1664 for _, child := range parent.Children { 1665 if child.IsSameResource(inme) { 1666 return true 1667 } 1668 descendantFound := p.isSameAsDescendantMenu(inme, child) 1669 if descendantFound { 1670 return descendantFound 1671 } 1672 } 1673 } 1674 return false 1675 } 1676 1677 func (p *Page) Menus() PageMenus { 1678 p.pageMenusInit.Do(func() { 1679 p.pageMenus = PageMenus{} 1680 1681 if ms, ok := p.params["menu"]; ok { 1682 link := p.RelPermalink() 1683 1684 me := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight, URL: link} 1685 1686 // Could be the name of the menu to attach it to 1687 mname, err := cast.ToStringE(ms) 1688 1689 if err == nil { 1690 me.Menu = mname 1691 p.pageMenus[mname] = &me 1692 return 1693 } 1694 1695 // Could be a slice of strings 1696 mnames, err := cast.ToStringSliceE(ms) 1697 1698 if err == nil { 1699 for _, mname := range mnames { 1700 me.Menu = mname 1701 p.pageMenus[mname] = &me 1702 } 1703 return 1704 } 1705 1706 // Could be a structured menu entry 1707 menus, err := cast.ToStringMapE(ms) 1708 1709 if err != nil { 1710 p.s.Log.ERROR.Printf("unable to process menus for %q\n", p.title) 1711 } 1712 1713 for name, menu := range menus { 1714 menuEntry := MenuEntry{Page: p, Name: p.LinkTitle(), URL: link, Weight: p.Weight, Menu: name} 1715 if menu != nil { 1716 p.s.Log.DEBUG.Printf("found menu: %q, in %q\n", name, p.title) 1717 ime, err := cast.ToStringMapE(menu) 1718 if err != nil { 1719 p.s.Log.ERROR.Printf("unable to process menus for %q: %s", p.title, err) 1720 } 1721 1722 menuEntry.marshallMap(ime) 1723 } 1724 p.pageMenus[name] = &menuEntry 1725 1726 } 1727 } 1728 }) 1729 1730 return p.pageMenus 1731 } 1732 1733 func (p *Page) shouldRenderTo(f output.Format) bool { 1734 _, found := p.outputFormats.GetByName(f.Name) 1735 return found 1736 } 1737 1738 func (p *Page) parse(reader io.Reader) error { 1739 psr, err := parser.ReadFrom(reader) 1740 if err != nil { 1741 return err 1742 } 1743 1744 p.renderable = psr.IsRenderable() 1745 p.frontmatter = psr.FrontMatter() 1746 p.rawContent = psr.Content() 1747 p.lang = p.Source.File.Lang() 1748 1749 meta, err := psr.Metadata() 1750 if err != nil { 1751 return fmt.Errorf("failed to parse page metadata for %q: %s", p.File.Path(), err) 1752 } 1753 if meta == nil { 1754 // missing frontmatter equivalent to empty frontmatter 1755 meta = map[string]interface{}{} 1756 } 1757 1758 if p.s != nil && p.s.owner != nil { 1759 gi, enabled := p.s.owner.gitInfo.forPage(p) 1760 if gi != nil { 1761 p.GitInfo = gi 1762 } else if enabled { 1763 p.s.Log.WARN.Printf("Failed to find GitInfo for page %q", p.Path()) 1764 } 1765 } 1766 1767 return p.update(meta) 1768 } 1769 1770 func (p *Page) RawContent() string { 1771 return string(p.rawContent) 1772 } 1773 1774 func (p *Page) SetSourceContent(content []byte) { 1775 p.Source.Content = content 1776 } 1777 1778 func (p *Page) SetSourceMetaData(in interface{}, mark rune) (err error) { 1779 // See https://github.com/gohugoio/hugo/issues/2458 1780 defer func() { 1781 if r := recover(); r != nil { 1782 var ok bool 1783 err, ok = r.(error) 1784 if !ok { 1785 err = fmt.Errorf("error from marshal: %v", r) 1786 } 1787 } 1788 }() 1789 1790 buf := bp.GetBuffer() 1791 defer bp.PutBuffer(buf) 1792 1793 err = parser.InterfaceToFrontMatter(in, mark, buf) 1794 if err != nil { 1795 return 1796 } 1797 1798 _, err = buf.WriteRune('\n') 1799 if err != nil { 1800 return 1801 } 1802 1803 p.Source.Frontmatter = buf.Bytes() 1804 1805 return 1806 } 1807 1808 func (p *Page) SafeSaveSourceAs(path string) error { 1809 return p.saveSourceAs(path, true) 1810 } 1811 1812 func (p *Page) SaveSourceAs(path string) error { 1813 return p.saveSourceAs(path, false) 1814 } 1815 1816 func (p *Page) saveSourceAs(path string, safe bool) error { 1817 b := bp.GetBuffer() 1818 defer bp.PutBuffer(b) 1819 1820 b.Write(p.Source.Frontmatter) 1821 b.Write(p.Source.Content) 1822 1823 bc := make([]byte, b.Len(), b.Len()) 1824 copy(bc, b.Bytes()) 1825 1826 return p.saveSource(bc, path, safe) 1827 } 1828 1829 func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) { 1830 if !filepath.IsAbs(inpath) { 1831 inpath = p.s.PathSpec.AbsPathify(inpath) 1832 } 1833 p.s.Log.INFO.Println("creating", inpath) 1834 if safe { 1835 err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source) 1836 } else { 1837 err = helpers.WriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source) 1838 } 1839 if err != nil { 1840 return 1841 } 1842 return nil 1843 } 1844 1845 func (p *Page) SaveSource() error { 1846 return p.SaveSourceAs(p.FullFilePath()) 1847 } 1848 1849 // TODO(bep) lazy consolidate 1850 func (p *Page) processShortcodes() error { 1851 p.shortcodeState = newShortcodeHandler(p) 1852 tmpContent, err := p.shortcodeState.extractShortcodes(string(p.workContent), p.withoutContent()) 1853 if err != nil { 1854 return err 1855 } 1856 p.workContent = []byte(tmpContent) 1857 1858 return nil 1859 1860 } 1861 1862 func (p *Page) FullFilePath() string { 1863 return filepath.Join(p.Dir(), p.LogicalName()) 1864 } 1865 1866 // Pre render prepare steps 1867 1868 func (p *Page) prepareLayouts() error { 1869 // TODO(bep): Check the IsRenderable logic. 1870 if p.Kind == KindPage { 1871 if !p.IsRenderable() { 1872 self := "__" + p.UniqueID() 1873 err := p.s.TemplateHandler().AddLateTemplate(self, string(p.content())) 1874 if err != nil { 1875 return err 1876 } 1877 p.selfLayout = self 1878 } 1879 } 1880 1881 return nil 1882 } 1883 1884 func (p *Page) prepareData(s *Site) error { 1885 if p.Kind != KindSection { 1886 var pages Pages 1887 p.Data = make(map[string]interface{}) 1888 1889 switch p.Kind { 1890 case KindPage: 1891 case KindHome: 1892 pages = s.RegularPages 1893 case KindTaxonomy: 1894 plural := p.sections[0] 1895 term := p.sections[1] 1896 1897 if s.Info.preserveTaxonomyNames { 1898 if v, ok := s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, term)]; ok { 1899 term = v 1900 } 1901 } 1902 1903 singular := s.taxonomiesPluralSingular[plural] 1904 taxonomy := s.Taxonomies[plural].Get(term) 1905 1906 p.Data[singular] = taxonomy 1907 p.Data["Singular"] = singular 1908 p.Data["Plural"] = plural 1909 p.Data["Term"] = term 1910 pages = taxonomy.Pages() 1911 case KindTaxonomyTerm: 1912 plural := p.sections[0] 1913 singular := s.taxonomiesPluralSingular[plural] 1914 1915 p.Data["Singular"] = singular 1916 p.Data["Plural"] = plural 1917 p.Data["Terms"] = s.Taxonomies[plural] 1918 // keep the following just for legacy reasons 1919 p.Data["OrderedIndex"] = p.Data["Terms"] 1920 p.Data["Index"] = p.Data["Terms"] 1921 1922 // A list of all KindTaxonomy pages with matching plural 1923 for _, p := range s.findPagesByKind(KindTaxonomy) { 1924 if p.sections[0] == plural { 1925 pages = append(pages, p) 1926 } 1927 } 1928 } 1929 1930 p.Data["Pages"] = pages 1931 p.Pages = pages 1932 } 1933 1934 // Now we know enough to set missing dates on home page etc. 1935 p.updatePageDates() 1936 1937 return nil 1938 } 1939 1940 func (p *Page) updatePageDates() { 1941 // TODO(bep) there is a potential issue with page sorting for home pages 1942 // etc. without front matter dates set, but let us wrap the head around 1943 // that in another time. 1944 if !p.IsNode() { 1945 return 1946 } 1947 1948 if !p.Date.IsZero() { 1949 if p.Lastmod.IsZero() { 1950 p.Lastmod = p.Date 1951 } 1952 return 1953 } else if !p.Lastmod.IsZero() { 1954 if p.Date.IsZero() { 1955 p.Date = p.Lastmod 1956 } 1957 return 1958 } 1959 1960 // Set it to the first non Zero date in children 1961 var foundDate, foundLastMod bool 1962 1963 for _, child := range p.Pages { 1964 if !child.Date.IsZero() { 1965 p.Date = child.Date 1966 foundDate = true 1967 } 1968 if !child.Lastmod.IsZero() { 1969 p.Lastmod = child.Lastmod 1970 foundLastMod = true 1971 } 1972 1973 if foundDate && foundLastMod { 1974 break 1975 } 1976 } 1977 } 1978 1979 // copy creates a copy of this page with the lazy sync.Once vars reset 1980 // so they will be evaluated again, for word count calculations etc. 1981 func (p *Page) copy(initContent bool) *Page { 1982 p.contentInitMu.Lock() 1983 c := *p 1984 p.contentInitMu.Unlock() 1985 c.pageInit = &pageInit{} 1986 if initContent { 1987 if len(p.outputFormats) < 2 { 1988 panic(fmt.Sprintf("programming error: page %q should not need to rebuild content as it has only %d outputs", p.Path(), len(p.outputFormats))) 1989 } 1990 c.pageContentInit = &pageContentInit{} 1991 } 1992 return &c 1993 } 1994 1995 func (p *Page) Hugo() *HugoInfo { 1996 return hugoInfo 1997 } 1998 1999 func (p *Page) Ref(refs ...string) (string, error) { 2000 if len(refs) == 0 { 2001 return "", nil 2002 } 2003 if len(refs) > 1 { 2004 return p.Site.Ref(refs[0], nil, refs[1]) 2005 } 2006 return p.Site.Ref(refs[0], nil) 2007 } 2008 2009 func (p *Page) RelRef(refs ...string) (string, error) { 2010 if len(refs) == 0 { 2011 return "", nil 2012 } 2013 if len(refs) > 1 { 2014 return p.Site.RelRef(refs[0], nil, refs[1]) 2015 } 2016 return p.Site.RelRef(refs[0], nil) 2017 } 2018 2019 func (p *Page) String() string { 2020 if p.Path() != "" { 2021 return fmt.Sprintf("Page(%s)", p.Path()) 2022 } 2023 return fmt.Sprintf("Page(%q)", p.title) 2024 2025 } 2026 2027 // Scratch returns the writable context associated with this Page. 2028 func (p *Page) Scratch() *Scratch { 2029 if p.scratch == nil { 2030 p.scratch = newScratch() 2031 } 2032 return p.scratch 2033 } 2034 2035 func (p *Page) Language() *langs.Language { 2036 p.initLanguage() 2037 return p.language 2038 } 2039 2040 func (p *Page) Lang() string { 2041 // When set, Language can be different from lang in the case where there is a 2042 // content file (doc.sv.md) with language indicator, but there is no language 2043 // config for that language. Then the language will fall back on the site default. 2044 if p.Language() != nil { 2045 return p.Language().Lang 2046 } 2047 return p.lang 2048 } 2049 2050 func (p *Page) isNewTranslation(candidate *Page) bool { 2051 2052 if p.Kind != candidate.Kind { 2053 return false 2054 } 2055 2056 if p.Kind == KindPage || p.Kind == kindUnknown { 2057 panic("Node type not currently supported for this op") 2058 } 2059 2060 // At this point, we know that this is a traditional Node (home page, section, taxonomy) 2061 // It represents the same node, but different language, if the sections is the same. 2062 if len(p.sections) != len(candidate.sections) { 2063 return false 2064 } 2065 2066 for i := 0; i < len(p.sections); i++ { 2067 if p.sections[i] != candidate.sections[i] { 2068 return false 2069 } 2070 } 2071 2072 // Finally check that it is not already added. 2073 for _, translation := range p.translations { 2074 if candidate == translation { 2075 return false 2076 } 2077 } 2078 2079 return true 2080 2081 } 2082 2083 func (p *Page) shouldAddLanguagePrefix() bool { 2084 if !p.Site.IsMultiLingual() { 2085 return false 2086 } 2087 2088 if p.s.owner.IsMultihost() { 2089 return true 2090 } 2091 2092 if p.Lang() == "" { 2093 return false 2094 } 2095 2096 if !p.Site.defaultContentLanguageInSubdir && p.Lang() == p.Site.multilingual.DefaultLang.Lang { 2097 return false 2098 } 2099 2100 return true 2101 } 2102 2103 func (p *Page) initLanguage() { 2104 p.languageInit.Do(func() { 2105 if p.language != nil { 2106 return 2107 } 2108 2109 ml := p.Site.multilingual 2110 if ml == nil { 2111 panic("Multilanguage not set") 2112 } 2113 if p.lang == "" { 2114 p.lang = ml.DefaultLang.Lang 2115 p.language = ml.DefaultLang 2116 return 2117 } 2118 2119 language := ml.Language(p.lang) 2120 2121 if language == nil { 2122 // It can be a file named stefano.chiodino.md. 2123 p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", p.lang) 2124 language = ml.DefaultLang 2125 } 2126 2127 p.language = language 2128 2129 }) 2130 } 2131 2132 func (p *Page) LanguagePrefix() string { 2133 return p.Site.LanguagePrefix 2134 } 2135 2136 func (p *Page) addLangPathPrefixIfFlagSet(outfile string, should bool) string { 2137 if helpers.IsAbsURL(outfile) { 2138 return outfile 2139 } 2140 2141 if !should { 2142 return outfile 2143 } 2144 2145 hadSlashSuffix := strings.HasSuffix(outfile, "/") 2146 2147 outfile = "/" + path.Join(p.Lang(), outfile) 2148 if hadSlashSuffix { 2149 outfile += "/" 2150 } 2151 return outfile 2152 } 2153 2154 func sectionsFromFile(fi *fileInfo) []string { 2155 dirname := fi.Dir() 2156 dirname = strings.Trim(dirname, helpers.FilePathSeparator) 2157 if dirname == "" { 2158 return nil 2159 } 2160 parts := strings.Split(dirname, helpers.FilePathSeparator) 2161 2162 if fi.bundleTp == bundleLeaf && len(parts) > 0 { 2163 // my-section/mybundle/index.md => my-section 2164 return parts[:len(parts)-1] 2165 } 2166 2167 return parts 2168 } 2169 2170 func kindFromFileInfo(fi *fileInfo) string { 2171 if fi.TranslationBaseName() == "_index" { 2172 if fi.Dir() == "" { 2173 return KindHome 2174 } 2175 // Could be index for section, taxonomy, taxonomy term 2176 // We don't know enough yet to determine which 2177 return kindUnknown 2178 } 2179 return KindPage 2180 } 2181 2182 func (p *Page) setValuesForKind(s *Site) { 2183 if p.Kind == kindUnknown { 2184 // This is either a taxonomy list, taxonomy term or a section 2185 nodeType := s.kindFromSections(p.sections) 2186 2187 if nodeType == kindUnknown { 2188 panic(fmt.Sprintf("Unable to determine page kind from %q", p.sections)) 2189 } 2190 2191 p.Kind = nodeType 2192 } 2193 2194 switch p.Kind { 2195 case KindHome: 2196 p.URLPath.URL = "/" 2197 case KindPage: 2198 default: 2199 if p.URLPath.URL == "" { 2200 p.URLPath.URL = "/" + path.Join(p.sections...) + "/" 2201 } 2202 } 2203 } 2204 2205 // Used in error logs. 2206 func (p *Page) pathOrTitle() string { 2207 if p.Path() != "" { 2208 return p.Path() 2209 } 2210 return p.title 2211 }