github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/resources/page/pages_related.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 page
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"sync"
    20  
    21  	"github.com/gohugoio/hugo/common/para"
    22  	"github.com/gohugoio/hugo/common/types"
    23  	"github.com/gohugoio/hugo/config"
    24  	"github.com/gohugoio/hugo/related"
    25  	"github.com/mitchellh/mapstructure"
    26  	"github.com/spf13/cast"
    27  )
    28  
    29  var (
    30  	// Assert that Pages and PageGroup implements the PageGenealogist interface.
    31  	_ PageGenealogist = (Pages)(nil)
    32  	_ PageGenealogist = PageGroup{}
    33  )
    34  
    35  // A PageGenealogist finds related pages in a page collection. This interface is implemented
    36  // by Pages and PageGroup, which makes it available as `{{ .RegularRelated . }}` etc.
    37  type PageGenealogist interface {
    38  
    39  	// Template example:
    40  	// {{ $related := .RegularPages.Related . }}
    41  	Related(ctx context.Context, opts any) (Pages, error)
    42  
    43  	// Template example:
    44  	// {{ $related := .RegularPages.RelatedIndices . "tags" "date" }}
    45  	// Deprecated: Use Related instead.
    46  	RelatedIndices(ctx context.Context, doc related.Document, indices ...any) (Pages, error)
    47  
    48  	// Template example:
    49  	// {{ $related := .RegularPages.RelatedTo ( keyVals "tags" "hugo", "rocks")  ( keyVals "date" .Date ) }}
    50  	// Deprecated: Use Related instead.
    51  	RelatedTo(ctx context.Context, args ...types.KeyValues) (Pages, error)
    52  }
    53  
    54  // Related searches all the configured indices with the search keywords from the
    55  // supplied document.
    56  func (p Pages) Related(ctx context.Context, optsv any) (Pages, error) {
    57  	if len(p) == 0 {
    58  		return nil, nil
    59  	}
    60  
    61  	var opts related.SearchOpts
    62  	switch v := optsv.(type) {
    63  	case related.Document:
    64  		opts.Document = v
    65  	case map[string]any:
    66  		if err := mapstructure.WeakDecode(v, &opts); err != nil {
    67  			return nil, err
    68  		}
    69  	default:
    70  		return nil, fmt.Errorf("invalid argument type %T", optsv)
    71  	}
    72  
    73  	result, err := p.search(ctx, opts)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	return result, nil
    79  
    80  }
    81  
    82  // RelatedIndices searches the given indices with the search keywords from the
    83  // supplied document.
    84  // Deprecated: Use Related instead.
    85  func (p Pages) RelatedIndices(ctx context.Context, doc related.Document, indices ...any) (Pages, error) {
    86  	indicesStr, err := cast.ToStringSliceE(indices)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	opts := related.SearchOpts{
    92  		Document: doc,
    93  		Indices:  indicesStr,
    94  	}
    95  
    96  	result, err := p.search(ctx, opts)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	return result, nil
   102  }
   103  
   104  // RelatedTo searches the given indices with the corresponding values.
   105  // Deprecated: Use Related instead.
   106  func (p Pages) RelatedTo(ctx context.Context, args ...types.KeyValues) (Pages, error) {
   107  	if len(p) == 0 {
   108  		return nil, nil
   109  	}
   110  
   111  	opts := related.SearchOpts{
   112  		NamedSlices: args,
   113  	}
   114  
   115  	return p.search(ctx, opts)
   116  }
   117  
   118  func (p Pages) search(ctx context.Context, opts related.SearchOpts) (Pages, error) {
   119  	return p.withInvertedIndex(ctx, func(idx *related.InvertedIndex) ([]related.Document, error) {
   120  		return idx.Search(ctx, opts)
   121  	})
   122  }
   123  
   124  func (p Pages) withInvertedIndex(ctx context.Context, search func(idx *related.InvertedIndex) ([]related.Document, error)) (Pages, error) {
   125  	if len(p) == 0 {
   126  		return nil, nil
   127  	}
   128  
   129  	d, ok := p[0].(InternalDependencies)
   130  	if !ok {
   131  		return nil, fmt.Errorf("invalid type %T in related search", p[0])
   132  	}
   133  
   134  	cache := d.GetRelatedDocsHandler()
   135  
   136  	searchIndex, err := cache.getOrCreateIndex(ctx, p)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	result, err := search(searchIndex)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	if len(result) > 0 {
   147  		mp := make(Pages, len(result))
   148  		for i, match := range result {
   149  			mp[i] = match.(Page)
   150  		}
   151  		return mp, nil
   152  	}
   153  
   154  	return nil, nil
   155  }
   156  
   157  type cachedPostingList struct {
   158  	p Pages
   159  
   160  	postingList *related.InvertedIndex
   161  }
   162  
   163  type RelatedDocsHandler struct {
   164  	cfg related.Config
   165  
   166  	postingLists []*cachedPostingList
   167  	mu           sync.RWMutex
   168  
   169  	workers *para.Workers
   170  }
   171  
   172  func NewRelatedDocsHandler(cfg related.Config) *RelatedDocsHandler {
   173  	return &RelatedDocsHandler{cfg: cfg, workers: para.New(config.GetNumWorkerMultiplier())}
   174  }
   175  
   176  func (s *RelatedDocsHandler) Clone() *RelatedDocsHandler {
   177  	return NewRelatedDocsHandler(s.cfg)
   178  }
   179  
   180  // This assumes that a lock has been acquired.
   181  func (s *RelatedDocsHandler) getIndex(p Pages) *related.InvertedIndex {
   182  	for _, ci := range s.postingLists {
   183  		if pagesEqual(p, ci.p) {
   184  			return ci.postingList
   185  		}
   186  	}
   187  	return nil
   188  }
   189  func (s *RelatedDocsHandler) getOrCreateIndex(ctx context.Context, p Pages) (*related.InvertedIndex, error) {
   190  	s.mu.RLock()
   191  	cachedIndex := s.getIndex(p)
   192  	if cachedIndex != nil {
   193  		s.mu.RUnlock()
   194  		return cachedIndex, nil
   195  	}
   196  	s.mu.RUnlock()
   197  
   198  	s.mu.Lock()
   199  	defer s.mu.Unlock()
   200  
   201  	// Double check.
   202  	if cachedIndex := s.getIndex(p); cachedIndex != nil {
   203  		return cachedIndex, nil
   204  	}
   205  
   206  	for _, c := range s.cfg.Indices {
   207  		if c.Type == related.TypeFragments {
   208  			// This will trigger building the Pages' fragment map.
   209  			g, _ := s.workers.Start(ctx)
   210  			for _, page := range p {
   211  				fp, ok := page.(related.FragmentProvider)
   212  				if !ok {
   213  					continue
   214  				}
   215  				g.Run(func() error {
   216  					fp.Fragments(ctx)
   217  					return nil
   218  				})
   219  			}
   220  
   221  			if err := g.Wait(); err != nil {
   222  				return nil, err
   223  			}
   224  
   225  			break
   226  		}
   227  	}
   228  
   229  	searchIndex := related.NewInvertedIndex(s.cfg)
   230  
   231  	for _, page := range p {
   232  		if err := searchIndex.Add(ctx, page); err != nil {
   233  			return nil, err
   234  		}
   235  	}
   236  
   237  	s.postingLists = append(s.postingLists, &cachedPostingList{p: p, postingList: searchIndex})
   238  
   239  	if err := searchIndex.Finalize(ctx); err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	return searchIndex, nil
   244  }