From 90ca86ec9c3db804ac32ef942fa04a0aa8c133e7 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Sat, 14 Oct 2017 13:51:01 -0400 Subject: Adding math functions Signed-off-by: Dave Henderson --- docs/content/functions/math.md | 167 +++++++++++++++++++++++++++++++++++++++++ funcs.go | 1 + funcs/math.go | 72 ++++++++++++++++++ funcs/math_test.go | 61 +++++++++++++++ math/math.go | 19 +++++ math/math_test.go | 12 +++ test/integration/math.bats | 39 ++++++++++ 7 files changed, 371 insertions(+) create mode 100644 docs/content/functions/math.md create mode 100644 funcs/math.go create mode 100644 funcs/math_test.go create mode 100644 math/math.go create mode 100644 math/math_test.go create mode 100644 test/integration/math.bats diff --git a/docs/content/functions/math.md b/docs/content/functions/math.md new file mode 100644 index 00000000..7b9d01f5 --- /dev/null +++ b/docs/content/functions/math.md @@ -0,0 +1,167 @@ +--- +title: math functions +menu: + main: + parent: functions +--- + +A set of basic math functions to be able to perform simple arithmetic operations with `gomplate`. + +### Supported input + +_**Note:** currently, `gomplate` supports only integer arithmetic. All functions +return 64-bit integers (`int64` type). Floating point support will be added in +later releases._ + +In general, any input will be converted to the correct input type by the various +functions in this package. For integer-based functions, floating-point inputs will +be truncated (not rounded). + +In addition to regular base-10 numbers, integers can be +[specified](https://golang.org/ref/spec#Integer_literals) as octal (prefix with +`0`) or hexadecimal (prefix with `0x`). + +Decimal/floating-point numbers can be [specified](https://golang.org/ref/spec#Floating-point_literals) +with optional exponents. + +Some examples demonstrating this: + +```console +$ NUM=50 gomplate -i '{{ div (getenv "NUM") 10 }}' +5 +$ gomplate -i '{{ add "0x2" "02" "2.0" "2e0" }}' +8 +$ gomplate -i '{{ add 2.5 2.5 }}' # decimals are truncated! +4 +``` + +## `math.Add` + +**Alias:** `add` + +Adds all given operators. + +### Usage +```go +math.Add n... +``` +```go +x | math.Add.Add n... +``` + +### Example + +```console +$ gomplate -i '{{ math.Add 1 2 3 4 }} +10 +``` + +## `math.Sub` + +**Alias:** `sub` + +Subtract the second from the first of the given operators. + +### Usage +```go +math.Sub a b +``` +```go +b | math.Sub a +``` + +### Example + +```console +$ gomplate -i '{{ math.Sub 3 1 }}' +2 +``` + +## `math.Mul` + +**Alias:** `mul` + +Multiply all given operators together. + +### Usage +```go +math.Mul n... +``` +```go +x | math.Mul n... +``` + +### Example + +```console +$ gomplate -i '{{ math.Mul 8 8 2 }}' +128 +``` + +## `math.Div` + +**Alias:** `div` + +Divide the first number by the second. Division by zero is disallowed. + +### Usage +```go +math.Div a b +``` +```go +b | math.Div a +``` + +### Example + +```console +$ gomplate -i '{{ math.Div 8 2 }}' +4 +``` + +## `math.Rem` + +**Alias:** `rem` + +Return the remainder from an integer division operation. + +### Usage +```go +math.Rem a b +``` +```go +b | math.Rem b +``` + +### Example + +```console +$ gomplate -i '{{ math.Rem 5 3 }}' +2 +$ gomplate -i '{{ math.Rem -5 3 }}' +-2 +``` + +## `math.Pow` + +**Alias:** `pow` + +Calculate an exponent - _bn_. This wraps Go's [`math.Pow`](https://golang.org/pkg/math/#Pow). + +### Usage +```go +math.Pow b n +``` +```go +n | math.Pow b +``` + +### Example + +```console +$ gomplate -i '{{ math.Pow 10 2 }}' +100 +$ gomplate -i '{{ math.Pow 2 32 }}' +4294967296 +``` + diff --git a/funcs.go b/funcs.go index 28bfc907..00967511 100644 --- a/funcs.go +++ b/funcs.go @@ -19,5 +19,6 @@ func initFuncs(d *data.Data) template.FuncMap { funcs.AddEnvFuncs(f) funcs.AddConvFuncs(f) funcs.AddTimeFuncs(f) + funcs.AddMathFuncs(f) return f } diff --git a/funcs/math.go b/funcs/math.go new file mode 100644 index 00000000..e26e42dc --- /dev/null +++ b/funcs/math.go @@ -0,0 +1,72 @@ +package funcs + +import ( + "fmt" + gmath "math" + "sync" + + "github.com/hairyhenderson/gomplate/conv" + + "github.com/hairyhenderson/gomplate/math" +) + +var ( + mathNS *MathFuncs + mathNSInit sync.Once +) + +// MathNS - the math namespace +func MathNS() *MathFuncs { + mathNSInit.Do(func() { mathNS = &MathFuncs{} }) + return mathNS +} + +// AddMathFuncs - +func AddMathFuncs(f map[string]interface{}) { + f["math"] = MathNS + + f["add"] = MathNS().Add + f["sub"] = MathNS().Sub + f["mul"] = MathNS().Mul + f["div"] = MathNS().Div + f["rem"] = MathNS().Rem + f["pow"] = MathNS().Pow +} + +// MathFuncs - +type MathFuncs struct{} + +// Add - +func (f *MathFuncs) Add(n ...interface{}) int64 { + return math.AddInt(conv.ToInt64s(n...)...) +} + +// Mul - +func (f *MathFuncs) Mul(n ...interface{}) int64 { + return math.MulInt(conv.ToInt64s(n...)...) +} + +// Sub - +func (f *MathFuncs) Sub(a, b interface{}) int64 { + return conv.ToInt64(a) - conv.ToInt64(b) +} + +// Div - +func (f *MathFuncs) Div(a, b interface{}) (int64, error) { + divisor := conv.ToInt64(a) + dividend := conv.ToInt64(b) + if dividend == 0 { + return 0, fmt.Errorf("Error: division by 0") + } + return divisor / dividend, nil +} + +// Rem - +func (f *MathFuncs) Rem(a, b interface{}) int64 { + return conv.ToInt64(a) % conv.ToInt64(b) +} + +// Pow - +func (f *MathFuncs) Pow(a, b interface{}) int64 { + return conv.ToInt64(gmath.Pow(conv.ToFloat64(a), conv.ToFloat64(b))) +} diff --git a/funcs/math_test.go b/funcs/math_test.go new file mode 100644 index 00000000..cb0a0922 --- /dev/null +++ b/funcs/math_test.go @@ -0,0 +1,61 @@ +package funcs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAdd(t *testing.T) { + m := MathNS() + assert.Equal(t, int64(12), m.Add(1, 1, 2, 3, 5)) + assert.Equal(t, int64(2), m.Add(1, 1)) + assert.Equal(t, int64(1), m.Add(1)) + assert.Equal(t, int64(0), m.Add(-5, 5)) +} + +func TestMul(t *testing.T) { + m := MathNS() + assert.Equal(t, int64(30), m.Mul(1, 1, 2, 3, 5)) + assert.Equal(t, int64(1), m.Mul(1, 1)) + assert.Equal(t, int64(1), m.Mul(1)) + assert.Equal(t, int64(-25), m.Mul("-5", 5)) + assert.Equal(t, int64(28), m.Mul(14, "2")) +} + +func TestSub(t *testing.T) { + m := MathNS() + assert.Equal(t, int64(0), m.Sub(1, 1)) + assert.Equal(t, int64(-10), m.Sub(-5, 5)) + assert.Equal(t, int64(-41), m.Sub(true, "42")) +} + +func mustDiv(a, b interface{}) int64 { + m := MathNS() + r, err := m.Div(a, b) + if err != nil { + return -1 + } + return r +} + +func TestDiv(t *testing.T) { + m := MathNS() + _, err := m.Div(1, 0) + assert.Error(t, err) + assert.Equal(t, int64(1), mustDiv(1, 1)) + assert.Equal(t, int64(-1), mustDiv(-5, 5)) + assert.Equal(t, int64(0), mustDiv(true, "42")) +} + +func TestRem(t *testing.T) { + m := MathNS() + assert.Equal(t, int64(0), m.Rem(1, 1)) + assert.Equal(t, int64(2), m.Rem(5, 3.0)) + // assert.Equal(t, int64(1), m.Mod(true, "42")) +} + +func TestPow(t *testing.T) { + m := MathNS() + assert.Equal(t, int64(4), m.Pow(2, "2")) +} diff --git a/math/math.go b/math/math.go new file mode 100644 index 00000000..9eaef051 --- /dev/null +++ b/math/math.go @@ -0,0 +1,19 @@ +package math + +// AddInt - +func AddInt(n ...int64) int64 { + x := int64(0) + for _, i := range n { + x += i + } + return x +} + +// MulInt - +func MulInt(n ...int64) int64 { + var x int64 = 1 + for _, i := range n { + x *= i + } + return x +} diff --git a/math/math_test.go b/math/math_test.go new file mode 100644 index 00000000..9c4da13a --- /dev/null +++ b/math/math_test.go @@ -0,0 +1,12 @@ +package math + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMath(t *testing.T) { + assert.Equal(t, int64(10), AddInt(1, 2, 3, 4)) + assert.Equal(t, int64(12), MulInt(3, 4, 1)) +} diff --git a/test/integration/math.bats b/test/integration/math.bats new file mode 100644 index 00000000..0756485d --- /dev/null +++ b/test/integration/math.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bats + +load helper + +@test "'math.Add'" { + gomplate -i '{{ math.Add 1 2 3 4 }} {{ add -5 5 }}' + [ "$status" -eq 0 ] + [[ "${output}" == "10 0" ]] +} + +@test "'math.Sub'" { + gomplate -i '{{ math.Sub 10 5 }} {{ sub -5 5 }}' + [ "$status" -eq 0 ] + [[ "${output}" == "5 -10" ]] +} + +@test "'math.Mul'" { + gomplate -i '{{ math.Mul 1 2 3 4 }} {{ mul -5 5 }}' + [ "$status" -eq 0 ] + [[ "${output}" == "24 -25" ]] +} + +@test "'math.Div'" { + gomplate -i '{{ math.Div 5 3 }} {{ div -5 5 }}' + [ "$status" -eq 0 ] + [[ "${output}" == "1 -1" ]] +} + +@test "'math.Rem'" { + gomplate -i '{{ math.Rem 5 3 }} {{ rem 2 2 }}' + [ "$status" -eq 0 ] + [[ "${output}" == "2 0" ]] +} + +@test "'math.Pow'" { + gomplate -i '{{ math.Pow 8 4 }} {{ pow 2 2 }}' + [ "$status" -eq 0 ] + [[ "${output}" == "4096 4" ]] +} \ No newline at end of file -- cgit v1.2.3