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 := &sectionWalker{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 &sectionAggregateHandler{}
   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  }