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