From 8aac7587f77de9e9c56290f5b2f42791c0794ac8 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Fri, 25 May 2018 12:57:26 -0700 Subject: [PATCH] port index and contains functions --- lang/funcs/collection.go | 76 +++++++++++++- lang/funcs/collection_test.go | 183 +++++++++++++++++++++++++++++++++- lang/functions.go | 4 +- 3 files changed, 258 insertions(+), 5 deletions(-) diff --git a/lang/funcs/collection.go b/lang/funcs/collection.go index a28dd3eeec..2df423a10b 100644 --- a/lang/funcs/collection.go +++ b/lang/funcs/collection.go @@ -173,8 +173,7 @@ var CompactFunc = function.New(&function.Spec{ var outputList []cty.Value - it := args[0].ElementIterator() - for it.Next() { + for it := args[0].ElementIterator(); it.Next(); { _, v := it.Element() if v.AsString() == "" { continue @@ -185,6 +184,68 @@ var CompactFunc = function.New(&function.Spec{ }, }) +// ContainsFunc contructs a function that determines whether a given list contains +// a given single value as one of its elements. +var ContainsFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.List(cty.DynamicPseudoType), + }, + { + Name: "value", + Type: cty.DynamicPseudoType, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + + _, err = Index(args[0], args[1]) + if err != nil { + fmt.Println(err) + return cty.False, nil + } + + return cty.True, nil + }, +}) + +// IndexFunc contructs a function that finds the element index for a given value in a list. +var IndexFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: cty.DynamicPseudoType, + }, + { + Name: "value", + Type: cty.DynamicPseudoType, + }, + }, + Type: function.StaticReturnType(cty.Number), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + if args[0].LengthInt() == 0 { // Easy path + return cty.NilVal, fmt.Errorf("Cannot search an empty list") + } + + for it := args[0].ElementIterator(); it.Next(); { + i, v := it.Element() + eq, err := stdlib.Equal(v, args[1]) + if err != nil { + return cty.NilVal, err + } + if !eq.IsKnown() { + return cty.UnknownVal(cty.Number), nil + } + if eq.True() { + return i, nil + } + } + return cty.NilVal, fmt.Errorf("item not found") + + }, +}) + // Element returns a single element from a given list at the given index. If // index is greater than the length of the list then it is wrapped modulo // the list length. @@ -208,3 +269,14 @@ func CoalesceList(args ...cty.Value) (cty.Value, error) { func Compact(list cty.Value) (cty.Value, error) { return CompactFunc.Call([]cty.Value{list}) } + +// Contains determines whether a given list contains a given single value +// as one of its elements. +func Contains(list, value cty.Value) (cty.Value, error) { + return ContainsFunc.Call([]cty.Value{list, value}) +} + +// Index finds the element index for a given value in a list. +func Index(list, value cty.Value) (cty.Value, error) { + return IndexFunc.Call([]cty.Value{list, value}) +} diff --git a/lang/funcs/collection_test.go b/lang/funcs/collection_test.go index c963a2bef7..2f786453a4 100644 --- a/lang/funcs/collection_test.go +++ b/lang/funcs/collection_test.go @@ -351,7 +351,7 @@ func TestCompact(t *testing.T) { }), false, }, - { // errrors on list of lists + { // errors on list of lists cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("test"), @@ -384,3 +384,184 @@ func TestCompact(t *testing.T) { }) } } + +func TestContains(t *testing.T) { + listOfStrings := cty.ListVal([]cty.Value{ + cty.StringVal("the"), + cty.StringVal("quick"), + cty.StringVal("brown"), + cty.StringVal("fox"), + }) + listOfInts := cty.ListVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(3), + cty.NumberIntVal(4), + }) + + tests := []struct { + List cty.Value + Value cty.Value + Want cty.Value + Err bool + }{ + { + listOfStrings, + cty.StringVal("the"), + cty.BoolVal(true), + false, + }, + { + listOfStrings, + cty.StringVal("penguin"), + cty.BoolVal(false), + false, + }, + { + listOfInts, + cty.NumberIntVal(1), + cty.BoolVal(true), + false, + }, + { + listOfInts, + cty.NumberIntVal(42), + cty.BoolVal(false), + false, + }, + { // And now we mix and match + listOfInts, + cty.StringVal("1"), + cty.BoolVal(false), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("contains(%#v, %#v)", test.List, test.Value), func(t *testing.T) { + got, err := Contains(test.List, test.Value) + + if test.Err { + if err == nil { + t.Fatal("succeeded; want error") + } + return + } else if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if got != test.Want { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) + } + }) + } +} + +func TestIndex(t *testing.T) { + tests := []struct { + List cty.Value + Value cty.Value + Want cty.Value + Err bool + }{ + { + cty.ListVal([]cty.Value{ + cty.StringVal("a"), + cty.StringVal("b"), + cty.StringVal("c"), + }), + cty.StringVal("a"), + cty.NumberIntVal(0), + false, + }, + { + cty.ListVal([]cty.Value{ + cty.StringVal("a"), + cty.StringVal("b"), + cty.StringVal("c"), + }), + cty.StringVal("b"), + cty.NumberIntVal(1), + false, + }, + { + cty.ListVal([]cty.Value{ + cty.StringVal("a"), + cty.StringVal("b"), + cty.StringVal("c"), + }), + cty.StringVal("z"), + cty.NilVal, + true, + }, + { + cty.ListVal([]cty.Value{ + cty.StringVal("1"), + cty.StringVal("2"), + cty.StringVal("3"), + }), + cty.NumberIntVal(1), + cty.NumberIntVal(0), + true, + }, + { + cty.ListVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(3), + }), + cty.NumberIntVal(2), + cty.NumberIntVal(1), + false, + }, + { + cty.ListVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(3), + }), + cty.NumberIntVal(4), + cty.NilVal, + true, + }, + { + cty.ListVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(3), + }), + cty.StringVal("1"), + cty.NumberIntVal(0), + true, + }, + { + cty.TupleVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(3), + }), + cty.NumberIntVal(1), + cty.NumberIntVal(0), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("index(%#v, %#v)", test.List, test.Value), func(t *testing.T) { + got, err := Index(test.List, test.Value) + + if test.Err { + if err == nil { + t.Fatal("succeeded; want error") + } + return + } else if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !got.RawEquals(test.Want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) + } + }) + } +} diff --git a/lang/functions.go b/lang/functions.go index cc653e86c2..158a6c8d1a 100644 --- a/lang/functions.go +++ b/lang/functions.go @@ -46,7 +46,7 @@ func (s *Scope) Functions() map[string]function.Function { "coalescelist": funcs.CoalesceListFunc, "compact": funcs.CompactFunc, "concat": stdlib.ConcatFunc, - "contains": unimplFunc, // TODO + "contains": funcs.ContainsFunc, "csvdecode": stdlib.CSVDecodeFunc, "dirname": funcs.DirnameFunc, "distinct": unimplFunc, // TODO @@ -60,7 +60,7 @@ func (s *Scope) Functions() map[string]function.Function { "format": stdlib.FormatFunc, "formatlist": stdlib.FormatListFunc, "indent": funcs.IndentFunc, - "index": unimplFunc, // TODO + "index": funcs.IndexFunc, "join": funcs.JoinFunc, "jsondecode": stdlib.JSONDecodeFunc, "jsonencode": stdlib.JSONEncodeFunc,