Add some basic math interpolation functions

Support the following math functions for interpolation:

* ceil
* floor
* max
* min

Fixes #7409
This commit is contained in:
Jesse Szwedko 2016-10-28 17:49:31 +00:00
parent 0befa15522
commit 0fbd72a355
3 changed files with 219 additions and 0 deletions

View File

@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"math"
"net"
"regexp"
"sort"
@ -54,6 +55,7 @@ func Funcs() map[string]ast.Function {
"base64decode": interpolationFuncBase64Decode(),
"base64encode": interpolationFuncBase64Encode(),
"base64sha256": interpolationFuncBase64Sha256(),
"ceil": interpolationFuncCeil(),
"cidrhost": interpolationFuncCidrHost(),
"cidrnetmask": interpolationFuncCidrNetmask(),
"cidrsubnet": interpolationFuncCidrSubnet(),
@ -63,6 +65,7 @@ func Funcs() map[string]ast.Function {
"distinct": interpolationFuncDistinct(),
"element": interpolationFuncElement(),
"file": interpolationFuncFile(),
"floor": interpolationFuncFloor(),
"format": interpolationFuncFormat(),
"formatlist": interpolationFuncFormatList(),
"index": interpolationFuncIndex(),
@ -72,8 +75,10 @@ func Funcs() map[string]ast.Function {
"list": interpolationFuncList(),
"lower": interpolationFuncLower(),
"map": interpolationFuncMap(),
"max": interpolationFuncMax(),
"md5": interpolationFuncMd5(),
"merge": interpolationFuncMerge(),
"min": interpolationFuncMin(),
"uuid": interpolationFuncUUID(),
"replace": interpolationFuncReplace(),
"sha1": interpolationFuncSha1(),
@ -387,6 +392,66 @@ func interpolationFuncFormat() ast.Function {
}
}
// interpolationFuncMax returns the maximum of the numeric arguments
func interpolationFuncMax() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeFloat},
ReturnType: ast.TypeFloat,
Variadic: true,
VariadicType: ast.TypeFloat,
Callback: func(args []interface{}) (interface{}, error) {
max := args[0].(float64)
for i := 1; i < len(args); i++ {
max = math.Max(max, args[i].(float64))
}
return max, nil
},
}
}
// interpolationFuncMin returns the minimum of the numeric arguments
func interpolationFuncMin() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeFloat},
ReturnType: ast.TypeFloat,
Variadic: true,
VariadicType: ast.TypeFloat,
Callback: func(args []interface{}) (interface{}, error) {
min := args[0].(float64)
for i := 1; i < len(args); i++ {
min = math.Min(min, args[i].(float64))
}
return min, nil
},
}
}
// interpolationFuncCeil returns the the least integer value greater than or equal to the argument
func interpolationFuncCeil() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeFloat},
ReturnType: ast.TypeInt,
Callback: func(args []interface{}) (interface{}, error) {
return int(math.Ceil(args[0].(float64))), nil
},
}
}
// interpolationFuncFloorreturns returns the greatest integer value less than or equal to the argument
func interpolationFuncFloor() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeFloat},
ReturnType: ast.TypeInt,
Callback: func(args []interface{}) (interface{}, error) {
return int(math.Floor(args[0].(float64))), nil
},
}
}
func interpolationFuncZipMap() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{

View File

@ -222,6 +222,150 @@ func TestInterpolateFuncList(t *testing.T) {
})
}
func TestInterpolateFuncMax(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
{
`${max()}`,
nil,
true,
},
{
`${max("")}`,
nil,
true,
},
{
`${max(-1, 0, 1)}`,
"1",
false,
},
{
`${max(1, 0, -1)}`,
"1",
false,
},
{
`${max(-1, -2)}`,
"-1",
false,
},
{
`${max(-1)}`,
"-1",
false,
},
},
})
}
func TestInterpolateFuncMin(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
{
`${min()}`,
nil,
true,
},
{
`${min("")}`,
nil,
true,
},
{
`${min(-1, 0, 1)}`,
"-1",
false,
},
{
`${min(1, 0, -1)}`,
"-1",
false,
},
{
`${min(-1, -2)}`,
"-2",
false,
},
{
`${min(-1)}`,
"-1",
false,
},
},
})
}
func TestInterpolateFuncFloor(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
{
`${floor()}`,
nil,
true,
},
{
`${floor("")}`,
nil,
true,
},
{
`${floor("-1.3")}`, // there appears to be a AST bug where the parsed argument ends up being -1 without the "s
"-2",
false,
},
{
`${floor(1.7)}`,
"1",
false,
},
},
})
}
func TestInterpolateFuncCeil(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{
{
`${ceil()}`,
nil,
true,
},
{
`${ceil("")}`,
nil,
true,
},
{
`${ceil(-1.8)}`,
"-1",
false,
},
{
`${ceil(1.2)}`,
"2",
false,
},
},
})
}
func TestInterpolateFuncMap(t *testing.T) {
testFunction(t, testFunctionConfig{
Cases: []testFunctionCase{

View File

@ -89,6 +89,9 @@ The supported built-in functions are:
**This is not equivalent** of `base64encode(sha256(string))`
since `sha256()` returns hexadecimal representation.
* `ceil(float)` - Returns the the least integer value greater than or equal
to the argument.
* `cidrhost(iprange, hostnum)` - Takes an IP address range in CIDR notation
and creates an IP address with the given host number. For example,
``cidrhost("10.0.0.0/8", 2)`` returns ``10.0.0.2``.
@ -137,6 +140,9 @@ The supported built-in functions are:
module, you generally want to make the path relative to the module base,
like this: `file("${path.module}/file")`.
* `floor(float)` - Returns the greatest integer value less than or equal to
the argument
* `format(format, args, ...)` - Formats a string according to the given
format. The syntax for the format is standard `sprintf` syntax.
Good documentation for the syntax can be [found here](https://golang.org/pkg/fmt/).
@ -197,11 +203,15 @@ The supported built-in functions are:
* `map("hello", "world")`
* `map("us-east", list("a", "b", "c"), "us-west", list("b", "c", "d"))`
* `max(float1, float2, ...)` - Returns the largest of the floats.
* `merge(map1, map2, ...)` - Returns the union of 2 or more maps. The maps
are consumed in the order provided, and duplicate keys overwrite previous
entries.
* `${merge(map("a", "b"), map("c", "d"))}` returns `{"a": "b", "c": "d"}`
* `min(float1, float2, ...)` - Returns the smallest of the floats.
* `md5(string)` - Returns a (conventional) hexadecimal representation of the
MD5 hash of the given string.