mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
764 lines
18 KiB
Go
764 lines
18 KiB
Go
// Copyright 2019 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 provides template functions for manipulating collections
|
||
// such as arrays, maps, and slices.
|
||
package collections
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"math/rand/v2"
|
||
"reflect"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gohugoio/hugo/common/collections"
|
||
"github.com/gohugoio/hugo/common/hmaps"
|
||
"github.com/gohugoio/hugo/common/hreflect"
|
||
"github.com/gohugoio/hugo/common/hstore"
|
||
"github.com/gohugoio/hugo/common/types"
|
||
"github.com/gohugoio/hugo/deps"
|
||
"github.com/gohugoio/hugo/langs"
|
||
"github.com/gohugoio/hugo/tpl/compare"
|
||
"github.com/spf13/cast"
|
||
)
|
||
|
||
// New returns a new instance of the collections-namespaced template functions.
|
||
func New(deps *deps.Deps) *Namespace {
|
||
language := deps.Conf.Language().(*langs.Language)
|
||
if language == nil {
|
||
panic("language must be set")
|
||
}
|
||
loc := langs.GetLocation(language)
|
||
|
||
dCache := hmaps.NewCacheWithOptions[dKey, []int](hmaps.CacheOptions{Size: 100})
|
||
|
||
return &Namespace{
|
||
loc: loc,
|
||
sortComp: compare.New(loc, true),
|
||
dCache: dCache,
|
||
deps: deps,
|
||
}
|
||
}
|
||
|
||
// Namespace provides template functions for the "collections" namespace.
|
||
type Namespace struct {
|
||
loc *time.Location
|
||
sortComp *compare.Namespace
|
||
dCache *hmaps.Cache[dKey, []int]
|
||
deps *deps.Deps
|
||
}
|
||
|
||
// After returns all the items after the first n items in list l.
|
||
func (ns *Namespace) After(n any, l any) (any, error) {
|
||
if n == nil || l == nil {
|
||
return nil, errors.New("both limit and seq must be provided")
|
||
}
|
||
|
||
nv, err := cast.ToIntE(n)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if nv < 0 {
|
||
return nil, errors.New("sequence bounds out of range [" + cast.ToString(nv) + ":]")
|
||
}
|
||
|
||
lv := reflect.ValueOf(l)
|
||
lv, isNil := hreflect.Indirect(lv)
|
||
if isNil {
|
||
return nil, errors.New("can't iterate over a nil value")
|
||
}
|
||
|
||
switch lv.Kind() {
|
||
case reflect.Array, reflect.Slice, reflect.String:
|
||
// okay
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l).Type().String())
|
||
}
|
||
|
||
if nv >= lv.Len() {
|
||
return lv.Slice(0, 0).Interface(), nil
|
||
}
|
||
|
||
return lv.Slice(nv, lv.Len()).Interface(), nil
|
||
}
|
||
|
||
// Delimit takes a given list l and returns a string delimited by sep.
|
||
// If last is passed to the function, it will be used as the final delimiter.
|
||
func (ns *Namespace) Delimit(ctx context.Context, l, sep any, last ...any) (string, error) {
|
||
d, err := cast.ToStringE(sep)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
var dLast *string
|
||
if len(last) > 0 {
|
||
l := last[0]
|
||
dStr, err := cast.ToStringE(l)
|
||
if err != nil {
|
||
dLast = nil
|
||
} else {
|
||
dLast = &dStr
|
||
}
|
||
}
|
||
|
||
lv := reflect.ValueOf(l)
|
||
lv, isNil := hreflect.Indirect(lv)
|
||
if isNil {
|
||
return "", errors.New("can't iterate over a nil value")
|
||
}
|
||
|
||
var str strings.Builder
|
||
switch lv.Kind() {
|
||
case reflect.Map:
|
||
sortSeq, err := ns.Sort(ctx, l)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
lv = reflect.ValueOf(sortSeq)
|
||
fallthrough
|
||
case reflect.Array, reflect.Slice, reflect.String:
|
||
for i := range lv.Len() {
|
||
val := lv.Index(i).Interface()
|
||
valStr, err := cast.ToStringE(val)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
switch {
|
||
case i == lv.Len()-2 && dLast != nil:
|
||
str.WriteString(valStr + *dLast)
|
||
case i == lv.Len()-1:
|
||
str.WriteString(valStr)
|
||
default:
|
||
str.WriteString(valStr + d)
|
||
}
|
||
}
|
||
|
||
default:
|
||
return "", fmt.Errorf("can't iterate over %T", l)
|
||
}
|
||
|
||
return str.String(), nil
|
||
}
|
||
|
||
// Dictionary creates a new map from the given parameters by
|
||
// treating values as key-value pairs. The number of values must be even.
|
||
// The keys can be string slices, which will create the needed nested structure.
|
||
func (ns *Namespace) Dictionary(values ...any) (map[string]any, error) {
|
||
if len(values)%2 != 0 {
|
||
return nil, errors.New("invalid dictionary call")
|
||
}
|
||
|
||
root := make(map[string]any)
|
||
|
||
for i := 0; i < len(values); i += 2 {
|
||
dict := root
|
||
var key string
|
||
switch v := values[i].(type) {
|
||
case string:
|
||
key = v
|
||
case []string:
|
||
for i := range len(v) - 1 {
|
||
key = v[i]
|
||
var m map[string]any
|
||
v, found := dict[key]
|
||
if found {
|
||
m = v.(map[string]any)
|
||
} else {
|
||
m = make(map[string]any)
|
||
dict[key] = m
|
||
}
|
||
dict = m
|
||
}
|
||
key = v[len(v)-1]
|
||
default:
|
||
return nil, errors.New("invalid dictionary key")
|
||
}
|
||
dict[key] = values[i+1]
|
||
}
|
||
|
||
return root, nil
|
||
}
|
||
|
||
// First returns the first limit items in list l.
|
||
func (ns *Namespace) First(limit any, l any) (any, error) {
|
||
if limit == nil || l == nil {
|
||
return nil, errors.New("both limit and seq must be provided")
|
||
}
|
||
|
||
limitv, err := cast.ToIntE(limit)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if limitv < 0 {
|
||
return nil, errors.New("sequence length must be non-negative")
|
||
}
|
||
|
||
lv := reflect.ValueOf(l)
|
||
lv, isNil := hreflect.Indirect(lv)
|
||
if isNil {
|
||
return nil, errors.New("can't iterate over a nil value")
|
||
}
|
||
|
||
switch lv.Kind() {
|
||
case reflect.Array, reflect.Slice, reflect.String:
|
||
// okay
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l).Type().String())
|
||
}
|
||
|
||
if limitv > lv.Len() {
|
||
limitv = lv.Len()
|
||
}
|
||
|
||
return lv.Slice(0, limitv).Interface(), nil
|
||
}
|
||
|
||
// In returns whether v is in the list l. l may be an array or slice.
|
||
func (ns *Namespace) In(l any, v any) (bool, error) {
|
||
if l == nil || v == nil {
|
||
return false, nil
|
||
}
|
||
|
||
lv := reflect.ValueOf(l)
|
||
vv := reflect.ValueOf(v)
|
||
|
||
vvk := normalize(vv)
|
||
|
||
switch lv.Kind() {
|
||
case reflect.Array, reflect.Slice:
|
||
for i := range lv.Len() {
|
||
lvv, isNil := hreflect.Indirect(lv.Index(i))
|
||
if isNil {
|
||
continue
|
||
}
|
||
|
||
lvvk := normalize(lvv)
|
||
|
||
if lvvk == vvk {
|
||
return true, nil
|
||
}
|
||
}
|
||
}
|
||
ss, err := cast.ToStringE(l)
|
||
if err != nil {
|
||
return false, nil
|
||
}
|
||
|
||
su, err := cast.ToStringE(v)
|
||
if err != nil {
|
||
return false, nil
|
||
}
|
||
return strings.Contains(ss, su), nil
|
||
}
|
||
|
||
// Intersect returns the common elements in the given sets, l1 and l2. l1 and
|
||
// l2 must be of the same type and may be either arrays or slices.
|
||
func (ns *Namespace) Intersect(l1, l2 any) (any, error) {
|
||
if l1 == nil || l2 == nil {
|
||
return make([]any, 0), nil
|
||
}
|
||
|
||
var ins *intersector
|
||
|
||
l1v := reflect.ValueOf(l1)
|
||
l2v := reflect.ValueOf(l2)
|
||
|
||
switch l1v.Kind() {
|
||
case reflect.Array, reflect.Slice:
|
||
ins = &intersector{r: reflect.MakeSlice(l1v.Type(), 0, 0), seen: make(map[any]bool)}
|
||
switch l2v.Kind() {
|
||
case reflect.Array, reflect.Slice:
|
||
for i := range l1v.Len() {
|
||
l1vv := l1v.Index(i)
|
||
if !l1vv.Type().Comparable() {
|
||
return make([]any, 0), errors.New("intersect does not support slices or arrays of uncomparable types")
|
||
}
|
||
|
||
for j := range l2v.Len() {
|
||
l2vv := l2v.Index(j)
|
||
if !l2vv.Type().Comparable() {
|
||
return make([]any, 0), errors.New("intersect does not support slices or arrays of uncomparable types")
|
||
}
|
||
|
||
ins.handleValuePair(l1vv, l2vv)
|
||
}
|
||
}
|
||
return ins.r.Interface(), nil
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String())
|
||
}
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String())
|
||
}
|
||
}
|
||
|
||
// Group groups a set of items by the given key.
|
||
// This is currently only supported for Pages.
|
||
func (ns *Namespace) Group(key any, items any) (any, error) {
|
||
if key == nil {
|
||
return nil, errors.New("nil is not a valid key to group by")
|
||
}
|
||
|
||
if g, ok := items.(collections.Grouper); ok {
|
||
return g.Group(key, items)
|
||
}
|
||
|
||
in := newSliceElement(items)
|
||
|
||
if g, ok := in.(collections.Grouper); ok {
|
||
return g.Group(key, items)
|
||
}
|
||
|
||
return nil, fmt.Errorf("grouping not supported for type %T %T", items, in)
|
||
}
|
||
|
||
// IsSet returns whether a given array, channel, slice, or map in c has the given key
|
||
// defined.
|
||
func (ns *Namespace) IsSet(c any, key any) (bool, error) {
|
||
av := reflect.ValueOf(c)
|
||
kv := reflect.ValueOf(key)
|
||
|
||
switch av.Kind() {
|
||
case reflect.Array, reflect.Chan, reflect.Slice:
|
||
k, err := cast.ToIntE(key)
|
||
if err != nil {
|
||
return false, fmt.Errorf("isset unable to use key of type %T as index", key)
|
||
}
|
||
if av.Len() > k {
|
||
return true, nil
|
||
}
|
||
case reflect.Map:
|
||
if kv.Type() == av.Type().Key() {
|
||
return av.MapIndex(kv).IsValid(), nil
|
||
}
|
||
default:
|
||
ns.deps.Log.Warnf("calling IsSet with unsupported type %q (%T) will always return false.\n", av.Kind(), c)
|
||
}
|
||
|
||
return false, nil
|
||
}
|
||
|
||
// Last returns the last limit items in the list l.
|
||
func (ns *Namespace) Last(limit any, l any) (any, error) {
|
||
if limit == nil || l == nil {
|
||
return nil, errors.New("both limit and seq must be provided")
|
||
}
|
||
|
||
limitv, err := cast.ToIntE(limit)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if limitv < 0 {
|
||
return nil, errors.New("sequence length must be non-negative")
|
||
}
|
||
|
||
seqv := reflect.ValueOf(l)
|
||
seqv, isNil := hreflect.Indirect(seqv)
|
||
if isNil {
|
||
return nil, errors.New("can't iterate over a nil value")
|
||
}
|
||
|
||
switch seqv.Kind() {
|
||
case reflect.Array, reflect.Slice, reflect.String:
|
||
// okay
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l).Type().String())
|
||
}
|
||
|
||
if limitv > seqv.Len() {
|
||
limitv = seqv.Len()
|
||
}
|
||
|
||
return seqv.Slice(seqv.Len()-limitv, seqv.Len()).Interface(), nil
|
||
}
|
||
|
||
// Reverse creates a copy of the list l and reverses it.
|
||
func (ns *Namespace) Reverse(l any) (any, error) {
|
||
if l == nil {
|
||
return nil, nil
|
||
}
|
||
v := reflect.ValueOf(l)
|
||
|
||
switch v.Kind() {
|
||
case reflect.Slice:
|
||
default:
|
||
return nil, errors.New("argument must be a slice")
|
||
}
|
||
|
||
sliceCopy := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
||
|
||
for i := v.Len() - 1; i >= 0; i-- {
|
||
element := sliceCopy.Index(i)
|
||
element.Set(v.Index(v.Len() - 1 - i))
|
||
}
|
||
|
||
return sliceCopy.Interface(), nil
|
||
}
|
||
|
||
// Sanity check for slices created by Seq and D.
|
||
const maxSeqSize = 1000000
|
||
|
||
var errSeqSizeExceedsLimit = errors.New("size of result exceeds limit")
|
||
|
||
// Seq creates a sequence of integers from args. It's named and used as GNU's seq.
|
||
//
|
||
// Examples:
|
||
//
|
||
// 3 => 1, 2, 3
|
||
// 1 2 4 => 1, 3
|
||
// -3 => -1, -2, -3
|
||
// 1 4 => 1, 2, 3, 4
|
||
// 1 -2 => 1, 0, -1, -2
|
||
func (ns *Namespace) Seq(args ...any) ([]int, error) {
|
||
if len(args) < 1 || len(args) > 3 {
|
||
return nil, errors.New("invalid number of arguments to Seq")
|
||
}
|
||
|
||
intArgs := cast.ToIntSlice(args)
|
||
if len(intArgs) < 1 || len(intArgs) > 3 {
|
||
return nil, errors.New("invalid arguments to Seq")
|
||
}
|
||
|
||
inc := 1
|
||
var last int
|
||
first := intArgs[0]
|
||
|
||
if len(intArgs) == 1 {
|
||
last = first
|
||
if last == 0 {
|
||
return []int{}, nil
|
||
} else if last > 0 {
|
||
first = 1
|
||
} else {
|
||
first = -1
|
||
inc = -1
|
||
}
|
||
} else if len(intArgs) == 2 {
|
||
last = intArgs[1]
|
||
if last < first {
|
||
inc = -1
|
||
}
|
||
} else {
|
||
inc = intArgs[1]
|
||
last = intArgs[2]
|
||
if inc == 0 {
|
||
return nil, errors.New("'increment' must not be 0")
|
||
}
|
||
if first < last && inc < 0 {
|
||
return nil, errors.New("'increment' must be > 0")
|
||
}
|
||
if first > last && inc > 0 {
|
||
return nil, errors.New("'increment' must be < 0")
|
||
}
|
||
}
|
||
|
||
// sanity check
|
||
if last < -maxSeqSize {
|
||
return nil, errSeqSizeExceedsLimit
|
||
}
|
||
size := ((last - first) / inc) + 1
|
||
|
||
// sanity check
|
||
if size <= 0 || size > maxSeqSize {
|
||
return nil, errSeqSizeExceedsLimit
|
||
}
|
||
|
||
seq := make([]int, size)
|
||
val := first
|
||
for i := 0; ; i++ {
|
||
seq[i] = val
|
||
val += inc
|
||
if (inc < 0 && val < last) || (inc > 0 && val > last) {
|
||
break
|
||
}
|
||
}
|
||
|
||
return seq, nil
|
||
}
|
||
|
||
// Shuffle returns list l in a randomized order.
|
||
func (ns *Namespace) Shuffle(l any) (any, error) {
|
||
if l == nil {
|
||
return nil, errors.New("both count and seq must be provided")
|
||
}
|
||
|
||
lv := reflect.ValueOf(l)
|
||
lv, isNil := hreflect.Indirect(lv)
|
||
if isNil {
|
||
return nil, errors.New("can't iterate over a nil value")
|
||
}
|
||
|
||
switch lv.Kind() {
|
||
case reflect.Array, reflect.Slice, reflect.String:
|
||
// okay
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l).Type().String())
|
||
}
|
||
|
||
shuffled := reflect.MakeSlice(reflect.TypeOf(l), lv.Len(), lv.Len())
|
||
|
||
randomIndices := rand.Perm(lv.Len())
|
||
|
||
for index, value := range randomIndices {
|
||
shuffled.Index(value).Set(lv.Index(index))
|
||
}
|
||
|
||
return shuffled.Interface(), nil
|
||
}
|
||
|
||
// Slice returns a slice of all passed arguments.
|
||
func (ns *Namespace) Slice(args ...any) any {
|
||
if len(args) == 0 {
|
||
return args
|
||
}
|
||
|
||
return collections.Slice(args...)
|
||
}
|
||
|
||
type dKey struct {
|
||
seed uint64
|
||
n int
|
||
hi int
|
||
}
|
||
|
||
// D returns a sorted slice of unique random integers in the half-open interval
|
||
// [0, hi) using the provided seed value. The number of elements in the
|
||
// resulting slice is n or hi, whichever is less.
|
||
//
|
||
// If n <= hi, it returns a sorted random sample of size n using J. S. Vitter’s
|
||
// Method D for sequential random sampling.
|
||
//
|
||
// If n > hi, it returns the full, sorted range [0, hi) of size hi.
|
||
//
|
||
// If n == 0 or hi == 0, it returns an empty slice.
|
||
//
|
||
// Reference:
|
||
//
|
||
// J. S. Vitter, "An efficient algorithm for sequential random sampling," ACM Trans. Math. Softw., vol. 11, no. 1, pp. 37–57, 1985.
|
||
// See also: https://getkerf.wordpress.com/2016/03/30/the-best-algorithm-no-one-knows-about/
|
||
func (ns *Namespace) D(seed, n, hi any) ([]int, error) {
|
||
seedInt, err := cast.ToInt64E(seed)
|
||
if err != nil || seedInt < 0 {
|
||
return nil, fmt.Errorf("the seed value (%v) must be a non-negative integer", seed)
|
||
}
|
||
|
||
nInt, err := cast.ToIntE(n)
|
||
if err != nil || nInt < 0 || nInt > maxSeqSize {
|
||
return nil, fmt.Errorf("the number of requested values (%v) must be a non-negative integer <= %d", n, maxSeqSize)
|
||
}
|
||
|
||
hiInt, err := cast.ToIntE(hi)
|
||
if err != nil || hiInt < 0 || hiInt > maxSeqSize {
|
||
return nil, fmt.Errorf("the maximum requested value (%v) must be a non-negative integer <= %d", hi, maxSeqSize)
|
||
}
|
||
|
||
if nInt == 0 || hiInt == 0 {
|
||
return []int{}, nil
|
||
}
|
||
|
||
key := dKey{seed: uint64(seedInt), n: nInt, hi: hiInt}
|
||
|
||
v, err := ns.dCache.GetOrCreate(key, func() ([]int, error) {
|
||
if key.n > key.hi {
|
||
result := make([]int, key.hi)
|
||
for i := 0; i < key.hi; i++ {
|
||
result[i] = i
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
prng := rand.New(rand.NewPCG(key.seed, 0))
|
||
result := make([]int, 0, key.n)
|
||
_d(prng, key.n, key.hi, func(i int) {
|
||
result = append(result, i)
|
||
})
|
||
|
||
return result, nil
|
||
})
|
||
|
||
return v, err
|
||
}
|
||
|
||
type intersector struct {
|
||
r reflect.Value
|
||
seen map[any]bool
|
||
}
|
||
|
||
func (i *intersector) appendIfNotSeen(v reflect.Value) {
|
||
k := normalize(v)
|
||
if !i.seen[k] {
|
||
i.r = reflect.Append(i.r, v)
|
||
i.seen[k] = true
|
||
}
|
||
}
|
||
|
||
func (i *intersector) handleValuePair(l1vv, l2vv reflect.Value) {
|
||
switch kind := l1vv.Kind(); {
|
||
case kind == reflect.String:
|
||
l2t, err := hreflect.ToStringE(l2vv)
|
||
if err == nil && l1vv.String() == l2t {
|
||
i.appendIfNotSeen(l1vv)
|
||
}
|
||
case hreflect.IsNumber(kind):
|
||
f1, err1 := hreflect.ToFloat64E(l1vv)
|
||
f2, err2 := hreflect.ToFloat64E(l2vv)
|
||
if err1 == nil && err2 == nil && f1 == f2 {
|
||
i.appendIfNotSeen(l1vv)
|
||
}
|
||
case kind == reflect.Pointer, kind == reflect.Struct:
|
||
if types.Unwrapv(l1vv.Interface()) == types.Unwrapv(l2vv.Interface()) {
|
||
i.appendIfNotSeen(l1vv)
|
||
}
|
||
case kind == reflect.Interface:
|
||
i.handleValuePair(reflect.ValueOf(l1vv.Interface()), l2vv)
|
||
}
|
||
}
|
||
|
||
// Union returns the union of the given sets, l1 and l2. l1 and
|
||
// l2 must be of the same type and may be either arrays or slices.
|
||
// If l1 and l2 aren't of the same type then l1 will be returned.
|
||
// If either l1 or l2 is nil then the non-nil list will be returned.
|
||
func (ns *Namespace) Union(l1, l2 any) (any, error) {
|
||
if l1 == nil && l2 == nil {
|
||
return []any{}, nil
|
||
} else if l1 == nil && l2 != nil {
|
||
return l2, nil
|
||
} else if l1 != nil && l2 == nil {
|
||
return l1, nil
|
||
}
|
||
|
||
l1v := reflect.ValueOf(l1)
|
||
l2v := reflect.ValueOf(l2)
|
||
|
||
var ins *intersector
|
||
|
||
switch l1v.Kind() {
|
||
case reflect.Array, reflect.Slice:
|
||
switch l2v.Kind() {
|
||
case reflect.Array, reflect.Slice:
|
||
ins = &intersector{r: reflect.MakeSlice(l1v.Type(), 0, 0), seen: make(map[any]bool)}
|
||
|
||
if l1v.Type() != l2v.Type() &&
|
||
l1v.Type().Elem().Kind() != reflect.Interface &&
|
||
l2v.Type().Elem().Kind() != reflect.Interface {
|
||
return ins.r.Interface(), nil
|
||
}
|
||
|
||
var (
|
||
l1vv reflect.Value
|
||
isNil bool
|
||
)
|
||
|
||
for i := range l1v.Len() {
|
||
l1vv, isNil = hreflect.Indirect(l1v.Index(i))
|
||
|
||
if !l1vv.Type().Comparable() {
|
||
return []any{}, errors.New("union does not support slices or arrays of uncomparable types")
|
||
}
|
||
|
||
if !isNil {
|
||
ins.appendIfNotSeen(l1vv)
|
||
}
|
||
}
|
||
|
||
if !l1vv.IsValid() {
|
||
// The first slice may be empty. Pick the first value of the second
|
||
// to use as a prototype.
|
||
if l2v.Len() > 0 {
|
||
l1vv = l2v.Index(0)
|
||
}
|
||
}
|
||
|
||
for j := range l2v.Len() {
|
||
l2vv := l2v.Index(j)
|
||
typ := l1vv.Type()
|
||
|
||
switch kind := l1vv.Kind(); {
|
||
case kind == reflect.String:
|
||
l2t, err := hreflect.ToStringE(l2vv)
|
||
if err == nil {
|
||
ins.appendIfNotSeen(reflect.ValueOf(l2t))
|
||
}
|
||
case hreflect.IsNumber(kind):
|
||
var err error
|
||
l2vv, err = convertNumber(l2vv, typ)
|
||
if err == nil {
|
||
ins.appendIfNotSeen(l2vv)
|
||
}
|
||
case kind == reflect.Interface, kind == reflect.Struct, kind == reflect.Pointer:
|
||
ins.appendIfNotSeen(l2vv)
|
||
|
||
}
|
||
}
|
||
|
||
return ins.r.Interface(), nil
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String())
|
||
}
|
||
default:
|
||
return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String())
|
||
}
|
||
}
|
||
|
||
// Uniq returns a new list with duplicate elements in the list l removed.
|
||
func (ns *Namespace) Uniq(l any) (any, error) {
|
||
if l == nil {
|
||
return make([]any, 0), nil
|
||
}
|
||
|
||
v := reflect.ValueOf(l)
|
||
var slice reflect.Value
|
||
|
||
switch v.Kind() {
|
||
case reflect.Slice:
|
||
slice = reflect.MakeSlice(v.Type(), 0, 0)
|
||
|
||
case reflect.Array:
|
||
slice = reflect.MakeSlice(reflect.SliceOf(v.Type().Elem()), 0, 0)
|
||
default:
|
||
return nil, fmt.Errorf("type %T not supported", l)
|
||
}
|
||
|
||
seen := make(map[any]bool)
|
||
|
||
for i := range v.Len() {
|
||
ev, _ := hreflect.Indirect(v.Index(i))
|
||
|
||
key := normalize(ev)
|
||
|
||
if _, found := seen[key]; !found {
|
||
slice = reflect.Append(slice, ev)
|
||
seen[key] = true
|
||
}
|
||
}
|
||
|
||
return slice.Interface(), nil
|
||
}
|
||
|
||
// KeyVals creates a key and values wrapper.
|
||
func (ns *Namespace) KeyVals(key any, values ...any) (types.KeyValues, error) {
|
||
return types.KeyValues{Key: key, Values: values}, nil
|
||
}
|
||
|
||
// NewScratch creates a new Scratch which can be used to store values in a
|
||
// thread safe way.
|
||
func (ns *Namespace) NewScratch() *hstore.Scratch {
|
||
return hstore.NewScratch()
|
||
}
|