hugo/resources/page/pagegroup.go

461 lines
12 KiB
Go
Raw Normal View History

// Copyright 2019 The Hugo Authors. All rights reserved.
2014-08-20 13:12:17 +00:00
//
2015-11-24 03:16:36 +00:00
// Licensed under the Apache License, Version 2.0 (the "License");
2014-08-20 13:12:17 +00:00
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2015-11-24 03:16:36 +00:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-08-20 13:12:17 +00: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 page
2014-08-20 13:12:17 +00:00
import (
"errors"
"fmt"
2014-08-20 13:12:17 +00:00
"reflect"
"sort"
2014-08-30 03:50:25 +00:00
"strings"
"time"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/common/collections"
Cache reflect.MethodByName The isolated benchmark for the function is obviously much faster: ```bash name old time/op new time/op delta GetMethodByName-10 1.21µs ± 7% 0.23µs ± 5% -81.42% (p=0.029 n=4+4) name old alloc/op new alloc/op delta GetMethodByName-10 680B ± 0% 0B -100.00% (p=0.029 n=4+4) name old allocs/op new allocs/op delta GetMethodByName-10 20.0 ± 0% 0.0 -100.00% (p=0.029 n=4+4) ``` But more pleasing is the overall performance looking at the site benchmarks: ```bash name old time/op new time/op delta SiteNew/Regular_Bundle_with_image-10 6.25ms ± 2% 6.10ms ± 2% ~ (p=0.057 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 6.30ms ± 2% 5.66ms ±11% ~ (p=0.057 n=4+4) SiteNew/Regular_Tags_and_categories-10 22.2ms ± 2% 17.4ms ± 1% -21.88% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 108ms ± 0% 107ms ± 0% -1.20% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 36.1ms ± 1% 33.8ms ± 1% -6.44% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 24.9ms ± 1% 22.6ms ± 1% -9.30% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 17.9ms ± 1% 16.7ms ± 1% -6.43% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 23.3ms ± 1% 22.0ms ± 0% -5.58% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 8.00ms ± 1% 7.63ms ± 0% -4.62% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Regular_Bundle_with_image-10 2.10MB ± 0% 2.07MB ± 0% -1.46% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 1.88MB ± 0% 1.85MB ± 0% -1.76% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 13.5MB ± 0% 11.6MB ± 0% -13.99% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 96.1MB ± 0% 95.8MB ± 0% -0.40% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 28.4MB ± 0% 27.3MB ± 0% -3.83% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 16.9MB ± 0% 15.1MB ± 0% -10.58% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 8.98MB ± 0% 8.44MB ± 0% -6.04% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 17.1MB ± 0% 16.5MB ± 0% -3.91% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 3.92MB ± 0% 3.72MB ± 0% -5.03% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Regular_Bundle_with_image-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 288k ± 0% 233k ± 0% -18.90% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 375k ± 0% 364k ± 0% -2.80% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 314k ± 0% 283k ± 0% -9.77% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 302k ± 0% 252k ± 0% -16.55% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 133k ± 0% 117k ± 0% -11.81% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 202k ± 0% 183k ± 0% -9.55% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 55.6k ± 0% 49.8k ± 0% -10.40% (p=0.029 n=4+4) ``` Thanks to @quasilyte for the suggestion. Fixes 9386
2022-03-08 09:06:12 +00:00
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/compare"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/resources/resource"
)
var (
_ collections.Slicer = PageGroup{}
_ compare.ProbablyEqer = PageGroup{}
_ compare.ProbablyEqer = PagesGroup{}
2014-08-20 13:12:17 +00:00
)
// PageGroup represents a group of pages, grouped by the key.
// The key is typically a year or similar.
2014-08-20 13:12:17 +00:00
type PageGroup struct {
// The key, typically a year or similar.
Key any
// The Pages in this group.
Pages
2014-08-20 13:12:17 +00: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 {
less func(a, b string) bool
mapKeyValues
}
2014-08-20 13:12:17 +00:00
func (s mapKeyByStr) Less(i, j int) bool {
return s.less(s.mapKeyValues[i].String(), s.mapKeyValues[j].String())
2014-08-20 13:12:17 +00:00
}
func sortKeys(examplePage Page, v []reflect.Value, order string) []reflect.Value {
2014-08-20 13:12:17 +00:00
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:
stringLess, close := collatorStringLess(examplePage)
defer close()
2014-08-20 13:12:17 +00:00
if order == "desc" {
sort.Sort(sort.Reverse(mapKeyByStr{stringLess, v}))
2014-08-20 13:12:17 +00:00
} else {
sort.Sort(mapKeyByStr{stringLess, v})
2014-08-20 13:12:17 +00:00
}
}
return v
}
// PagesGroup represents a list of page groups.
// This is what you get when doing page grouping in the templates.
2014-08-30 03:50:25 +00:00
type PagesGroup []PageGroup
// Reverse reverses the order of this list of page groups.
2014-08-30 03:50:25 +00: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)).Elem()
pagesType = reflect.TypeOf(Pages{})
)
// 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 13:12:17 +00: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 13:12:17 +00:00
}
var ft any
Cache reflect.MethodByName The isolated benchmark for the function is obviously much faster: ```bash name old time/op new time/op delta GetMethodByName-10 1.21µs ± 7% 0.23µs ± 5% -81.42% (p=0.029 n=4+4) name old alloc/op new alloc/op delta GetMethodByName-10 680B ± 0% 0B -100.00% (p=0.029 n=4+4) name old allocs/op new allocs/op delta GetMethodByName-10 20.0 ± 0% 0.0 -100.00% (p=0.029 n=4+4) ``` But more pleasing is the overall performance looking at the site benchmarks: ```bash name old time/op new time/op delta SiteNew/Regular_Bundle_with_image-10 6.25ms ± 2% 6.10ms ± 2% ~ (p=0.057 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 6.30ms ± 2% 5.66ms ±11% ~ (p=0.057 n=4+4) SiteNew/Regular_Tags_and_categories-10 22.2ms ± 2% 17.4ms ± 1% -21.88% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 108ms ± 0% 107ms ± 0% -1.20% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 36.1ms ± 1% 33.8ms ± 1% -6.44% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 24.9ms ± 1% 22.6ms ± 1% -9.30% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 17.9ms ± 1% 16.7ms ± 1% -6.43% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 23.3ms ± 1% 22.0ms ± 0% -5.58% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 8.00ms ± 1% 7.63ms ± 0% -4.62% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Regular_Bundle_with_image-10 2.10MB ± 0% 2.07MB ± 0% -1.46% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 1.88MB ± 0% 1.85MB ± 0% -1.76% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 13.5MB ± 0% 11.6MB ± 0% -13.99% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 96.1MB ± 0% 95.8MB ± 0% -0.40% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 28.4MB ± 0% 27.3MB ± 0% -3.83% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 16.9MB ± 0% 15.1MB ± 0% -10.58% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 8.98MB ± 0% 8.44MB ± 0% -6.04% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 17.1MB ± 0% 16.5MB ± 0% -3.91% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 3.92MB ± 0% 3.72MB ± 0% -5.03% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Regular_Bundle_with_image-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 288k ± 0% 233k ± 0% -18.90% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 375k ± 0% 364k ± 0% -2.80% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 314k ± 0% 283k ± 0% -9.77% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 302k ± 0% 252k ± 0% -16.55% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 133k ± 0% 117k ± 0% -11.81% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 202k ± 0% 183k ± 0% -9.55% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 55.6k ± 0% 49.8k ± 0% -10.40% (p=0.029 n=4+4) ``` Thanks to @quasilyte for the suggestion. Fixes 9386
2022-03-08 09:06:12 +00:00
index := hreflect.GetMethodIndexByName(pagePtrType, key)
if index != -1 {
m := pagePtrType.Method(index)
if 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 {
Cache reflect.MethodByName The isolated benchmark for the function is obviously much faster: ```bash name old time/op new time/op delta GetMethodByName-10 1.21µs ± 7% 0.23µs ± 5% -81.42% (p=0.029 n=4+4) name old alloc/op new alloc/op delta GetMethodByName-10 680B ± 0% 0B -100.00% (p=0.029 n=4+4) name old allocs/op new allocs/op delta GetMethodByName-10 20.0 ± 0% 0.0 -100.00% (p=0.029 n=4+4) ``` But more pleasing is the overall performance looking at the site benchmarks: ```bash name old time/op new time/op delta SiteNew/Regular_Bundle_with_image-10 6.25ms ± 2% 6.10ms ± 2% ~ (p=0.057 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 6.30ms ± 2% 5.66ms ±11% ~ (p=0.057 n=4+4) SiteNew/Regular_Tags_and_categories-10 22.2ms ± 2% 17.4ms ± 1% -21.88% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 108ms ± 0% 107ms ± 0% -1.20% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 36.1ms ± 1% 33.8ms ± 1% -6.44% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 24.9ms ± 1% 22.6ms ± 1% -9.30% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 17.9ms ± 1% 16.7ms ± 1% -6.43% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 23.3ms ± 1% 22.0ms ± 0% -5.58% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 8.00ms ± 1% 7.63ms ± 0% -4.62% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Regular_Bundle_with_image-10 2.10MB ± 0% 2.07MB ± 0% -1.46% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 1.88MB ± 0% 1.85MB ± 0% -1.76% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 13.5MB ± 0% 11.6MB ± 0% -13.99% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 96.1MB ± 0% 95.8MB ± 0% -0.40% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 28.4MB ± 0% 27.3MB ± 0% -3.83% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 16.9MB ± 0% 15.1MB ± 0% -10.58% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 8.98MB ± 0% 8.44MB ± 0% -6.04% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 17.1MB ± 0% 16.5MB ± 0% -3.91% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 3.92MB ± 0% 3.72MB ± 0% -5.03% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Regular_Bundle_with_image-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 288k ± 0% 233k ± 0% -18.90% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 375k ± 0% 364k ± 0% -2.80% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 314k ± 0% 283k ± 0% -9.77% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 302k ± 0% 252k ± 0% -16.55% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 133k ± 0% 117k ± 0% -11.81% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 202k ± 0% 183k ± 0% -9.55% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 55.6k ± 0% 49.8k ± 0% -10.40% (p=0.029 n=4+4) ``` Thanks to @quasilyte for the suggestion. Fixes 9386
2022-03-08 09:06:12 +00:00
var ok bool
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 13:12:17 +00:00
}
var tmp reflect.Value
switch e := ft.(type) {
case reflect.StructField:
tmp = reflect.MakeMap(reflect.MapOf(e.Type, pagesType))
case reflect.Method:
tmp = reflect.MakeMap(reflect.MapOf(e.Type.Out(0), pagesType))
}
2014-08-20 13:12:17 +00: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:
Cache reflect.MethodByName The isolated benchmark for the function is obviously much faster: ```bash name old time/op new time/op delta GetMethodByName-10 1.21µs ± 7% 0.23µs ± 5% -81.42% (p=0.029 n=4+4) name old alloc/op new alloc/op delta GetMethodByName-10 680B ± 0% 0B -100.00% (p=0.029 n=4+4) name old allocs/op new allocs/op delta GetMethodByName-10 20.0 ± 0% 0.0 -100.00% (p=0.029 n=4+4) ``` But more pleasing is the overall performance looking at the site benchmarks: ```bash name old time/op new time/op delta SiteNew/Regular_Bundle_with_image-10 6.25ms ± 2% 6.10ms ± 2% ~ (p=0.057 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 6.30ms ± 2% 5.66ms ±11% ~ (p=0.057 n=4+4) SiteNew/Regular_Tags_and_categories-10 22.2ms ± 2% 17.4ms ± 1% -21.88% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 108ms ± 0% 107ms ± 0% -1.20% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 36.1ms ± 1% 33.8ms ± 1% -6.44% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 24.9ms ± 1% 22.6ms ± 1% -9.30% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 17.9ms ± 1% 16.7ms ± 1% -6.43% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 23.3ms ± 1% 22.0ms ± 0% -5.58% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 8.00ms ± 1% 7.63ms ± 0% -4.62% (p=0.029 n=4+4) name old alloc/op new alloc/op delta SiteNew/Regular_Bundle_with_image-10 2.10MB ± 0% 2.07MB ± 0% -1.46% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 1.88MB ± 0% 1.85MB ± 0% -1.76% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 13.5MB ± 0% 11.6MB ± 0% -13.99% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 96.1MB ± 0% 95.8MB ± 0% -0.40% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 28.4MB ± 0% 27.3MB ± 0% -3.83% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 16.9MB ± 0% 15.1MB ± 0% -10.58% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 8.98MB ± 0% 8.44MB ± 0% -6.04% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 17.1MB ± 0% 16.5MB ± 0% -3.91% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 3.92MB ± 0% 3.72MB ± 0% -5.03% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Regular_Bundle_with_image-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Bundle_with_JSON_file-10 25.8k ± 0% 24.9k ± 0% -3.49% (p=0.029 n=4+4) SiteNew/Regular_Tags_and_categories-10 288k ± 0% 233k ± 0% -18.90% (p=0.029 n=4+4) SiteNew/Regular_Canonify_URLs-10 375k ± 0% 364k ± 0% -2.80% (p=0.029 n=4+4) SiteNew/Regular_Deep_content_tree-10 314k ± 0% 283k ± 0% -9.77% (p=0.029 n=4+4) SiteNew/Regular_TOML_front_matter-10 302k ± 0% 252k ± 0% -16.55% (p=0.029 n=4+4) SiteNew/Regular_Many_HTML_templates-10 133k ± 0% 117k ± 0% -11.81% (p=0.029 n=4+4) SiteNew/Regular_Page_collections-10 202k ± 0% 183k ± 0% -9.55% (p=0.029 n=4+4) SiteNew/Regular_List_terms-10 55.6k ± 0% 49.8k ± 0% -10.40% (p=0.029 n=4+4) ``` Thanks to @quasilyte for the suggestion. Fixes 9386
2022-03-08 09:06:12 +00:00
fv = hreflect.GetMethodByName(ppv, key).Call([]reflect.Value{})[0]
}
if !fv.IsValid() {
continue
}
if !tmp.MapIndex(fv).IsValid() {
tmp.SetMapIndex(fv, reflect.MakeSlice(pagesType, 0, 0))
}
tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv))
}
sortedKeys := sortKeys(p[0], tmp.MapKeys(), direction)
2017-11-11 08:39:43 +00:00
r := make([]PageGroup, len(sortedKeys))
for i, k := range sortedKeys {
r[i] = PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().(Pages)}
}
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 := resource.GetParamToLower(e, key)
if param != nil {
if _, ok := param.([]string); !ok {
keyt = reflect.TypeOf(param)
tmp = reflect.MakeMap(reflect.MapOf(keyt, pagesType))
break
}
2014-08-20 13:12:17 +00:00
}
}
if !tmp.IsValid() {
return nil, errors.New("there is no such a param")
}
for _, e := range p {
param := resource.GetParam(e, key)
if param == nil || reflect.TypeOf(param) != keyt {
continue
}
v := reflect.ValueOf(param)
if !tmp.MapIndex(v).IsValid() {
tmp.SetMapIndex(v, reflect.MakeSlice(pagesType, 0, 0))
}
tmp.SetMapIndex(v, reflect.Append(tmp.MapIndex(v), reflect.ValueOf(e)))
}
2014-08-20 13:12:17 +00:00
var r []PageGroup
for _, k := range sortKeys(p[0], tmp.MapKeys(), direction) {
r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().(Pages)})
2014-08-20 13:12:17 +00:00
}
return r, nil
}
func (p Pages) groupByDateField(format string, sorter func(p Pages) Pages, getDate func(p Page) time.Time, order ...string) (PagesGroup, error) {
2014-08-20 13:12:17 +00: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 13:12:17 +00:00
sp = sp.Reverse()
}
if sp == nil {
return nil, nil
}
firstPage := sp[0].(Page)
date := getDate(firstPage)
// Pages may be a mix of multiple languages, so we need to use the language
// for the currently rendered Site.
currentSite := firstPage.Site().Current()
formatter := langs.GetTimeFormatter(currentSite.Language())
formatted := formatter.Format(date, format)
2014-08-20 13:12:17 +00:00
var r []PageGroup
r = append(r, PageGroup{Key: formatted, Pages: make(Pages, 0)})
r[0].Pages = append(r[0].Pages, sp[0])
2014-08-20 13:12:17 +00:00
i := 0
for _, e := range sp[1:] {
date = getDate(e.(Page))
formatted := formatter.Format(date, format)
if r[i].Key.(string) != formatted {
r = append(r, PageGroup{Key: formatted})
2014-08-20 13:12:17 +00:00
i++
}
r[i].Pages = append(r[i].Pages, e)
2014-08-20 13:12:17 +00: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()
}
getDate := func(p Page) time.Time {
return p.Date()
}
return p.groupByDateField(format, sorter, getDate, 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()
}
getDate := func(p Page) time.Time {
return p.PublishDate()
}
return p.groupByDateField(format, sorter, getDate, 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()
}
getDate := func(p Page) time.Time {
return p.ExpiryDate()
}
return p.groupByDateField(format, sorter, getDate, order...)
}
2020-06-19 07:37:37 +00:00
// GroupByLastmod groups by the given page's Lastmod 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) GroupByLastmod(format string, order ...string) (PagesGroup, error) {
sorter := func(p Pages) Pages {
return p.ByLastmod()
}
getDate := func(p Page) time.Time {
return p.Lastmod()
2020-06-19 07:37:37 +00:00
}
return p.groupByDateField(format, sorter, getDate, order...)
2020-06-19 07:37:37 +00:00
}
// 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) {
// Cache the dates.
dates := make(map[Page]time.Time)
sorter := func(pages Pages) Pages {
var r Pages
for _, p := range pages {
param := resource.GetParam(p, key)
var t time.Time
if param != nil {
var ok bool
if t, ok = param.(time.Time); !ok {
// Probably a string. Try to convert it to time.Time.
t = cast.ToTime(param)
}
}
dates[p] = t
r = append(r, p)
}
pdate := func(p1, p2 Page) bool {
return dates[p1].Unix() < dates[p2].Unix()
}
2016-03-25 02:12:03 +00:00
pageBy(pdate).Sort(r)
return r
}
getDate := func(p Page) time.Time {
return dates[p]
}
return p.groupByDateField(format, sorter, getDate, order...)
}
2020-12-16 11:11:32 +00:00
// ProbablyEq wraps compare.ProbablyEqer
// For internal use.
func (p PageGroup) ProbablyEq(other any) bool {
otherP, ok := other.(PageGroup)
if !ok {
return false
}
if p.Key != otherP.Key {
return false
}
return p.Pages.ProbablyEq(otherP.Pages)
}
// Slice is for internal use.
// for the template functions. See collections.Slice.
func (p PageGroup) Slice(in any) (any, error) {
switch items := in.(type) {
case PageGroup:
return items, nil
case []any:
groups := make(PagesGroup, len(items))
for i, v := range items {
g, ok := v.(PageGroup)
if !ok {
return nil, fmt.Errorf("type %T is not a PageGroup", v)
}
groups[i] = g
}
return groups, nil
default:
return nil, fmt.Errorf("invalid slice type %T", items)
}
}
// Len returns the number of pages in the page group.
func (psg PagesGroup) Len() int {
l := 0
for _, pg := range psg {
l += len(pg.Pages)
}
return l
}
2020-12-16 11:11:32 +00:00
// ProbablyEq wraps compare.ProbablyEqer
func (psg PagesGroup) ProbablyEq(other any) bool {
otherPsg, ok := other.(PagesGroup)
if !ok {
return false
}
if len(psg) != len(otherPsg) {
return false
}
for i := range psg {
if !psg[i].ProbablyEq(otherPsg[i]) {
return false
}
}
return true
}
// ToPagesGroup tries to convert seq into a PagesGroup.
func ToPagesGroup(seq any) (PagesGroup, error) {
switch v := seq.(type) {
case nil:
return nil, nil
case PagesGroup:
return v, nil
case []PageGroup:
return PagesGroup(v), nil
case []any:
l := len(v)
if l == 0 {
break
}
switch v[0].(type) {
case PageGroup:
pagesGroup := make(PagesGroup, l)
for i, ipg := range v {
if pg, ok := ipg.(PageGroup); ok {
pagesGroup[i] = pg
} else {
return nil, fmt.Errorf("unsupported type in paginate from slice, got %T instead of PageGroup", ipg)
}
}
return pagesGroup, nil
}
}
return nil, nil
}