mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
47e39af2cf
```bash
goos: darwin
goarch: arm64
pkg: github.com/gohugoio/hugo/tpl/collections
cpu: Apple M1 Pro
│ master.bench │ fix-whereperfalloc.bench │
│ sec/op │ sec/op vs base │
WhereOps/eq-10 139.62µ ± 3% 66.04µ ± 2% -52.70% (p=0.002 n=6)
WhereOps/ne-10 139.54µ ± 1% 65.68µ ± 2% -52.93% (p=0.002 n=6)
WhereOps/like-10 179.8µ ± 3% 106.7µ ± 1% -40.66% (p=0.002 n=6)
WhereSliceOfStructPointersWithMethod-10 350.7µ ± 1% 252.2µ ± 2% -28.09% (p=0.002 n=6)
WhereAndSortPages/Where-10 120.93µ ± 1% 81.69µ ± 1% -32.45% (p=0.002 n=6)
WhereAndSortPages/Sort-10 1.608m ± 1% 1.165m ± 1% -27.52% (p=0.002 n=6)
geomean 117.2µ 149.4µ -40.03% ¹
│ master.bench │ fix-whereperfalloc.bench │
│ B/op │ B/op vs base │
WhereOps/eq-10 52.18Ki ± 0% 36.62Ki ± 0% -29.82% (p=0.002 n=6)
WhereOps/ne-10 52.18Ki ± 0% 36.62Ki ± 0% -29.82% (p=0.002 n=6)
WhereOps/like-10 52.20Ki ± 0% 36.63Ki ± 0% -29.83% (p=0.002 n=6)
WhereSliceOfStructPointersWithMethod-10 64.52Ki ± 0% 64.59Ki ± 0% +0.12% (p=0.002 n=6)
WhereAndSortPages/Where-10 21.05Ki ± 0% 21.11Ki ± 0% +0.30% (p=0.002 n=6)
WhereAndSortPages/Sort-10 591.15Ki ± 0% 51.82Ki ± 0% -91.23% (p=0.002 n=6)
geomean 7.690Ki 38.91Ki -44.13% ¹
│ master.bench │ fix-whereperfalloc.bench │
│ allocs/op │ allocs/op vs base │
WhereOps/eq-10 2.515k ± 0% 1.517k ± 0% -39.68% (p=0.002 n=6)
WhereOps/ne-10 2.515k ± 0% 1.517k ± 0% -39.68% (p=0.002 n=6)
WhereOps/like-10 2.515k ± 0% 1.517k ± 0% -39.68% (p=0.002 n=6)
WhereSliceOfStructPointersWithMethod-10 4.503k ± 0% 4.504k ± 0% +0.02% (p=0.002 n=6)
WhereAndSortPages/Where-10 1.029k ± 0% 1.030k ± 0% +0.10% (p=0.002 n=6)
WhereAndSortPages/Sort-10 20.736k ± 0% 1.012k ± 0% -95.12% (p=0.002 n=6)
geomean 358.5 1.594k -53.04% ¹
```
198 lines
5.0 KiB
Go
198 lines
5.0 KiB
Go
// Copyright 2017 The Hugo Authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// 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 collections
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/gohugoio/hugo/common/hmaps"
|
|
"github.com/gohugoio/hugo/common/hreflect"
|
|
"github.com/gohugoio/hugo/langs"
|
|
"github.com/gohugoio/hugo/tpl/compare"
|
|
"github.com/spf13/cast"
|
|
)
|
|
|
|
// Sort returns a sorted copy of the list l.
|
|
func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error) {
|
|
if l == nil {
|
|
return nil, errors.New("sequence must be provided")
|
|
}
|
|
|
|
seqv, isNil := hreflect.Indirect(reflect.ValueOf(l))
|
|
if isNil {
|
|
return nil, errors.New("can't iterate over a nil value")
|
|
}
|
|
|
|
ctxv := reflect.ValueOf(ctx)
|
|
|
|
var sliceType reflect.Type
|
|
switch seqv.Kind() {
|
|
case reflect.Array, reflect.Slice:
|
|
sliceType = seqv.Type()
|
|
case reflect.Map:
|
|
sliceType = reflect.SliceOf(seqv.Type().Elem())
|
|
default:
|
|
return nil, errors.New("can't sort " + reflect.ValueOf(l).Type().String())
|
|
}
|
|
|
|
collator := langs.GetCollator1(ns.deps.Conf.Language().(*langs.Language))
|
|
|
|
// Create a list of pairs that will be used to do the sort
|
|
p := pairList{Collator: collator, sortComp: ns.sortComp, SortAsc: true, SliceType: sliceType}
|
|
p.Pairs = make([]pair, seqv.Len())
|
|
|
|
var sortByField string
|
|
for i, l := range args {
|
|
dStr, err := cast.ToStringE(l)
|
|
switch {
|
|
case i == 0 && err != nil:
|
|
sortByField = ""
|
|
case i == 0 && err == nil:
|
|
sortByField = dStr
|
|
case i == 1 && err == nil && dStr == "desc":
|
|
p.SortAsc = false
|
|
case i == 1:
|
|
p.SortAsc = true
|
|
}
|
|
}
|
|
path := strings.Split(strings.Trim(sortByField, "."), ".")
|
|
|
|
switch seqv.Kind() {
|
|
case reflect.Array, reflect.Slice:
|
|
for i := range seqv.Len() {
|
|
p.Pairs[i].Value = seqv.Index(i)
|
|
if sortByField == "" || sortByField == "value" {
|
|
p.Pairs[i].Key = p.Pairs[i].Value
|
|
} else {
|
|
v := p.Pairs[i].Value
|
|
var err error
|
|
for i, elemName := range path {
|
|
v, err = evaluateSubElem(ctxv, v, elemName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !v.IsValid() {
|
|
continue
|
|
}
|
|
// Special handling of lower cased maps.
|
|
if params, ok := v.Interface().(hmaps.Params); ok {
|
|
v = reflect.ValueOf(params.GetNested(path[i+1:]...))
|
|
break
|
|
}
|
|
}
|
|
p.Pairs[i].Key = v
|
|
}
|
|
}
|
|
|
|
case reflect.Map:
|
|
|
|
iter := seqv.MapRange()
|
|
i := 0
|
|
for iter.Next() {
|
|
key := iter.Key()
|
|
value := iter.Value()
|
|
p.Pairs[i].Value = value
|
|
if sortByField == "" {
|
|
p.Pairs[i].Key = key
|
|
} else if sortByField == "value" {
|
|
p.Pairs[i].Key = p.Pairs[i].Value
|
|
} else {
|
|
v := p.Pairs[i].Value
|
|
var err error
|
|
for j, elemName := range path {
|
|
v, err = evaluateSubElem(ctxv, v, elemName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !v.IsValid() {
|
|
continue
|
|
}
|
|
// Special handling of lower cased hmaps.
|
|
if params, ok := v.Interface().(hmaps.Params); ok {
|
|
v = reflect.ValueOf(params.GetNested(path[j+1:]...))
|
|
break
|
|
}
|
|
}
|
|
p.Pairs[i].Key = v
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
collator.Lock()
|
|
defer collator.Unlock()
|
|
|
|
return p.sort(), nil
|
|
}
|
|
|
|
// Credit for pair sorting method goes to Andrew Gerrand
|
|
// https://groups.google.com/forum/#!topic/golang-nuts/FT7cjmcL7gw
|
|
// A data structure to hold a key/value pair.
|
|
type pair struct {
|
|
Key reflect.Value
|
|
Value reflect.Value
|
|
}
|
|
|
|
// A slice of pairs that implements sort.Interface to sort by Value.
|
|
type pairList struct {
|
|
Collator *langs.Collator
|
|
sortComp *compare.Namespace
|
|
Pairs []pair
|
|
SortAsc bool
|
|
SliceType reflect.Type
|
|
}
|
|
|
|
func (p pairList) Swap(i, j int) { p.Pairs[i], p.Pairs[j] = p.Pairs[j], p.Pairs[i] }
|
|
func (p pairList) Len() int { return len(p.Pairs) }
|
|
func (p pairList) Less(i, j int) bool {
|
|
iv := p.Pairs[i].Key
|
|
jv := p.Pairs[j].Key
|
|
|
|
if iv.IsValid() {
|
|
if jv.IsValid() {
|
|
// can only call Interface() on valid reflect Values
|
|
return p.sortComp.LtCollate(p.Collator, iv.Interface(), jv.Interface())
|
|
}
|
|
|
|
// if j is invalid, test i against i's zero value
|
|
return p.sortComp.LtCollate(p.Collator, iv.Interface(), reflect.Zero(iv.Type()))
|
|
}
|
|
|
|
if jv.IsValid() {
|
|
// if i is invalid, test j against j's zero value
|
|
return p.sortComp.LtCollate(p.Collator, reflect.Zero(jv.Type()), jv.Interface())
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// sorts a pairList and returns a slice of sorted values
|
|
func (p pairList) sort() any {
|
|
if p.SortAsc {
|
|
sort.Stable(p)
|
|
} else {
|
|
sort.Stable(sort.Reverse(p))
|
|
}
|
|
sorted := reflect.MakeSlice(p.SliceType, len(p.Pairs), len(p.Pairs))
|
|
for i, v := range p.Pairs {
|
|
sorted.Index(i).Set(v.Value)
|
|
}
|
|
|
|
return sorted.Interface()
|
|
}
|