From 9ed8bcad895421cbe67299b6eefeff72c805cf03 Mon Sep 17 00:00:00 2001 From: Oleg Sidorov Date: Sat, 19 Oct 2019 23:19:25 +0200 Subject: [PATCH] Fixing regression for numeric values refs #6708 This commit is a fix for the regression introduced in 2.15 with an attempt to carry all numeric values in json.Number container (see https://github.com/helm/helm/issues/6708 for more details). The fix overloads a series of Helm template functions: * int64 * int * float64 * add1 * add * sub * div * mod * mul * max * min * ceil * floor * round * until * untilStep * splitn * abbrev * abbrevboth * trunc * substr * repeat * randAlphaNum * randAlpha * randAscii * randNumeric * wrap * wrapWith * indent * nindent * plural * slice * eq * gt * ge * lt * le * ne Corresponding test cases are included in the engine test suite. Signed-off-by: Oleg Sidorov --- pkg/engine/engine.go | 2 +- pkg/engine/engine_test.go | 461 ++++++++++++++++++++++++++++++++++++++ pkg/engine/overload.go | 422 ++++++++++++++++++++++++++++++++++ 3 files changed, 884 insertions(+), 1 deletion(-) create mode 100644 pkg/engine/overload.go diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index b4b6475c9..a7ebc9192 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -96,7 +96,7 @@ func FuncMap() template.FuncMap { f[k] = v } - return f + return OverloadJsonNumberFuncs(f) } // Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 712b3b3df..c7a2cddd7 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -17,6 +17,7 @@ limitations under the License. package engine import ( + "encoding/json" "fmt" "sync" "testing" @@ -587,5 +588,465 @@ func TestAlterFuncMap(t *testing.T) { if gotStrTplWithInclude := outTplWithInclude["TplFunction/templates/base"]; gotStrTplWithInclude != expectedTplStrWithInclude { t.Errorf("Expected %q, got %q (%v)", expectedTplStrWithInclude, gotStrTplWithInclude, outTplWithInclude) } +} +func TestOverloadFuncs(t *testing.T) { + tests := []struct { + Name string + Templates []*chart.Template + Values chartutil.Values + ExpectTplStr string + }{ + { + Name: "TplIntFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | int}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplInt64Function", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | int64}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplFloat64Function", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | float64}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("3.14159265359"), + }, + ExpectTplStr: "Evaluate tpl Value: 3.14159265359", + }, + { + Name: "TplAdd1Function", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | add1}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 43", + }, + { + Name: "TplAddFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ add .Values.value 1 2 3}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 48", + }, + { + Name: "TplSubFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ sub .Values.value 20}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 22", + }, + { + Name: "TplDivFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ div .Values.value 2}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 21", + }, + { + Name: "TplModFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ mod .Values.value 5}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 2", + }, + { + Name: "TplMulFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ mul .Values.value 1 2 3}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 252", + }, + { + Name: "TplMaxFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ max .Values.value 100 1 0 -1}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 100", + }, + { + Name: "TplMinFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ min .Values.value 100 1 0 -1}}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: -1", + }, + { + Name: "TplCeilFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ ceil .Values.value }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("3.14159265359"), + }, + ExpectTplStr: "Evaluate tpl Value: 4", + }, + { + Name: "TplFloorFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ floor .Values.value }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("3.14159265359"), + }, + ExpectTplStr: "Evaluate tpl Value: 3", + }, + { + Name: "TplRoundFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ round .Values.value 2 }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("3.14159265359"), + }, + ExpectTplStr: "Evaluate tpl Value: 3.14", + }, + { + Name: "TplEqFunctionInt", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if eq .Values.value 42 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplEqFunctionFloat", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if eq .Values.value 42.0 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplGtFunctionInt", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if gt .Values.value 41 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplGtFunctionFloat", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if gt .Values.value 41.0 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplGeFunctionInt", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if ge .Values.value 42 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplGeFunctionFloat", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if ge .Values.value 42.0 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplLtFunctionInt", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if lt .Values.value 43 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplLtFunctionFloat", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if lt .Values.value 43.0 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplLeFunctionInt", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if le .Values.value 42 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplLeFunctionFloat", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if le .Values.value 42.0 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplNeFunctionInt", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if ne .Values.value 43 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplNeFunctionFloat", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ if ne .Values.value 43.0 }}Value: {{ .Values.value }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl Value: 42", + }, + { + Name: "TplUntilFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ range $ix := until .Values.value }}{{ $ix }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("5"), + }, + ExpectTplStr: "Evaluate tpl 01234", + }, + { + Name: "TplUntilStepFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ range $ix := untilStep 0 .Values.value 7 }}{{ $ix }} {{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl 0 7 14 21 28 35 ", + }, + { + Name: "TplSplitnFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ range $s := splitn \".\" .Values.value \"foo.bar.baz.boo\" }}{{ $s }}{{ end }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("3"), + }, + ExpectTplStr: "Evaluate tpl foobarbaz.boo", + }, + { + Name: "TplAbbrevFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ abbrev .Values.value \"hello world\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("5"), + }, + ExpectTplStr: "Evaluate tpl he...", + }, + { + Name: "TplAbbrevBothFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ abbrevboth .Values.value 10 \"1234 5678 9123\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("5"), + }, + ExpectTplStr: "Evaluate tpl ...5678...", + }, + { + Name: "TplTruncFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ trunc .Values.value \"hello world\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("5"), + }, + ExpectTplStr: "Evaluate tpl hello", + }, + { + Name: "TplSubstrFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ substr 0 .Values.value \"hello world\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("5"), + }, + ExpectTplStr: "Evaluate tpl hello", + }, + { + Name: "TplRepeatFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ repeat .Values.value \"hello\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("3"), + }, + ExpectTplStr: "Evaluate tpl hellohellohello", + }, + { + Name: "TplWrapFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ wrap .Values.value \"hello world\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("5"), + }, + ExpectTplStr: "Evaluate tpl hello\nworld", + }, + { + Name: "TplWrapWithFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ wrapWith .Values.value \"\t\" \"hello world\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("5"), + }, + ExpectTplStr: "Evaluate tpl hello\tworld", + }, + { + Name: "TplIndentFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ indent .Values.value \"hello world\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("4"), + }, + ExpectTplStr: "Evaluate tpl hello world", + }, + { + Name: "TplNindentFunction", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ nindent .Values.value \"hello world\" }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("4"), + }, + ExpectTplStr: "Evaluate tpl \n hello world", + }, + { + Name: "TplPluralFunctionSingular", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ plural \"one anchovy\" \"many anchovies\" .Values.value }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("1"), + }, + ExpectTplStr: "Evaluate tpl one anchovy", + }, + { + Name: "TplPluralFunctionPlural", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ plural \"one anchovy\" \"many anchovies\" .Values.value }}" .}}`)}, + }, + Values: chartutil.Values{ + "value": json.Number("42"), + }, + ExpectTplStr: "Evaluate tpl many anchovies", + }, + { + Name: "TplSliceFunctionNoOffset", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ slice .Values.slice .Values.value }}" .}}`)}, + }, + Values: chartutil.Values{ + "slice": []int{1, 2, 3, 4, 5}, + "value": json.Number("2"), + }, + ExpectTplStr: "Evaluate tpl [3 4 5]", + }, + { + Name: "TplSliceFunctionWithOffset", + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "{{ slice .Values.slice .Values.start .Values.end }}" .}}`)}, + }, + Values: chartutil.Values{ + "slice": []int{1, 2, 3, 4, 5}, + "start": json.Number("2"), + "end": json.Number("4"), + }, + ExpectTplStr: "Evaluate tpl [3 4]", + }, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + tplChart := &chart.Chart{ + Metadata: &chart.Metadata{Name: tt.Name}, + Templates: tt.Templates, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{}, + } + + tplValues := chartutil.Values{ + "Values": tt.Values, + "Chart": tplChart.Metadata, + "Release": chartutil.Values{ + "Name": "TestRelease", + }, + } + + outTpl, err := New().Render(tplChart, tplValues) + if err != nil { + t.Fatal(err) + } + + if gotTplStr := outTpl[tt.Name+"/templates/base"]; gotTplStr != tt.ExpectTplStr { + t.Errorf("Expected %q, got %q (%v)", tt.ExpectTplStr, gotTplStr, outTpl) + } + }) + } } diff --git a/pkg/engine/overload.go b/pkg/engine/overload.go new file mode 100644 index 000000000..ef8e200fa --- /dev/null +++ b/pkg/engine/overload.go @@ -0,0 +1,422 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "C" + "encoding/json" + "reflect" + "text/template" + _ "unsafe" +) + +// These functions below are linked to unexported test/template functions. +// See https://golang.org/src/text/template/funcs.go for more details. + +//go:linkname _templateBuiltinEq text/template.eq +func _templateBuiltinEq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) + +//go:linkname _templateBuiltinGe text/template.ge +func _templateBuiltinGe(arg1, arg2 reflect.Value) (bool, error) + +//go:linkname _templateBuiltinGt text/template.gt +func _templateBuiltinGt(arg1, arg2 reflect.Value) (bool, error) + +//go:linkname _templateBuiltinLe text/template.le +func _templateBuiltinLe(arg1, arg2 reflect.Value) (bool, error) + +//go:linkname _templateBuiltinLt text/template.lt +func _templateBuiltinLt(arg1, arg2 reflect.Value) (bool, error) + +//go:linkname _templateBuiltinNe text/template.ne +func _templateBuiltinNe(arg1, arg2 reflect.Value) (bool, error) + +func OverloadJsonNumberFuncs(f template.FuncMap) template.FuncMap { + // Locally overloaded functions. The first block is a simple decoration + // on top of existing function map. + // The second block is tricky: it overloads built-in template functions. + overloads := template.FuncMap{ + "int64": overloadInt64(f["int64"]), + "int": overloadInt(f["int"]), + "float64": overloadFloat64(f["float64"]), + "add1": overloadAdd1(f["add1"]), + "add": overloadAdd(f["add"]), + "sub": overloadSub(f["sub"]), + "div": overloadDiv(f["div"]), + "mod": overloadMod(f["mod"]), + "mul": overloadMul(f["mul"]), + "max": overloadMax(f["max"]), + "biggest": overloadBiggest(f["biggest"]), + "min": overloadMin(f["min"]), + "ceil": overloadCeil(f["ceil"]), + "floor": overloadFloor(f["floor"]), + "round": overloadRound(f["round"]), + "until": overloadUntil(f["until"]), + "untilStep": overloadUntilStep(f["untilStep"]), + "splitn": overloadSplitn(f["splitn"]), + "abbrev": overloadAbbrev(f["abbrev"]), + "abbrevboth": overloadAbbrevBoth(f["abbrevboth"]), + "trunc": overloadTrunc(f["trunc"]), + "substr": overloadSubstr(f["substr"]), + "repeat": overloadRepeat(f["repeat"]), + "randAlphaNum": overloadRandAlphaNum(f["randAlphaNum"]), + "randAlpha": overloadRandAlpha(f["randAlpha"]), + "randAscii": overloadRandAscii(f["randAscii"]), + "randNumeric": overloadRandNumeric(f["randNumeric"]), + "wrap": overloadWrap(f["wrap"]), + "wrapWith": overloadWrapWith(f["wrapWith"]), + "indent": overloadIndent(f["indent"]), + "nindent": overloadNindent(f["nindent"]), + "plural": overloadPlural(f["plural"]), + "slice": overloadSlice(f["slice"]), + + "eq": _overloadTemplateBuiltinOnePlus(_templateBuiltinEq), + "ge": _overloadTemplateBuiltinTuple(_templateBuiltinGe), + "gt": _overloadTemplateBuiltinTuple(_templateBuiltinGt), + "le": _overloadTemplateBuiltinTuple(_templateBuiltinLe), + "lt": _overloadTemplateBuiltinTuple(_templateBuiltinLt), + "ne": _overloadTemplateBuiltinTuple(_templateBuiltinNe), + } + + for k, o := range overloads { + f[k] = o + } + + return f +} + +var ( + overloadInt64 = _overloadSingleInt64 + overloadInt = _overloadSingleInt + overloadFloat64 = _overloadSingleFloat64 + overloadAdd1 = _overloadSingleInt64 + overloadAdd = _overloadMultiInt64 + overloadSub = _overloadTupleInt64 + overloadDiv = _overloadTupleInt64 + overloadMod = _overloadTupleInt64 + overloadMul = _overloadOnePlusInt64 + overloadBiggest = _overloadOnePlusInt64 + overloadMax = _overloadOnePlusInt64 + overloadMin = _overloadOnePlusInt64 + overloadCeil = _overloadSingleFloat64 + overloadFloor = _overloadSingleFloat64 + overloadAbbrev = _overloadSingleIntStrToStr + overloadAbbrevBoth = _overloadDualIntStrToStr + overloadTrunc = _overloadSingleIntStrToStr + overloadSubstr = _overloadDualIntStrToStr + overloadRepeat = _overloadSingleIntStrToStr + overloadRandAlphaNum = _overloadSingleIntToStr + overloadRandAlpha = _overloadSingleIntToStr + overloadRandAscii = _overloadSingleIntToStr + overloadRandNumeric = _overloadSingleIntToStr + overloadWrap = _overloadSingleIntStrToStr + overloadIndent = _overloadSingleIntStrToStr + overloadNindent = _overloadSingleIntStrToStr +) + +// context for built-ins: template builtins are context-dependant and will try +// to cast the arguments to comparable primitives. Here we define 2 constants: +// integer-context and float-context. These are bit flags which we expect to +// check on guessArgsNumCtx return. +const ( + ctxInt uint8 = 1 << iota + ctxFloat +) + +// guessArgsNumCtx tries to guess numeric argument context. As a result, +// it returns a bit mask with int or/and float context bit set. +// 0 means we couldn't conclude any specific context. +// both 0b01 and 0b10 are normal masks denoting a clear mono-context. +// 0b11 means both float and int arguments have been met in the argument list, +// therefore the context is dirty. +func guessArgsNumCtx(i []interface{}) uint8 { + var ctx uint8 + for _, v := range i { + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + ctx |= ctxInt + case reflect.Float32, reflect.Float64: + ctx |= ctxFloat + } + } + return ctx +} + +// convArgsToVals takes a list of interface{} arguments and converts them to an +// array of reflect.Value with a little modification. Firstly, it will try to +// guess the argument context: whether it's an int, float or neither. Based on +// this conclusion, it will convert all json.Number values to: +// * int64 for int context +// * float64 for float context +// * string otherwise +// Returns an error if json.Number conversion fails or the original routine +// returns it. +func convArgsToVals(i []interface{}, ctx uint8) ([]reflect.Value, error) { + vals := make([]reflect.Value, 0, len(i)) + for _, v := range i { + if jsnum, ok := v.(json.Number); ok { + switch ctx { + case ctxFloat: + fv64, err := jsnum.Float64() + if err != nil { + return nil, err + } + v = fv64 + case ctxInt: + iv64, err := jsnum.Int64() + if err != nil { + return nil, err + } + v = iv64 + default: + v = jsnum.String() + } + } + vals = append(vals, reflect.ValueOf(v)) + } + return vals, nil +} + +func _overloadTemplateBuiltinOnePlus(orig interface{}) func(interface{}, ...interface{}) (bool, error) { + return func(a interface{}, i ...interface{}) (bool, error) { + args := append([]interface{}{a}, i...) + ctx := guessArgsNumCtx(args) + vals, err := convArgsToVals(args, ctx) + if err != nil { + return false, err + } + res, err := orig.(func(reflect.Value, ...reflect.Value) (bool, error))(vals[0], vals[1:]...) + return res, err + } +} + +func _overloadTemplateBuiltinTuple(orig interface{}) func(interface{}, interface{}) (bool, error) { + return func(a interface{}, b interface{}) (bool, error) { + args := []interface{}{a, b} + ctx := guessArgsNumCtx(args) + vals, err := convArgsToVals(args, ctx) + if err != nil { + return false, err + } + res, err := orig.(func(reflect.Value, reflect.Value) (bool, error))(vals[0], vals[1]) + return res, err + } +} + +func _overloadSingleInt(orig interface{}) func(interface{}) int { + return func(v interface{}) int { + if num, ok := v.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + v = int(iv64) + } + } + return orig.(func(interface{}) int)(v) + } +} + +func _overloadSingleInt64(orig interface{}) func(interface{}) int64 { + return func(v interface{}) int64 { + if num, ok := v.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + v = iv64 + } + } + return orig.(func(interface{}) int64)(v) + } +} + +func _overloadSingleFloat64(orig interface{}) func(interface{}) float64 { + return func(v interface{}) float64 { + if num, ok := v.(json.Number); ok { + if fv64, err := num.Float64(); err == nil { + v = fv64 + } + } + return orig.(func(interface{}) float64)(v) + } +} + +func _overloadMultiInt64(orig interface{}) func(...interface{}) int64 { + return func(i ...interface{}) int64 { + convs := make([]interface{}, 0, len(i)) + for _, conv := range i { + if num, ok := conv.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + conv = iv64 + } + } + convs = append(convs, conv) + } + val := orig.(func(...interface{}) int64)(convs...) + return val + } +} + +func _overloadTupleInt64(orig interface{}) func(interface{}, interface{}) int64 { + return func(a, b interface{}) int64 { + convs := [2]interface{}{a, b} + for i := 0; i < len(convs); i++ { + conv := convs[i] + if num, ok := conv.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + conv = iv64 + } + } + convs[i] = conv + } + return orig.(func(interface{}, interface{}) int64)(convs[0], convs[1]) + } + +} + +func _overloadOnePlusInt64(orig interface{}) func(interface{}, ...interface{}) int64 { + return func(a interface{}, i ...interface{}) int64 { + convs := make([]interface{}, 0, len(i)+1) + for _, conv := range append([]interface{}{a}, i...) { + if num, ok := conv.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + conv = iv64 + } + } + convs = append(convs, conv) + } + return orig.(func(interface{}, ...interface{}) int64)(convs[0], convs[1:]...) + } +} + +func _overloadSingleIntStrToStr(orig interface{}) func(interface{}, string) string { + return func(a interface{}, s string) string { + if num, ok := a.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + a = int(iv64) + } + } + return orig.(func(int, string) string)(a.(int), s) + } +} + +func _overloadDualIntStrToStr(orig interface{}) func(interface{}, interface{}, string) string { + return func(a, b interface{}, s string) string { + vals := make([]int, 0, 2) + for _, v := range []interface{}{a, b} { + if num, ok := v.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + v = int(iv64) + } + } + vals = append(vals, v.(int)) + } + return orig.(func(int, int, string) string)(vals[0], vals[1], s) + } +} + +func _overloadSingleIntToStr(orig interface{}) func(interface{}) string { + return func(n interface{}) string { + if num, ok := n.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + n = int(iv64) + } + } + return orig.(func(int) string)(n.(int)) + } +} + +func overloadRound(orig interface{}) func(interface{}, int, ...float64) float64 { + return func(a interface{}, p int, r_opt ...float64) float64 { + if num, ok := a.(json.Number); ok { + if fv64, err := num.Float64(); err == nil { + a = fv64 + } + } + return orig.(func(interface{}, int, ...float64) float64)(a, p, r_opt...) + } +} + +func overloadUntil(orig interface{}) func(interface{}) []int { + return func(a interface{}) []int { + if num, ok := a.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + a = int(iv64) + } + } + return orig.(func(int) []int)(a.(int)) + } +} + +func overloadUntilStep(orig interface{}) func(interface{}, interface{}, interface{}) []int { + return func(start, stop, step interface{}) []int { + vals := make([]int, 0, 3) + for _, v := range []interface{}{start, stop, step} { + if num, ok := v.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + v = int(iv64) + } + } + vals = append(vals, v.(int)) + } + return orig.(func(int, int, int) []int)(vals[0], vals[1], vals[2]) + } +} + +func overloadSplitn(orig interface{}) func(string, interface{}, string) map[string]string { + return func(sep string, n interface{}, str string) map[string]string { + if num, ok := n.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + n = int(iv64) + } + } + return orig.(func(string, int, string) map[string]string)(sep, n.(int), str) + } +} + +func overloadWrapWith(orig interface{}) func(interface{}, string, string) string { + return func(l interface{}, sep string, str string) string { + if num, ok := l.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + l = int(iv64) + } + } + return orig.(func(int, string, string) string)(l.(int), sep, str) + } +} + +func overloadPlural(orig interface{}) func(string, string, interface{}) string { + return func(one, many string, count interface{}) string { + if num, ok := count.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + count = int(iv64) + } + } + return orig.(func(string, string, int) string)(one, many, count.(int)) + } +} + +func overloadSlice(orig interface{}) func(interface{}, ...interface{}) interface{} { + return func(list interface{}, indices ...interface{}) interface{} { + convs := make([]interface{}, 0, len(indices)) + for _, ix := range indices { + if num, ok := ix.(json.Number); ok { + if iv64, err := num.Int64(); err == nil { + ix = int(iv64) + } + } + convs = append(convs, ix) + } + return orig.(func(interface{}, ...interface{}) interface{})(list, convs...) + } +}