diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index 92a61e575c6..15f17ff6df2 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -16,7 +16,6 @@ package collections import ( - "errors" "fmt" "html/template" "math/rand" @@ -30,6 +29,7 @@ import ( "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" + "github.com/pkg/errors" "github.com/spf13/cast" ) @@ -655,40 +655,38 @@ func (ns *Namespace) Union(l1, l2 interface{}) (interface{}, error) { // Uniq takes in a slice or array and returns a slice with subsequent // duplicate elements removed. -func (ns *Namespace) Uniq(l interface{}) (interface{}, error) { - if l == nil { +func (ns *Namespace) Uniq(seq interface{}) (interface{}, error) { + if seq == nil { return make([]interface{}, 0), nil } - lv := reflect.ValueOf(l) - lv, isNil := indirect(lv) - if isNil { - return nil, errors.New("invalid nil argument to Uniq") - } - - var ret reflect.Value + v := reflect.ValueOf(seq) + var slice reflect.Value - switch lv.Kind() { + switch v.Kind() { case reflect.Slice: - ret = reflect.MakeSlice(lv.Type(), 0, 0) + slice = reflect.MakeSlice(v.Type(), 0, 0) case reflect.Array: - ret = reflect.MakeSlice(reflect.SliceOf(lv.Type().Elem()), 0, 0) + slice = reflect.MakeSlice(reflect.SliceOf(v.Type().Elem()), 0, 0) default: - return nil, errors.New("Can't use Uniq on " + reflect.ValueOf(lv).Type().String()) + return nil, errors.Errorf("type %T not supported", seq) } - for i := 0; i != lv.Len(); i++ { - lvv := lv.Index(i) - lvv, isNil := indirect(lvv) - if isNil { - continue + seen := make(map[interface{}]bool) + for i := 0; i < v.Len(); i++ { + ev, _ := indirectInterface(v.Index(i)) + if !ev.Type().Comparable() { + return nil, errors.New("elements must be comparable") } - - if !ns.In(ret.Interface(), lvv.Interface()) { - ret = reflect.Append(ret, lvv) + key := normalize(ev) + if _, found := seen[key]; !found { + slice = reflect.Append(slice, ev) + seen[key] = true } } - return ret.Interface(), nil + + return slice.Interface(), nil + } // KeyVals creates a key and values wrapper. diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index 103aee59e67..741dd074dd0 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -322,23 +322,23 @@ func (p testPage) String() string { type pagesPtr []*testPage type pagesVals []testPage +var ( + p1 = &testPage{"A"} + p2 = &testPage{"B"} + p3 = &testPage{"C"} + p4 = &testPage{"D"} + + p1v = testPage{"A"} + p2v = testPage{"B"} + p3v = testPage{"C"} + p4v = testPage{"D"} +) + func TestIntersect(t *testing.T) { t.Parallel() ns := New(&deps.Deps{}) - var ( - p1 = &testPage{"A"} - p2 = &testPage{"B"} - p3 = &testPage{"C"} - p4 = &testPage{"D"} - - p1v = testPage{"A"} - p2v = testPage{"B"} - p3v = testPage{"C"} - p4v = testPage{"D"} - ) - for i, test := range []struct { l1, l2 interface{} expect interface{} @@ -671,18 +671,6 @@ func TestUnion(t *testing.T) { ns := New(&deps.Deps{}) - var ( - p1 = &testPage{"A"} - p2 = &testPage{"B"} - // p3 = &page{"C"} - p4 = &testPage{"D"} - - p1v = testPage{"A"} - //p2v = page{"B"} - p3v = testPage{"C"} - //p4v = page{"D"} - ) - for i, test := range []struct { l1 interface{} l2 interface{} @@ -786,7 +774,15 @@ func TestUniq(t *testing.T) { {[]int{1, 2, 3, 2}, []int{1, 2, 3}, false}, {[4]int{1, 2, 3, 2}, []int{1, 2, 3}, false}, {nil, make([]interface{}, 0), false}, - // should-errors + // Pointers + {pagesPtr{p1, p2, p3, p2}, pagesPtr{p1, p2, p3}, false}, + {pagesPtr{}, pagesPtr{}, false}, + // Structs + {pagesVals{p3v, p2v, p3v, p2v}, pagesVals{p3v, p2v}, false}, + + // should fail + // uncomparable types + {[]map[string]int{{"K1": 1}}, []map[string]int{{"K2": 2}, {"K2": 2}}, true}, {1, 1, true}, {"foo", "fo", true}, } {