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