github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugolib/content_map_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 "context" 18 "fmt" 19 "path" 20 "path/filepath" 21 "strings" 22 "sync" 23 24 "github.com/gohugoio/hugo/common/maps" 25 26 "github.com/gohugoio/hugo/common/types" 27 "github.com/gohugoio/hugo/resources" 28 29 "github.com/gohugoio/hugo/common/hugio" 30 "github.com/gohugoio/hugo/hugofs" 31 "github.com/gohugoio/hugo/hugofs/files" 32 "github.com/gohugoio/hugo/parser/pageparser" 33 "github.com/gohugoio/hugo/resources/page" 34 "github.com/gohugoio/hugo/resources/resource" 35 "github.com/spf13/cast" 36 37 "github.com/gohugoio/hugo/common/para" 38 "github.com/pkg/errors" 39 ) 40 41 func newPageMaps(h *HugoSites) *pageMaps { 42 mps := make([]*pageMap, len(h.Sites)) 43 for i, s := range h.Sites { 44 mps[i] = s.pageMap 45 } 46 return &pageMaps{ 47 workers: para.New(h.numWorkers), 48 pmaps: mps, 49 } 50 } 51 52 type pageMap struct { 53 s *Site 54 *contentMap 55 } 56 57 func (m *pageMap) Len() int { 58 l := 0 59 for _, t := range m.contentMap.pageTrees { 60 l += t.Len() 61 } 62 return l 63 } 64 65 func (m *pageMap) createMissingTaxonomyNodes() error { 66 if m.cfg.taxonomyDisabled { 67 return nil 68 } 69 m.taxonomyEntries.Walk(func(s string, v interface{}) bool { 70 n := v.(*contentNode) 71 vi := n.viewInfo 72 k := cleanSectionTreeKey(vi.name.plural + "/" + vi.termKey) 73 74 if _, found := m.taxonomies.Get(k); !found { 75 vic := &contentBundleViewInfo{ 76 name: vi.name, 77 termKey: vi.termKey, 78 termOrigin: vi.termOrigin, 79 } 80 m.taxonomies.Insert(k, &contentNode{viewInfo: vic}) 81 } 82 return false 83 }) 84 85 return nil 86 } 87 88 func (m *pageMap) newPageFromContentNode(n *contentNode, parentBucket *pagesMapBucket, owner *pageState) (*pageState, error) { 89 if n.fi == nil { 90 panic("FileInfo must (currently) be set") 91 } 92 93 f, err := newFileInfo(m.s.SourceSpec, n.fi) 94 if err != nil { 95 return nil, err 96 } 97 98 meta := n.fi.Meta() 99 content := func() (hugio.ReadSeekCloser, error) { 100 return meta.Open() 101 } 102 103 bundled := owner != nil 104 s := m.s 105 106 sections := s.sectionsFromFile(f) 107 108 kind := s.kindFromFileInfoOrSections(f, sections) 109 if kind == page.KindTerm { 110 s.PathSpec.MakePathsSanitized(sections) 111 } 112 113 metaProvider := &pageMeta{kind: kind, sections: sections, bundled: bundled, s: s, f: f} 114 115 ps, err := newPageBase(metaProvider) 116 if err != nil { 117 return nil, err 118 } 119 120 if n.fi.Meta().IsRootFile { 121 // Make sure that the bundle/section we start walking from is always 122 // rendered. 123 // This is only relevant in server fast render mode. 124 ps.forceRender = true 125 } 126 127 n.p = ps 128 if ps.IsNode() { 129 ps.bucket = newPageBucket(ps) 130 } 131 132 gi, err := s.h.gitInfoForPage(ps) 133 if err != nil { 134 return nil, errors.Wrap(err, "failed to load Git data") 135 } 136 ps.gitInfo = gi 137 138 owners, err := s.h.codeownersForPage(ps) 139 if err != nil { 140 return nil, errors.Wrap(err, "failed to load CODEOWNERS") 141 } 142 ps.codeowners = owners 143 144 r, err := content() 145 if err != nil { 146 return nil, err 147 } 148 defer r.Close() 149 150 parseResult, err := pageparser.Parse( 151 r, 152 pageparser.Config{EnableEmoji: s.siteCfg.enableEmoji}, 153 ) 154 if err != nil { 155 return nil, err 156 } 157 158 ps.pageContent = pageContent{ 159 source: rawPageContent{ 160 parsed: parseResult, 161 posMainContent: -1, 162 posSummaryEnd: -1, 163 posBodyStart: -1, 164 }, 165 } 166 167 ps.shortcodeState = newShortcodeHandler(ps, ps.s, nil) 168 169 if err := ps.mapContent(parentBucket, metaProvider); err != nil { 170 return nil, ps.wrapError(err) 171 } 172 173 if err := metaProvider.applyDefaultValues(n); err != nil { 174 return nil, err 175 } 176 177 ps.init.Add(func() (interface{}, error) { 178 pp, err := newPagePaths(s, ps, metaProvider) 179 if err != nil { 180 return nil, err 181 } 182 183 outputFormatsForPage := ps.m.outputFormats() 184 185 // Prepare output formats for all sites. 186 // We do this even if this page does not get rendered on 187 // its own. It may be referenced via .Site.GetPage and 188 // it will then need an output format. 189 ps.pageOutputs = make([]*pageOutput, len(ps.s.h.renderFormats)) 190 created := make(map[string]*pageOutput) 191 shouldRenderPage := !ps.m.noRender() 192 193 for i, f := range ps.s.h.renderFormats { 194 if po, found := created[f.Name]; found { 195 ps.pageOutputs[i] = po 196 continue 197 } 198 199 render := shouldRenderPage 200 if render { 201 _, render = outputFormatsForPage.GetByName(f.Name) 202 } 203 204 po := newPageOutput(ps, pp, f, render) 205 206 // Create a content provider for the first, 207 // we may be able to reuse it. 208 if i == 0 { 209 contentProvider, err := newPageContentOutput(ps, po) 210 if err != nil { 211 return nil, err 212 } 213 po.initContentProvider(contentProvider) 214 } 215 216 ps.pageOutputs[i] = po 217 created[f.Name] = po 218 219 } 220 221 if err := ps.initCommonProviders(pp); err != nil { 222 return nil, err 223 } 224 225 return nil, nil 226 }) 227 228 ps.parent = owner 229 230 return ps, nil 231 } 232 233 func (m *pageMap) newResource(fim hugofs.FileMetaInfo, owner *pageState) (resource.Resource, error) { 234 if owner == nil { 235 panic("owner is nil") 236 } 237 // TODO(bep) consolidate with multihost logic + clean up 238 outputFormats := owner.m.outputFormats() 239 seen := make(map[string]bool) 240 var targetBasePaths []string 241 // Make sure bundled resources are published to all of the output formats' 242 // sub paths. 243 for _, f := range outputFormats { 244 p := f.Path 245 if seen[p] { 246 continue 247 } 248 seen[p] = true 249 targetBasePaths = append(targetBasePaths, p) 250 251 } 252 253 meta := fim.Meta() 254 r := func() (hugio.ReadSeekCloser, error) { 255 return meta.Open() 256 } 257 258 target := strings.TrimPrefix(meta.Path, owner.File().Dir()) 259 260 return owner.s.ResourceSpec.New( 261 resources.ResourceSourceDescriptor{ 262 TargetPaths: owner.getTargetPaths, 263 OpenReadSeekCloser: r, 264 FileInfo: fim, 265 RelTargetFilename: target, 266 TargetBasePaths: targetBasePaths, 267 LazyPublish: !owner.m.buildConfig.PublishResources, 268 }) 269 } 270 271 func (m *pageMap) createSiteTaxonomies() error { 272 m.s.taxonomies = make(TaxonomyList) 273 var walkErr error 274 m.taxonomies.Walk(func(s string, v interface{}) bool { 275 n := v.(*contentNode) 276 t := n.viewInfo 277 278 viewName := t.name 279 280 if t.termKey == "" { 281 m.s.taxonomies[viewName.plural] = make(Taxonomy) 282 } else { 283 taxonomy := m.s.taxonomies[viewName.plural] 284 if taxonomy == nil { 285 walkErr = errors.Errorf("missing taxonomy: %s", viewName.plural) 286 return true 287 } 288 m.taxonomyEntries.WalkPrefix(s, func(ss string, v interface{}) bool { 289 b2 := v.(*contentNode) 290 info := b2.viewInfo 291 taxonomy.add(info.termKey, page.NewWeightedPage(info.weight, info.ref.p, n.p)) 292 293 return false 294 }) 295 } 296 297 return false 298 }) 299 300 for _, taxonomy := range m.s.taxonomies { 301 for _, v := range taxonomy { 302 v.Sort() 303 } 304 } 305 306 return walkErr 307 } 308 309 func (m *pageMap) createListAllPages() page.Pages { 310 pages := make(page.Pages, 0) 311 312 m.contentMap.pageTrees.Walk(func(s string, n *contentNode) bool { 313 if n.p == nil { 314 panic(fmt.Sprintf("BUG: page not set for %q", s)) 315 } 316 if contentTreeNoListAlwaysFilter(s, n) { 317 return false 318 } 319 pages = append(pages, n.p) 320 return false 321 }) 322 323 page.SortByDefault(pages) 324 return pages 325 } 326 327 func (m *pageMap) assemblePages() error { 328 m.taxonomyEntries.DeletePrefix("/") 329 330 if err := m.assembleSections(); err != nil { 331 return err 332 } 333 334 var err error 335 336 if err != nil { 337 return err 338 } 339 340 m.pages.Walk(func(s string, v interface{}) bool { 341 n := v.(*contentNode) 342 343 var shouldBuild bool 344 345 defer func() { 346 // Make sure we always rebuild the view cache. 347 if shouldBuild && err == nil && n.p != nil { 348 m.attachPageToViews(s, n) 349 } 350 }() 351 352 if n.p != nil { 353 // A rebuild 354 shouldBuild = true 355 return false 356 } 357 358 var parent *contentNode 359 var parentBucket *pagesMapBucket 360 361 _, parent = m.getSection(s) 362 if parent == nil { 363 panic(fmt.Sprintf("BUG: parent not set for %q", s)) 364 } 365 parentBucket = parent.p.bucket 366 367 n.p, err = m.newPageFromContentNode(n, parentBucket, nil) 368 if err != nil { 369 return true 370 } 371 372 shouldBuild = !(n.p.Kind() == page.KindPage && m.cfg.pageDisabled) && m.s.shouldBuild(n.p) 373 if !shouldBuild { 374 m.deletePage(s) 375 return false 376 } 377 378 n.p.treeRef = &contentTreeRef{ 379 m: m, 380 t: m.pages, 381 n: n, 382 key: s, 383 } 384 385 if err = m.assembleResources(s, n.p, parentBucket); err != nil { 386 return true 387 } 388 389 return false 390 }) 391 392 m.deleteOrphanSections() 393 394 return err 395 } 396 397 func (m *pageMap) assembleResources(s string, p *pageState, parentBucket *pagesMapBucket) error { 398 var err error 399 400 m.resources.WalkPrefix(s, func(s string, v interface{}) bool { 401 n := v.(*contentNode) 402 meta := n.fi.Meta() 403 classifier := meta.Classifier 404 var r resource.Resource 405 switch classifier { 406 case files.ContentClassContent: 407 var rp *pageState 408 rp, err = m.newPageFromContentNode(n, parentBucket, p) 409 if err != nil { 410 return true 411 } 412 rp.m.resourcePath = filepath.ToSlash(strings.TrimPrefix(rp.File().Path(), p.File().Dir())) 413 r = rp 414 415 case files.ContentClassFile: 416 r, err = m.newResource(n.fi, p) 417 if err != nil { 418 return true 419 } 420 default: 421 panic(fmt.Sprintf("invalid classifier: %q", classifier)) 422 } 423 424 p.resources = append(p.resources, r) 425 return false 426 }) 427 428 return err 429 } 430 431 func (m *pageMap) assembleSections() error { 432 var sectionsToDelete []string 433 var err error 434 435 m.sections.Walk(func(s string, v interface{}) bool { 436 n := v.(*contentNode) 437 var shouldBuild bool 438 439 defer func() { 440 // Make sure we always rebuild the view cache. 441 if shouldBuild && err == nil && n.p != nil { 442 m.attachPageToViews(s, n) 443 if n.p.IsHome() { 444 m.s.home = n.p 445 } 446 } 447 }() 448 449 sections := m.splitKey(s) 450 451 if n.p != nil { 452 if n.p.IsHome() { 453 m.s.home = n.p 454 } 455 shouldBuild = true 456 return false 457 } 458 459 var parent *contentNode 460 var parentBucket *pagesMapBucket 461 462 if s != "/" { 463 _, parent = m.getSection(s) 464 if parent == nil || parent.p == nil { 465 panic(fmt.Sprintf("BUG: parent not set for %q", s)) 466 } 467 } 468 469 if parent != nil { 470 parentBucket = parent.p.bucket 471 } else if s == "/" { 472 parentBucket = m.s.siteBucket 473 } 474 475 kind := page.KindSection 476 if s == "/" { 477 kind = page.KindHome 478 } 479 480 if n.fi != nil { 481 n.p, err = m.newPageFromContentNode(n, parentBucket, nil) 482 if err != nil { 483 return true 484 } 485 } else { 486 n.p = m.s.newPage(n, parentBucket, kind, "", sections...) 487 } 488 489 shouldBuild = m.s.shouldBuild(n.p) 490 if !shouldBuild { 491 sectionsToDelete = append(sectionsToDelete, s) 492 return false 493 } 494 495 n.p.treeRef = &contentTreeRef{ 496 m: m, 497 t: m.sections, 498 n: n, 499 key: s, 500 } 501 502 if err = m.assembleResources(s+cmLeafSeparator, n.p, parentBucket); err != nil { 503 return true 504 } 505 506 return false 507 }) 508 509 for _, s := range sectionsToDelete { 510 m.deleteSectionByPath(s) 511 } 512 513 return err 514 } 515 516 func (m *pageMap) assembleTaxonomies() error { 517 var taxonomiesToDelete []string 518 var err error 519 520 m.taxonomies.Walk(func(s string, v interface{}) bool { 521 n := v.(*contentNode) 522 523 if n.p != nil { 524 return false 525 } 526 527 kind := n.viewInfo.kind() 528 sections := n.viewInfo.sections() 529 530 _, parent := m.getTaxonomyParent(s) 531 if parent == nil || parent.p == nil { 532 panic(fmt.Sprintf("BUG: parent not set for %q", s)) 533 } 534 parentBucket := parent.p.bucket 535 536 if n.fi != nil { 537 n.p, err = m.newPageFromContentNode(n, parent.p.bucket, nil) 538 if err != nil { 539 return true 540 } 541 } else { 542 title := "" 543 if kind == page.KindTerm { 544 title = n.viewInfo.term() 545 } 546 n.p = m.s.newPage(n, parent.p.bucket, kind, title, sections...) 547 } 548 549 if !m.s.shouldBuild(n.p) { 550 taxonomiesToDelete = append(taxonomiesToDelete, s) 551 return false 552 } 553 554 n.p.treeRef = &contentTreeRef{ 555 m: m, 556 t: m.taxonomies, 557 n: n, 558 key: s, 559 } 560 561 if err = m.assembleResources(s+cmLeafSeparator, n.p, parentBucket); err != nil { 562 return true 563 } 564 565 return false 566 }) 567 568 for _, s := range taxonomiesToDelete { 569 m.deleteTaxonomy(s) 570 } 571 572 return err 573 } 574 575 func (m *pageMap) attachPageToViews(s string, b *contentNode) { 576 if m.cfg.taxonomyDisabled { 577 return 578 } 579 580 for _, viewName := range m.cfg.taxonomyConfig { 581 vals := types.ToStringSlicePreserveString(getParam(b.p, viewName.plural, false)) 582 if vals == nil { 583 continue 584 } 585 w := getParamToLower(b.p, viewName.plural+"_weight") 586 weight, err := cast.ToIntE(w) 587 if err != nil { 588 m.s.Log.Errorf("Unable to convert taxonomy weight %#v to int for %q", w, b.p.Pathc()) 589 // weight will equal zero, so let the flow continue 590 } 591 592 for i, v := range vals { 593 termKey := m.s.getTaxonomyKey(v) 594 595 bv := &contentNode{ 596 viewInfo: &contentBundleViewInfo{ 597 ordinal: i, 598 name: viewName, 599 termKey: termKey, 600 termOrigin: v, 601 weight: weight, 602 ref: b, 603 }, 604 } 605 606 var key string 607 if strings.HasSuffix(s, "/") { 608 key = cleanSectionTreeKey(path.Join(viewName.plural, termKey, s)) 609 } else { 610 key = cleanTreeKey(path.Join(viewName.plural, termKey, s)) 611 } 612 m.taxonomyEntries.Insert(key, bv) 613 } 614 } 615 } 616 617 type pageMapQuery struct { 618 Prefix string 619 Filter contentTreeNodeCallback 620 } 621 622 func (m *pageMap) collectPages(query pageMapQuery, fn func(c *contentNode)) error { 623 if query.Filter == nil { 624 query.Filter = contentTreeNoListAlwaysFilter 625 } 626 627 m.pages.WalkQuery(query, func(s string, n *contentNode) bool { 628 fn(n) 629 return false 630 }) 631 632 return nil 633 } 634 635 func (m *pageMap) collectPagesAndSections(query pageMapQuery, fn func(c *contentNode)) error { 636 if err := m.collectSections(query, fn); err != nil { 637 return err 638 } 639 640 query.Prefix = query.Prefix + cmBranchSeparator 641 if err := m.collectPages(query, fn); err != nil { 642 return err 643 } 644 645 return nil 646 } 647 648 func (m *pageMap) collectSections(query pageMapQuery, fn func(c *contentNode)) error { 649 level := strings.Count(query.Prefix, "/") 650 651 return m.collectSectionsFn(query, func(s string, c *contentNode) bool { 652 if strings.Count(s, "/") != level+1 { 653 return false 654 } 655 656 fn(c) 657 658 return false 659 }) 660 } 661 662 func (m *pageMap) collectSectionsFn(query pageMapQuery, fn func(s string, c *contentNode) bool) error { 663 if !strings.HasSuffix(query.Prefix, "/") { 664 query.Prefix += "/" 665 } 666 667 m.sections.WalkQuery(query, func(s string, n *contentNode) bool { 668 return fn(s, n) 669 }) 670 671 return nil 672 } 673 674 func (m *pageMap) collectSectionsRecursiveIncludingSelf(query pageMapQuery, fn func(c *contentNode)) error { 675 return m.collectSectionsFn(query, func(s string, c *contentNode) bool { 676 fn(c) 677 return false 678 }) 679 } 680 681 func (m *pageMap) collectTaxonomies(prefix string, fn func(c *contentNode)) error { 682 m.taxonomies.WalkQuery(pageMapQuery{Prefix: prefix}, func(s string, n *contentNode) bool { 683 fn(n) 684 return false 685 }) 686 return nil 687 } 688 689 // withEveryBundlePage applies fn to every Page, including those bundled inside 690 // leaf bundles. 691 func (m *pageMap) withEveryBundlePage(fn func(p *pageState) bool) { 692 m.bundleTrees.Walk(func(s string, n *contentNode) bool { 693 if n.p != nil { 694 return fn(n.p) 695 } 696 return false 697 }) 698 } 699 700 type pageMaps struct { 701 workers *para.Workers 702 pmaps []*pageMap 703 } 704 705 // deleteSection deletes the entire section from s. 706 func (m *pageMaps) deleteSection(s string) { 707 m.withMaps(func(pm *pageMap) error { 708 pm.deleteSectionByPath(s) 709 return nil 710 }) 711 } 712 713 func (m *pageMaps) AssemblePages() error { 714 return m.withMaps(func(pm *pageMap) error { 715 if err := pm.CreateMissingNodes(); err != nil { 716 return err 717 } 718 719 if err := pm.assemblePages(); err != nil { 720 return err 721 } 722 723 if err := pm.createMissingTaxonomyNodes(); err != nil { 724 return err 725 } 726 727 // Handle any new sections created in the step above. 728 if err := pm.assembleSections(); err != nil { 729 return err 730 } 731 732 if pm.s.home == nil { 733 // Home is disabled, everything is. 734 pm.bundleTrees.DeletePrefix("") 735 return nil 736 } 737 738 if err := pm.assembleTaxonomies(); err != nil { 739 return err 740 } 741 742 if err := pm.createSiteTaxonomies(); err != nil { 743 return err 744 } 745 746 sw := §ionWalker{m: pm.contentMap} 747 a := sw.applyAggregates() 748 _, mainSectionsSet := pm.s.s.Info.Params()["mainsections"] 749 if !mainSectionsSet && a.mainSection != "" { 750 mainSections := []string{strings.TrimRight(a.mainSection, "/")} 751 pm.s.s.Info.Params()["mainSections"] = mainSections 752 pm.s.s.Info.Params()["mainsections"] = mainSections 753 } 754 755 pm.s.lastmod = a.datesAll.Lastmod() 756 if resource.IsZeroDates(pm.s.home) { 757 pm.s.home.m.Dates = a.datesAll 758 } 759 760 return nil 761 }) 762 } 763 764 func (m *pageMaps) walkBundles(fn func(n *contentNode) bool) { 765 _ = m.withMaps(func(pm *pageMap) error { 766 pm.bundleTrees.Walk(func(s string, n *contentNode) bool { 767 return fn(n) 768 }) 769 return nil 770 }) 771 } 772 773 func (m *pageMaps) walkBranchesPrefix(prefix string, fn func(s string, n *contentNode) bool) { 774 _ = m.withMaps(func(pm *pageMap) error { 775 pm.branchTrees.WalkPrefix(prefix, func(s string, n *contentNode) bool { 776 return fn(s, n) 777 }) 778 return nil 779 }) 780 } 781 782 func (m *pageMaps) withMaps(fn func(pm *pageMap) error) error { 783 g, _ := m.workers.Start(context.Background()) 784 for _, pm := range m.pmaps { 785 pm := pm 786 g.Run(func() error { 787 return fn(pm) 788 }) 789 } 790 return g.Wait() 791 } 792 793 type pagesMapBucket struct { 794 // Cascading front matter. 795 cascade map[page.PageMatcher]maps.Params 796 797 owner *pageState // The branch node 798 799 *pagesMapBucketPages 800 } 801 802 type pagesMapBucketPages struct { 803 pagesInit sync.Once 804 pages page.Pages 805 806 pagesAndSectionsInit sync.Once 807 pagesAndSections page.Pages 808 809 sectionsInit sync.Once 810 sections page.Pages 811 } 812 813 func (b *pagesMapBucket) getPages() page.Pages { 814 b.pagesInit.Do(func() { 815 b.pages = b.owner.treeRef.getPages() 816 page.SortByDefault(b.pages) 817 }) 818 return b.pages 819 } 820 821 func (b *pagesMapBucket) getPagesRecursive() page.Pages { 822 pages := b.owner.treeRef.getPagesRecursive() 823 page.SortByDefault(pages) 824 return pages 825 } 826 827 func (b *pagesMapBucket) getPagesAndSections() page.Pages { 828 b.pagesAndSectionsInit.Do(func() { 829 b.pagesAndSections = b.owner.treeRef.getPagesAndSections() 830 }) 831 return b.pagesAndSections 832 } 833 834 func (b *pagesMapBucket) getSections() page.Pages { 835 b.sectionsInit.Do(func() { 836 if b.owner.treeRef == nil { 837 return 838 } 839 b.sections = b.owner.treeRef.getSections() 840 }) 841 842 return b.sections 843 } 844 845 func (b *pagesMapBucket) getTaxonomies() page.Pages { 846 b.sectionsInit.Do(func() { 847 var pas page.Pages 848 ref := b.owner.treeRef 849 ref.m.collectTaxonomies(ref.key, func(c *contentNode) { 850 pas = append(pas, c.p) 851 }) 852 page.SortByDefault(pas) 853 b.sections = pas 854 }) 855 856 return b.sections 857 } 858 859 func (b *pagesMapBucket) getTaxonomyEntries() page.Pages { 860 var pas page.Pages 861 ref := b.owner.treeRef 862 viewInfo := ref.n.viewInfo 863 prefix := strings.ToLower("/" + viewInfo.name.plural + "/" + viewInfo.termKey + "/") 864 ref.m.taxonomyEntries.WalkPrefix(prefix, func(s string, v interface{}) bool { 865 n := v.(*contentNode) 866 pas = append(pas, n.viewInfo.ref.p) 867 return false 868 }) 869 page.SortByDefault(pas) 870 return pas 871 } 872 873 type sectionAggregate struct { 874 datesAll resource.Dates 875 datesSection resource.Dates 876 pageCount int 877 mainSection string 878 mainSectionPageCount int 879 } 880 881 type sectionAggregateHandler struct { 882 sectionAggregate 883 sectionPageCount int 884 885 // Section 886 b *contentNode 887 s string 888 } 889 890 func (h *sectionAggregateHandler) String() string { 891 return fmt.Sprintf("%s/%s - %d - %s", h.sectionAggregate.datesAll, h.sectionAggregate.datesSection, h.sectionPageCount, h.s) 892 } 893 894 func (h *sectionAggregateHandler) isRootSection() bool { 895 return h.s != "/" && strings.Count(h.s, "/") == 2 896 } 897 898 func (h *sectionAggregateHandler) handleNested(v sectionWalkHandler) error { 899 nested := v.(*sectionAggregateHandler) 900 h.sectionPageCount += nested.pageCount 901 h.pageCount += h.sectionPageCount 902 h.datesAll.UpdateDateAndLastmodIfAfter(nested.datesAll) 903 h.datesSection.UpdateDateAndLastmodIfAfter(nested.datesAll) 904 return nil 905 } 906 907 func (h *sectionAggregateHandler) handlePage(s string, n *contentNode) error { 908 h.sectionPageCount++ 909 910 var d resource.Dated 911 if n.p != nil { 912 d = n.p 913 } else if n.viewInfo != nil && n.viewInfo.ref != nil { 914 d = n.viewInfo.ref.p 915 } else { 916 return nil 917 } 918 919 h.datesAll.UpdateDateAndLastmodIfAfter(d) 920 h.datesSection.UpdateDateAndLastmodIfAfter(d) 921 return nil 922 } 923 924 func (h *sectionAggregateHandler) handleSectionPost() error { 925 if h.sectionPageCount > h.mainSectionPageCount && h.isRootSection() { 926 h.mainSectionPageCount = h.sectionPageCount 927 h.mainSection = strings.TrimPrefix(h.s, "/") 928 } 929 930 if resource.IsZeroDates(h.b.p) { 931 h.b.p.m.Dates = h.datesSection 932 } 933 934 h.datesSection = resource.Dates{} 935 936 return nil 937 } 938 939 func (h *sectionAggregateHandler) handleSectionPre(s string, b *contentNode) error { 940 h.s = s 941 h.b = b 942 h.sectionPageCount = 0 943 h.datesAll.UpdateDateAndLastmodIfAfter(b.p) 944 return nil 945 } 946 947 type sectionWalkHandler interface { 948 handleNested(v sectionWalkHandler) error 949 handlePage(s string, b *contentNode) error 950 handleSectionPost() error 951 handleSectionPre(s string, b *contentNode) error 952 } 953 954 type sectionWalker struct { 955 err error 956 m *contentMap 957 } 958 959 func (w *sectionWalker) applyAggregates() *sectionAggregateHandler { 960 return w.walkLevel("/", func() sectionWalkHandler { 961 return §ionAggregateHandler{} 962 }).(*sectionAggregateHandler) 963 } 964 965 func (w *sectionWalker) walkLevel(prefix string, createVisitor func() sectionWalkHandler) sectionWalkHandler { 966 level := strings.Count(prefix, "/") 967 968 visitor := createVisitor() 969 970 w.m.taxonomies.WalkBelow(prefix, func(s string, v interface{}) bool { 971 currentLevel := strings.Count(s, "/") 972 973 if currentLevel > level+1 { 974 return false 975 } 976 977 n := v.(*contentNode) 978 979 if w.err = visitor.handleSectionPre(s, n); w.err != nil { 980 return true 981 } 982 983 if currentLevel == 2 { 984 nested := w.walkLevel(s, createVisitor) 985 if w.err = visitor.handleNested(nested); w.err != nil { 986 return true 987 } 988 } else { 989 w.m.taxonomyEntries.WalkPrefix(s, func(ss string, v interface{}) bool { 990 n := v.(*contentNode) 991 w.err = visitor.handlePage(ss, n) 992 return w.err != nil 993 }) 994 } 995 996 w.err = visitor.handleSectionPost() 997 998 return w.err != nil 999 }) 1000 1001 w.m.sections.WalkBelow(prefix, func(s string, v interface{}) bool { 1002 currentLevel := strings.Count(s, "/") 1003 if currentLevel > level+1 { 1004 return false 1005 } 1006 1007 n := v.(*contentNode) 1008 1009 if w.err = visitor.handleSectionPre(s, n); w.err != nil { 1010 return true 1011 } 1012 1013 w.m.pages.WalkPrefix(s+cmBranchSeparator, func(s string, v interface{}) bool { 1014 w.err = visitor.handlePage(s, v.(*contentNode)) 1015 return w.err != nil 1016 }) 1017 1018 if w.err != nil { 1019 return true 1020 } 1021 1022 nested := w.walkLevel(s, createVisitor) 1023 if w.err = visitor.handleNested(nested); w.err != nil { 1024 return true 1025 } 1026 1027 w.err = visitor.handleSectionPost() 1028 1029 return w.err != nil 1030 }) 1031 1032 return visitor 1033 } 1034 1035 type viewName struct { 1036 singular string // e.g. "category" 1037 plural string // e.g. "categories" 1038 } 1039 1040 func (v viewName) IsZero() bool { 1041 return v.singular == "" 1042 }