Skip to content

Commit

Permalink
lang/funcs: Port the "reverse" function from the old functions set
Browse files Browse the repository at this point in the history
This has the same functionality as the "reverse" function that was
implemented in the "config" package, but adapted to the new language type
system.
  • Loading branch information
apparentlymart committed Mar 20, 2019
1 parent 238efff commit 5922f5a
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 0 deletions.
49 changes: 49 additions & 0 deletions lang/funcs/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,49 @@ var MergeFunc = function.New(&function.Spec{
},
})

// ReverseFunc takes a sequence and produces a new sequence of the same length
// with all of the same elements as the given sequence but in reverse order.
var ReverseFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.DynamicPseudoType,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
argTy := args[0].Type()
switch {
case argTy.IsTupleType():
argTys := argTy.TupleElementTypes()
retTys := make([]cty.Type, len(argTys))
for i, ty := range argTys {
retTys[len(retTys)-i-1] = ty
}
return cty.Tuple(retTys), nil
case argTy.IsListType(), argTy.IsSetType(): // We accept sets here to mimic the usual behavior of auto-converting to list
return cty.List(argTy.ElementType()), nil
default:
return cty.NilType, function.NewArgErrorf(0, "can only reverse list or tuple values, not %s", argTy.FriendlyName())
}
},
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
in := args[0].AsValueSlice()
outVals := make([]cty.Value, len(in))
for i, v := range in {
outVals[len(outVals)-i-1] = v
}
switch {
case retType.IsTupleType():
return cty.TupleVal(outVals), nil
default:
if len(outVals) == 0 {
return cty.ListValEmpty(retType.ElementType()), nil
}
return cty.ListVal(outVals), nil
}
},
})

// SetProductFunc calculates the cartesian product of two or more sets or
// sequences. If the arguments are all lists then the result is a list of tuples,
// preserving the ordering of all of the input lists. Otherwise the result is a
Expand Down Expand Up @@ -1292,6 +1335,12 @@ func Merge(maps ...cty.Value) (cty.Value, error) {
return MergeFunc.Call(maps)
}

// Reverse takes a sequence and produces a new sequence of the same length
// with all of the same elements as the given sequence but in reverse order.
func Reverse(list cty.Value) (cty.Value, error) {
return ReverseFunc.Call([]cty.Value{list})
}

// SetProduct computes the cartesian product of sets or sequences.
func SetProduct(sets ...cty.Value) (cty.Value, error) {
return SetProductFunc.Call(sets)
Expand Down
122 changes: 122 additions & 0 deletions lang/funcs/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,128 @@ func TestMerge(t *testing.T) {
}
}

func TestReverse(t *testing.T) {
tests := []struct {
List cty.Value
Want cty.Value
Err string
}{
{
cty.ListValEmpty(cty.String),
cty.ListValEmpty(cty.String),
"",
},
{
cty.ListVal([]cty.Value{cty.StringVal("a")}),
cty.ListVal([]cty.Value{cty.StringVal("a")}),
"",
},
{
cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
cty.ListVal([]cty.Value{cty.StringVal("b"), cty.StringVal("a")}),
"",
},
{
cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c")}),
cty.ListVal([]cty.Value{cty.StringVal("c"), cty.StringVal("b"), cty.StringVal("a")}),
"",
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String), cty.StringVal("b"), cty.StringVal("c")}),
cty.ListVal([]cty.Value{cty.StringVal("c"), cty.StringVal("b"), cty.UnknownVal(cty.String)}),
"",
},
{
cty.EmptyTupleVal,
cty.EmptyTupleVal,
"",
},
{
cty.TupleVal([]cty.Value{cty.StringVal("a")}),
cty.TupleVal([]cty.Value{cty.StringVal("a")}),
"",
},
{
cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.True}),
cty.TupleVal([]cty.Value{cty.True, cty.StringVal("a")}),
"",
},
{
cty.TupleVal([]cty.Value{cty.StringVal("a"), cty.True, cty.Zero}),
cty.TupleVal([]cty.Value{cty.Zero, cty.True, cty.StringVal("a")}),
"",
},
{
cty.SetValEmpty(cty.String),
cty.ListValEmpty(cty.String),
"",
},
{
cty.SetVal([]cty.Value{cty.StringVal("a")}),
cty.ListVal([]cty.Value{cty.StringVal("a")}),
"",
},
{
cty.SetVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}),
"",
},
{
cty.SetVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c")}),
cty.ListVal([]cty.Value{cty.StringVal("a"), cty.StringVal("c"), cty.StringVal("b")}),
"",
},
{
cty.StringVal("no"),
cty.NilVal,
"can only reverse list or tuple values, not string",
},
{
cty.True,
cty.NilVal,
"can only reverse list or tuple values, not bool",
},
{
cty.MapValEmpty(cty.String),
cty.NilVal,
"can only reverse list or tuple values, not map of string",
},
{
cty.NullVal(cty.List(cty.String)),
cty.NilVal,
"argument must not be null",
},
{
cty.UnknownVal(cty.List(cty.String)),
cty.UnknownVal(cty.List(cty.String)),
"",
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("reverse(%#v)", test.List), func(t *testing.T) {
got, err := Reverse(test.List)

if test.Err != "" {
if err == nil {
t.Fatal("succeeded; want error")
}
if got, want := err.Error(), test.Err; got != want {
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
}
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 TestSetProduct(t *testing.T) {
tests := []struct {
Sets []cty.Value
Expand Down
1 change: 1 addition & 0 deletions lang/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (s *Scope) Functions() map[string]function.Function {
"pathexpand": funcs.PathExpandFunc,
"pow": funcs.PowFunc,
"replace": funcs.ReplaceFunc,
"reverse": funcs.ReverseFunc,
"rsadecrypt": funcs.RsaDecryptFunc,
"sethaselement": stdlib.SetHasElementFunc,
"setintersection": stdlib.SetIntersectionFunc,
Expand Down
27 changes: 27 additions & 0 deletions website/docs/configuration/functions/reverse.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
layout: "functions"
page_title: "reverse - Functions - Configuration Language"
sidebar_current: "docs-funcs-collection-reverse"
description: |-
The reverse function reverses a sequence.
---

# `reverse` Function

-> **Note:** This page is about Terraform 0.12 and later. For Terraform 0.11 and
earlier, see
[0.11 Configuration Language: Interpolation Syntax](../../configuration-0-11/interpolation.html).

`reverse` takes a sequence and produces a new sequence of the same length
with all of the same elements as the given sequence but in reverse order.

## Examples

```
> reverse([1, 2, 3])
[
3,
2,
1,
]
```
4 changes: 4 additions & 0 deletions website/layouts/functions.erb
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@
<a href="/docs/configuration/functions/merge.html">merge</a>
</li>

<li<%= sidebar_current("docs-funcs-collection-reverse") %>>
<a href="/docs/configuration/functions/reverse.html">reverse</a>
</li>

<li<%= sidebar_current("docs-funcs-collection-sethaselement") %>>
<a href="/docs/configuration/functions/sethaselement.html">sethaselement</a>
</li>
Expand Down

0 comments on commit 5922f5a

Please sign in to comment.