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