Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions lang/funcs/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,53 @@ var MatchkeysFunc = function.New(&function.Spec{
},
})

// SumFunc constructs a function that returns the sum of all
// numbers provided in a list
var SumFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "list",
Type: cty.DynamicPseudoType,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {

if !args[0].CanIterateElements() {
return cty.NilVal, function.NewArgErrorf(0, "cannot sum noniterable")
}

if args[0].LengthInt() == 0 { // Easy path
return cty.NilVal, function.NewArgErrorf(0, "cannot sum an empty list")
}

arg := args[0].AsValueSlice()
ty := args[0].Type()

var i float64
var s float64

if !ty.IsListType() && !ty.IsSetType() && !ty.IsTupleType() {
return cty.NilVal, function.NewArgErrorf(0, fmt.Sprintf("argument must be list, set, or tuple. Received %s", ty.FriendlyName()))
}

if !args[0].IsKnown() {
return cty.UnknownVal(cty.Number), nil
}

for _, v := range arg {

if err := gocty.FromCtyValue(v, &i); err != nil {
return cty.UnknownVal(cty.Number), function.NewArgErrorf(0, "argument must be list, set, or tuple of number values")
} else {
s += i
}
}

return cty.NumberFloatVal(s), nil
},
})

// TransposeFunc constructs 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{
Expand Down Expand Up @@ -570,6 +617,11 @@ func Matchkeys(values, keys, searchset cty.Value) (cty.Value, error) {
return MatchkeysFunc.Call([]cty.Value{values, keys, searchset})
}

// Sum adds numbers in a list, set, or tuple
func Sum(list cty.Value) (cty.Value, error) {
return SumFunc.Call([]cty.Value{list})
}

// 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) {
Expand Down
172 changes: 172 additions & 0 deletions lang/funcs/collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,178 @@ func TestMatchkeys(t *testing.T) {
}
}

func TestSum(t *testing.T) {
tests := []struct {
List cty.Value
Want cty.Value
Err bool
}{
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
cty.NumberIntVal(3),
}),
cty.NumberIntVal(6),
false,
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(1476),
cty.NumberIntVal(2093),
cty.NumberIntVal(2092495),
cty.NumberIntVal(64589234),
cty.NumberIntVal(234),
}),
cty.NumberIntVal(66685532),
false,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.UnknownVal(cty.String),
true,
},
{
cty.ListVal([]cty.Value{
cty.NumberIntVal(10),
cty.NumberIntVal(-19),
cty.NumberIntVal(5),
}),
cty.NumberIntVal(-4),
false,
},
{
cty.ListVal([]cty.Value{
cty.NumberFloatVal(10.2),
cty.NumberFloatVal(19.4),
cty.NumberFloatVal(5.7),
}),
cty.NumberFloatVal(35.3),
false,
},
{
cty.ListVal([]cty.Value{
cty.NumberFloatVal(-10.2),
cty.NumberFloatVal(-19.4),
cty.NumberFloatVal(-5.7),
}),
cty.NumberFloatVal(-35.3),
false,
},
{
cty.ListVal([]cty.Value{cty.NullVal(cty.Number)}),
cty.NilVal,
true,
},
{
cty.SetVal([]cty.Value{
cty.StringVal("a"),
cty.StringVal("b"),
cty.StringVal("c"),
}),
cty.UnknownVal(cty.String),
true,
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(10),
cty.NumberIntVal(-19),
cty.NumberIntVal(5),
}),
cty.NumberIntVal(-4),
false,
},
{
cty.SetVal([]cty.Value{
cty.NumberIntVal(10),
cty.NumberIntVal(25),
cty.NumberIntVal(30),
}),
cty.NumberIntVal(65),
false,
},
{
cty.SetVal([]cty.Value{
cty.NumberFloatVal(2340.8),
cty.NumberFloatVal(10.2),
cty.NumberFloatVal(3),
}),
cty.NumberFloatVal(2354),
false,
},
{
cty.SetVal([]cty.Value{
cty.NumberFloatVal(2),
}),
cty.NumberFloatVal(2),
false,
},
{
cty.SetVal([]cty.Value{
cty.NumberFloatVal(-2),
cty.NumberFloatVal(-50),
cty.NumberFloatVal(-20),
cty.NumberFloatVal(-123),
cty.NumberFloatVal(-4),
}),
cty.NumberFloatVal(-199),
false,
},
{
cty.TupleVal([]cty.Value{
cty.NumberIntVal(12),
cty.StringVal("a"),
cty.NumberIntVal(38),
}),
cty.UnknownVal(cty.String),
true,
},
{
cty.NumberIntVal(12),
cty.NilVal,
true,
},
{
cty.ListValEmpty(cty.Number),
cty.NilVal,
true,
},
{
cty.MapVal(map[string]cty.Value{"hello": cty.True}),
cty.NilVal,
true,
},
{
cty.UnknownVal(cty.Number),
cty.UnknownVal(cty.Number),
false,
},
}

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

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
Expand Down
1 change: 1 addition & 0 deletions lang/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ func (s *Scope) Functions() map[string]function.Function {
"split": stdlib.SplitFunc,
"strrev": stdlib.ReverseFunc,
"substr": stdlib.SubstrFunc,
"sum": funcs.SumFunc,
"timestamp": funcs.TimestampFunc,
"timeadd": stdlib.TimeAddFunc,
"title": stdlib.TitleFunc,
Expand Down
7 changes: 7 additions & 0 deletions lang/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,13 @@ func TestFunctions(t *testing.T) {
},
},

"sum": {
{
`sum([2340.5,10,3])`,
cty.NumberFloatVal(2353.5),
},
},

"templatefile": {
{
`templatefile("hello.tmpl", {name = "Jodie"})`,
Expand Down
24 changes: 24 additions & 0 deletions website/docs/configuration/functions/sum.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
layout: "functions"
page_title: "sum - Functions - Configuration Language"
sidebar_current: "docs-funcs-collection-sum"
description: |-
The sum function takes a list or set of numbers and returns the sum of those
numbers.
---

# `sum` Function

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

`sum` takes a list or set of numbers and returns the sum of those numbers.


## Examples

```
> sum([10, 13, 6, 4.5])
33.5
```
4 changes: 4 additions & 0 deletions website/layouts/functions.erb
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@
<a href="/docs/configuration/functions/sort.html">sort</a>
</li>

<li>
<a href="/docs/configuration/functions/sum.html">sum</a>
</li>

<li>
<a href="/docs/configuration/functions/transpose.html">transpose</a>
</li>
Expand Down