github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/resources/page/pagegroup.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 "errors" 18 "fmt" 19 "reflect" 20 "sort" 21 "strings" 22 "time" 23 24 "github.com/spf13/cast" 25 26 "github.com/gohugoio/hugo/common/collections" 27 "github.com/gohugoio/hugo/compare" 28 29 "github.com/gohugoio/hugo/resources/resource" 30 ) 31 32 var ( 33 _ collections.Slicer = PageGroup{} 34 _ compare.ProbablyEqer = PageGroup{} 35 _ compare.ProbablyEqer = PagesGroup{} 36 ) 37 38 // PageGroup represents a group of pages, grouped by the key. 39 // The key is typically a year or similar. 40 type PageGroup struct { 41 Key interface{} 42 Pages 43 } 44 45 type mapKeyValues []reflect.Value 46 47 func (v mapKeyValues) Len() int { return len(v) } 48 func (v mapKeyValues) Swap(i, j int) { v[i], v[j] = v[j], v[i] } 49 50 type mapKeyByInt struct{ mapKeyValues } 51 52 func (s mapKeyByInt) Less(i, j int) bool { return s.mapKeyValues[i].Int() < s.mapKeyValues[j].Int() } 53 54 type mapKeyByStr struct{ mapKeyValues } 55 56 func (s mapKeyByStr) Less(i, j int) bool { 57 return compare.LessStrings(s.mapKeyValues[i].String(), s.mapKeyValues[j].String()) 58 } 59 60 func sortKeys(v []reflect.Value, order string) []reflect.Value { 61 if len(v) <= 1 { 62 return v 63 } 64 65 switch v[0].Kind() { 66 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 67 if order == "desc" { 68 sort.Sort(sort.Reverse(mapKeyByInt{v})) 69 } else { 70 sort.Sort(mapKeyByInt{v}) 71 } 72 case reflect.String: 73 if order == "desc" { 74 sort.Sort(sort.Reverse(mapKeyByStr{v})) 75 } else { 76 sort.Sort(mapKeyByStr{v}) 77 } 78 } 79 return v 80 } 81 82 // PagesGroup represents a list of page groups. 83 // This is what you get when doing page grouping in the templates. 84 type PagesGroup []PageGroup 85 86 // Reverse reverses the order of this list of page groups. 87 func (p PagesGroup) Reverse() PagesGroup { 88 for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 { 89 p[i], p[j] = p[j], p[i] 90 } 91 92 return p 93 } 94 95 var ( 96 errorType = reflect.TypeOf((*error)(nil)).Elem() 97 pagePtrType = reflect.TypeOf((*Page)(nil)).Elem() 98 pagesType = reflect.TypeOf(Pages{}) 99 ) 100 101 // GroupBy groups by the value in the given field or method name and with the given order. 102 // Valid values for order is asc, desc, rev and reverse. 103 func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) { 104 if len(p) < 1 { 105 return nil, nil 106 } 107 108 direction := "asc" 109 110 if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") { 111 direction = "desc" 112 } 113 114 var ft interface{} 115 m, ok := pagePtrType.MethodByName(key) 116 if ok { 117 if m.Type.NumOut() == 0 || m.Type.NumOut() > 2 { 118 return nil, errors.New(key + " is a Page method but you can't use it with GroupBy") 119 } 120 if m.Type.NumOut() == 1 && m.Type.Out(0).Implements(errorType) { 121 return nil, errors.New(key + " is a Page method but you can't use it with GroupBy") 122 } 123 if m.Type.NumOut() == 2 && !m.Type.Out(1).Implements(errorType) { 124 return nil, errors.New(key + " is a Page method but you can't use it with GroupBy") 125 } 126 ft = m 127 } else { 128 ft, ok = pagePtrType.Elem().FieldByName(key) 129 if !ok { 130 return nil, errors.New(key + " is neither a field nor a method of Page") 131 } 132 } 133 134 var tmp reflect.Value 135 switch e := ft.(type) { 136 case reflect.StructField: 137 tmp = reflect.MakeMap(reflect.MapOf(e.Type, pagesType)) 138 case reflect.Method: 139 tmp = reflect.MakeMap(reflect.MapOf(e.Type.Out(0), pagesType)) 140 } 141 142 for _, e := range p { 143 ppv := reflect.ValueOf(e) 144 var fv reflect.Value 145 switch ft.(type) { 146 case reflect.StructField: 147 fv = ppv.Elem().FieldByName(key) 148 case reflect.Method: 149 fv = ppv.MethodByName(key).Call([]reflect.Value{})[0] 150 } 151 if !fv.IsValid() { 152 continue 153 } 154 if !tmp.MapIndex(fv).IsValid() { 155 tmp.SetMapIndex(fv, reflect.MakeSlice(pagesType, 0, 0)) 156 } 157 tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv)) 158 } 159 160 sortedKeys := sortKeys(tmp.MapKeys(), direction) 161 r := make([]PageGroup, len(sortedKeys)) 162 for i, k := range sortedKeys { 163 r[i] = PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().(Pages)} 164 } 165 166 return r, nil 167 } 168 169 // GroupByParam groups by the given page parameter key's value and with the given order. 170 // Valid values for order is asc, desc, rev and reverse. 171 func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) { 172 if len(p) < 1 { 173 return nil, nil 174 } 175 176 direction := "asc" 177 178 if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") { 179 direction = "desc" 180 } 181 182 var tmp reflect.Value 183 var keyt reflect.Type 184 for _, e := range p { 185 param := resource.GetParamToLower(e, key) 186 if param != nil { 187 if _, ok := param.([]string); !ok { 188 keyt = reflect.TypeOf(param) 189 tmp = reflect.MakeMap(reflect.MapOf(keyt, pagesType)) 190 break 191 } 192 } 193 } 194 if !tmp.IsValid() { 195 return nil, errors.New("there is no such a param") 196 } 197 198 for _, e := range p { 199 param := resource.GetParam(e, key) 200 201 if param == nil || reflect.TypeOf(param) != keyt { 202 continue 203 } 204 v := reflect.ValueOf(param) 205 if !tmp.MapIndex(v).IsValid() { 206 tmp.SetMapIndex(v, reflect.MakeSlice(pagesType, 0, 0)) 207 } 208 tmp.SetMapIndex(v, reflect.Append(tmp.MapIndex(v), reflect.ValueOf(e))) 209 } 210 211 var r []PageGroup 212 for _, k := range sortKeys(tmp.MapKeys(), direction) { 213 r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().(Pages)}) 214 } 215 216 return r, nil 217 } 218 219 func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p Page) string, order ...string) (PagesGroup, error) { 220 if len(p) < 1 { 221 return nil, nil 222 } 223 224 sp := sorter(p) 225 226 if !(len(order) > 0 && (strings.ToLower(order[0]) == "asc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse")) { 227 sp = sp.Reverse() 228 } 229 230 if sp == nil { 231 return nil, nil 232 } 233 234 date := formatter(sp[0].(Page)) 235 var r []PageGroup 236 r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)}) 237 r[0].Pages = append(r[0].Pages, sp[0]) 238 239 i := 0 240 for _, e := range sp[1:] { 241 date = formatter(e.(Page)) 242 if r[i].Key.(string) != date { 243 r = append(r, PageGroup{Key: date}) 244 i++ 245 } 246 r[i].Pages = append(r[i].Pages, e) 247 } 248 return r, nil 249 } 250 251 // GroupByDate groups by the given page's Date value in 252 // the given format and with the given order. 253 // Valid values for order is asc, desc, rev and reverse. 254 // For valid format strings, see https://golang.org/pkg/time/#Time.Format 255 func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) { 256 sorter := func(p Pages) Pages { 257 return p.ByDate() 258 } 259 formatter := func(p Page) string { 260 return p.Date().Format(format) 261 } 262 return p.groupByDateField(sorter, formatter, order...) 263 } 264 265 // GroupByPublishDate groups by the given page's PublishDate value in 266 // the given format and with the given order. 267 // Valid values for order is asc, desc, rev and reverse. 268 // For valid format strings, see https://golang.org/pkg/time/#Time.Format 269 func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, error) { 270 sorter := func(p Pages) Pages { 271 return p.ByPublishDate() 272 } 273 formatter := func(p Page) string { 274 return p.PublishDate().Format(format) 275 } 276 return p.groupByDateField(sorter, formatter, order...) 277 } 278 279 // GroupByExpiryDate groups by the given page's ExpireDate value in 280 // the given format and with the given order. 281 // Valid values for order is asc, desc, rev and reverse. 282 // For valid format strings, see https://golang.org/pkg/time/#Time.Format 283 func (p Pages) GroupByExpiryDate(format string, order ...string) (PagesGroup, error) { 284 sorter := func(p Pages) Pages { 285 return p.ByExpiryDate() 286 } 287 formatter := func(p Page) string { 288 return p.ExpiryDate().Format(format) 289 } 290 return p.groupByDateField(sorter, formatter, order...) 291 } 292 293 // GroupByLastmod groups by the given page's Lastmod value in 294 // the given format and with the given order. 295 // Valid values for order is asc, desc, rev and reverse. 296 // For valid format strings, see https://golang.org/pkg/time/#Time.Format 297 func (p Pages) GroupByLastmod(format string, order ...string) (PagesGroup, error) { 298 sorter := func(p Pages) Pages { 299 return p.ByLastmod() 300 } 301 formatter := func(p Page) string { 302 return p.Lastmod().Format(format) 303 } 304 return p.groupByDateField(sorter, formatter, order...) 305 } 306 307 // GroupByParamDate groups by a date set as a param on the page in 308 // the given format and with the given order. 309 // Valid values for order is asc, desc, rev and reverse. 310 // For valid format strings, see https://golang.org/pkg/time/#Time.Format 311 func (p Pages) GroupByParamDate(key string, format string, order ...string) (PagesGroup, error) { 312 // Cache the dates. 313 dates := make(map[Page]time.Time) 314 315 sorter := func(pages Pages) Pages { 316 var r Pages 317 318 for _, p := range pages { 319 param := resource.GetParam(p, key) 320 var t time.Time 321 322 if param != nil { 323 var ok bool 324 if t, ok = param.(time.Time); !ok { 325 // Probably a string. Try to convert it to time.Time. 326 t = cast.ToTime(param) 327 } 328 } 329 330 dates[p] = t 331 r = append(r, p) 332 } 333 334 pdate := func(p1, p2 Page) bool { 335 return dates[p1].Unix() < dates[p2].Unix() 336 } 337 pageBy(pdate).Sort(r) 338 return r 339 } 340 formatter := func(p Page) string { 341 return dates[p].Format(format) 342 } 343 return p.groupByDateField(sorter, formatter, order...) 344 } 345 346 // ProbablyEq wraps compare.ProbablyEqer 347 func (p PageGroup) ProbablyEq(other interface{}) bool { 348 otherP, ok := other.(PageGroup) 349 if !ok { 350 return false 351 } 352 353 if p.Key != otherP.Key { 354 return false 355 } 356 357 return p.Pages.ProbablyEq(otherP.Pages) 358 } 359 360 // Slice is not meant to be used externally. It's a bridge function 361 // for the template functions. See collections.Slice. 362 func (p PageGroup) Slice(in interface{}) (interface{}, error) { 363 switch items := in.(type) { 364 case PageGroup: 365 return items, nil 366 case []interface{}: 367 groups := make(PagesGroup, len(items)) 368 for i, v := range items { 369 g, ok := v.(PageGroup) 370 if !ok { 371 return nil, fmt.Errorf("type %T is not a PageGroup", v) 372 } 373 groups[i] = g 374 } 375 return groups, nil 376 default: 377 return nil, fmt.Errorf("invalid slice type %T", items) 378 } 379 } 380 381 // Len returns the number of pages in the page group. 382 func (psg PagesGroup) Len() int { 383 l := 0 384 for _, pg := range psg { 385 l += len(pg.Pages) 386 } 387 return l 388 } 389 390 // ProbablyEq wraps compare.ProbablyEqer 391 func (psg PagesGroup) ProbablyEq(other interface{}) bool { 392 otherPsg, ok := other.(PagesGroup) 393 if !ok { 394 return false 395 } 396 397 if len(psg) != len(otherPsg) { 398 return false 399 } 400 401 for i := range psg { 402 if !psg[i].ProbablyEq(otherPsg[i]) { 403 return false 404 } 405 } 406 407 return true 408 } 409 410 // ToPagesGroup tries to convert seq into a PagesGroup. 411 func ToPagesGroup(seq interface{}) (PagesGroup, error) { 412 switch v := seq.(type) { 413 case nil: 414 return nil, nil 415 case PagesGroup: 416 return v, nil 417 case []PageGroup: 418 return PagesGroup(v), nil 419 case []interface{}: 420 l := len(v) 421 if l == 0 { 422 break 423 } 424 switch v[0].(type) { 425 case PageGroup: 426 pagesGroup := make(PagesGroup, l) 427 for i, ipg := range v { 428 if pg, ok := ipg.(PageGroup); ok { 429 pagesGroup[i] = pg 430 } else { 431 return nil, fmt.Errorf("unsupported type in paginate from slice, got %T instead of PageGroup", ipg) 432 } 433 } 434 return pagesGroup, nil 435 } 436 } 437 438 return nil, nil 439 }