github.com/kristoff-it/hugo@v0.47.1/hugolib/page_collections.go (about) 1 // Copyright 2016 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 "fmt" 18 "path" 19 "path/filepath" 20 "strings" 21 22 "github.com/gohugoio/hugo/cache" 23 "github.com/gohugoio/hugo/helpers" 24 ) 25 26 // PageCollections contains the page collections for a site. 27 type PageCollections struct { 28 // Includes only pages of all types, and only pages in the current language. 29 Pages Pages 30 31 // Includes all pages in all languages, including the current one. 32 // Includes pages of all types. 33 AllPages Pages 34 35 // A convenience cache for the traditional index types, taxonomies, home page etc. 36 // This is for the current language only. 37 indexPages Pages 38 39 // A convenience cache for the regular pages. 40 // This is for the current language only. 41 RegularPages Pages 42 43 // A convenience cache for the all the regular pages. 44 AllRegularPages Pages 45 46 // Includes absolute all pages (of all types), including drafts etc. 47 rawAllPages Pages 48 49 // Includes headless bundles, i.e. bundles that produce no output for its content page. 50 headlessPages Pages 51 52 pageIndex *cache.Lazy 53 } 54 55 // Get initializes the index if not already done so, then 56 // looks up the given page ref, returns nil if no value found. 57 func (c *PageCollections) getFromCache(ref string) (*Page, error) { 58 v, found, err := c.pageIndex.Get(ref) 59 if err != nil { 60 return nil, err 61 } 62 if !found { 63 return nil, nil 64 } 65 66 p := v.(*Page) 67 68 if p != ambiguityFlag { 69 return p, nil 70 } 71 return nil, fmt.Errorf("page reference %q is ambiguous", ref) 72 } 73 74 var ambiguityFlag = &Page{Kind: kindUnknown, title: "ambiguity flag"} 75 76 func (c *PageCollections) refreshPageCaches() { 77 c.indexPages = c.findPagesByKindNotIn(KindPage, c.Pages) 78 c.RegularPages = c.findPagesByKindIn(KindPage, c.Pages) 79 c.AllRegularPages = c.findPagesByKindIn(KindPage, c.AllPages) 80 81 indexLoader := func() (map[string]interface{}, error) { 82 index := make(map[string]interface{}) 83 84 add := func(ref string, p *Page) { 85 existing := index[ref] 86 if existing == nil { 87 index[ref] = p 88 } else if existing != ambiguityFlag && existing != p { 89 index[ref] = ambiguityFlag 90 } 91 } 92 93 for _, pageCollection := range []Pages{c.RegularPages, c.headlessPages} { 94 for _, p := range pageCollection { 95 sourceRef := p.absoluteSourceRef() 96 97 if sourceRef != "" { 98 // index the canonical ref 99 // e.g. /section/article.md 100 add(sourceRef, p) 101 } 102 103 // Ref/Relref supports this potentially ambiguous lookup. 104 add(p.Source.LogicalName(), p) 105 106 translationBaseName := p.Source.TranslationBaseName() 107 108 dir, _ := path.Split(sourceRef) 109 dir = strings.TrimSuffix(dir, "/") 110 111 if translationBaseName == "index" { 112 add(dir, p) 113 add(path.Base(dir), p) 114 } else { 115 add(translationBaseName, p) 116 } 117 118 // We need a way to get to the current language version. 119 pathWithNoExtensions := path.Join(dir, translationBaseName) 120 add(pathWithNoExtensions, p) 121 } 122 } 123 124 for _, p := range c.indexPages { 125 // index the canonical, unambiguous ref for any backing file 126 // e.g. /section/_index.md 127 sourceRef := p.absoluteSourceRef() 128 if sourceRef != "" { 129 add(sourceRef, p) 130 } 131 132 ref := path.Join(p.sections...) 133 134 // index the canonical, unambiguous virtual ref 135 // e.g. /section 136 // (this may already have been indexed above) 137 add("/"+ref, p) 138 } 139 140 return index, nil 141 } 142 143 c.pageIndex = cache.NewLazy(indexLoader) 144 } 145 146 func newPageCollections() *PageCollections { 147 return &PageCollections{} 148 } 149 150 func newPageCollectionsFromPages(pages Pages) *PageCollections { 151 return &PageCollections{rawAllPages: pages} 152 } 153 154 // This is an adapter func for the old API with Kind as first argument. 155 // This is invoked when you do .Site.GetPage. We drop the Kind and fails 156 // if there are more than 2 arguments, which would be ambigous. 157 func (c *PageCollections) getPageOldVersion(ref ...string) (*Page, error) { 158 var refs []string 159 for _, r := range ref { 160 // A common construct in the wild is 161 // .Site.GetPage "home" "" or 162 // .Site.GetPage "home" "/" 163 if r != "" && r != "/" { 164 refs = append(refs, r) 165 } 166 } 167 168 var key string 169 170 if len(refs) > 2 { 171 // This was allowed in Hugo <= 0.44, but we cannot support this with the 172 // new API. This should be the most unusual case. 173 return nil, fmt.Errorf(`too many arguments to .Site.GetPage: %v. Use lookups on the form {{ .Site.GetPage "/posts/mypage-md" }}`, ref) 174 } 175 176 if len(refs) == 0 || refs[0] == KindHome { 177 key = "/" 178 } else if len(refs) == 1 { 179 if len(ref) == 2 && refs[0] == KindSection { 180 // This is an old style reference to the "Home Page section". 181 // Typically fetched via {{ .Site.GetPage "section" .Section }} 182 // See https://github.com/gohugoio/hugo/issues/4989 183 key = "/" 184 } else { 185 key = refs[0] 186 } 187 } else { 188 key = refs[1] 189 } 190 191 key = filepath.ToSlash(key) 192 if !strings.HasPrefix(key, "/") { 193 key = "/" + key 194 } 195 196 return c.getPageNew(nil, key) 197 } 198 199 // Only used in tests. 200 func (c *PageCollections) getPage(typ string, sections ...string) *Page { 201 refs := append([]string{typ}, path.Join(sections...)) 202 p, _ := c.getPageOldVersion(refs...) 203 return p 204 } 205 206 // Ref is either unix-style paths (i.e. callers responsible for 207 // calling filepath.ToSlash as necessary) or shorthand refs. 208 func (c *PageCollections) getPageNew(context *Page, ref string) (*Page, error) { 209 210 // Absolute (content root relative) reference. 211 if strings.HasPrefix(ref, "/") { 212 if p, err := c.getFromCache(ref); err == nil && p != nil { 213 return p, nil 214 } 215 } else if context != nil { 216 // Try the page-relative path. 217 ppath := path.Join("/", strings.Join(context.sections, "/"), ref) 218 if p, err := c.getFromCache(ppath); err == nil && p != nil { 219 return p, nil 220 } 221 } 222 223 if !strings.HasPrefix(ref, "/") { 224 // Many people will have "post/foo.md" in their content files. 225 if p, err := c.getFromCache("/" + ref); err == nil && p != nil { 226 if context != nil { 227 // TODO(bep) remove this case and the message below when the storm has passed 228 helpers.DistinctFeedbackLog.Printf(`WARNING: make non-relative ref/relref page reference(s) in page %q absolute, e.g. {{< ref "/blog/my-post.md" >}}`, context.absoluteSourceRef()) 229 } 230 return p, nil 231 } 232 } 233 234 // Last try. 235 ref = strings.TrimPrefix(ref, "/") 236 p, err := c.getFromCache(ref) 237 238 if err != nil { 239 if context != nil { 240 return nil, fmt.Errorf("failed to resolve path from page %q: %s", context.absoluteSourceRef(), err) 241 } 242 return nil, fmt.Errorf("failed to resolve page: %s", err) 243 } 244 245 return p, nil 246 } 247 248 func (*PageCollections) findPagesByKindIn(kind string, inPages Pages) Pages { 249 var pages Pages 250 for _, p := range inPages { 251 if p.Kind == kind { 252 pages = append(pages, p) 253 } 254 } 255 return pages 256 } 257 258 func (*PageCollections) findFirstPageByKindIn(kind string, inPages Pages) *Page { 259 for _, p := range inPages { 260 if p.Kind == kind { 261 return p 262 } 263 } 264 return nil 265 } 266 267 func (*PageCollections) findPagesByKindNotIn(kind string, inPages Pages) Pages { 268 var pages Pages 269 for _, p := range inPages { 270 if p.Kind != kind { 271 pages = append(pages, p) 272 } 273 } 274 return pages 275 } 276 277 func (c *PageCollections) findPagesByKind(kind string) Pages { 278 return c.findPagesByKindIn(kind, c.Pages) 279 } 280 281 func (c *PageCollections) addPage(page *Page) { 282 c.rawAllPages = append(c.rawAllPages, page) 283 } 284 285 func (c *PageCollections) removePageFilename(filename string) { 286 if i := c.rawAllPages.findPagePosByFilename(filename); i >= 0 { 287 c.clearResourceCacheForPage(c.rawAllPages[i]) 288 c.rawAllPages = append(c.rawAllPages[:i], c.rawAllPages[i+1:]...) 289 } 290 291 } 292 293 func (c *PageCollections) removePage(page *Page) { 294 if i := c.rawAllPages.findPagePos(page); i >= 0 { 295 c.clearResourceCacheForPage(c.rawAllPages[i]) 296 c.rawAllPages = append(c.rawAllPages[:i], c.rawAllPages[i+1:]...) 297 } 298 299 } 300 301 func (c *PageCollections) findPagesByShortcode(shortcode string) Pages { 302 var pages Pages 303 304 for _, p := range c.rawAllPages { 305 if p.shortcodeState != nil { 306 if _, ok := p.shortcodeState.nameSet[shortcode]; ok { 307 pages = append(pages, p) 308 } 309 } 310 } 311 return pages 312 } 313 314 func (c *PageCollections) replacePage(page *Page) { 315 // will find existing page that matches filepath and remove it 316 c.removePage(page) 317 c.addPage(page) 318 } 319 320 func (c *PageCollections) clearResourceCacheForPage(page *Page) { 321 if len(page.Resources) > 0 { 322 page.s.ResourceSpec.DeleteCacheByPrefix(page.relTargetPathBase) 323 } 324 }