hugo

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

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

pages_sort.go (10284B)

    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 page
   15 
   16 import (
   17 	"sort"
   18 
   19 	"github.com/gohugoio/hugo/common/collections"
   20 	"github.com/gohugoio/hugo/langs"
   21 
   22 	"github.com/gohugoio/hugo/resources/resource"
   23 
   24 	"github.com/gohugoio/hugo/compare"
   25 	"github.com/spf13/cast"
   26 )
   27 
   28 var spc = newPageCache()
   29 
   30 /*
   31  * Implementation of a custom sorter for Pages
   32  */
   33 
   34 // A pageSorter implements the sort interface for Pages
   35 type pageSorter struct {
   36 	pages Pages
   37 	by    pageBy
   38 }
   39 
   40 // pageBy is a closure used in the Sort.Less method.
   41 type pageBy func(p1, p2 Page) bool
   42 
   43 func getOrdinals(p1, p2 Page) (int, int) {
   44 	p1o, ok1 := p1.(collections.Order)
   45 	if !ok1 {
   46 		return -1, -1
   47 	}
   48 	p2o, ok2 := p2.(collections.Order)
   49 	if !ok2 {
   50 		return -1, -1
   51 	}
   52 
   53 	return p1o.Ordinal(), p2o.Ordinal()
   54 }
   55 
   56 // Sort stable sorts the pages given the receiver's sort order.
   57 func (by pageBy) Sort(pages Pages) {
   58 	ps := &pageSorter{
   59 		pages: pages,
   60 		by:    by, // The Sort method's receiver is the function (closure) that defines the sort order.
   61 	}
   62 	sort.Stable(ps)
   63 }
   64 
   65 var (
   66 
   67 	// DefaultPageSort is the default sort func for pages in Hugo:
   68 	// Order by Ordinal, Weight, Date, LinkTitle and then full file path.
   69 	DefaultPageSort = func(p1, p2 Page) bool {
   70 		o1, o2 := getOrdinals(p1, p2)
   71 		if o1 != o2 && o1 != -1 && o2 != -1 {
   72 			return o1 < o2
   73 		}
   74 		if p1.Weight() == p2.Weight() {
   75 			if p1.Date().Unix() == p2.Date().Unix() {
   76 				c := collatorStringCompare(func(p Page) string { return p.LinkTitle() }, p1, p2)
   77 				if c == 0 {
   78 					if p1.File().IsZero() || p2.File().IsZero() {
   79 						return p1.File().IsZero()
   80 					}
   81 					return compare.LessStrings(p1.File().Filename(), p2.File().Filename())
   82 				}
   83 				return c < 0
   84 			}
   85 			return p1.Date().Unix() > p2.Date().Unix()
   86 		}
   87 
   88 		if p2.Weight() == 0 {
   89 			return true
   90 		}
   91 
   92 		if p1.Weight() == 0 {
   93 			return false
   94 		}
   95 
   96 		return p1.Weight() < p2.Weight()
   97 	}
   98 
   99 	lessPageLanguage = func(p1, p2 Page) bool {
  100 		if p1.Language().Weight == p2.Language().Weight {
  101 			if p1.Date().Unix() == p2.Date().Unix() {
  102 				c := compare.Strings(p1.LinkTitle(), p2.LinkTitle())
  103 				if c == 0 {
  104 					if !p1.File().IsZero() && !p2.File().IsZero() {
  105 						return compare.LessStrings(p1.File().Filename(), p2.File().Filename())
  106 					}
  107 				}
  108 				return c < 0
  109 			}
  110 			return p1.Date().Unix() > p2.Date().Unix()
  111 		}
  112 
  113 		if p2.Language().Weight == 0 {
  114 			return true
  115 		}
  116 
  117 		if p1.Language().Weight == 0 {
  118 			return false
  119 		}
  120 
  121 		return p1.Language().Weight < p2.Language().Weight
  122 	}
  123 
  124 	lessPageTitle = func(p1, p2 Page) bool {
  125 		return collatorStringCompare(func(p Page) string { return p.Title() }, p1, p2) < 0
  126 	}
  127 
  128 	lessPageLinkTitle = func(p1, p2 Page) bool {
  129 		return collatorStringCompare(func(p Page) string { return p.LinkTitle() }, p1, p2) < 0
  130 	}
  131 
  132 	lessPageDate = func(p1, p2 Page) bool {
  133 		return p1.Date().Unix() < p2.Date().Unix()
  134 	}
  135 
  136 	lessPagePubDate = func(p1, p2 Page) bool {
  137 		return p1.PublishDate().Unix() < p2.PublishDate().Unix()
  138 	}
  139 )
  140 
  141 func (ps *pageSorter) Len() int      { return len(ps.pages) }
  142 func (ps *pageSorter) Swap(i, j int) { ps.pages[i], ps.pages[j] = ps.pages[j], ps.pages[i] }
  143 
  144 // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
  145 func (ps *pageSorter) Less(i, j int) bool { return ps.by(ps.pages[i], ps.pages[j]) }
  146 
  147 // Limit limits the number of pages returned to n.
  148 func (p Pages) Limit(n int) Pages {
  149 	if len(p) > n {
  150 		return p[0:n]
  151 	}
  152 	return p
  153 }
  154 
  155 var collatorStringSort = func(getString func(Page) string) func(p Pages) {
  156 	return func(p Pages) {
  157 		if len(p) == 0 {
  158 			return
  159 		}
  160 		// Pages may be a mix of multiple languages, so we need to use the language
  161 		// for the currently rendered Site.
  162 		currentSite := p[0].Site().Current()
  163 		coll := langs.GetCollator(currentSite.Language())
  164 		coll.Lock()
  165 		defer coll.Unlock()
  166 
  167 		sort.SliceStable(p, func(i, j int) bool {
  168 			return coll.CompareStrings(getString(p[i]), getString(p[j])) < 0
  169 		})
  170 	}
  171 }
  172 
  173 var collatorStringCompare = func(getString func(Page) string, p1, p2 Page) int {
  174 	currentSite := p1.Site().Current()
  175 	coll := langs.GetCollator(currentSite.Language())
  176 	coll.Lock()
  177 	c := coll.CompareStrings(getString(p1), getString(p2))
  178 	coll.Unlock()
  179 	return c
  180 }
  181 
  182 var collatorStringLess = func(p Page) (less func(s1, s2 string) bool, close func()) {
  183 	currentSite := p.Site().Current()
  184 	coll := langs.GetCollator(currentSite.Language())
  185 	coll.Lock()
  186 	return func(s1, s2 string) bool {
  187 			return coll.CompareStrings(s1, s2) < 1
  188 		},
  189 		func() {
  190 			coll.Unlock()
  191 		}
  192 
  193 }
  194 
  195 // ByWeight sorts the Pages by weight and returns a copy.
  196 //
  197 // Adjacent invocations on the same receiver will return a cached result.
  198 //
  199 // This may safely be executed  in parallel.
  200 func (p Pages) ByWeight() Pages {
  201 	const key = "pageSort.ByWeight"
  202 	pages, _ := spc.get(key, pageBy(DefaultPageSort).Sort, p)
  203 	return pages
  204 }
  205 
  206 // SortByDefault sorts pages by the default sort.
  207 func SortByDefault(pages Pages) {
  208 	pageBy(DefaultPageSort).Sort(pages)
  209 }
  210 
  211 // ByTitle sorts the Pages by title and returns a copy.
  212 //
  213 // Adjacent invocations on the same receiver will return a cached result.
  214 //
  215 // This may safely be executed  in parallel.
  216 func (p Pages) ByTitle() Pages {
  217 	const key = "pageSort.ByTitle"
  218 
  219 	pages, _ := spc.get(key, collatorStringSort(func(p Page) string { return p.Title() }), p)
  220 
  221 	return pages
  222 }
  223 
  224 // ByLinkTitle sorts the Pages by link title and returns a copy.
  225 //
  226 // Adjacent invocations on the same receiver will return a cached result.
  227 //
  228 // This may safely be executed  in parallel.
  229 func (p Pages) ByLinkTitle() Pages {
  230 	const key = "pageSort.ByLinkTitle"
  231 
  232 	pages, _ := spc.get(key, collatorStringSort(func(p Page) string { return p.LinkTitle() }), p)
  233 
  234 	return pages
  235 }
  236 
  237 // ByDate sorts the Pages by date and returns a copy.
  238 //
  239 // Adjacent invocations on the same receiver will return a cached result.
  240 //
  241 // This may safely be executed  in parallel.
  242 func (p Pages) ByDate() Pages {
  243 	const key = "pageSort.ByDate"
  244 
  245 	pages, _ := spc.get(key, pageBy(lessPageDate).Sort, p)
  246 
  247 	return pages
  248 }
  249 
  250 // ByPublishDate sorts the Pages by publish date and returns a copy.
  251 //
  252 // Adjacent invocations on the same receiver will return a cached result.
  253 //
  254 // This may safely be executed  in parallel.
  255 func (p Pages) ByPublishDate() Pages {
  256 	const key = "pageSort.ByPublishDate"
  257 
  258 	pages, _ := spc.get(key, pageBy(lessPagePubDate).Sort, p)
  259 
  260 	return pages
  261 }
  262 
  263 // ByExpiryDate sorts the Pages by publish date and returns a copy.
  264 //
  265 // Adjacent invocations on the same receiver will return a cached result.
  266 //
  267 // This may safely be executed  in parallel.
  268 func (p Pages) ByExpiryDate() Pages {
  269 	const key = "pageSort.ByExpiryDate"
  270 
  271 	expDate := func(p1, p2 Page) bool {
  272 		return p1.ExpiryDate().Unix() < p2.ExpiryDate().Unix()
  273 	}
  274 
  275 	pages, _ := spc.get(key, pageBy(expDate).Sort, p)
  276 
  277 	return pages
  278 }
  279 
  280 // ByLastmod sorts the Pages by the last modification date and returns a copy.
  281 //
  282 // Adjacent invocations on the same receiver will return a cached result.
  283 //
  284 // This may safely be executed  in parallel.
  285 func (p Pages) ByLastmod() Pages {
  286 	const key = "pageSort.ByLastmod"
  287 
  288 	date := func(p1, p2 Page) bool {
  289 		return p1.Lastmod().Unix() < p2.Lastmod().Unix()
  290 	}
  291 
  292 	pages, _ := spc.get(key, pageBy(date).Sort, p)
  293 
  294 	return pages
  295 }
  296 
  297 // ByLength sorts the Pages by length and returns a copy.
  298 //
  299 // Adjacent invocations on the same receiver will return a cached result.
  300 //
  301 // This may safely be executed  in parallel.
  302 func (p Pages) ByLength() Pages {
  303 	const key = "pageSort.ByLength"
  304 
  305 	length := func(p1, p2 Page) bool {
  306 		p1l, ok1 := p1.(resource.LengthProvider)
  307 		p2l, ok2 := p2.(resource.LengthProvider)
  308 
  309 		if !ok1 {
  310 			return true
  311 		}
  312 
  313 		if !ok2 {
  314 			return false
  315 		}
  316 
  317 		return p1l.Len() < p2l.Len()
  318 	}
  319 
  320 	pages, _ := spc.get(key, pageBy(length).Sort, p)
  321 
  322 	return pages
  323 }
  324 
  325 // ByLanguage sorts the Pages by the language's Weight.
  326 //
  327 // Adjacent invocations on the same receiver will return a cached result.
  328 //
  329 // This may safely be executed  in parallel.
  330 func (p Pages) ByLanguage() Pages {
  331 	const key = "pageSort.ByLanguage"
  332 
  333 	pages, _ := spc.get(key, pageBy(lessPageLanguage).Sort, p)
  334 
  335 	return pages
  336 }
  337 
  338 // SortByLanguage sorts the pages by language.
  339 func SortByLanguage(pages Pages) {
  340 	pageBy(lessPageLanguage).Sort(pages)
  341 }
  342 
  343 // Reverse reverses the order in Pages and returns a copy.
  344 //
  345 // Adjacent invocations on the same receiver will return a cached result.
  346 //
  347 // This may safely be executed  in parallel.
  348 func (p Pages) Reverse() Pages {
  349 	const key = "pageSort.Reverse"
  350 
  351 	reverseFunc := func(pages Pages) {
  352 		for i, j := 0, len(pages)-1; i < j; i, j = i+1, j-1 {
  353 			pages[i], pages[j] = pages[j], pages[i]
  354 		}
  355 	}
  356 
  357 	pages, _ := spc.get(key, reverseFunc, p)
  358 
  359 	return pages
  360 }
  361 
  362 // ByParam sorts the pages according to the given page Params key.
  363 //
  364 // Adjacent invocations on the same receiver with the same paramsKey will return a cached result.
  365 //
  366 // This may safely be executed  in parallel.
  367 func (p Pages) ByParam(paramsKey any) Pages {
  368 	if len(p) < 2 {
  369 		return p
  370 	}
  371 	paramsKeyStr := cast.ToString(paramsKey)
  372 	key := "pageSort.ByParam." + paramsKeyStr
  373 
  374 	stringLess, close := collatorStringLess(p[0])
  375 	defer close()
  376 
  377 	paramsKeyComparator := func(p1, p2 Page) bool {
  378 		v1, _ := p1.Param(paramsKeyStr)
  379 		v2, _ := p2.Param(paramsKeyStr)
  380 
  381 		if v1 == nil {
  382 			return false
  383 		}
  384 
  385 		if v2 == nil {
  386 			return true
  387 		}
  388 
  389 		isNumeric := func(v any) bool {
  390 			switch v.(type) {
  391 			case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64, float32, float64:
  392 				return true
  393 			default:
  394 				return false
  395 			}
  396 		}
  397 
  398 		if isNumeric(v1) && isNumeric(v2) {
  399 			return cast.ToFloat64(v1) < cast.ToFloat64(v2)
  400 		}
  401 
  402 		s1 := cast.ToString(v1)
  403 		s2 := cast.ToString(v2)
  404 
  405 		return stringLess(s1, s2)
  406 
  407 	}
  408 
  409 	pages, _ := spc.get(key, pageBy(paramsKeyComparator).Sort, p)
  410 
  411 	return pages
  412 }