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  }