tpl/collections: Make Pages etc. work in uniq
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 18 Apr 2019 08:27:23 +0000 (10:27 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 18 Apr 2019 14:50:13 +0000 (16:50 +0200)
Fixes #5852

tpl/collections/collections.go
tpl/collections/collections_test.go

index 92a61e575c637c5ccd72fa799cff7c30afbc8f2a..15f17ff6df202b85a9e17bf9bbf31842f8f2f8db 100644 (file)
@@ -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.
index 103aee59e6785c92b1aec64a28796499bc2b7ad5..741dd074dd0c9fe3e3ecc63725e03f67653ec27c 100644 (file)
@@ -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},
        } {