hugo/hugolib/site_sections.go
Bjørn Erik Pedersen b39689393c hugolib: Enable nested sections
Fixes #465
2017-06-08 11:21:34 +02:00

295 lines
7.2 KiB
Go

// Copyright 2017 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
"fmt"
"path"
"strconv"
"strings"
radix "github.com/hashicorp/go-immutable-radix"
"github.com/spf13/hugo/helpers"
)
// Deprecated: Use .Site.Home.Sections.
// TODO(bep) Hugo 0.23 = Reuse as an alias for home's sections.
func (s *SiteInfo) Sections() Taxonomy {
helpText := `In Hugo 0.22 we introduced nested sections, so this method now returns an empty taxonomy.
To list sections with its pages, you can do something like this:
{{ range $.Site.Home.Sections }}
Section: {{ .Title }}
{{ range .Pages }}
Section Page: {{ .Title }}
{{ end }}
{{ end }}
To get a specific section, you can do this:
{{ $section := $.Site.GetPage "section" "blog" }}
`
helpers.Deprecated("Site", "Sections", helpText, true)
return Taxonomy{}
}
// Home is a shortcut to the home page, equivalent to .Site.GetPage "home".
func (s *SiteInfo) Home() (*Page, error) {
return s.GetPage(KindHome)
}
// Parent returns a section's parent section or a page's section.
// To get a section's subsections, see Page's Sections method.
func (p *Page) Parent() *Page {
return p.parent
}
// current returns the page's current section.
// Note that this will return nil for pages that is not regular, home or section pages.
// Note that for paginated sections and home pages, this will return the original page pointer.
func (p *Page) current() *Page {
v := p
if v.origOnCopy != nil {
v = v.origOnCopy
}
if v.IsHome() || v.IsSection() {
return v
}
return v.parent
}
// InSection returns whether the given page is in the current section.
// Note that this will always return false for pages that are
// not either regular, home or section pages.
func (p *Page) InSection(other interface{}) (bool, error) {
if p == nil || other == nil {
return false, nil
}
if po, ok := other.(*PageOutput); ok {
other = po.Page
}
pp, ok := other.(*Page)
if !ok {
return false, fmt.Errorf("%T not supported in InSection", other)
}
if pp == nil {
return false, nil
}
return pp.current() == p.current(), nil
}
// Sections returns this section's subsections, if any.
// Note that for non-sections, this method will always return an empty list.
func (p *Page) Sections() Pages {
return p.subSections
}
func (s *Site) assembleSections() Pages {
var newPages Pages
if !s.isEnabled(KindSection) {
return newPages
}
// Maps section kind pages to their path, i.e. "my/section"
sectionPages := make(map[string]*Page)
// The sections with content files will already have been created.
for _, sect := range s.findPagesByKind(KindSection) {
sectionPages[path.Join(sect.sections...)] = sect
}
const (
sectKey = "__hs"
sectSectKey = "_a" + sectKey
sectPageKey = "_b" + sectKey
)
var (
home *Page
inPages = radix.New().Txn()
inSections = radix.New().Txn()
undecided Pages
)
for i, p := range s.Pages {
if p.Kind != KindPage {
if p.Kind == KindHome {
home = p
}
continue
}
if len(p.sections) == 0 {
// Root level pages. These will have the home page as their Parent.
p.parent = home
continue
}
sectionKey := path.Join(p.sections...)
sect, found := sectionPages[sectionKey]
if !found && len(p.sections) == 1 {
// We only create content-file-less sections for the root sections.
sect = s.newSectionPage(p.sections[0])
sectionPages[sectionKey] = sect
newPages = append(newPages, sect)
} else if !found {
// We don't know what to do with this section yet.
undecided = append(undecided, p)
}
pagePath := path.Join(sectionKey, sectPageKey, strconv.Itoa(i))
inPages.Insert([]byte(pagePath), p)
}
// Create any missing sections in the tree.
// A sub-section needs a content file, but to create a navigational tree,
// given a content file in /content/a/b/c/_index.md, we cannot create just
// the c section.
for _, sect := range sectionPages {
for i := len(sect.sections); i > 0; i-- {
sectionPath := sect.sections[:i]
sectionKey := path.Join(sectionPath...)
sect, found := sectionPages[sectionKey]
if !found {
sect = s.newSectionPage(sectionPath[len(sectionPath)-1])
sect.sections = sectionPath
sectionPages[sectionKey] = sect
newPages = append(newPages, sect)
}
}
}
// Create any missing root sections.
for _, p := range undecided {
sectionKey := p.sections[0]
sect, found := sectionPages[sectionKey]
if !found {
sect = s.newSectionPage(sectionKey)
sectionPages[sectionKey] = sect
newPages = append(newPages, sect)
}
}
for k, sect := range sectionPages {
inPages.Insert([]byte(path.Join(k, sectSectKey)), sect)
inSections.Insert([]byte(k), sect)
}
var (
currentSection *Page
children Pages
rootPages = inPages.Commit().Root()
rootSections = inSections.Commit().Root()
)
rootPages.Walk(func(path []byte, v interface{}) bool {
p := v.(*Page)
if p.Kind == KindSection {
if currentSection != nil {
// A new section
currentSection.setPagePages(children)
}
currentSection = p
children = make(Pages, 0)
return false
}
// Regular page
p.parent = currentSection
children = append(children, p)
return false
})
if currentSection != nil {
currentSection.setPagePages(children)
}
// Build the sections hierarchy
for _, sect := range sectionPages {
if len(sect.sections) == 1 {
sect.parent = home
} else {
parentSearchKey := path.Join(sect.sections[:len(sect.sections)-1]...)
_, v, _ := rootSections.LongestPrefix([]byte(parentSearchKey))
p := v.(*Page)
sect.parent = p
}
if sect.parent != nil {
sect.parent.subSections = append(sect.parent.subSections, sect)
}
}
var (
sectionsParamId = "mainSections"
sectionsParamIdLower = strings.ToLower(sectionsParamId)
mainSections interface{}
mainSectionsFound bool
maxSectionWeight int
)
mainSections, mainSectionsFound = s.Info.Params[sectionsParamIdLower]
for _, sect := range sectionPages {
if sect.parent != nil {
sect.parent.subSections.Sort()
}
for i, p := range sect.Pages {
if i > 0 {
p.NextInSection = sect.Pages[i-1]
}
if i < len(sect.Pages)-1 {
p.PrevInSection = sect.Pages[i+1]
}
}
if !mainSectionsFound {
weight := len(sect.Pages) + (len(sect.Sections()) * 5)
if weight >= maxSectionWeight {
mainSections = []string{sect.Section()}
maxSectionWeight = weight
}
}
}
// Try to make this as backwards compatible as possible.
s.Info.Params[sectionsParamId] = mainSections
s.Info.Params[sectionsParamIdLower] = mainSections
return newPages
}
func (p *Page) setPagePages(pages Pages) {
pages.Sort()
p.Pages = pages
p.Data = make(map[string]interface{})
p.Data["Pages"] = pages
}