From 602b59cdc40c6fb0fd70817e2586cdc2db58b907 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Wed, 23 May 2018 13:57:37 -0700 Subject: [PATCH] porting functions --- lang/funcs/number.go | 122 ++++++++++ lang/funcs/number_test.go | 215 +++++++++++++++++- lang/funcs/string.go | 21 ++ lang/funcs/string_test.go | 63 +++++ lang/functions.go | 8 +- .../docs/configuration/functions/log.html.md | 5 +- 6 files changed, 426 insertions(+), 8 deletions(-) diff --git a/lang/funcs/number.go b/lang/funcs/number.go index 930b8ed48..15cfe7182 100644 --- a/lang/funcs/number.go +++ b/lang/funcs/number.go @@ -27,7 +27,129 @@ var CeilFunc = function.New(&function.Spec{ }, }) +// FloorFunc contructs a function that returns the closest whole number lesser +// than or equal to the given value. +var FloorFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "num", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.Number), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var val float64 + if err := gocty.FromCtyValue(args[0], &val); err != nil { + return cty.UnknownVal(cty.String), err + } + return cty.NumberIntVal(int64(math.Floor(val))), nil + }, +}) + +// LogFunc contructs a function that returns the logarithm of a given number in a given base. +var LogFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "num", + Type: cty.Number, + }, + { + Name: "base", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.Number), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var num float64 + if err := gocty.FromCtyValue(args[0], &num); err != nil { + return cty.UnknownVal(cty.String), err + } + + var base float64 + if err := gocty.FromCtyValue(args[1], &base); err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.NumberFloatVal(math.Log(num) / math.Log(base)), nil + }, +}) + +// PowFunc contructs a function that returns the logarithm of a given number in a given base. +var PowFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "num", + Type: cty.Number, + }, + { + Name: "power", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.Number), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var num float64 + if err := gocty.FromCtyValue(args[0], &num); err != nil { + return cty.UnknownVal(cty.String), err + } + + var power float64 + if err := gocty.FromCtyValue(args[1], &power); err != nil { + return cty.UnknownVal(cty.String), err + } + + return cty.NumberFloatVal(math.Pow(num, power)), nil + }, +}) + +// SignumFunc contructs a function that returns the closest whole number greater +// than or equal to the given value. +var SignumFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "num", + Type: cty.Number, + }, + }, + Type: function.StaticReturnType(cty.Number), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + var num int + if err := gocty.FromCtyValue(args[0], &num); err != nil { + return cty.UnknownVal(cty.String), err + } + switch { + case num < 0: + return cty.NumberIntVal(-1), nil + case num > 0: + return cty.NumberIntVal(+1), nil + default: + return cty.NumberIntVal(0), nil + } + }, +}) + // Ceil returns the closest whole number greater than or equal to the given value. func Ceil(num cty.Value) (cty.Value, error) { return CeilFunc.Call([]cty.Value{num}) } + +// Floor returns the closest whole number lesser than or equal to the given value. +func Floor(num cty.Value) (cty.Value, error) { + return FloorFunc.Call([]cty.Value{num}) +} + +// Log returns returns the logarithm of a given number in a given base. +func Log(num, base cty.Value) (cty.Value, error) { + return LogFunc.Call([]cty.Value{num, base}) +} + +// Pow returns the logarithm of a given number in a given base. +func Pow(num, power cty.Value) (cty.Value, error) { + return PowFunc.Call([]cty.Value{num, power}) +} + +// Signum determines the sign of a number, returning a number between -1 and +// 1 to represent the sign. +func Signum(num cty.Value) (cty.Value, error) { + return SignumFunc.Call([]cty.Value{num}) +} diff --git a/lang/funcs/number_test.go b/lang/funcs/number_test.go index 30896c2bb..25b91e7e4 100644 --- a/lang/funcs/number_test.go +++ b/lang/funcs/number_test.go @@ -26,7 +26,7 @@ func TestCeil(t *testing.T) { } for _, test := range tests { - t.Run(fmt.Sprintf("Ceil(%#v)", test.Num), func(t *testing.T) { + t.Run(fmt.Sprintf("ceil(%#v)", test.Num), func(t *testing.T) { got, err := Ceil(test.Num) if test.Err { @@ -44,3 +44,216 @@ func TestCeil(t *testing.T) { }) } } + +func TestFloor(t *testing.T) { + tests := []struct { + Num cty.Value + Want cty.Value + Err bool + }{ + { + cty.NumberFloatVal(-1.8), + cty.NumberFloatVal(-2), + false, + }, + { + cty.NumberFloatVal(1.2), + cty.NumberFloatVal(1), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("floor(%#v)", test.Num), func(t *testing.T) { + got, err := Floor(test.Num) + + 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 TestLog(t *testing.T) { + tests := []struct { + Num cty.Value + Base cty.Value + Want cty.Value + Err bool + }{ + { + cty.NumberFloatVal(1), + cty.NumberFloatVal(10), + cty.NumberFloatVal(0), + false, + }, + { + cty.NumberFloatVal(10), + cty.NumberFloatVal(10), + cty.NumberFloatVal(1), + false, + }, + + { + cty.NumberFloatVal(0), + cty.NumberFloatVal(10), + cty.NegativeInfinity, + false, + }, + { + cty.NumberFloatVal(10), + cty.NumberFloatVal(0), + cty.NumberFloatVal(-0), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("log(%#v, %#v)", test.Num, test.Base), func(t *testing.T) { + got, err := Log(test.Num, test.Base) + + 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 TestPow(t *testing.T) { + tests := []struct { + Num cty.Value + Power cty.Value + Want cty.Value + Err bool + }{ + { + cty.NumberFloatVal(1), + cty.NumberFloatVal(0), + cty.NumberFloatVal(1), + false, + }, + { + cty.NumberFloatVal(1), + cty.NumberFloatVal(1), + cty.NumberFloatVal(1), + false, + }, + + { + cty.NumberFloatVal(2), + cty.NumberFloatVal(0), + cty.NumberFloatVal(1), + false, + }, + { + cty.NumberFloatVal(2), + cty.NumberFloatVal(1), + cty.NumberFloatVal(2), + false, + }, + { + cty.NumberFloatVal(3), + cty.NumberFloatVal(2), + cty.NumberFloatVal(9), + false, + }, + { + cty.NumberFloatVal(-3), + cty.NumberFloatVal(2), + cty.NumberFloatVal(9), + false, + }, + { + cty.NumberFloatVal(2), + cty.NumberFloatVal(-2), + cty.NumberFloatVal(0.25), + false, + }, + { + cty.NumberFloatVal(0), + cty.NumberFloatVal(2), + cty.NumberFloatVal(0), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("pow(%#v, %#v)", test.Num, test.Power), func(t *testing.T) { + got, err := Pow(test.Num, test.Power) + + 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 TestSignum(t *testing.T) { + tests := []struct { + Num cty.Value + Want cty.Value + Err bool + }{ + { + cty.NumberFloatVal(0), + cty.NumberFloatVal(0), + false, + }, + { + cty.NumberFloatVal(12), + cty.NumberFloatVal(1), + false, + }, + { + cty.NumberFloatVal(-29), + cty.NumberFloatVal(-1), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("signum(%#v)", test.Num), func(t *testing.T) { + got, err := Signum(test.Num) + + 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) + } + }) + } +} diff --git a/lang/funcs/string.go b/lang/funcs/string.go index e7653ed7a..d4971580a 100644 --- a/lang/funcs/string.go +++ b/lang/funcs/string.go @@ -2,6 +2,7 @@ package funcs import ( "fmt" + "regexp" "sort" "strings" @@ -110,6 +111,21 @@ var SplitFunc = function.New(&function.Spec{ }, }) +// ChompFunc constructions a function that removes newline characters at the end of a string. +var ChompFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) + return cty.StringVal(newlines.ReplaceAllString(args[0].AsString(), "")), nil + }, +}) + // Join concatenates together the string elements of one or more lists with a // given separator. func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) { @@ -130,3 +146,8 @@ func Sort(list cty.Value) (cty.Value, error) { func Split(sep, str cty.Value) (cty.Value, error) { return SplitFunc.Call([]cty.Value{sep, str}) } + +// Chomp removes newline characters at the end of a string. +func Chomp(str cty.Value) (cty.Value, error) { + return ChompFunc.Call([]cty.Value{str}) +} diff --git a/lang/funcs/string_test.go b/lang/funcs/string_test.go index 6c27a8d2e..1696738a1 100644 --- a/lang/funcs/string_test.go +++ b/lang/funcs/string_test.go @@ -244,3 +244,66 @@ func TestSplit(t *testing.T) { }) } } + +func TestChomp(t *testing.T) { + tests := []struct { + String cty.Value + Want cty.Value + Err bool + }{ + { + cty.StringVal("hello world"), + cty.StringVal("hello world"), + false, + }, + { + cty.StringVal("goodbye\ncruel\nworld"), + cty.StringVal("goodbye\ncruel\nworld"), + false, + }, + { + cty.StringVal("goodbye\r\nwindows\r\nworld"), + cty.StringVal("goodbye\r\nwindows\r\nworld"), + false, + }, + { + cty.StringVal("goodbye\ncruel\nworld\n"), + cty.StringVal("goodbye\ncruel\nworld"), + false, + }, + { + cty.StringVal("goodbye\ncruel\nworld\n\n\n\n"), + cty.StringVal("goodbye\ncruel\nworld"), + false, + }, + { + cty.StringVal("goodbye\r\nwindows\r\nworld\r\n"), + cty.StringVal("goodbye\r\nwindows\r\nworld"), + false, + }, + { + cty.StringVal("goodbye\r\nwindows\r\nworld\r\n\r\n\r\n\r\n"), + cty.StringVal("goodbye\r\nwindows\r\nworld"), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("chomp(%#v)", test.String), func(t *testing.T) { + got, err := Chomp(test.String) + + 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) + } + }) + } +} diff --git a/lang/functions.go b/lang/functions.go index 93e2bdc3b..d123fc56a 100644 --- a/lang/functions.go +++ b/lang/functions.go @@ -38,7 +38,7 @@ func (s *Scope) Functions() map[string]function.Function { "base64sha512": funcs.Base64Sha512Func, "bcrypt": funcs.BcryptFunc, "ceil": funcs.CeilFunc, - "chomp": unimplFunc, // TODO + "chomp": funcs.ChompFunc, "cidrhost": unimplFunc, // TODO "cidrnetmask": unimplFunc, // TODO "cidrsubnet": unimplFunc, // TODO @@ -56,7 +56,7 @@ func (s *Scope) Functions() map[string]function.Function { "filebase64": funcs.MakeFileFunc(s.BaseDir, true), "matchkeys": unimplFunc, // TODO "flatten": unimplFunc, // TODO - "floor": unimplFunc, // TODO + "floor": funcs.FloorFunc, "format": stdlib.FormatFunc, "formatlist": stdlib.FormatListFunc, "indent": unimplFunc, // TODO @@ -67,7 +67,7 @@ func (s *Scope) Functions() map[string]function.Function { "keys": unimplFunc, // TODO "length": funcs.LengthFunc, "list": unimplFunc, // TODO - "log": unimplFunc, // TODO + "log": funcs.LogFunc, "lookup": unimplFunc, // TODO "lower": stdlib.LowerFunc, "map": unimplFunc, // TODO @@ -76,7 +76,7 @@ func (s *Scope) Functions() map[string]function.Function { "merge": unimplFunc, // TODO "min": stdlib.MinFunc, "pathexpand": funcs.PathExpandFunc, - "pow": unimplFunc, // TODO + "pow": funcs.PowFunc, "replace": unimplFunc, // TODO "rsadecrypt": funcs.RsaDecryptFunc, "sha1": funcs.Sha1Func, diff --git a/website/docs/configuration/functions/log.html.md b/website/docs/configuration/functions/log.html.md index 3869b5fe6..a3b02cd5c 100644 --- a/website/docs/configuration/functions/log.html.md +++ b/website/docs/configuration/functions/log.html.md @@ -3,13 +3,12 @@ layout: "functions" page_title: "log function" sidebar_current: "docs-funcs-numeric-log" description: |- - The log function returns the closest whole number less than or equal to - the given value. + The log function returns the logarithm of a given number in a given base. --- # `log` Function -`log` returns the the logarithm of a given number in a given base. +`log` returns the logarithm of a given number in a given base. ```hcl log(number, base)