summaryrefslogtreecommitdiff
path: root/coll
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2019-01-28 22:18:00 -0500
committerDave Henderson <dhenderson@gmail.com>2019-01-28 22:41:18 -0500
commit400af7d6edb5022bf6dd88887d0af17b2c3cade0 (patch)
treea6c6bba20be0092fb0403375ce75cb56725c1d7e /coll
parentad7c7479a4425aff310038e7538622b4347322de (diff)
New collections (coll) namespace, plus new functions
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
Diffstat (limited to 'coll')
-rw-r--r--coll/coll.go164
-rw-r--r--coll/coll_test.go199
2 files changed, 363 insertions, 0 deletions
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)
+}