functions: TransposeFunc, SliceFunc
This commit is contained in:
parent
30671d85ad
commit
6463dd90e9
|
@ -2,6 +2,7 @@ package funcs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"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
|
||||
func appendIfMissing(slice []cty.Value, element cty.Value) ([]cty.Value, error) {
|
||||
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) {
|
||||
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,
|
||||
"sha512": funcs.Sha512Func,
|
||||
"signum": funcs.SignumFunc,
|
||||
"slice": unimplFunc, // TODO
|
||||
"slice": funcs.SliceFunc,
|
||||
"sort": funcs.SortFunc,
|
||||
"split": funcs.SplitFunc,
|
||||
"substr": stdlib.SubstrFunc,
|
||||
"timestamp": funcs.TimestampFunc,
|
||||
"timeadd": funcs.TimeAddFunc,
|
||||
"title": funcs.TitleFunc,
|
||||
"transpose": unimplFunc, // TODO
|
||||
"transpose": funcs.TransposeFunc,
|
||||
"trimspace": funcs.TrimSpaceFunc,
|
||||
"upper": stdlib.UpperFunc,
|
||||
"urlencode": funcs.URLEncodeFunc,
|
||||
|
|
Loading…
Reference in New Issue