From 400af7d6edb5022bf6dd88887d0af17b2c3cade0 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Mon, 28 Jan 2019 22:18:00 -0500 Subject: New collections (coll) namespace, plus new functions Signed-off-by: Dave Henderson --- coll/coll.go | 164 ++++++++++++++++++++++++++++++++++++++++++++ coll/coll_test.go | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 coll/coll.go create mode 100644 coll/coll_test.go (limited to 'coll') diff --git a/coll/coll.go b/coll/coll.go new file mode 100644 index 00000000..a9331055 --- /dev/null +++ b/coll/coll.go @@ -0,0 +1,164 @@ +package coll + +import ( + "fmt" + "reflect" + "sort" + + "github.com/hairyhenderson/gomplate/conv" + "github.com/pkg/errors" +) + +// Slice creates a slice from a bunch of arguments +func Slice(args ...interface{}) []interface{} { + return args +} + +func interfaceSlice(slice interface{}) ([]interface{}, error) { + s := reflect.ValueOf(slice) + kind := s.Kind() + switch kind { + case reflect.Slice, reflect.Array: + ret := make([]interface{}, s.Len()) + for i := 0; i < s.Len(); i++ { + ret[i] = s.Index(i).Interface() + } + return ret, nil + default: + return nil, errors.Errorf("expected an array or slice, but got a %T", s) + } +} + +// Has determines whether or not a given object has a property with the given key +func Has(in interface{}, key interface{}) bool { + av := reflect.ValueOf(in) + + switch av.Kind() { + case reflect.Map: + kv := reflect.ValueOf(key) + return av.MapIndex(kv).IsValid() + case reflect.Slice, reflect.Array: + l := av.Len() + for i := 0; i < l; i++ { + v := av.Index(i).Interface() + if reflect.DeepEqual(v, key) { + return true + } + } + } + + return false +} + +// Dict is a convenience function that creates a map with string keys. +// Provide arguments as key/value pairs. If an odd number of arguments +// is provided, the last is used as the key, and an empty string is +// set as the value. +// All keys are converted to strings, regardless of input type. +func Dict(v ...interface{}) (map[string]interface{}, error) { + dict := map[string]interface{}{} + lenv := len(v) + for i := 0; i < lenv; i += 2 { + key := conv.ToString(v[i]) + if i+1 >= lenv { + dict[key] = "" + continue + } + dict[key] = v[i+1] + } + return dict, nil +} + +// Keys returns the list of keys in one or more maps. The returned list of keys +// is ordered by map, each in sorted key order. +func Keys(in ...map[string]interface{}) ([]string, error) { + if len(in) == 0 { + return nil, fmt.Errorf("need at least one argument") + } + keys := []string{} + for _, m := range in { + k, _ := splitMap(m) + keys = append(keys, k...) + } + return keys, nil +} + +func splitMap(m map[string]interface{}) ([]string, []interface{}) { + keys := make([]string, len(m)) + values := make([]interface{}, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Strings(keys) + for i, k := range keys { + values[i] = m[k] + } + return keys, values +} + +// Values returns the list of values in one or more maps. The returned list of values +// is ordered by map, each in sorted key order. If the Keys function is called with +// the same arguments, the key/value mappings will be maintained. +func Values(in ...map[string]interface{}) ([]interface{}, error) { + if len(in) == 0 { + return nil, fmt.Errorf("need at least one argument") + } + values := []interface{}{} + for _, m := range in { + _, v := splitMap(m) + values = append(values, v...) + } + return values, nil +} + +// Append v to the end of list. No matter what type of input slice or array list is, a new []interface{} is always returned. +func Append(v interface{}, list interface{}) ([]interface{}, error) { + l, err := interfaceSlice(list) + if err != nil { + return nil, err + } + + return append(l, v), nil +} + +// Prepend v to the beginning of list. No matter what type of input slice or array list is, a new []interface{} is always returned. +func Prepend(v interface{}, list interface{}) ([]interface{}, error) { + l, err := interfaceSlice(list) + if err != nil { + return nil, err + } + + return append([]interface{}{v}, l...), nil +} + +// Uniq finds the unique values within list. No matter what type of input slice or array list is, a new []interface{} is always returned. +func Uniq(list interface{}) ([]interface{}, error) { + l, err := interfaceSlice(list) + if err != nil { + return nil, err + } + + out := []interface{}{} + for _, v := range l { + if !Has(out, v) { + out = append(out, v) + } + } + return out, nil +} + +// Reverse the list. No matter what type of input slice or array list is, a new []interface{} is always returned. +func Reverse(list interface{}) ([]interface{}, error) { + l, err := interfaceSlice(list) + if err != nil { + return nil, err + } + + // nifty trick from https://github.com/golang/go/wiki/SliceTricks#reversing + for left, right := 0, len(l)-1; left < right; left, right = left+1, right-1 { + l[left], l[right] = l[right], l[left] + } + return l, nil +} diff --git a/coll/coll_test.go b/coll/coll_test.go new file mode 100644 index 00000000..fff2b02f --- /dev/null +++ b/coll/coll_test.go @@ -0,0 +1,199 @@ +package coll + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSlice(t *testing.T) { + expected := []string{"foo", "bar"} + actual := Slice("foo", "bar") + assert.Equal(t, expected[0], actual[0]) + assert.Equal(t, expected[1], actual[1]) +} + +func TestHas(t *testing.T) { + + in := map[string]interface{}{ + "foo": "bar", + "baz": map[string]interface{}{ + "qux": "quux", + }, + } + + testdata := []struct { + in interface{} + key interface{} + out bool + }{ + {in, "foo", true}, + {in, "bar", false}, + {in["baz"], "qux", true}, + {[]string{"foo", "bar", "baz"}, "bar", true}, + {[]interface{}{"foo", "bar", "baz"}, "bar", true}, + {[]interface{}{"foo", "bar", "baz"}, 42, false}, + {[]int{1, 2, 42}, 42, true}, + } + + for _, d := range testdata { + assert.Equal(t, d.out, Has(d.in, d.key)) + } +} + +func TestDict(t *testing.T) { + testdata := []struct { + args []interface{} + expected map[string]interface{} + }{ + {nil, map[string]interface{}{}}, + {[]interface{}{}, map[string]interface{}{}}, + {[]interface{}{"foo"}, map[string]interface{}{"foo": ""}}, + {[]interface{}{42}, map[string]interface{}{"42": ""}}, + {[]interface{}{"foo", nil}, map[string]interface{}{"foo": nil}}, + {[]interface{}{"foo", "bar"}, map[string]interface{}{"foo": "bar"}}, + {[]interface{}{"foo", "bar", "baz", true}, map[string]interface{}{ + "foo": "bar", + "baz": true, + }}, + } + + for _, d := range testdata { + actual, _ := Dict(d.args...) + assert.Equal(t, d.expected, actual) + } +} + +func TestKeys(t *testing.T) { + _, err := Keys() + assert.Error(t, err) + + in := map[string]interface{}{ + "foo": 1, + "bar": 2, + } + expected := []string{"bar", "foo"} + keys, err := Keys(in) + assert.NoError(t, err) + assert.EqualValues(t, expected, keys) + + in2 := map[string]interface{}{ + "baz": 3, + "qux": 4, + } + expected = []string{"bar", "foo", "baz", "qux"} + keys, err = Keys(in, in2) + assert.NoError(t, err) + assert.EqualValues(t, expected, keys) + + in3 := map[string]interface{}{ + "Foo": 5, + "Bar": 6, + "foo": 7, + "bar": 8, + } + expected = []string{"bar", "foo", "baz", "qux", "Bar", "Foo", "bar", "foo"} + keys, err = Keys(in, in2, in3) + assert.NoError(t, err) + assert.EqualValues(t, expected, keys) +} + +func TestValues(t *testing.T) { + _, err := Values() + assert.Error(t, err) + + in := map[string]interface{}{ + "foo": 1, + "bar": 2, + } + expected := []interface{}{2, 1} + values, err := Values(in) + assert.NoError(t, err) + assert.EqualValues(t, expected, values) + + in2 := map[string]interface{}{ + "baz": 3, + "qux": 4, + } + expected = []interface{}{2, 1, 3, 4} + values, err = Values(in, in2) + assert.NoError(t, err) + assert.EqualValues(t, expected, values) + + in3 := map[string]interface{}{ + "Foo": 5, + "Bar": 6, + "foo": 7, + "bar": 8, + } + expected = []interface{}{2, 1, 3, 4, 6, 5, 8, 7} + values, err = Values(in, in2, in3) + assert.NoError(t, err) + assert.EqualValues(t, expected, values) +} + +func TestAppend(t *testing.T) { + out, err := Append(42, []interface{}{}) + assert.NoError(t, err) + assert.EqualValues(t, out, []interface{}{42}) + + out, err = Append(42, []interface{}{4.9, false, "foo"}) + assert.NoError(t, err) + assert.EqualValues(t, out, []interface{}{4.9, false, "foo", 42}) + + // a strange but valid use-cases, since we're converting to an []interface{} + out, err = Append(42, []string{"foo"}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{"foo", 42}, out) + + out, err = Append("baz", []string{"foo", "bar"}) + assert.NoError(t, err) + assert.EqualValues(t, out, []interface{}{"foo", "bar", "baz"}) +} + +func TestPrepend(t *testing.T) { + out, err := Prepend(42, []interface{}{}) + assert.NoError(t, err) + assert.EqualValues(t, out, []interface{}{42}) + + out, err = Prepend(42, []interface{}{4.9, false, "foo"}) + assert.NoError(t, err) + assert.EqualValues(t, out, []interface{}{42, 4.9, false, "foo"}) + + // a strange but valid use-cases, since we're converting to an []interface{} + out, err = Prepend(42, []string{"foo"}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{42, "foo"}, out) + + out, err = Prepend("foo", []string{"bar", "baz"}) + assert.NoError(t, err) + assert.EqualValues(t, out, []interface{}{"foo", "bar", "baz"}) +} + +func TestUniq(t *testing.T) { + out, err := Uniq([]interface{}{1, 2, 3, 1, true, false, true, "1", 2}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{1, 2, 3, true, false, "1"}, out) + + out, err = Uniq([]string{"one", "two", "one", "three"}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{"one", "two", "three"}, out) +} + +func TestReverse(t *testing.T) { + out, err := Reverse([]interface{}{}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{}, out) + + out, err = Reverse([]interface{}{8}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{8}, out) + + out, err = Reverse([]interface{}{1, 2, 3, 4}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{4, 3, 2, 1}, out) + + out, err = Reverse([]int{1, 2, 3, 4}) + assert.NoError(t, err) + assert.EqualValues(t, []interface{}{4, 3, 2, 1}, out) +} -- cgit v1.2.3