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