mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
7ff0a8ee9f
This is preparation for #6041. For historic reasons, the code for bulding the section tree and the taxonomies were very much separate. This works, but makes it hard to extend, maintain, and possibly not so fast as it could be. This simplification also introduces 3 slightly breaking changes, which I suspect most people will be pleased about. See referenced issues: This commit also switches the radix tree dependency to a mutable implementation: github.com/armon/go-radix. Fixes #6154 Fixes #6153 Fixes #6152
367 lines
7.8 KiB
Go
367 lines
7.8 KiB
Go
// Copyright 2019 The Hugo Authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package hugolib
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
radix "github.com/armon/go-radix"
|
|
"github.com/spf13/cast"
|
|
|
|
"github.com/gohugoio/hugo/resources/page"
|
|
)
|
|
|
|
func newPagesMap(s *Site) *pagesMap {
|
|
return &pagesMap{
|
|
r: radix.New(),
|
|
s: s,
|
|
}
|
|
}
|
|
|
|
type pagesMap struct {
|
|
r *radix.Tree
|
|
s *Site
|
|
}
|
|
|
|
func (m *pagesMap) Get(key string) *pagesMapBucket {
|
|
key = m.cleanKey(key)
|
|
v, found := m.r.Get(key)
|
|
if !found {
|
|
return nil
|
|
}
|
|
|
|
return v.(*pagesMapBucket)
|
|
}
|
|
|
|
func (m *pagesMap) getKey(p *pageState) string {
|
|
if !p.File().IsZero() {
|
|
return m.cleanKey(p.File().Dir())
|
|
}
|
|
return m.cleanKey(p.SectionsPath())
|
|
}
|
|
|
|
func (m *pagesMap) getOrCreateHome() *pageState {
|
|
var home *pageState
|
|
b, found := m.r.Get("/")
|
|
if !found {
|
|
home = m.s.newPage(page.KindHome)
|
|
m.addBucketFor("/", home, nil)
|
|
} else {
|
|
home = b.(*pagesMapBucket).owner
|
|
}
|
|
|
|
return home
|
|
}
|
|
|
|
func (m *pagesMap) createSectionIfNotExists(section string) {
|
|
key := m.cleanKey(section)
|
|
_, found := m.r.Get(key)
|
|
if !found {
|
|
kind := m.s.kindFromSectionPath(section)
|
|
p := m.s.newPage(kind, section)
|
|
m.addBucketFor(key, p, nil)
|
|
}
|
|
}
|
|
|
|
func (m *pagesMap) addBucket(p *pageState) {
|
|
key := m.getKey(p)
|
|
|
|
m.addBucketFor(key, p, nil)
|
|
}
|
|
|
|
func (m *pagesMap) addBucketFor(key string, p *pageState, meta map[string]interface{}) *pagesMapBucket {
|
|
var isView bool
|
|
switch p.Kind() {
|
|
case page.KindTaxonomy, page.KindTaxonomyTerm:
|
|
isView = true
|
|
}
|
|
|
|
disabled := !m.s.isEnabled(p.Kind())
|
|
|
|
bucket := &pagesMapBucket{owner: p, view: isView, meta: meta, disabled: disabled}
|
|
p.bucket = bucket
|
|
|
|
m.r.Insert(key, bucket)
|
|
|
|
return bucket
|
|
}
|
|
|
|
func (m *pagesMap) addPage(p *pageState) {
|
|
if !p.IsPage() {
|
|
m.addBucket(p)
|
|
return
|
|
}
|
|
|
|
if !m.s.isEnabled(page.KindPage) {
|
|
return
|
|
}
|
|
|
|
key := m.getKey(p)
|
|
|
|
var bucket *pagesMapBucket
|
|
|
|
_, v, found := m.r.LongestPrefix(key)
|
|
if !found {
|
|
panic(fmt.Sprintf("[BUG] bucket with key %q not found", key))
|
|
}
|
|
|
|
bucket = v.(*pagesMapBucket)
|
|
p.bucket = bucket
|
|
|
|
bucket.pages = append(bucket.pages, p)
|
|
}
|
|
|
|
func (m *pagesMap) withEveryPage(f func(p *pageState)) {
|
|
m.r.Walk(func(k string, v interface{}) bool {
|
|
b := v.(*pagesMapBucket)
|
|
f(b.owner)
|
|
if !b.view {
|
|
for _, p := range b.pages {
|
|
f(p.(*pageState))
|
|
}
|
|
}
|
|
|
|
return false
|
|
})
|
|
}
|
|
|
|
func (m *pagesMap) assembleTaxonomies(s *Site) error {
|
|
s.Taxonomies = make(TaxonomyList)
|
|
|
|
type bucketKey struct {
|
|
plural string
|
|
termKey string
|
|
}
|
|
|
|
// Temporary cache.
|
|
taxonomyBuckets := make(map[bucketKey]*pagesMapBucket)
|
|
|
|
for singular, plural := range s.siteCfg.taxonomiesConfig {
|
|
s.Taxonomies[plural] = make(Taxonomy)
|
|
bkey := bucketKey{
|
|
plural: plural,
|
|
}
|
|
|
|
bucket := m.Get(plural)
|
|
|
|
if bucket == nil {
|
|
// Create the page and bucket
|
|
n := s.newPage(page.KindTaxonomyTerm, plural)
|
|
|
|
key := m.cleanKey(plural)
|
|
bucket = m.addBucketFor(key, n, nil)
|
|
}
|
|
|
|
if bucket.meta == nil {
|
|
bucket.meta = map[string]interface{}{
|
|
"singular": singular,
|
|
"plural": plural,
|
|
}
|
|
}
|
|
|
|
// Add it to the temporary cache.
|
|
taxonomyBuckets[bkey] = bucket
|
|
|
|
// Taxonomy entries used in page front matter will be picked up later,
|
|
// but there may be some yet to be used.
|
|
pluralPrefix := m.cleanKey(plural) + "/"
|
|
m.r.WalkPrefix(pluralPrefix, func(k string, v interface{}) bool {
|
|
tb := v.(*pagesMapBucket)
|
|
termKey := strings.TrimPrefix(k, pluralPrefix)
|
|
if tb.meta == nil {
|
|
tb.meta = map[string]interface{}{
|
|
"singular": singular,
|
|
"plural": plural,
|
|
"term": tb.owner.Title(),
|
|
"termKey": termKey,
|
|
}
|
|
}
|
|
|
|
bucket.pages = append(bucket.pages, tb.owner)
|
|
bkey.termKey = termKey
|
|
taxonomyBuckets[bkey] = tb
|
|
|
|
return false
|
|
})
|
|
|
|
}
|
|
|
|
addTaxonomy := func(singular, plural, term string, weight int, p page.Page) {
|
|
bkey := bucketKey{
|
|
plural: plural,
|
|
}
|
|
|
|
termKey := s.getTaxonomyKey(term)
|
|
|
|
b1 := taxonomyBuckets[bkey]
|
|
|
|
var b2 *pagesMapBucket
|
|
bkey.termKey = termKey
|
|
b, found := taxonomyBuckets[bkey]
|
|
if found {
|
|
b2 = b
|
|
} else {
|
|
|
|
// Create the page and bucket
|
|
n := s.newTaxonomyPage(term, plural, termKey)
|
|
meta := map[string]interface{}{
|
|
"singular": singular,
|
|
"plural": plural,
|
|
"term": term,
|
|
"termKey": termKey,
|
|
}
|
|
|
|
key := m.cleanKey(path.Join(plural, termKey))
|
|
b2 = m.addBucketFor(key, n, meta)
|
|
b1.pages = append(b1.pages, b2.owner)
|
|
taxonomyBuckets[bkey] = b2
|
|
|
|
}
|
|
|
|
w := page.NewWeightedPage(weight, p, b2.owner)
|
|
|
|
s.Taxonomies[plural].add(termKey, w)
|
|
|
|
b1.owner.m.Dates.UpdateDateAndLastmodIfAfter(p)
|
|
b2.owner.m.Dates.UpdateDateAndLastmodIfAfter(p)
|
|
}
|
|
|
|
m.r.Walk(func(k string, v interface{}) bool {
|
|
b := v.(*pagesMapBucket)
|
|
if b.view {
|
|
return false
|
|
}
|
|
|
|
for singular, plural := range s.siteCfg.taxonomiesConfig {
|
|
for _, p := range b.pages {
|
|
|
|
vals := getParam(p, plural, false)
|
|
|
|
w := getParamToLower(p, plural+"_weight")
|
|
weight, err := cast.ToIntE(w)
|
|
if err != nil {
|
|
m.s.Log.ERROR.Printf("Unable to convert taxonomy weight %#v to int for %q", w, p.Path())
|
|
// weight will equal zero, so let the flow continue
|
|
}
|
|
|
|
if vals != nil {
|
|
if v, ok := vals.([]string); ok {
|
|
for _, idx := range v {
|
|
addTaxonomy(singular, plural, idx, weight, p)
|
|
}
|
|
} else if v, ok := vals.(string); ok {
|
|
addTaxonomy(singular, plural, v, weight, p)
|
|
} else {
|
|
m.s.Log.ERROR.Printf("Invalid %s in %q\n", plural, p.Path())
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
|
|
for _, plural := range s.siteCfg.taxonomiesConfig {
|
|
for k := range s.Taxonomies[plural] {
|
|
s.Taxonomies[plural][k].Sort()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *pagesMap) cleanKey(key string) string {
|
|
key = filepath.ToSlash(strings.ToLower(key))
|
|
key = strings.Trim(key, "/")
|
|
return "/" + key
|
|
}
|
|
|
|
func (m *pagesMap) dump() {
|
|
m.r.Walk(func(s string, v interface{}) bool {
|
|
b := v.(*pagesMapBucket)
|
|
fmt.Println("-------\n", s, ":", b.owner.Kind(), ":")
|
|
if b.owner != nil {
|
|
fmt.Println("Owner:", b.owner.Path())
|
|
}
|
|
for _, p := range b.pages {
|
|
fmt.Println(p.Path())
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
type pagesMapBucket struct {
|
|
// Set if the pages in this bucket is also present in another bucket.
|
|
view bool
|
|
|
|
// Some additional metatadata attached to this node.
|
|
meta map[string]interface{}
|
|
|
|
owner *pageState // The branch node
|
|
|
|
// When disableKinds is enabled for this node.
|
|
disabled bool
|
|
|
|
// Used to navigate the sections tree
|
|
parent *pagesMapBucket
|
|
bucketSections []*pagesMapBucket
|
|
|
|
pagesInit sync.Once
|
|
pages page.Pages
|
|
|
|
pagesAndSectionsInit sync.Once
|
|
pagesAndSections page.Pages
|
|
|
|
sectionsInit sync.Once
|
|
sections page.Pages
|
|
}
|
|
|
|
func (b *pagesMapBucket) isEmpty() bool {
|
|
return len(b.pages) == 0 && len(b.bucketSections) == 0
|
|
}
|
|
|
|
func (b *pagesMapBucket) getPages() page.Pages {
|
|
b.pagesInit.Do(func() {
|
|
page.SortByDefault(b.pages)
|
|
})
|
|
return b.pages
|
|
}
|
|
|
|
func (b *pagesMapBucket) getPagesAndSections() page.Pages {
|
|
b.pagesAndSectionsInit.Do(func() {
|
|
var pas page.Pages
|
|
pas = append(pas, b.pages...)
|
|
for _, p := range b.bucketSections {
|
|
pas = append(pas, p.owner)
|
|
}
|
|
b.pagesAndSections = pas
|
|
page.SortByDefault(b.pagesAndSections)
|
|
})
|
|
return b.pagesAndSections
|
|
}
|
|
|
|
func (b *pagesMapBucket) getSections() page.Pages {
|
|
b.sectionsInit.Do(func() {
|
|
for _, p := range b.bucketSections {
|
|
b.sections = append(b.sections, p.owner)
|
|
}
|
|
page.SortByDefault(b.sections)
|
|
})
|
|
|
|
return b.sections
|
|
}
|