summaryrefslogtreecommitdiffstats
path: root/hugolib/pagecollections.go
diff options
context:
space:
mode:
Diffstat (limited to 'hugolib/pagecollections.go')
-rw-r--r--hugolib/pagecollections.go353
1 files changed, 141 insertions, 212 deletions
diff --git a/hugolib/pagecollections.go b/hugolib/pagecollections.go
index 26da4905e..8e05ad7e6 100644
--- a/hugolib/pagecollections.go
+++ b/hugolib/pagecollections.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
+// Copyright 2024 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.
@@ -18,91 +18,65 @@ import (
"path"
"path/filepath"
"strings"
- "sync"
-
- "github.com/gohugoio/hugo/common/paths"
+ "github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
- "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
)
-// PageCollections contains the page collections for a site.
-type PageCollections struct {
+// pageFinder provides ways to find a Page in a Site.
+type pageFinder struct {
pageMap *pageMap
-
- // Lazy initialized page collections
- pages *lazyPagesFactory
- regularPages *lazyPagesFactory
- allPages *lazyPagesFactory
- allRegularPages *lazyPagesFactory
-}
-
-// Pages returns all pages.
-// This is for the current language only.
-func (c *PageCollections) Pages() page.Pages {
- return c.pages.get()
-}
-
-// RegularPages returns all the regular pages.
-// This is for the current language only.
-func (c *PageCollections) RegularPages() page.Pages {
- return c.regularPages.get()
-}
-
-// AllPages returns all pages for all languages.
-func (c *PageCollections) AllPages() page.Pages {
- return c.allPages.get()
}
-// AllRegularPages returns all regular pages for all languages.
-func (c *PageCollections) AllRegularPages() page.Pages {
- return c.allRegularPages.get()
-}
-
-type lazyPagesFactory struct {
- pages page.Pages
-
- init sync.Once
- factory page.PagesFactory
-}
-
-func (l *lazyPagesFactory) get() page.Pages {
- l.init.Do(func() {
- l.pages = l.factory()
- })
- return l.pages
-}
-
-func newLazyPagesFactory(factory page.PagesFactory) *lazyPagesFactory {
- return &lazyPagesFactory{factory: factory}
-}
-
-func newPageCollections(m *pageMap) *PageCollections {
+func newPageFinder(m *pageMap) *pageFinder {
if m == nil {
panic("must provide a pageMap")
}
+ c := &pageFinder{pageMap: m}
+ return c
+}
- c := &PageCollections{pageMap: m}
+// getPageRef resolves a Page from ref/relRef, with a slightly more comprehensive
+// search path than getPage.
+func (c *pageFinder) getPageRef(context page.Page, ref string) (page.Page, error) {
+ n, err := c.getContentNode(context, true, ref)
+ if err != nil {
+ return nil, err
+ }
- c.pages = newLazyPagesFactory(func() page.Pages {
- return m.createListAllPages()
- })
+ if p, ok := n.(page.Page); ok {
+ return p, nil
+ }
+ return nil, nil
+}
- c.regularPages = newLazyPagesFactory(func() page.Pages {
- return c.findPagesByKindIn(kinds.KindPage, c.pages.get())
- })
+func (c *pageFinder) getPage(context page.Page, ref string) (page.Page, error) {
+ n, err := c.getContentNode(context, false, filepath.ToSlash(ref))
+ if err != nil {
+ return nil, err
+ }
+ if p, ok := n.(page.Page); ok {
+ return p, nil
+ }
+ return nil, nil
+}
- return c
+// Only used in tests.
+func (c *pageFinder) getPageOldVersion(kind string, sections ...string) page.Page {
+ refs := append([]string{kind}, path.Join(sections...))
+ p, _ := c.getPageForRefs(refs...)
+ return p
}
// This is an adapter func for the old API with Kind as first argument.
// This is invoked when you do .Site.GetPage. We drop the Kind and fails
// if there are more than 2 arguments, which would be ambiguous.
-func (c *PageCollections) getPageOldVersion(ref ...string) (page.Page, error) {
+func (c *pageFinder) getPageForRefs(ref ...string) (page.Page, error) {
var refs []string
for _, r := range ref {
// A common construct in the wild is
@@ -141,201 +115,156 @@ func (c *PageCollections) getPageOldVersion(ref ...string) (page.Page, error) {
key = "/" + key
}
- return c.getPageNew(nil, key)
+ return c.getPage(nil, key)
}
-// Only used in tests.
-func (c *PageCollections) getPage(typ string, sections ...string) page.Page {
- refs := append([]string{typ}, path.Join(sections...))
- p, _ := c.getPageOldVersion(refs...)
- return p
-}
+const defaultContentExt = ".md"
-// getPageRef resolves a Page from ref/relRef, with a slightly more comprehensive
-// search path than getPageNew.
-func (c *PageCollections) getPageRef(context page.Page, ref string) (page.Page, error) {
- n, err := c.getContentNode(context, true, ref)
- if err != nil || n == nil || n.p == nil {
- return nil, err
- }
- return n.p, nil
-}
-
-func (c *PageCollections) getPageNew(context page.Page, ref string) (page.Page, error) {
- n, err := c.getContentNode(context, false, ref)
- if err != nil || n == nil || n.p == nil {
- return nil, err
- }
- return n.p, nil
-}
-
-func (c *PageCollections) getSectionOrPage(ref string) (*contentNode, string) {
- var n *contentNode
-
- pref := helpers.AddTrailingSlash(ref)
- s, v, found := c.pageMap.sections.LongestPrefix(pref)
-
- if found {
- n = v.(*contentNode)
+func (c *pageFinder) getContentNode(context page.Page, isReflink bool, ref string) (contentNodeI, error) {
+ inRef := ref
+ if ref == "" {
+ ref = "/"
}
- if found && s == pref {
- // A section
- return n, ""
+ if paths.HasExt(ref) {
+ return c.getContentNodeForRef(context, isReflink, true, inRef, ref)
}
- m := c.pageMap
-
- filename := strings.TrimPrefix(strings.TrimPrefix(ref, s), "/")
- langSuffix := "." + m.s.Lang()
-
- // Trim both extension and any language code.
- name := paths.PathNoExt(filename)
- name = strings.TrimSuffix(name, langSuffix)
-
- // These are reserved bundle names and will always be stored by their owning
- // folder name.
- name = strings.TrimSuffix(name, "/index")
- name = strings.TrimSuffix(name, "/_index")
+ var refs []string
- if !found {
- return nil, name
+ // We are always looking for a content file and having an extension greatly simplifies the code that follows,
+ // even in the case where the extension does not match this one.
+ if ref == "/" {
+ refs = append(refs, "/_index"+defaultContentExt)
+ } else if strings.HasSuffix(ref, "/index") {
+ refs = append(refs, ref+"/index"+defaultContentExt)
+ refs = append(refs, ref+defaultContentExt)
+ } else {
+ refs = append(refs, ref+defaultContentExt)
}
- // Check if it's a section with filename provided.
- if !n.p.File().IsZero() && n.p.File().LogicalName() == filename {
- return n, name
+ for _, ref := range refs {
+ n, err := c.getContentNodeForRef(context, isReflink, false, inRef, ref)
+ if n != nil || err != nil {
+ return n, err
+ }
}
- return m.getPage(s, name), name
+ return nil, nil
}
-// For Ref/Reflink and .Site.GetPage do simple name lookups for the potentially ambiguous myarticle.md and /myarticle.md,
-// but not when we get ./myarticle*, section/myarticle.
-func shouldDoSimpleLookup(ref string) bool {
- if ref[0] == '.' {
- return false
- }
+func (c *pageFinder) getContentNodeForRef(context page.Page, isReflink, hadExtension bool, inRef, ref string) (contentNodeI, error) {
+ s := c.pageMap.s
+ contentPathParser := s.Conf.PathParser()
- slashCount := strings.Count(ref, "/")
+ if context != nil && !strings.HasPrefix(ref, "/") {
+ // Try the page-relative path first.
+ // Branch pages: /mysection, "./mypage" => /mysection/mypage
+ // Regular pages: /mysection/mypage.md, Path=/mysection/mypage, "./someotherpage" => /mysection/mypage/../someotherpage
+ // Regular leaf bundles: /mysection/mypage/index.md, Path=/mysection/mypage, "./someotherpage" => /mysection/mypage/../someotherpage
+ // Given the above, for regular pages we use the containing folder.
+ var baseDir string
+ if pi := context.PathInfo(); pi != nil {
+ if pi.IsBranchBundle() || (hadExtension) {
+ baseDir = pi.Dir()
+ } else {
+ baseDir = pi.ContainerDir()
+ }
+ }
- if slashCount > 1 {
- return false
- }
+ rel := path.Join(baseDir, inRef)
- return slashCount == 0 || ref[0] == '/'
-}
+ if !hadExtension && !paths.HasExt(rel) {
+ // See comment above.
+ rel += defaultContentExt
+ }
-func (c *PageCollections) getContentNode(context page.Page, isReflink bool, ref string) (*contentNode, error) {
- ref = filepath.ToSlash(strings.ToLower(strings.TrimSpace(ref)))
+ relPath := contentPathParser.Parse(files.ComponentFolderContent, rel)
- if ref == "" {
- ref = "/"
- }
-
- inRef := ref
- navUp := strings.HasPrefix(ref, "..")
- var doSimpleLookup bool
- if isReflink || context == nil {
- doSimpleLookup = shouldDoSimpleLookup(ref)
- }
+ n, err := c.getContentNodeFromPath(relPath, ref)
+ if n != nil || err != nil {
+ return n, err
+ }
- if context != nil && !strings.HasPrefix(ref, "/") {
- // Try the page-relative path.
- var base string
- if context.File().IsZero() {
- base = context.SectionsPath()
- } else {
- meta := context.File().FileInfo().Meta()
- base = filepath.ToSlash(filepath.Dir(meta.Path))
- if meta.Classifier == files.ContentClassLeaf {
- // Bundles are stored in subfolders e.g. blog/mybundle/index.md,
- // so if the user has not explicitly asked to go up,
- // look on the "blog" level.
- if !navUp {
- base = path.Dir(base)
- }
+ if hadExtension && context.File() != nil {
+ if n, err := c.getContentNodeFromRefReverseLookup(inRef, context.File().FileInfo()); n != nil || err != nil {
+ return n, err
}
}
- ref = path.Join("/", strings.ToLower(base), ref)
+
}
- if !strings.HasPrefix(ref, "/") {
- ref = "/" + ref
+ if strings.HasPrefix(ref, ".") {
+ // Page relative, no need to look further.
+ return nil, nil
}
- m := c.pageMap
+ refPath := contentPathParser.Parse(files.ComponentFolderContent, ref)
- // It's either a section, a page in a section or a taxonomy node.
- // Start with the most likely:
- n, name := c.getSectionOrPage(ref)
- if n != nil {
- return n, nil
- }
+ n, err := c.getContentNodeFromPath(refPath, ref)
- if !strings.HasPrefix(inRef, "/") {
- // Many people will have "post/foo.md" in their content files.
- if n, _ := c.getSectionOrPage("/" + inRef); n != nil {
- return n, nil
- }
+ if n != nil || err != nil {
+ return n, err
}
- // Check if it's a taxonomy node
- pref := helpers.AddTrailingSlash(ref)
- s, v, found := m.taxonomies.LongestPrefix(pref)
-
- if found {
- if !m.onSameLevel(pref, s) {
- return nil, nil
+ if hadExtension && s.home != nil && s.home.File() != nil {
+ if n, err := c.getContentNodeFromRefReverseLookup(inRef, s.home.File().FileInfo()); n != nil || err != nil {
+ return n, err
}
- return v.(*contentNode), nil
}
- getByName := func(s string) (*contentNode, error) {
- n := m.pageReverseIndex.Get(s)
- if n != nil {
- if n == ambiguousContentNode {
- return nil, fmt.Errorf("page reference %q is ambiguous", ref)
- }
- return n, nil
+ var doSimpleLookup bool
+ if isReflink || context == nil {
+ slashCount := strings.Count(inRef, "/")
+ if slashCount <= 1 {
+ doSimpleLookup = slashCount == 0 || ref[0] == '/'
}
+ }
+ if !doSimpleLookup {
return nil, nil
}
- var module string
- if context != nil && !context.File().IsZero() {
- module = context.File().FileInfo().Meta().Module
+ n = c.pageMap.pageReverseIndex.Get(refPath.BaseNameNoIdentifier())
+ if n == ambiguousContentNode {
+ return nil, fmt.Errorf("page reference %q is ambiguous", inRef)
}
- if module == "" && !c.pageMap.s.home.File().IsZero() {
- module = c.pageMap.s.home.File().FileInfo().Meta().Module
- }
+ return n, nil
+}
- if module != "" {
- n, err := getByName(module + ref)
- if err != nil {
- return nil, err
- }
- if n != nil {
- return n, nil
- }
+func (c *pageFinder) getContentNodeFromRefReverseLookup(ref string, fi hugofs.FileMetaInfo) (contentNodeI, error) {
+ s := c.pageMap.s
+ meta := fi.Meta()
+ dir := meta.Filename
+ if !fi.IsDir() {
+ dir = filepath.Dir(meta.Filename)
}
- if !doSimpleLookup {
- return nil, nil
+ realFilename := filepath.Join(dir, ref)
+
+ pcs, err := s.BaseFs.Content.ReverseLookup(realFilename)
+ if err != nil {
+ return nil, err
}
- // Ref/relref supports this potentially ambiguous lookup.
- return getByName(path.Base(name))
+ // There may be multiple matches, but we will only use the first one.
+ for _, pc := range pcs {
+ pi := s.Conf.PathParser().Parse(pc.Component, pc.Path)
+ if n := c.pageMap.treePages.Get(pi.Base()); n != nil {
+ return n, nil
+ }
+ }
+ return nil, nil
}
-func (*PageCollections) findPagesByKindIn(kind string, inPages page.Pages) page.Pages {
- var pages page.Pages
- for _, p := range inPages {
- if p.Kind() == kind {
- pages = append(pages, p)
- }
+func (c *pageFinder) getContentNodeFromPath(refPath *paths.Path, ref string) (contentNodeI, error) {
+ s := refPath.Base()
+
+ n := c.pageMap.treePages.Get(s)
+ if n != nil {
+ return n, nil
}
- return pages
+
+ return nil, nil
}