From cbf7b5c5107b147cafbf558078b57740bde8acb8 Mon Sep 17 00:00:00 2001 From: Olivier Bourdon Date: Wed, 4 Dec 2019 17:39:43 +0100 Subject: [PATCH] Add minimum and maximum math functions --- CHANGELOG.md | 6 ++ README.md | 30 ++++++++ template/funcs.go | 142 ++++++++++++++++++++++++++++++++++++++ template/template.go | 2 + template/template_test.go | 22 ++++++ 5 files changed, 202 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1af8c951..cc392e516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.24.0 (Unreleased) + +IMPROVEMENTS: + +* Add minimum and maximum math functions + ## v0.23.0 (Nov 13, 2019) IMPROVEMENTS: diff --git a/README.md b/README.md index b1537d414..c2a957c21 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ this functionality might prove useful. - [multiply](#multiply) - [divide](#divide) - [modulo](#modulo) + - [minimum](#minimum) + - [maximum](#maximum) - [Plugins](#plugins) - [Authoring Plugins](#authoring-plugins) - [Important Notes](#important-notes) @@ -2085,6 +2087,34 @@ This can also be used with a pipe function. Please take careful note of the order of arguments. +##### `minimum` + +Returns the minimum of the two values. + +```liquid +{{ minimum 2 5 }} // 2 +``` + +This can also be used with a pipe function. + +```liquid +{{ 5 | minimum 2 }} // 2 +``` + +##### `maximum` + +Returns the maximum of the two values. + +```liquid +{{ maximum 2 5 }} // 2 +``` + +This can also be used with a pipe function. + +```liquid +{{ 5 | maximum 2 }} // 2 +``` + ## Plugins ### Authoring Plugins diff --git a/template/funcs.go b/template/funcs.go index 987a7d821..dca218e62 100644 --- a/template/funcs.go +++ b/template/funcs.go @@ -1346,6 +1346,148 @@ func modulo(b, a interface{}) (interface{}, error) { } } +// minimum returns the minimum between a and b. +func minimum(b, a interface{}) (interface{}, error) { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + + switch av.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if av.Int() < bv.Int() { + return av.Int(), nil + } + return bv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if av.Int() < int64(bv.Uint()) { + return av.Int(), nil + } + return bv.Uint(), nil + case reflect.Float32, reflect.Float64: + if float64(av.Int()) < bv.Float() { + return av.Int(), nil + } + return bv.Float(), nil + default: + return nil, fmt.Errorf("minimum: unknown type for %q (%T)", bv, b) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if int64(av.Uint()) < bv.Int() { + return av.Uint(), nil + } + return bv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if av.Uint() < bv.Uint() { + return av.Uint(), nil + } + return bv.Uint(), nil + case reflect.Float32, reflect.Float64: + if float64(av.Uint()) < bv.Float() { + return av.Uint(), nil + } + return bv.Float(), nil + default: + return nil, fmt.Errorf("minimum: unknown type for %q (%T)", bv, b) + } + case reflect.Float32, reflect.Float64: + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if av.Float() < float64(bv.Int()) { + return av.Float(), nil + } + return bv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if av.Float() < float64(bv.Uint()) { + return av.Float(), nil + } + return bv.Uint(), nil + case reflect.Float32, reflect.Float64: + if av.Float() < bv.Float() { + return av.Float(), nil + } + return bv.Float(), nil + default: + return nil, fmt.Errorf("minimum: unknown type for %q (%T)", bv, b) + } + default: + return nil, fmt.Errorf("minimum: unknown type for %q (%T)", av, a) + } +} + +// maximum returns the maximum between a and b. +func maximum(b, a interface{}) (interface{}, error) { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + + switch av.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if av.Int() > bv.Int() { + return av.Int(), nil + } + return bv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if av.Int() > int64(bv.Uint()) { + return av.Int(), nil + } + return bv.Uint(), nil + case reflect.Float32, reflect.Float64: + if float64(av.Int()) > bv.Float() { + return av.Int(), nil + } + return bv.Float(), nil + default: + return nil, fmt.Errorf("maximum: unknown type for %q (%T)", bv, b) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if int64(av.Uint()) > bv.Int() { + return av.Uint(), nil + } + return bv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if av.Uint() > bv.Uint() { + return av.Uint(), nil + } + return bv.Uint(), nil + case reflect.Float32, reflect.Float64: + if float64(av.Uint()) > bv.Float() { + return av.Uint(), nil + } + return bv.Float(), nil + default: + return nil, fmt.Errorf("maximum: unknown type for %q (%T)", bv, b) + } + case reflect.Float32, reflect.Float64: + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if av.Float() > float64(bv.Int()) { + return av.Float(), nil + } + return bv.Int(), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if av.Float() > float64(bv.Uint()) { + return av.Float(), nil + } + return bv.Uint(), nil + case reflect.Float32, reflect.Float64: + if av.Float() > bv.Float() { + return av.Float(), nil + } + return bv.Float(), nil + default: + return nil, fmt.Errorf("maximum: unknown type for %q (%T)", bv, b) + } + default: + return nil, fmt.Errorf("maximum: unknown type for %q (%T)", av, a) + } +} + // blacklisted always returns an error, to be used in place of blacklisted template functions func blacklisted(...string) (string, error) { return "", errors.New("function is disabled") diff --git a/template/template.go b/template/template.go index b56a79e2e..62ea3e6c0 100644 --- a/template/template.go +++ b/template/template.go @@ -294,6 +294,8 @@ func funcMap(i *funcMapInput) template.FuncMap { "multiply": multiply, "divide": divide, "modulo": modulo, + "minimum": minimum, + "maximum": maximum, } for _, bf := range i.functionBlacklist { diff --git a/template/template_test.go b/template/template_test.go index 1eaf0bf7f..f4e66eb46 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -1607,6 +1607,28 @@ func TestTemplate_Execute(t *testing.T) { "1", false, }, + { + "math_minimum", + &NewTemplateInput{ + Contents: `{{ 3 | minimum 2 }}`, + }, + &ExecuteInput{ + Brain: NewBrain(), + }, + "2", + false, + }, + { + "math_maximum", + &NewTemplateInput{ + Contents: `{{ 3 | maximum 2 }}`, + }, + &ExecuteInput{ + Brain: NewBrain(), + }, + "3", + false, + }, { "leaf_cert", &NewTemplateInput{