hugo

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

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

pagegroup.go (12318B)

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