github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/tpl/collections/sort.go (about)

     1  // Copyright 2017 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 collections
    15  
    16  import (
    17  	"context"
    18  	"errors"
    19  	"reflect"
    20  	"sort"
    21  	"strings"
    22  
    23  	"github.com/gohugoio/hugo/common/maps"
    24  	"github.com/gohugoio/hugo/langs"
    25  	"github.com/gohugoio/hugo/tpl/compare"
    26  	"github.com/spf13/cast"
    27  )
    28  
    29  // Sort returns a sorted copy of the list l.
    30  func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error) {
    31  	if l == nil {
    32  		return nil, errors.New("sequence must be provided")
    33  	}
    34  
    35  	seqv, isNil := indirect(reflect.ValueOf(l))
    36  	if isNil {
    37  		return nil, errors.New("can't iterate over a nil value")
    38  	}
    39  
    40  	ctxv := reflect.ValueOf(ctx)
    41  
    42  	var sliceType reflect.Type
    43  	switch seqv.Kind() {
    44  	case reflect.Array, reflect.Slice:
    45  		sliceType = seqv.Type()
    46  	case reflect.Map:
    47  		sliceType = reflect.SliceOf(seqv.Type().Elem())
    48  	default:
    49  		return nil, errors.New("can't sort " + reflect.ValueOf(l).Type().String())
    50  	}
    51  
    52  	collator := langs.GetCollator1(ns.deps.Conf.Language())
    53  
    54  	// Create a list of pairs that will be used to do the sort
    55  	p := pairList{Collator: collator, sortComp: ns.sortComp, SortAsc: true, SliceType: sliceType}
    56  	p.Pairs = make([]pair, seqv.Len())
    57  
    58  	var sortByField string
    59  	for i, l := range args {
    60  		dStr, err := cast.ToStringE(l)
    61  		switch {
    62  		case i == 0 && err != nil:
    63  			sortByField = ""
    64  		case i == 0 && err == nil:
    65  			sortByField = dStr
    66  		case i == 1 && err == nil && dStr == "desc":
    67  			p.SortAsc = false
    68  		case i == 1:
    69  			p.SortAsc = true
    70  		}
    71  	}
    72  	path := strings.Split(strings.Trim(sortByField, "."), ".")
    73  
    74  	switch seqv.Kind() {
    75  	case reflect.Array, reflect.Slice:
    76  		for i := 0; i < seqv.Len(); i++ {
    77  			p.Pairs[i].Value = seqv.Index(i)
    78  			if sortByField == "" || sortByField == "value" {
    79  				p.Pairs[i].Key = p.Pairs[i].Value
    80  			} else {
    81  				v := p.Pairs[i].Value
    82  				var err error
    83  				for i, elemName := range path {
    84  					v, err = evaluateSubElem(ctxv, v, elemName)
    85  					if err != nil {
    86  						return nil, err
    87  					}
    88  					if !v.IsValid() {
    89  						continue
    90  					}
    91  					// Special handling of lower cased maps.
    92  					if params, ok := v.Interface().(maps.Params); ok {
    93  						v = reflect.ValueOf(params.GetNested(path[i+1:]...))
    94  						break
    95  					}
    96  				}
    97  				p.Pairs[i].Key = v
    98  			}
    99  		}
   100  
   101  	case reflect.Map:
   102  		keys := seqv.MapKeys()
   103  		for i := 0; i < seqv.Len(); i++ {
   104  			p.Pairs[i].Value = seqv.MapIndex(keys[i])
   105  
   106  			if sortByField == "" {
   107  				p.Pairs[i].Key = keys[i]
   108  			} else if sortByField == "value" {
   109  				p.Pairs[i].Key = p.Pairs[i].Value
   110  			} else {
   111  				v := p.Pairs[i].Value
   112  				var err error
   113  				for i, elemName := range path {
   114  					v, err = evaluateSubElem(ctxv, v, elemName)
   115  					if err != nil {
   116  						return nil, err
   117  					}
   118  					if !v.IsValid() {
   119  						continue
   120  					}
   121  					// Special handling of lower cased maps.
   122  					if params, ok := v.Interface().(maps.Params); ok {
   123  						v = reflect.ValueOf(params.GetNested(path[i+1:]...))
   124  						break
   125  					}
   126  				}
   127  				p.Pairs[i].Key = v
   128  			}
   129  		}
   130  	}
   131  
   132  	collator.Lock()
   133  	defer collator.Unlock()
   134  
   135  	return p.sort(), nil
   136  }
   137  
   138  // Credit for pair sorting method goes to Andrew Gerrand
   139  // https://groups.google.com/forum/#!topic/golang-nuts/FT7cjmcL7gw
   140  // A data structure to hold a key/value pair.
   141  type pair struct {
   142  	Key   reflect.Value
   143  	Value reflect.Value
   144  }
   145  
   146  // A slice of pairs that implements sort.Interface to sort by Value.
   147  type pairList struct {
   148  	Collator  *langs.Collator
   149  	sortComp  *compare.Namespace
   150  	Pairs     []pair
   151  	SortAsc   bool
   152  	SliceType reflect.Type
   153  }
   154  
   155  func (p pairList) Swap(i, j int) { p.Pairs[i], p.Pairs[j] = p.Pairs[j], p.Pairs[i] }
   156  func (p pairList) Len() int      { return len(p.Pairs) }
   157  func (p pairList) Less(i, j int) bool {
   158  	iv := p.Pairs[i].Key
   159  	jv := p.Pairs[j].Key
   160  
   161  	if iv.IsValid() {
   162  		if jv.IsValid() {
   163  			// can only call Interface() on valid reflect Values
   164  			return p.sortComp.LtCollate(p.Collator, iv.Interface(), jv.Interface())
   165  		}
   166  
   167  		// if j is invalid, test i against i's zero value
   168  		return p.sortComp.LtCollate(p.Collator, iv.Interface(), reflect.Zero(iv.Type()))
   169  	}
   170  
   171  	if jv.IsValid() {
   172  		// if i is invalid, test j against j's zero value
   173  		return p.sortComp.LtCollate(p.Collator, reflect.Zero(jv.Type()), jv.Interface())
   174  	}
   175  
   176  	return false
   177  }
   178  
   179  // sorts a pairList and returns a slice of sorted values
   180  func (p pairList) sort() any {
   181  	if p.SortAsc {
   182  		sort.Stable(p)
   183  	} else {
   184  		sort.Stable(sort.Reverse(p))
   185  	}
   186  	sorted := reflect.MakeSlice(p.SliceType, len(p.Pairs), len(p.Pairs))
   187  	for i, v := range p.Pairs {
   188  		sorted.Index(i).Set(v.Value)
   189  	}
   190  
   191  	return sorted.Interface()
   192  }