From 08cda5d1651cd59e4231363f4655ddce1d40adda Mon Sep 17 00:00:00 2001 From: James Elliott Date: Wed, 4 Jan 2023 00:11:10 +1100 Subject: [PATCH] refactor: include additional important template funcs (#4690) * refactor: include additional important template funcs * fix: use of interface * test: improve test cases --- .../content/en/reference/guides/templating.md | 2 + internal/templates/funcs.go | 226 ++++++++---------- internal/templates/funcs_test.go | 75 ++++++ internal/templates/util.go | 53 ++++ 4 files changed, 236 insertions(+), 120 deletions(-) diff --git a/docs/content/en/reference/guides/templating.md b/docs/content/en/reference/guides/templating.md index b20a0b37b..7eb963cdd 100644 --- a/docs/content/en/reference/guides/templating.md +++ b/docs/content/en/reference/guides/templating.md @@ -76,6 +76,8 @@ The following functions which mimic the behaviour of helm exist in most templati - typeIsLike - kindOf - kindIs +- default +- empty See the [Helm Documentation](https://helm.sh/docs/chart_template_guide/function_list/) for more information. Please note that only the functions listed above are supported and the functions don't necessarily behave exactly the same. diff --git a/internal/templates/funcs.go b/internal/templates/funcs.go index 8f0f5dcac..7d173f636 100644 --- a/internal/templates/funcs.go +++ b/internal/templates/funcs.go @@ -71,77 +71,11 @@ func FuncMap() map[string]any { "typeIsLike": FuncTypeIsLike, "kindOf": FuncKindOf, "kindIs": FuncKindIs, + "default": FuncDefault, + "empty": FuncEmpty, } } -// FuncTypeIs is a helper function that provides similar functionality to the helm typeIs func. -func FuncTypeIs(is string, v any) bool { - return is == FuncTypeOf(v) -} - -// FuncTypeIsLike is a helper function that provides similar functionality to the helm typeIsLike func. -func FuncTypeIsLike(is string, v any) bool { - t := FuncTypeOf(v) - - return is == t || "*"+is == t -} - -// FuncTypeOf is a helper function that provides similar functionality to the helm typeOf func. -func FuncTypeOf(v any) string { - return reflect.ValueOf(v).Type().String() -} - -// FuncKindIs is a helper function that provides similar functionality to the helm kindIs func. -func FuncKindIs(is string, v any) bool { - return is == FuncKindOf(v) -} - -// FuncKindOf is a helper function that provides similar functionality to the helm kindOf func. -func FuncKindOf(v any) string { - return reflect.ValueOf(v).Kind().String() -} - -// FuncList is a helper function that provides similar functionality to the helm list func. -func FuncList(items ...any) []any { - return items -} - -// FuncDict is a helper function that provides similar functionality to the helm dict func. -func FuncDict(pairs ...any) map[string]any { - m := map[string]any{} - p := len(pairs) - - for i := 0; i < p; i += 2 { - key := strval(pairs[i]) - - if i+1 >= p { - m[key] = "" - - continue - } - - m[key] = pairs[i+1] - } - - return m -} - -// FuncGet is a helper function that provides similar functionality to the helm get func. -func FuncGet(m map[string]any, key string) any { - if val, ok := m[key]; ok { - return val - } - - return "" -} - -// FuncSet is a helper function that provides similar functionality to the helm set func. -func FuncSet(m map[string]any, key string, value any) map[string]any { - m[key] = value - - return m -} - // FuncB64Enc is a helper function that provides similar functionality to the helm b64enc func. func FuncB64Enc(input string) string { return base64.StdEncoding.EncodeToString([]byte(input)) @@ -292,58 +226,6 @@ func FuncStringQuote(in ...any) string { return strings.Join(out, " ") } -func strval(v any) string { - switch v := v.(type) { - case string: - return v - case []byte: - return string(v) - case fmt.Stringer: - return v.String() - default: - return fmt.Sprintf("%v", v) - } -} - -func strslice(v any) []string { - switch v := v.(type) { - case []string: - return v - case []any: - b := make([]string, 0, len(v)) - - for _, s := range v { - if s != nil { - b = append(b, strval(s)) - } - } - - return b - default: - val := reflect.ValueOf(v) - switch val.Kind() { - case reflect.Array, reflect.Slice: - l := val.Len() - b := make([]string, 0, l) - - for i := 0; i < l; i++ { - value := val.Index(i).Interface() - if value != nil { - b = append(b, strval(value)) - } - } - - return b - default: - if v == nil { - return []string{} - } - - return []string{strval(v)} - } - } -} - // FuncIterate is a template function which takes a single uint returning a slice of units from 0 up to that number. func FuncIterate(count *uint) (out []uint) { var i uint @@ -398,3 +280,107 @@ func FuncStringJoinX(elems []string, sep string, n int, p string) string { return buf.String() } + +// FuncTypeIs is a helper function that provides similar functionality to the helm typeIs func. +func FuncTypeIs(is string, v any) bool { + return is == FuncTypeOf(v) +} + +// FuncTypeIsLike is a helper function that provides similar functionality to the helm typeIsLike func. +func FuncTypeIsLike(is string, v any) bool { + t := FuncTypeOf(v) + + return is == t || "*"+is == t +} + +// FuncTypeOf is a helper function that provides similar functionality to the helm typeOf func. +func FuncTypeOf(v any) string { + return reflect.ValueOf(v).Type().String() +} + +// FuncKindIs is a helper function that provides similar functionality to the helm kindIs func. +func FuncKindIs(is string, v any) bool { + return is == FuncKindOf(v) +} + +// FuncKindOf is a helper function that provides similar functionality to the helm kindOf func. +func FuncKindOf(v any) string { + return reflect.ValueOf(v).Kind().String() +} + +// FuncList is a helper function that provides similar functionality to the helm list func. +func FuncList(items ...any) []any { + return items +} + +// FuncDict is a helper function that provides similar functionality to the helm dict func. +func FuncDict(pairs ...any) map[string]any { + m := map[string]any{} + p := len(pairs) + + for i := 0; i < p; i += 2 { + key := strval(pairs[i]) + + if i+1 >= p { + m[key] = "" + + continue + } + + m[key] = pairs[i+1] + } + + return m +} + +// FuncGet is a helper function that provides similar functionality to the helm get func. +func FuncGet(m map[string]any, key string) any { + if val, ok := m[key]; ok { + return val + } + + return "" +} + +// FuncSet is a helper function that provides similar functionality to the helm set func. +func FuncSet(m map[string]any, key string, value any) map[string]any { + m[key] = value + + return m +} + +// FuncDefault is a helper function that provides similar functionality to the helm default func. +func FuncDefault(d any, vals ...any) any { + if FuncEmpty(vals) || FuncEmpty(vals[0]) { + return d + } + + return vals[0] +} + +// FuncEmpty is a helper function that provides similar functionality to the helm empty func. +func FuncEmpty(v any) bool { + rv := reflect.ValueOf(v) + if !rv.IsValid() { + return true + } + + switch rv.Kind() { + default: + return rv.IsNil() + case reflect.Array, reflect.Slice, reflect.Map, reflect.String: + return rv.Len() == 0 + case reflect.Bool: + return !rv.Bool() + case reflect.Complex64, reflect.Complex128: + return rv.Complex() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return rv.Uint() == 0 + case reflect.Float32, reflect.Float64: + return rv.Float() == 0 + case reflect.Struct: + return false + } +} diff --git a/internal/templates/funcs_test.go b/internal/templates/funcs_test.go index 3bfd776a3..a3eb5b23f 100644 --- a/internal/templates/funcs_test.go +++ b/internal/templates/funcs_test.go @@ -357,6 +357,7 @@ func TestFuncSortAlpha(t *testing.T) { }{ {"ShouldSortStrings", []string{"a", "c", "b"}, []string{"a", "b", "c"}}, {"ShouldSortIntegers", []int{2, 3, 1}, []string{"1", "2", "3"}}, + {"ShouldSortSingleValue", 1, []string{"1"}}, } for _, tc := range testCases { @@ -563,3 +564,77 @@ func TestFuncSet(t *testing.T) { assert.Equal(t, map[string]any{"abc": 123, "123": true}, FuncSet(map[string]any{"abc": 123}, "123", true)) assert.Equal(t, map[string]any{"abc": true}, FuncSet(map[string]any{"abc": 123}, "abc", true)) } + +func TestFuncDefault(t *testing.T) { + testCases := []struct { + name string + value []any + have any + expected any + }{ + {"ShouldDefaultEmptyString", []any{""}, "default", "default"}, + {"ShouldNotDefaultString", []any{"not default"}, "default", "not default"}, + {"ShouldDefaultEmptyInteger", []any{0}, 1, 1}, + {"ShouldNotDefaultInteger", []any{20}, 1, 20}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, FuncDefault(tc.have, tc.value...)) + }) + } +} + +func TestFuncEmpty(t *testing.T) { + var nilv *string + + testCases := []struct { + name string + value any + expected bool + }{ + {"ShouldBeEmptyNil", nilv, true}, + {"ShouldBeEmptyNilNil", nil, true}, + {"ShouldBeEmptyString", "", true}, + {"ShouldNotBeEmptyString", "abc", false}, + {"ShouldBeEmptyArray", []string{}, true}, + {"ShouldNotBeEmptyArray", []string{"abc"}, false}, + {"ShouldBeEmptyInteger", 0, true}, + {"ShouldNotBeEmptyInteger", 1, false}, + {"ShouldBeEmptyInteger8", int8(0), true}, + {"ShouldNotBeEmptyInteger8", int8(1), false}, + {"ShouldBeEmptyInteger16", int16(0), true}, + {"ShouldNotBeEmptyInteger16", int16(1), false}, + {"ShouldBeEmptyInteger32", int32(0), true}, + {"ShouldNotBeEmptyInteger32", int32(1), false}, + {"ShouldBeEmptyInteger64", int64(0), true}, + {"ShouldNotBeEmptyInteger64", int64(1), false}, + {"ShouldBeEmptyUnsignedInteger", uint(0), true}, + {"ShouldNotBeEmptyUnsignedInteger", uint(1), false}, + {"ShouldBeEmptyUnsignedInteger8", uint8(0), true}, + {"ShouldNotBeEmptyUnsignedInteger8", uint8(1), false}, + {"ShouldBeEmptyUnsignedInteger16", uint16(0), true}, + {"ShouldNotBeEmptyUnsignedInteger16", uint16(1), false}, + {"ShouldBeEmptyUnsignedInteger32", uint32(0), true}, + {"ShouldNotBeEmptyUnsignedInteger32", uint32(1), false}, + {"ShouldBeEmptyUnsignedInteger64", uint64(0), true}, + {"ShouldNotBeEmptyUnsignedInteger64", uint64(1), false}, + {"ShouldBeEmptyComplex64", complex64(complex(0, 0)), true}, + {"ShouldNotBeEmptyComplex64", complex64(complex(100000, 7.5)), false}, + {"ShouldBeEmptyComplex128", complex128(complex(0, 0)), true}, + {"ShouldNotBeEmptyComplex128", complex128(complex(100000, 7.5)), false}, + {"ShouldBeEmptyFloat32", float32(0), true}, + {"ShouldNotBeEmptyFloat32", float32(1), false}, + {"ShouldBeEmptyFloat64", float64(0), true}, + {"ShouldNotBeEmptyFloat64", float64(1), false}, + {"ShouldBeEmptyBoolean", false, true}, + {"ShouldNotBeEmptyBoolean", true, false}, + {"ShouldNotBeEmptyStruct", struct{}{}, false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, FuncEmpty(tc.value)) + }) + } +} diff --git a/internal/templates/util.go b/internal/templates/util.go index 17b336ccc..f584c291e 100644 --- a/internal/templates/util.go +++ b/internal/templates/util.go @@ -6,6 +6,7 @@ import ( "os" "path" "path/filepath" + "reflect" "strings" tt "text/template" ) @@ -118,3 +119,55 @@ func loadEmailTemplate(name, overridePath string) (t *EmailTemplate, err error) return t, nil } + +func strval(v any) string { + switch v := v.(type) { + case string: + return v + case []byte: + return string(v) + case fmt.Stringer: + return v.String() + default: + return fmt.Sprintf("%v", v) + } +} + +func strslice(v any) []string { + switch v := v.(type) { + case []string: + return v + case []any: + b := make([]string, 0, len(v)) + + for _, s := range v { + if s != nil { + b = append(b, strval(s)) + } + } + + return b + default: + val := reflect.ValueOf(v) + switch val.Kind() { + case reflect.Array, reflect.Slice: + l := val.Len() + b := make([]string, 0, l) + + for i := 0; i < l; i++ { + value := val.Index(i).Interface() + if value != nil { + b = append(b, strval(value)) + } + } + + return b + default: + if v == nil { + return []string{} + } + + return []string{strval(v)} + } + } +}