hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

pagecollections.go (8447B)

    1 // Copyright 2019 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 	"path/filepath"
   20 	"strings"
   21 	"sync"
   22 
   23 	"github.com/gohugoio/hugo/common/paths"
   24 
   25 	"github.com/gohugoio/hugo/hugofs/files"
   26 
   27 	"github.com/gohugoio/hugo/helpers"
   28 
   29 	"github.com/gohugoio/hugo/resources/page"
   30 )
   31 
   32 // PageCollections contains the page collections for a site.
   33 type PageCollections struct {
   34 	pageMap *pageMap
   35 
   36 	// Lazy initialized page collections
   37 	pages           *lazyPagesFactory
   38 	regularPages    *lazyPagesFactory
   39 	allPages        *lazyPagesFactory
   40 	allRegularPages *lazyPagesFactory
   41 }
   42 
   43 // Pages returns all pages.
   44 // This is for the current language only.
   45 func (c *PageCollections) Pages() page.Pages {
   46 	return c.pages.get()
   47 }
   48 
   49 // RegularPages returns all the regular pages.
   50 // This is for the current language only.
   51 func (c *PageCollections) RegularPages() page.Pages {
   52 	return c.regularPages.get()
   53 }
   54 
   55 // AllPages returns all pages for all languages.
   56 func (c *PageCollections) AllPages() page.Pages {
   57 	return c.allPages.get()
   58 }
   59 
   60 // AllPages returns all regular pages for all languages.
   61 func (c *PageCollections) AllRegularPages() page.Pages {
   62 	return c.allRegularPages.get()
   63 }
   64 
   65 type lazyPagesFactory struct {
   66 	pages page.Pages
   67 
   68 	init    sync.Once
   69 	factory page.PagesFactory
   70 }
   71 
   72 func (l *lazyPagesFactory) get() page.Pages {
   73 	l.init.Do(func() {
   74 		l.pages = l.factory()
   75 	})
   76 	return l.pages
   77 }
   78 
   79 func newLazyPagesFactory(factory page.PagesFactory) *lazyPagesFactory {
   80 	return &lazyPagesFactory{factory: factory}
   81 }
   82 
   83 func newPageCollections(m *pageMap) *PageCollections {
   84 	if m == nil {
   85 		panic("must provide a pageMap")
   86 	}
   87 
   88 	c := &PageCollections{pageMap: m}
   89 
   90 	c.pages = newLazyPagesFactory(func() page.Pages {
   91 		return m.createListAllPages()
   92 	})
   93 
   94 	c.regularPages = newLazyPagesFactory(func() page.Pages {
   95 		return c.findPagesByKindIn(page.KindPage, c.pages.get())
   96 	})
   97 
   98 	return c
   99 }
  100 
  101 // This is an adapter func for the old API with Kind as first argument.
  102 // This is invoked when you do .Site.GetPage. We drop the Kind and fails
  103 // if there are more than 2 arguments, which would be ambiguous.
  104 func (c *PageCollections) getPageOldVersion(ref ...string) (page.Page, error) {
  105 	var refs []string
  106 	for _, r := range ref {
  107 		// A common construct in the wild is
  108 		// .Site.GetPage "home" "" or
  109 		// .Site.GetPage "home" "/"
  110 		if r != "" && r != "/" {
  111 			refs = append(refs, r)
  112 		}
  113 	}
  114 
  115 	var key string
  116 
  117 	if len(refs) > 2 {
  118 		// This was allowed in Hugo <= 0.44, but we cannot support this with the
  119 		// new API. This should be the most unusual case.
  120 		return nil, fmt.Errorf(`too many arguments to .Site.GetPage: %v. Use lookups on the form {{ .Site.GetPage "/posts/mypage-md" }}`, ref)
  121 	}
  122 
  123 	if len(refs) == 0 || refs[0] == page.KindHome {
  124 		key = "/"
  125 	} else if len(refs) == 1 {
  126 		if len(ref) == 2 && refs[0] == page.KindSection {
  127 			// This is an old style reference to the "Home Page section".
  128 			// Typically fetched via {{ .Site.GetPage "section" .Section }}
  129 			// See https://github.com/gohugoio/hugo/issues/4989
  130 			key = "/"
  131 		} else {
  132 			key = refs[0]
  133 		}
  134 	} else {
  135 		key = refs[1]
  136 	}
  137 
  138 	key = filepath.ToSlash(key)
  139 	if !strings.HasPrefix(key, "/") {
  140 		key = "/" + key
  141 	}
  142 
  143 	return c.getPageNew(nil, key)
  144 }
  145 
  146 // 	Only used in tests.
  147 func (c *PageCollections) getPage(typ string, sections ...string) page.Page {
  148 	refs := append([]string{typ}, path.Join(sections...))
  149 	p, _ := c.getPageOldVersion(refs...)
  150 	return p
  151 }
  152 
  153 // getPageRef resolves a Page from ref/relRef, with a slightly more comprehensive
  154 // search path than getPageNew.
  155 func (c *PageCollections) getPageRef(context page.Page, ref string) (page.Page, error) {
  156 	n, err := c.getContentNode(context, true, ref)
  157 	if err != nil || n == nil || n.p == nil {
  158 		return nil, err
  159 	}
  160 	return n.p, nil
  161 }
  162 
  163 func (c *PageCollections) getPageNew(context page.Page, ref string) (page.Page, error) {
  164 	n, err := c.getContentNode(context, false, ref)
  165 	if err != nil || n == nil || n.p == nil {
  166 		return nil, err
  167 	}
  168 	return n.p, nil
  169 }
  170 
  171 func (c *PageCollections) getSectionOrPage(ref string) (*contentNode, string) {
  172 	var n *contentNode
  173 
  174 	pref := helpers.AddTrailingSlash(ref)
  175 	s, v, found := c.pageMap.sections.LongestPrefix(pref)
  176 
  177 	if found {
  178 		n = v.(*contentNode)
  179 	}
  180 
  181 	if found && s == pref {
  182 		// A section
  183 		return n, ""
  184 	}
  185 
  186 	m := c.pageMap
  187 
  188 	filename := strings.TrimPrefix(strings.TrimPrefix(ref, s), "/")
  189 	langSuffix := "." + m.s.Lang()
  190 
  191 	// Trim both extension and any language code.
  192 	name := paths.PathNoExt(filename)
  193 	name = strings.TrimSuffix(name, langSuffix)
  194 
  195 	// These are reserved bundle names and will always be stored by their owning
  196 	// folder name.
  197 	name = strings.TrimSuffix(name, "/index")
  198 	name = strings.TrimSuffix(name, "/_index")
  199 
  200 	if !found {
  201 		return nil, name
  202 	}
  203 
  204 	// Check if it's a section with filename provided.
  205 	if !n.p.File().IsZero() && n.p.File().LogicalName() == filename {
  206 		return n, name
  207 	}
  208 
  209 	return m.getPage(s, name), name
  210 }
  211 
  212 // For Ref/Reflink and .Site.GetPage do simple name lookups for the potentially ambigous myarticle.md and /myarticle.md,
  213 // but not when we get ./myarticle*, section/myarticle.
  214 func shouldDoSimpleLookup(ref string) bool {
  215 	if ref[0] == '.' {
  216 		return false
  217 	}
  218 
  219 	slashCount := strings.Count(ref, "/")
  220 
  221 	if slashCount > 1 {
  222 		return false
  223 	}
  224 
  225 	return slashCount == 0 || ref[0] == '/'
  226 }
  227 
  228 func (c *PageCollections) getContentNode(context page.Page, isReflink bool, ref string) (*contentNode, error) {
  229 	ref = filepath.ToSlash(strings.ToLower(strings.TrimSpace(ref)))
  230 
  231 	if ref == "" {
  232 		ref = "/"
  233 	}
  234 
  235 	inRef := ref
  236 	navUp := strings.HasPrefix(ref, "..")
  237 	var doSimpleLookup bool
  238 	if isReflink || context == nil {
  239 		doSimpleLookup = shouldDoSimpleLookup(ref)
  240 	}
  241 
  242 	if context != nil && !strings.HasPrefix(ref, "/") {
  243 		// Try the page-relative path.
  244 		var base string
  245 		if context.File().IsZero() {
  246 			base = context.SectionsPath()
  247 		} else {
  248 			meta := context.File().FileInfo().Meta()
  249 			base = filepath.ToSlash(filepath.Dir(meta.Path))
  250 			if meta.Classifier == files.ContentClassLeaf {
  251 				// Bundles are stored in subfolders e.g. blog/mybundle/index.md,
  252 				// so if the user has not explicitly asked to go up,
  253 				// look on the "blog" level.
  254 				if !navUp {
  255 					base = path.Dir(base)
  256 				}
  257 			}
  258 		}
  259 		ref = path.Join("/", strings.ToLower(base), ref)
  260 	}
  261 
  262 	if !strings.HasPrefix(ref, "/") {
  263 		ref = "/" + ref
  264 	}
  265 
  266 	m := c.pageMap
  267 
  268 	// It's either a section, a page in a section or a taxonomy node.
  269 	// Start with the most likely:
  270 	n, name := c.getSectionOrPage(ref)
  271 	if n != nil {
  272 		return n, nil
  273 	}
  274 
  275 	if !strings.HasPrefix(inRef, "/") {
  276 		// Many people will have "post/foo.md" in their content files.
  277 		if n, _ := c.getSectionOrPage("/" + inRef); n != nil {
  278 			return n, nil
  279 		}
  280 	}
  281 
  282 	// Check if it's a taxonomy node
  283 	pref := helpers.AddTrailingSlash(ref)
  284 	s, v, found := m.taxonomies.LongestPrefix(pref)
  285 
  286 	if found {
  287 		if !m.onSameLevel(pref, s) {
  288 			return nil, nil
  289 		}
  290 		return v.(*contentNode), nil
  291 	}
  292 
  293 	getByName := func(s string) (*contentNode, error) {
  294 		n := m.pageReverseIndex.Get(s)
  295 		if n != nil {
  296 			if n == ambiguousContentNode {
  297 				return nil, fmt.Errorf("page reference %q is ambiguous", ref)
  298 			}
  299 			return n, nil
  300 		}
  301 
  302 		return nil, nil
  303 	}
  304 
  305 	var module string
  306 	if context != nil && !context.File().IsZero() {
  307 		module = context.File().FileInfo().Meta().Module
  308 	}
  309 
  310 	if module == "" && !c.pageMap.s.home.File().IsZero() {
  311 		module = c.pageMap.s.home.File().FileInfo().Meta().Module
  312 	}
  313 
  314 	if module != "" {
  315 		n, err := getByName(module + ref)
  316 		if err != nil {
  317 			return nil, err
  318 		}
  319 		if n != nil {
  320 			return n, nil
  321 		}
  322 	}
  323 
  324 	if !doSimpleLookup {
  325 		return nil, nil
  326 	}
  327 
  328 	// Ref/relref supports this potentially ambigous lookup.
  329 	return getByName(path.Base(name))
  330 }
  331 
  332 func (*PageCollections) findPagesByKindIn(kind string, inPages page.Pages) page.Pages {
  333 	var pages page.Pages
  334 	for _, p := range inPages {
  335 		if p.Kind() == kind {
  336 			pages = append(pages, p)
  337 		}
  338 	}
  339 	return pages
  340 }