functions: TransposeFunc, SliceFunc
This commit is contained in:
parent
30671d85ad
commit
6463dd90e9
|
@ -2,6 +2,7 @@ package funcs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
|
@ -666,6 +667,112 @@ var MergeFunc = function.New(&function.Spec{
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// SliceFunc contructs a function that extracts some consecutive elements
|
||||||
|
// from within a list.
|
||||||
|
var SliceFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "list",
|
||||||
|
Type: cty.List(cty.DynamicPseudoType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "startIndex",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "endIndex",
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.List(cty.DynamicPseudoType)),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
inputList := args[0]
|
||||||
|
var startIndex, endIndex int
|
||||||
|
|
||||||
|
if err = gocty.FromCtyValue(args[1], &startIndex); err != nil {
|
||||||
|
return cty.NilVal, fmt.Errorf("invalid start index: %s", err)
|
||||||
|
}
|
||||||
|
if err = gocty.FromCtyValue(args[2], &endIndex); err != nil {
|
||||||
|
return cty.NilVal, fmt.Errorf("invalid start index: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if startIndex < 0 {
|
||||||
|
return cty.NilVal, fmt.Errorf("from index must be >= 0")
|
||||||
|
}
|
||||||
|
if endIndex > inputList.LengthInt() {
|
||||||
|
return cty.NilVal, fmt.Errorf("to index must be <= length of the input list")
|
||||||
|
}
|
||||||
|
if startIndex > endIndex {
|
||||||
|
return cty.NilVal, fmt.Errorf("from index must be <= to index")
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputList []cty.Value
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for it := inputList.ElementIterator(); it.Next(); {
|
||||||
|
_, v := it.Element()
|
||||||
|
if i >= startIndex && i < endIndex {
|
||||||
|
outputList = append(outputList, v)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outputList) == 0 {
|
||||||
|
return cty.ListValEmpty(cty.DynamicPseudoType), nil
|
||||||
|
}
|
||||||
|
return cty.ListVal(outputList), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// TransposeFunc contructs a function that takes a map of lists of strings and
|
||||||
|
// swaps the keys and values to produce a new map of lists of strings.
|
||||||
|
var TransposeFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{
|
||||||
|
{
|
||||||
|
Name: "values",
|
||||||
|
Type: cty.Map(cty.List(cty.String)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: function.StaticReturnType(cty.Map(cty.List(cty.String))),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||||
|
inputMap := args[0]
|
||||||
|
outputMap := make(map[string]cty.Value)
|
||||||
|
tmpMap := make(map[string][]string)
|
||||||
|
|
||||||
|
for it := inputMap.ElementIterator(); it.Next(); {
|
||||||
|
inKey, inVal := it.Element()
|
||||||
|
if !inVal.Type().IsListType() {
|
||||||
|
return cty.MapValEmpty(cty.List(cty.String)), fmt.Errorf("input must be a map of lists of strings")
|
||||||
|
}
|
||||||
|
for iter := inVal.ElementIterator(); iter.Next(); {
|
||||||
|
_, val := iter.Element()
|
||||||
|
if !val.Type().Equals(cty.String) {
|
||||||
|
return cty.MapValEmpty(cty.List(cty.String)), fmt.Errorf("input must be a map of lists of strings")
|
||||||
|
}
|
||||||
|
|
||||||
|
outKey := val.AsString()
|
||||||
|
if _, ok := tmpMap[outKey]; !ok {
|
||||||
|
tmpMap[outKey] = make([]string, 0)
|
||||||
|
}
|
||||||
|
outVal := tmpMap[outKey]
|
||||||
|
outVal = append(outVal, inKey.AsString())
|
||||||
|
sort.Strings(outVal)
|
||||||
|
tmpMap[outKey] = outVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for outKey, outVal := range tmpMap {
|
||||||
|
values := make([]cty.Value, 0)
|
||||||
|
for _, v := range outVal {
|
||||||
|
values = append(values, cty.StringVal(v))
|
||||||
|
}
|
||||||
|
outputMap[outKey] = cty.ListVal(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.MapVal(outputMap), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// helper function to add an element to a list, if it does not already exist
|
// helper function to add an element to a list, if it does not already exist
|
||||||
func appendIfMissing(slice []cty.Value, element cty.Value) ([]cty.Value, error) {
|
func appendIfMissing(slice []cty.Value, element cty.Value) ([]cty.Value, error) {
|
||||||
for _, ele := range slice {
|
for _, ele := range slice {
|
||||||
|
@ -769,3 +876,14 @@ func Matchkeys(values, keys, searchset cty.Value) (cty.Value, error) {
|
||||||
func Merge(maps ...cty.Value) (cty.Value, error) {
|
func Merge(maps ...cty.Value) (cty.Value, error) {
|
||||||
return MergeFunc.Call(maps)
|
return MergeFunc.Call(maps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Slice extracts some consecutive elements from within a list.
|
||||||
|
func Slice(list, start, end cty.Value) (cty.Value, error) {
|
||||||
|
return SliceFunc.Call([]cty.Value{list, start, end})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transpose takes a map of lists of strings and swaps the keys and values to
|
||||||
|
// produce a new map of lists of strings.
|
||||||
|
func Transpose(values cty.Value) (cty.Value, error) {
|
||||||
|
return TransposeFunc.Call([]cty.Value{values})
|
||||||
|
}
|
||||||
|
|
|
@ -1529,3 +1529,161 @@ func TestMerge(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSlice(t *testing.T) {
|
||||||
|
listOfStrings := cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
})
|
||||||
|
listOfInts := cty.ListVal([]cty.Value{
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
})
|
||||||
|
tests := []struct {
|
||||||
|
List cty.Value
|
||||||
|
StartIndex cty.Value
|
||||||
|
EndIndex cty.Value
|
||||||
|
Want cty.Value
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{ // normal usage
|
||||||
|
listOfStrings,
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("b"),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // normal usage
|
||||||
|
listOfInts,
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
cty.ListVal([]cty.Value{
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // empty result
|
||||||
|
listOfStrings,
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.ListValEmpty(cty.DynamicPseudoType),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // index out of bounds
|
||||||
|
listOfStrings,
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NumberIntVal(4),
|
||||||
|
cty.NilVal,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{ // StartIndex index > EndIndex
|
||||||
|
listOfStrings,
|
||||||
|
cty.NumberIntVal(2),
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
cty.NilVal,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{ // negative StartIndex
|
||||||
|
listOfStrings,
|
||||||
|
cty.NumberIntVal(-1),
|
||||||
|
cty.NumberIntVal(0),
|
||||||
|
cty.NilVal,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("slice(%#v, %#v, %#v)", test.List, test.StartIndex, test.EndIndex), func(t *testing.T) {
|
||||||
|
got, err := Slice(test.List, test.StartIndex, test.EndIndex)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTranspose(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Values cty.Value
|
||||||
|
Want cty.Value
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"key1": cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
}),
|
||||||
|
"key2": cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("a"),
|
||||||
|
cty.StringVal("b"),
|
||||||
|
cty.StringVal("c"),
|
||||||
|
}),
|
||||||
|
"key3": cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("c"),
|
||||||
|
}),
|
||||||
|
"key4": cty.ListValEmpty(cty.String),
|
||||||
|
}),
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"a": cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("key1"),
|
||||||
|
cty.StringVal("key2"),
|
||||||
|
}),
|
||||||
|
"b": cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("key1"),
|
||||||
|
cty.StringVal("key2"),
|
||||||
|
}),
|
||||||
|
"c": cty.ListVal([]cty.Value{
|
||||||
|
cty.StringVal("key2"),
|
||||||
|
cty.StringVal("key3"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{ // bad map - empty value
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"key1": cty.ListValEmpty(cty.String),
|
||||||
|
}),
|
||||||
|
cty.NilVal,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{ // bad map - value not a list
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"key1": cty.StringVal("a"),
|
||||||
|
}),
|
||||||
|
cty.NilVal,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("transpose(%#v)", test.Values), func(t *testing.T) {
|
||||||
|
got, err := Transpose(test.Values)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -83,14 +83,14 @@ func (s *Scope) Functions() map[string]function.Function {
|
||||||
"sha256": funcs.Sha256Func,
|
"sha256": funcs.Sha256Func,
|
||||||
"sha512": funcs.Sha512Func,
|
"sha512": funcs.Sha512Func,
|
||||||
"signum": funcs.SignumFunc,
|
"signum": funcs.SignumFunc,
|
||||||
"slice": unimplFunc, // TODO
|
"slice": funcs.SliceFunc,
|
||||||
"sort": funcs.SortFunc,
|
"sort": funcs.SortFunc,
|
||||||
"split": funcs.SplitFunc,
|
"split": funcs.SplitFunc,
|
||||||
"substr": stdlib.SubstrFunc,
|
"substr": stdlib.SubstrFunc,
|
||||||
"timestamp": funcs.TimestampFunc,
|
"timestamp": funcs.TimestampFunc,
|
||||||
"timeadd": funcs.TimeAddFunc,
|
"timeadd": funcs.TimeAddFunc,
|
||||||
"title": funcs.TitleFunc,
|
"title": funcs.TitleFunc,
|
||||||
"transpose": unimplFunc, // TODO
|
"transpose": funcs.TransposeFunc,
|
||||||
"trimspace": funcs.TrimSpaceFunc,
|
"trimspace": funcs.TrimSpaceFunc,
|
||||||
"upper": stdlib.UpperFunc,
|
"upper": stdlib.UpperFunc,
|
||||||
"urlencode": funcs.URLEncodeFunc,
|
"urlencode": funcs.URLEncodeFunc,
|
||||||
|
|
Loading…
Reference in New Issue