github.com/lyeb/hugo@v0.47.1/hugolib/site_sections.go (about) 1 // Copyright 2017 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 "strconv" 20 "strings" 21 22 "github.com/gohugoio/hugo/helpers" 23 24 radix "github.com/hashicorp/go-immutable-radix" 25 ) 26 27 // Sections returns the top level sections. 28 func (s *SiteInfo) Sections() Pages { 29 home, err := s.Home() 30 if err == nil { 31 return home.Sections() 32 } 33 return nil 34 } 35 36 // Home is a shortcut to the home page, equivalent to .Site.GetPage "home". 37 func (s *SiteInfo) Home() (*Page, error) { 38 return s.GetPage(KindHome) 39 } 40 41 // Parent returns a section's parent section or a page's section. 42 // To get a section's subsections, see Page's Sections method. 43 func (p *Page) Parent() *Page { 44 return p.parent 45 } 46 47 // CurrentSection returns the page's current section or the page itself if home or a section. 48 // Note that this will return nil for pages that is not regular, home or section pages. 49 func (p *Page) CurrentSection() *Page { 50 v := p 51 if v.origOnCopy != nil { 52 v = v.origOnCopy 53 } 54 if v.IsHome() || v.IsSection() { 55 return v 56 } 57 58 return v.parent 59 } 60 61 // FirstSection returns the section on level 1 below home, e.g. "/docs". 62 // For the home page, this will return itself. 63 func (p *Page) FirstSection() *Page { 64 v := p 65 if v.origOnCopy != nil { 66 v = v.origOnCopy 67 } 68 69 if v.parent == nil || v.parent.IsHome() { 70 return v 71 } 72 73 parent := v.parent 74 for { 75 current := parent 76 parent = parent.parent 77 if parent == nil || parent.IsHome() { 78 return current 79 } 80 } 81 82 } 83 84 // InSection returns whether the given page is in the current section. 85 // Note that this will always return false for pages that are 86 // not either regular, home or section pages. 87 func (p *Page) InSection(other interface{}) (bool, error) { 88 if p == nil || other == nil { 89 return false, nil 90 } 91 92 pp, err := unwrapPage(other) 93 if err != nil { 94 return false, err 95 } 96 97 if pp == nil { 98 return false, nil 99 } 100 101 return pp.CurrentSection() == p.CurrentSection(), nil 102 } 103 104 // IsDescendant returns whether the current page is a descendant of the given page. 105 // Note that this method is not relevant for taxonomy lists and taxonomy terms pages. 106 func (p *Page) IsDescendant(other interface{}) (bool, error) { 107 pp, err := unwrapPage(other) 108 if err != nil { 109 return false, err 110 } 111 112 if pp.Kind == KindPage && len(p.sections) == len(pp.sections) { 113 // A regular page is never its section's descendant. 114 return false, nil 115 } 116 return helpers.HasStringsPrefix(p.sections, pp.sections), nil 117 } 118 119 // IsAncestor returns whether the current page is an ancestor of the given page. 120 // Note that this method is not relevant for taxonomy lists and taxonomy terms pages. 121 func (p *Page) IsAncestor(other interface{}) (bool, error) { 122 pp, err := unwrapPage(other) 123 if err != nil { 124 return false, err 125 } 126 127 if p.Kind == KindPage && len(p.sections) == len(pp.sections) { 128 // A regular page is never its section's ancestor. 129 return false, nil 130 } 131 132 return helpers.HasStringsPrefix(pp.sections, p.sections), nil 133 } 134 135 // Eq returns whether the current page equals the given page. 136 // Note that this is more accurate than doing `{{ if eq $page $otherPage }}` 137 // since a Page can be embedded in another type. 138 func (p *Page) Eq(other interface{}) bool { 139 pp, err := unwrapPage(other) 140 if err != nil { 141 return false 142 } 143 144 return p == pp 145 } 146 147 func unwrapPage(in interface{}) (*Page, error) { 148 switch v := in.(type) { 149 case *Page: 150 return v, nil 151 case *PageOutput: 152 return v.Page, nil 153 case *PageWithoutContent: 154 return v.Page, nil 155 default: 156 return nil, fmt.Errorf("%T not supported", in) 157 } 158 } 159 160 // Sections returns this section's subsections, if any. 161 // Note that for non-sections, this method will always return an empty list. 162 func (p *Page) Sections() Pages { 163 return p.subSections 164 } 165 166 func (s *Site) assembleSections() Pages { 167 var newPages Pages 168 169 if !s.isEnabled(KindSection) { 170 return newPages 171 } 172 173 // Maps section kind pages to their path, i.e. "my/section" 174 sectionPages := make(map[string]*Page) 175 176 // The sections with content files will already have been created. 177 for _, sect := range s.findPagesByKind(KindSection) { 178 sectionPages[path.Join(sect.sections...)] = sect 179 } 180 181 const ( 182 sectKey = "__hs" 183 sectSectKey = "_a" + sectKey 184 sectPageKey = "_b" + sectKey 185 ) 186 187 var ( 188 inPages = radix.New().Txn() 189 inSections = radix.New().Txn() 190 undecided Pages 191 ) 192 193 home := s.findFirstPageByKindIn(KindHome, s.Pages) 194 195 for i, p := range s.Pages { 196 if p.Kind != KindPage { 197 continue 198 } 199 200 if len(p.sections) == 0 { 201 // Root level pages. These will have the home page as their Parent. 202 p.parent = home 203 continue 204 } 205 206 sectionKey := path.Join(p.sections...) 207 sect, found := sectionPages[sectionKey] 208 209 if !found && len(p.sections) == 1 { 210 // We only create content-file-less sections for the root sections. 211 sect = s.newSectionPage(p.sections[0]) 212 sectionPages[sectionKey] = sect 213 newPages = append(newPages, sect) 214 found = true 215 } 216 217 if len(p.sections) > 1 { 218 // Create the root section if not found. 219 _, rootFound := sectionPages[p.sections[0]] 220 if !rootFound { 221 sect = s.newSectionPage(p.sections[0]) 222 sectionPages[p.sections[0]] = sect 223 newPages = append(newPages, sect) 224 } 225 } 226 227 if found { 228 pagePath := path.Join(sectionKey, sectPageKey, strconv.Itoa(i)) 229 inPages.Insert([]byte(pagePath), p) 230 } else { 231 undecided = append(undecided, p) 232 } 233 } 234 235 // Create any missing sections in the tree. 236 // A sub-section needs a content file, but to create a navigational tree, 237 // given a content file in /content/a/b/c/_index.md, we cannot create just 238 // the c section. 239 for _, sect := range sectionPages { 240 for i := len(sect.sections); i > 0; i-- { 241 sectionPath := sect.sections[:i] 242 sectionKey := path.Join(sectionPath...) 243 sect, found := sectionPages[sectionKey] 244 if !found { 245 sect = s.newSectionPage(sectionPath[len(sectionPath)-1]) 246 sect.sections = sectionPath 247 sectionPages[sectionKey] = sect 248 newPages = append(newPages, sect) 249 } 250 } 251 } 252 253 for k, sect := range sectionPages { 254 inPages.Insert([]byte(path.Join(k, sectSectKey)), sect) 255 inSections.Insert([]byte(k), sect) 256 } 257 258 var ( 259 currentSection *Page 260 children Pages 261 rootSections = inSections.Commit().Root() 262 ) 263 264 for i, p := range undecided { 265 // Now we can decide where to put this page into the tree. 266 sectionKey := path.Join(p.sections...) 267 _, v, _ := rootSections.LongestPrefix([]byte(sectionKey)) 268 sect := v.(*Page) 269 pagePath := path.Join(path.Join(sect.sections...), sectSectKey, "u", strconv.Itoa(i)) 270 inPages.Insert([]byte(pagePath), p) 271 } 272 273 var rootPages = inPages.Commit().Root() 274 275 rootPages.Walk(func(path []byte, v interface{}) bool { 276 p := v.(*Page) 277 278 if p.Kind == KindSection { 279 if currentSection != nil { 280 // A new section 281 currentSection.setPagePages(children) 282 } 283 284 currentSection = p 285 children = make(Pages, 0) 286 287 return false 288 289 } 290 291 // Regular page 292 p.parent = currentSection 293 children = append(children, p) 294 return false 295 }) 296 297 if currentSection != nil { 298 currentSection.setPagePages(children) 299 } 300 301 // Build the sections hierarchy 302 for _, sect := range sectionPages { 303 if len(sect.sections) == 1 { 304 sect.parent = home 305 } else { 306 parentSearchKey := path.Join(sect.sections[:len(sect.sections)-1]...) 307 _, v, _ := rootSections.LongestPrefix([]byte(parentSearchKey)) 308 p := v.(*Page) 309 sect.parent = p 310 } 311 312 if sect.parent != nil { 313 sect.parent.subSections = append(sect.parent.subSections, sect) 314 } 315 } 316 317 var ( 318 sectionsParamId = "mainSections" 319 sectionsParamIdLower = strings.ToLower(sectionsParamId) 320 mainSections interface{} 321 mainSectionsFound bool 322 maxSectionWeight int 323 ) 324 325 mainSections, mainSectionsFound = s.Info.Params[sectionsParamIdLower] 326 327 for _, sect := range sectionPages { 328 if sect.parent != nil { 329 sect.parent.subSections.Sort() 330 } 331 332 for i, p := range sect.Pages { 333 if i > 0 { 334 p.NextInSection = sect.Pages[i-1] 335 } 336 if i < len(sect.Pages)-1 { 337 p.PrevInSection = sect.Pages[i+1] 338 } 339 } 340 341 if !mainSectionsFound { 342 weight := len(sect.Pages) + (len(sect.Sections()) * 5) 343 if weight >= maxSectionWeight { 344 mainSections = []string{sect.Section()} 345 maxSectionWeight = weight 346 } 347 } 348 } 349 350 // Try to make this as backwards compatible as possible. 351 s.Info.Params[sectionsParamId] = mainSections 352 s.Info.Params[sectionsParamIdLower] = mainSections 353 354 return newPages 355 356 } 357 358 func (p *Page) setPagePages(pages Pages) { 359 pages.Sort() 360 p.Pages = pages 361 p.data = make(map[string]interface{}) 362 p.data["Pages"] = pages 363 }