github.com/pietrocarrara/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  }