mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			465 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			465 lines
		
	
	
		
			10 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"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/common/maps"
 | |
| 
 | |
| 	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) initPageMeta(p *pageState, bucket *pagesMapBucket) error {
 | |
| 	var err error
 | |
| 	p.metaInit.Do(func() {
 | |
| 		if p.metaInitFn != nil {
 | |
| 			err = p.metaInitFn(bucket)
 | |
| 		}
 | |
| 	})
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (m *pagesMap) initPageMetaFor(prefix string, bucket *pagesMapBucket) error {
 | |
| 	parentBucket := m.parentBucket(prefix)
 | |
| 
 | |
| 	m.mergeCascades(bucket, parentBucket)
 | |
| 
 | |
| 	if err := m.initPageMeta(bucket.owner, bucket); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if !bucket.view {
 | |
| 		for _, p := range bucket.pages {
 | |
| 			ps := p.(*pageState)
 | |
| 			if err := m.initPageMeta(ps, bucket); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			for _, p := range ps.resources.ByType(pageResourceType) {
 | |
| 				if err := m.initPageMeta(p.(*pageState), bucket); err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Now that the metadata is initialized (with dates, draft set etc.)
 | |
| 		// we can remove the pages that we for some reason should not include
 | |
| 		// in this build.
 | |
| 		tmp := bucket.pages[:0]
 | |
| 		for _, x := range bucket.pages {
 | |
| 			if m.s.shouldBuild(x) {
 | |
| 				if x.(*pageState).m.headless {
 | |
| 					bucket.headlessPages = append(bucket.headlessPages, x)
 | |
| 				} else {
 | |
| 					tmp = append(tmp, x)
 | |
| 				}
 | |
| 
 | |
| 			}
 | |
| 		}
 | |
| 		bucket.pages = tmp
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| 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) assemblePageMeta() error {
 | |
| 	var walkErr error
 | |
| 	m.r.Walk(func(s string, v interface{}) bool {
 | |
| 		bucket := v.(*pagesMapBucket)
 | |
| 
 | |
| 		if err := m.initPageMetaFor(s, bucket); err != nil {
 | |
| 			walkErr = err
 | |
| 			return true
 | |
| 		}
 | |
| 		return false
 | |
| 	})
 | |
| 
 | |
| 	return walkErr
 | |
| }
 | |
| 
 | |
| 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 err := m.initPageMetaFor(key, bucket); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		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) error {
 | |
| 		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)
 | |
| 			if err := m.initPageMetaFor(key, b2); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			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)
 | |
| 
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	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 {
 | |
| 							if err := addTaxonomy(singular, plural, idx, weight, p); err != nil {
 | |
| 								m.s.Log.ERROR.Printf("Failed to add taxonomy %q for %q: %s", plural, p.Path(), err)
 | |
| 							}
 | |
| 						}
 | |
| 					} else if v, ok := vals.(string); ok {
 | |
| 						if err := addTaxonomy(singular, plural, v, weight, p); err != nil {
 | |
| 							m.s.Log.ERROR.Printf("Failed to add taxonomy %q for %q: %s", plural, p.Path(), err)
 | |
| 						}
 | |
| 					} 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) mergeCascades(b1, b2 *pagesMapBucket) {
 | |
| 	if b1.cascade == nil {
 | |
| 		b1.cascade = make(maps.Params)
 | |
| 	}
 | |
| 	if b2 != nil && b2.cascade != nil {
 | |
| 		for k, v := range b2.cascade {
 | |
| 			if _, found := b1.cascade[k]; !found {
 | |
| 				b1.cascade[k] = v
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (m *pagesMap) parentBucket(prefix string) *pagesMapBucket {
 | |
| 	if prefix == "/" {
 | |
| 		return nil
 | |
| 	}
 | |
| 	_, parentv, found := m.r.LongestPrefix(path.Dir(prefix))
 | |
| 	if !found {
 | |
| 		panic(fmt.Sprintf("[BUG] parent bucket not found for %q", prefix))
 | |
| 	}
 | |
| 	return parentv.(*pagesMapBucket)
 | |
| 
 | |
| }
 | |
| 
 | |
| 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
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 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{}
 | |
| 
 | |
| 	// Cascading front matter.
 | |
| 	cascade 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
 | |
| 	headlessPages 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.getPages()...)
 | |
| 		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
 | |
| }
 |