gitee.com/mirrors/Hugo-Go@v0.47.1/hugolib/pages_related.go (about)

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