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 }