hugo/hugolib/pageGroup.go

310 lines
9 KiB
Go
Raw Normal View History

// Copyright 2015 The Hugo Authors. All rights reserved.
2014-08-20 09:12:17 -04:00
//
2015-11-23 22:16:36 -05:00
// Licensed under the Apache License, Version 2.0 (the "License");
2014-08-20 09:12:17 -04:00
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2015-11-23 22:16:36 -05:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-08-20 09:12:17 -04:00
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
"errors"
"reflect"
"sort"
2014-08-29 23:50:25 -04:00
"strings"
"time"
2014-08-20 09:12:17 -04:00
)
// PageGroup represents a group of pages, grouped by the key.
// The key is typically a year or similar.
2014-08-20 09:12:17 -04:00
type PageGroup struct {
Key interface{}
Pages
2014-08-20 09:12:17 -04:00
}
type mapKeyValues []reflect.Value
func (v mapKeyValues) Len() int { return len(v) }
func (v mapKeyValues) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
type mapKeyByInt struct{ mapKeyValues }
func (s mapKeyByInt) Less(i, j int) bool { return s.mapKeyValues[i].Int() < s.mapKeyValues[j].Int() }
type mapKeyByStr struct{ mapKeyValues }
func (s mapKeyByStr) Less(i, j int) bool {
return s.mapKeyValues[i].String() < s.mapKeyValues[j].String()
}
func sortKeys(v []reflect.Value, order string) []reflect.Value {
if len(v) <= 1 {
return v
}
switch v[0].Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if order == "desc" {
sort.Sort(sort.Reverse(mapKeyByInt{v}))
} else {
sort.Sort(mapKeyByInt{v})
}
case reflect.String:
if order == "desc" {
sort.Sort(sort.Reverse(mapKeyByStr{v}))
} else {
sort.Sort(mapKeyByStr{v})
}
}
return v
}
// PagesGroup represents a list of page groups.
// This is what you get when doing page grouping in the templates.
2014-08-29 23:50:25 -04:00
type PagesGroup []PageGroup
// Reverse reverses the order of this list of page groups.
2014-08-29 23:50:25 -04:00
func (p PagesGroup) Reverse() PagesGroup {
for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 {
p[i], p[j] = p[j], p[i]
}
return p
}
var (
errorType = reflect.TypeOf((*error)(nil)).Elem()
pagePtrType = reflect.TypeOf((*Page)(nil))
)
// GroupBy groups by the value in the given field or method name and with the given order.
// Valid values for order is asc, desc, rev and reverse.
func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) {
2014-08-20 09:12:17 -04:00
if len(p) < 1 {
return nil, nil
}
direction := "asc"
if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") {
direction = "desc"
2014-08-20 09:12:17 -04:00
}
var ft interface{}
m, ok := pagePtrType.MethodByName(key)
if ok {
if m.Type.NumIn() != 1 || m.Type.NumOut() == 0 || m.Type.NumOut() > 2 {
return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
}
if m.Type.NumOut() == 1 && m.Type.Out(0).Implements(errorType) {
return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
}
if m.Type.NumOut() == 2 && !m.Type.Out(1).Implements(errorType) {
return nil, errors.New(key + " is a Page method but you can't use it with GroupBy")
}
ft = m
} else {
ft, ok = pagePtrType.Elem().FieldByName(key)
if !ok {
return nil, errors.New(key + " is neither a field nor a method of Page")
}
2014-08-20 09:12:17 -04:00
}
var tmp reflect.Value
switch e := ft.(type) {
case reflect.StructField:
tmp = reflect.MakeMap(reflect.MapOf(e.Type, reflect.SliceOf(pagePtrType)))
case reflect.Method:
tmp = reflect.MakeMap(reflect.MapOf(e.Type.Out(0), reflect.SliceOf(pagePtrType)))
}
2014-08-20 09:12:17 -04:00
for _, e := range p {
ppv := reflect.ValueOf(e)
var fv reflect.Value
switch ft.(type) {
case reflect.StructField:
fv = ppv.Elem().FieldByName(key)
case reflect.Method:
fv = ppv.MethodByName(key).Call([]reflect.Value{})[0]
}
if !fv.IsValid() {
continue
}
if !tmp.MapIndex(fv).IsValid() {
tmp.SetMapIndex(fv, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0))
}
tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv))
}
2017-11-11 03:39:43 -05:00
sortedKeys := sortKeys(tmp.MapKeys(), direction)
r := make([]PageGroup, len(sortedKeys))
for i, k := range sortedKeys {
r[i] = PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)}
}
return r, nil
}
// GroupByParam groups by the given page parameter key's value and with the given order.
// Valid values for order is asc, desc, rev and reverse.
func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) {
if len(p) < 1 {
return nil, nil
}
direction := "asc"
if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") {
direction = "desc"
}
var tmp reflect.Value
var keyt reflect.Type
for _, e := range p {
param := e.getParamToLower(key)
if param != nil {
if _, ok := param.([]string); !ok {
keyt = reflect.TypeOf(param)
tmp = reflect.MakeMap(reflect.MapOf(keyt, reflect.SliceOf(pagePtrType)))
break
}
2014-08-20 09:12:17 -04:00
}
}
if !tmp.IsValid() {
return nil, errors.New("There is no such a param")
}
for _, e := range p {
param := e.getParam(key, false)
if param == nil || reflect.TypeOf(param) != keyt {
continue
}
v := reflect.ValueOf(param)
if !tmp.MapIndex(v).IsValid() {
tmp.SetMapIndex(v, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0))
}
tmp.SetMapIndex(v, reflect.Append(tmp.MapIndex(v), reflect.ValueOf(e)))
}
2014-08-20 09:12:17 -04:00
var r []PageGroup
for _, k := range sortKeys(tmp.MapKeys(), direction) {
r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)})
2014-08-20 09:12:17 -04:00
}
return r, nil
}
func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p *Page) string, order ...string) (PagesGroup, error) {
2014-08-20 09:12:17 -04:00
if len(p) < 1 {
return nil, nil
}
sp := sorter(p)
if !(len(order) > 0 && (strings.ToLower(order[0]) == "asc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse")) {
2014-08-20 09:12:17 -04:00
sp = sp.Reverse()
}
date := formatter(sp[0])
2014-08-20 09:12:17 -04:00
var r []PageGroup
r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)})
r[0].Pages = append(r[0].Pages, sp[0])
2014-08-20 09:12:17 -04:00
i := 0
for _, e := range sp[1:] {
date = formatter(e)
2014-08-20 09:12:17 -04:00
if r[i].Key.(string) != date {
r = append(r, PageGroup{Key: date})
i++
}
r[i].Pages = append(r[i].Pages, e)
2014-08-20 09:12:17 -04:00
}
return r, nil
}
// GroupByDate groups by the given page's Date value in
// the given format and with the given order.
// Valid values for order is asc, desc, rev and reverse.
// For valid format strings, see https://golang.org/pkg/time/#Time.Format
func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) {
sorter := func(p Pages) Pages {
return p.ByDate()
}
formatter := func(p *Page) string {
return p.Date.Format(format)
}
return p.groupByDateField(sorter, formatter, order...)
}
// GroupByPublishDate groups by the given page's PublishDate value in
// the given format and with the given order.
// Valid values for order is asc, desc, rev and reverse.
// For valid format strings, see https://golang.org/pkg/time/#Time.Format
func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, error) {
sorter := func(p Pages) Pages {
return p.ByPublishDate()
}
formatter := func(p *Page) string {
return p.PublishDate.Format(format)
}
return p.groupByDateField(sorter, formatter, order...)
}
// GroupByExpiryDate groups by the given page's ExpireDate value in
// the given format and with the given order.
// Valid values for order is asc, desc, rev and reverse.
// For valid format strings, see https://golang.org/pkg/time/#Time.Format
func (p Pages) GroupByExpiryDate(format string, order ...string) (PagesGroup, error) {
sorter := func(p Pages) Pages {
return p.ByExpiryDate()
}
formatter := func(p *Page) string {
return p.ExpiryDate.Format(format)
}
return p.groupByDateField(sorter, formatter, order...)
}
// GroupByParamDate groups by a date set as a param on the page in
// the given format and with the given order.
// Valid values for order is asc, desc, rev and reverse.
// For valid format strings, see https://golang.org/pkg/time/#Time.Format
func (p Pages) GroupByParamDate(key string, format string, order ...string) (PagesGroup, error) {
sorter := func(p Pages) Pages {
var r Pages
for _, e := range p {
param := e.getParamToLower(key)
if param != nil {
if _, ok := param.(time.Time); ok {
r = append(r, e)
}
}
}
pdate := func(p1, p2 *Page) bool {
return p1.getParamToLower(key).(time.Time).Unix() < p2.getParamToLower(key).(time.Time).Unix()
}
2016-03-24 22:12:03 -04:00
pageBy(pdate).Sort(r)
return r
}
formatter := func(p *Page) string {
return p.getParamToLower(key).(time.Time).Format(format)
}
return p.groupByDateField(sorter, formatter, order...)
}
// Group creates a PageGroup from a key and a Pages object
// This method is not meant for external use. It got its non-typed arguments to satisfy
// a very generic interface in the tpl package.
func (p Page) Group(key interface{}, in interface{}) (interface{}, error) {
pages, err := toPages(in)
if err != nil {
return nil, err
}
return PageGroup{Key: key, Pages: pages}, nil
}