hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

pages_related.go (4899B)

    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 page
   15 
   16 import (
   17 	"fmt"
   18 	"sync"
   19 
   20 	"github.com/gohugoio/hugo/common/types"
   21 	"github.com/gohugoio/hugo/related"
   22 	"github.com/spf13/cast"
   23 )
   24 
   25 var (
   26 	// Assert that Pages and PageGroup implements the PageGenealogist interface.
   27 	_ PageGenealogist = (Pages)(nil)
   28 	_ PageGenealogist = PageGroup{}
   29 )
   30 
   31 // A PageGenealogist finds related pages in a page collection. This interface is implemented
   32 // by Pages and PageGroup, which makes it available as `{{ .RegularRelated . }}` etc.
   33 type PageGenealogist interface {
   34 
   35 	// Template example:
   36 	// {{ $related := .RegularPages.Related . }}
   37 	Related(doc related.Document) (Pages, error)
   38 
   39 	// Template example:
   40 	// {{ $related := .RegularPages.RelatedIndices . "tags" "date" }}
   41 	RelatedIndices(doc related.Document, indices ...any) (Pages, error)
   42 
   43 	// Template example:
   44 	// {{ $related := .RegularPages.RelatedTo ( keyVals "tags" "hugo", "rocks")  ( keyVals "date" .Date ) }}
   45 	RelatedTo(args ...types.KeyValues) (Pages, error)
   46 }
   47 
   48 // Related searches all the configured indices with the search keywords from the
   49 // supplied document.
   50 func (p Pages) Related(doc related.Document) (Pages, error) {
   51 	result, err := p.searchDoc(doc)
   52 	if err != nil {
   53 		return nil, err
   54 	}
   55 
   56 	if page, ok := doc.(Page); ok {
   57 		return result.removeFirstIfFound(page), nil
   58 	}
   59 
   60 	return result, nil
   61 }
   62 
   63 // RelatedIndices searches the given indices with the search keywords from the
   64 // supplied document.
   65 func (p Pages) RelatedIndices(doc related.Document, indices ...any) (Pages, error) {
   66 	indicesStr, err := cast.ToStringSliceE(indices)
   67 	if err != nil {
   68 		return nil, err
   69 	}
   70 
   71 	result, err := p.searchDoc(doc, indicesStr...)
   72 	if err != nil {
   73 		return nil, err
   74 	}
   75 
   76 	if page, ok := doc.(Page); ok {
   77 		return result.removeFirstIfFound(page), nil
   78 	}
   79 
   80 	return result, nil
   81 }
   82 
   83 // RelatedTo searches the given indices with the corresponding values.
   84 func (p Pages) RelatedTo(args ...types.KeyValues) (Pages, error) {
   85 	if len(p) == 0 {
   86 		return nil, nil
   87 	}
   88 
   89 	return p.search(args...)
   90 }
   91 
   92 func (p Pages) search(args ...types.KeyValues) (Pages, error) {
   93 	return p.withInvertedIndex(func(idx *related.InvertedIndex) ([]related.Document, error) {
   94 		return idx.SearchKeyValues(args...)
   95 	})
   96 }
   97 
   98 func (p Pages) searchDoc(doc related.Document, indices ...string) (Pages, error) {
   99 	return p.withInvertedIndex(func(idx *related.InvertedIndex) ([]related.Document, error) {
  100 		return idx.SearchDoc(doc, indices...)
  101 	})
  102 }
  103 
  104 func (p Pages) withInvertedIndex(search func(idx *related.InvertedIndex) ([]related.Document, error)) (Pages, error) {
  105 	if len(p) == 0 {
  106 		return nil, nil
  107 	}
  108 
  109 	d, ok := p[0].(InternalDependencies)
  110 	if !ok {
  111 		return nil, fmt.Errorf("invalid type %T in related search", p[0])
  112 	}
  113 
  114 	cache := d.GetRelatedDocsHandler()
  115 
  116 	searchIndex, err := cache.getOrCreateIndex(p)
  117 	if err != nil {
  118 		return nil, err
  119 	}
  120 
  121 	result, err := search(searchIndex)
  122 	if err != nil {
  123 		return nil, err
  124 	}
  125 
  126 	if len(result) > 0 {
  127 		mp := make(Pages, len(result))
  128 		for i, match := range result {
  129 			mp[i] = match.(Page)
  130 		}
  131 		return mp, nil
  132 	}
  133 
  134 	return nil, nil
  135 }
  136 
  137 type cachedPostingList struct {
  138 	p Pages
  139 
  140 	postingList *related.InvertedIndex
  141 }
  142 
  143 type RelatedDocsHandler struct {
  144 	cfg related.Config
  145 
  146 	postingLists []*cachedPostingList
  147 	mu           sync.RWMutex
  148 }
  149 
  150 func NewRelatedDocsHandler(cfg related.Config) *RelatedDocsHandler {
  151 	return &RelatedDocsHandler{cfg: cfg}
  152 }
  153 
  154 func (s *RelatedDocsHandler) Clone() *RelatedDocsHandler {
  155 	return NewRelatedDocsHandler(s.cfg)
  156 }
  157 
  158 // This assumes that a lock has been acquired.
  159 func (s *RelatedDocsHandler) getIndex(p Pages) *related.InvertedIndex {
  160 	for _, ci := range s.postingLists {
  161 		if pagesEqual(p, ci.p) {
  162 			return ci.postingList
  163 		}
  164 	}
  165 	return nil
  166 }
  167 
  168 func (s *RelatedDocsHandler) getOrCreateIndex(p Pages) (*related.InvertedIndex, error) {
  169 	s.mu.RLock()
  170 	cachedIndex := s.getIndex(p)
  171 	if cachedIndex != nil {
  172 		s.mu.RUnlock()
  173 		return cachedIndex, nil
  174 	}
  175 	s.mu.RUnlock()
  176 
  177 	s.mu.Lock()
  178 	defer s.mu.Unlock()
  179 
  180 	if cachedIndex := s.getIndex(p); cachedIndex != nil {
  181 		return cachedIndex, nil
  182 	}
  183 
  184 	searchIndex := related.NewInvertedIndex(s.cfg)
  185 
  186 	for _, page := range p {
  187 		if err := searchIndex.Add(page); err != nil {
  188 			return nil, err
  189 		}
  190 	}
  191 
  192 	s.postingLists = append(s.postingLists, &cachedPostingList{p: p, postingList: searchIndex})
  193 
  194 	return searchIndex, nil
  195 }