Skip to content

Commit

Permalink
refactor: complete conversion category migration 🌱✨
Browse files Browse the repository at this point in the history
  • Loading branch information
42atomys committed May 9, 2024
1 parent 6b4669b commit 8b921aa
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 200 deletions.
5 changes: 4 additions & 1 deletion alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ var bc_registerSprigFuncs = FunctionAliasMap{
"ellipsis": {"abbrev"}, //! Deprecated: Should use ellipsis instead
"ellipsisBoth": {"abbrevboth"}, //! Deprecated: Should use ellipsisBoth instead
"trimAll": {"trimall"}, //! Deprecated: Should use trimAll instead
"int": {"atoi"}, //! Deprecated: Should use toInt instead
"append": {"push"}, //! Deprecated: Should use append instead
"mustAppend": {"mustPush"}, //! Deprecated: Should use mustAppend instead
"list": {"tuple"}, // FIXME: with the addition of append/prepend these are no longer immutable.
Expand All @@ -40,6 +39,10 @@ var bc_registerSprigFuncs = FunctionAliasMap{
"expandEnv": {"expandenv"}, //! Deprecated: Should use expandEnv instead
"dateAgo": {"ago"}, //! Deprecated: Should use dateAgo instead
"strSlice": {"toStrings"}, //! Deprecated: Should use strSlice instead
"toInt": {"int", "atoi"}, //! Deprecated: Should use toInt instead
"toInt64": {"int64"}, //! Deprecated: Should use toInt64 instead
"toFloat64": {"float64"}, //! Deprecated: Should use toFloat64 instead
"toOctal": {"toDecimal"}, //! Deprecated: Should use toOctal instead
}

//\ BACKWARDS COMPATIBILITY
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/compare.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ Without: {{without (list 1 2 3) 2}} | {{without (list 1 2 3) 2 3}}
HasKey: {{hasKey $dict "key"}}
Slice: {{slice (list 1 2 3) 1 2}} | {{slice (list 1 2 3) 1}}
Concat: {{concat (list 1 2) (list 3 4)}}
Dig: {{dict "a" 1 | dig "a" ""}}
# Incompatible with sprig
# Dig: {{/* dig "b" "a" (dict "a" 1 "b" (dict "a" 2)) */}}
Chunk: {{list 1 2 3 4 5 | chunk 2}}

{{/* Crypt Functions */}}
Expand Down
149 changes: 149 additions & 0 deletions conversion_functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package sprout

import (
"fmt"
"strconv"
"time"

"github.com/spf13/cast"
)

// ToInt converts a value to an int using robust type casting.
//
// Parameters:
//
// v any - the value to convert to an int.
//
// Returns:
//
// int - the integer representation of the value.
//
// Example:
//
// {{ "123" | toInt }} // Output: 123
func (fh *FunctionHandler) ToInt(v any) int {
return cast.ToInt(v)
}

// ToInt64 converts a value to an int64, accommodating larger integer values.
//
// Parameters:
//
// v any - the value to convert to an int64.
//
// Returns:
//
// int64 - the int64 representation of the value.
//
// Example:
//
// {{ "123456789012" | toInt64 }} // Output: 123456789012
func (fh *FunctionHandler) ToInt64(v any) int64 {
return cast.ToInt64(v)
}

// ToFloat64 converts a value to a float64.
//
// Parameters:
//
// v any - the value to convert to a float64.
//
// Returns:
//
// float64 - the float64 representation of the value.
//
// Example:
//
// {{ "123.456" | toFloat64 }} // Output: 123.456
func (fh *FunctionHandler) ToFloat64(v any) float64 {
return cast.ToFloat64(v)
}

// ToOctal parses a string value as an octal (base 8) integer.
//
// Parameters:
//
// v any - the string representing an octal number.
//
// Returns:
//
// int64 - the decimal (base 10) representation of the octal value.
// If parsing fails, returns 0.
//
// Example:
//
// {{ "123" | toOctal }} // Output: 83 (since "123" in octal is 83 in decimal)
func (fh *FunctionHandler) ToOctal(v any) int64 {
result, err := strconv.ParseInt(fmt.Sprint(v), 8, 64)
if err != nil {
return 0
}
return result
}

// ToString converts a value to a string, handling various types effectively.
//
// Parameters:
//
// v any - the value to convert to a string.
//
// Returns:
//
// string - the string representation of the value.
//
// Example:
//
// {{ 123 | toString }} // Output: "123"
func (fh *FunctionHandler) ToString(v any) string {
switch v := v.(type) {
case string:
return v
case []byte:
return string(v)
case error:
return v.Error()
case fmt.Stringer:
return v.String()
default:
return fmt.Sprintf("%v", v)
}
}

// ToDate converts a string to a time.Time object based on a format specification.
//
// Parameters:
//
// fmt string - the date format string.
// str string - the date string to parse.
//
// Returns:
//
// time.Time - the parsed date.
//
// Example:
//
// {{ "2006-01-02", "2023-05-04" | toDate }} // Output: 2023-05-04 00:00:00 +0000 UTC
func (fh *FunctionHandler) ToDate(fmt, str string) time.Time {
result, _ := fh.MustToDate(fmt, str)
return result
}

// MustToDate tries to parse a string into a time.Time object based on a format,
// returning an error if parsing fails.
//
// Parameters:
//
// fmt string - the date format string.
// str string - the date string to parse.
//
// Returns:
//
// time.Time - the parsed date.
// error - error if the date string does not conform to the format.
//
// Example:
//
// {{ "2006-01-02", "2023-05-04" | mustToDate }} // Output: 2023-05-04 00:00:00 +0000 UTC, nil
func (fh *FunctionHandler) MustToDate(fmt, str string) (time.Time, error) {
return time.ParseInLocation(fmt, str, time.Local)
}
92 changes: 92 additions & 0 deletions conversion_functions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package sprout

import (
"fmt"
"testing"
)

func TestToInt(t *testing.T) {
var tests = testCases{
{"TestInt", `{{$v := toInt .V }}{{kindOf $v}}-{{$v}}`, "int-1", map[string]any{"V": 1}},
{"TestInt32", `{{$v := toInt .V }}{{kindOf $v}}-{{$v}}`, "int-1", map[string]any{"V": int32(1)}},
{"TestFloat64", `{{$v := toInt .V }}{{kindOf $v}}-{{$v}}`, "int-1", map[string]any{"V": float64(1.42)}},
{"TestBool", `{{$v := toInt .V }}{{kindOf $v}}-{{$v}}`, "int-1", map[string]any{"V": true}},
{"TestString", `{{$v := toInt .V }}{{kindOf $v}}-{{$v}}`, "int-1", map[string]any{"V": "1"}},
}

runTestCases(t, tests)
}

func TestToInt64(t *testing.T) {
var tests = testCases{
{"TestInt", `{{$v := toInt64 .V }}{{typeOf $v}}-{{$v}}`, "int64-1", map[string]any{"V": 1}},
{"TestInt32", `{{$v := toInt64 .V }}{{typeOf $v}}-{{$v}}`, "int64-1", map[string]any{"V": int32(1)}},
{"TestFloat64", `{{$v := toInt64 .V }}{{typeOf $v}}-{{$v}}`, "int64-1", map[string]any{"V": float64(1.42)}},
{"TestBool", `{{$v := toInt64 .V }}{{typeOf $v}}-{{$v}}`, "int64-1", map[string]any{"V": true}},
{"TestString", `{{$v := toInt64 .V }}{{typeOf $v}}-{{$v}}`, "int64-1", map[string]any{"V": "1"}},
}

runTestCases(t, tests)
}

func TestToFloat64(t *testing.T) {
var tests = testCases{
{"TestInt", `{{$v := toFloat64 .V }}{{typeOf $v}}-{{$v}}`, "float64-1", map[string]any{"V": 1}},
{"TestInt32", `{{$v := toFloat64 .V }}{{typeOf $v}}-{{$v}}`, "float64-1", map[string]any{"V": int32(1)}},
{"TestFloat64", `{{$v := toFloat64 .V }}{{typeOf $v}}-{{$v}}`, "float64-1.42", map[string]any{"V": float64(1.42)}},
{"TestBool", `{{$v := toFloat64 .V }}{{typeOf $v}}-{{$v}}`, "float64-1", map[string]any{"V": true}},
{"TestString", `{{$v := toFloat64 .V }}{{typeOf $v}}-{{$v}}`, "float64-1", map[string]any{"V": "1"}},
}

runTestCases(t, tests)
}

func TestToDecimal(t *testing.T) {
var tests = testCases{
{"TestInt", `{{$v := toDecimal .V }}{{typeOf $v}}-{{$v}}`, "int64-511", map[string]any{"V": 777}},
{"TestInt32", `{{$v := toDecimal .V }}{{typeOf $v}}-{{$v}}`, "int64-504", map[string]any{"V": int32(770)}},
{"TestString", `{{$v := toDecimal .V }}{{typeOf $v}}-{{$v}}`, "int64-1", map[string]any{"V": "1"}},
}

runTestCases(t, tests)
}

type testStringer struct{}

func (s testStringer) String() string {
return "stringer"
}

func TestToString(t *testing.T) {

var tests = testCases{
{"TestInt", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-1", map[string]any{"V": 1}},
{"TestInt32", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-1", map[string]any{"V": int32(1)}},
{"TestFloat64", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-1.42", map[string]any{"V": float64(1.42)}},
{"TestBool", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-true", map[string]any{"V": true}},
{"TestString", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-1", map[string]any{"V": "1"}},
{"TestError", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-error", map[string]any{"V": fmt.Errorf("error")}},
{"TestStringer", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-stringer", map[string]any{"V": testStringer{}}},
{"TestSliceOfBytes", `{{$v := toString .V }}{{typeOf $v}}-{{$v}}`, "string-abc", map[string]any{"V": []byte("abc")}},
}

runTestCases(t, tests)
}

func TestToDate(t *testing.T) {
var tests = testCases{
{"TestDate", `{{$v := toDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, "time.Time-2024-05-09 00:00:00 +0000 UTC", map[string]any{"V": "2024-05-09"}},
}

runTestCases(t, tests)
}

func TestMustToDate(t *testing.T) {
var tests = mustTestCases{
{testCase{"TestDate", `{{$v := mustToDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, "time.Time-2024-05-09 00:00:00 +0000 UTC", map[string]any{"V": "2024-05-09"}}, ""},
{testCase{"TestInvalidValue", `{{$v := mustToDate "2006-01-02" .V }}{{typeOf $v}}-{{$v}}`, "", map[string]any{"V": ""}}, "cannot parse \"\" as \"2006\""},
{testCase{"TestInvalidLayout", `{{$v := mustToDate "invalid" .V }}{{typeOf $v}}-{{$v}}`, "", map[string]any{"V": "2024-05-09"}}, "cannot parse \"2024-05-09\" as \"invalid\""},
}

runMustTestCases(t, tests)
}
2 changes: 1 addition & 1 deletion maps_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (fh *FunctionHandler) Dict(values ...any) map[string]any {
dict := make(map[string]any, len(values)/2)

for i := 0; i < len(values); i += 2 {
dict[fh.Strval(values[i])] = values[i+1]
dict[fh.ToString(values[i])] = values[i+1]
}

return dict
Expand Down
47 changes: 0 additions & 47 deletions migrated_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,15 @@ import (
"net"
"net/url"
"reflect"
"strconv"
"strings"
"time"

sv2 "github.com/Masterminds/semver/v3"
"github.com/shopspring/decimal"
"github.com/spf13/cast"
bcrypt_lib "golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/scrypt"
)

func (fh *FunctionHandler) Strval(v any) string {
switch v := v.(type) {
case string:
return v
case []byte:
return string(v)
case error:
return v.Error()
case fmt.Stringer:
return v.String()
default:
// Handles any other types by leveraging fmt.Sprintf for a string representation.
return fmt.Sprintf("%v", v)
}
}

func (fh *FunctionHandler) FillMapWithParts(parts []string) map[string]string {
res := make(map[string]string, len(parts))
for i, v := range parts {
Expand Down Expand Up @@ -122,26 +104,6 @@ func (fh *FunctionHandler) UrlJoin(d map[string]any) string {
return resURL.String()
}

func (fh *FunctionHandler) ToFloat64(v any) float64 {
return cast.ToFloat64(v)
}

func (fh *FunctionHandler) ToInt(v any) int {
return cast.ToInt(v)
}

func (fh *FunctionHandler) ToInt64(v any) int64 {
return cast.ToInt64(v)
}

func (fh *FunctionHandler) ToDecimal(v any) int64 {
result, err := strconv.ParseInt(fmt.Sprint(v), 8, 64)
if err != nil {
return 0
}
return result
}

func (fh *FunctionHandler) IntArrayToString(slice []int, delimeter string) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(slice)), delimeter), "[]")
}
Expand Down Expand Up @@ -171,15 +133,6 @@ func (fh *FunctionHandler) InList(haystack []any, needle any) bool {
return false
}

func (fh *FunctionHandler) ToDate(fmt, str string) time.Time {
t, _ := time.ParseInLocation(fmt, str, time.Local)
return t
}

func (fh *FunctionHandler) MustToDate(fmt, str string) (time.Time, error) {
return time.ParseInLocation(fmt, str, time.Local)
}

func (fh *FunctionHandler) SemverCompare(constraint, version string) (bool, error) {
c, err := sv2.NewConstraint(constraint)
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions slices_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ func (fh *FunctionHandler) SortAlpha(list any) []string {
sortedList.Sort()
return sortedList
}
return []string{fh.Strval(list)}
return []string{fh.ToString(list)}
}

// SplitList divides a string into a slice of substrings separated by the
Expand Down Expand Up @@ -422,7 +422,7 @@ func (fh *FunctionHandler) StrSlice(value any) []string {
var result []string
for _, s := range interfaces {
if s != nil {
result = append(result, fh.Strval(s))
result = append(result, fh.ToString(s))
}
}
return result
Expand All @@ -435,14 +435,14 @@ func (fh *FunctionHandler) StrSlice(value any) []string {
for i := 0; i < reflectedValue.Len(); i++ {
value := reflectedValue.Index(i).Interface()
if value != nil {
result = append(result, fh.Strval(value))
result = append(result, fh.ToString(value))
}
}
return result
}

// If it's not a slice, array, or nil, return a slice with the string representation of v.
return []string{fh.Strval(value)}
return []string{fh.ToString(value)}
}

// MustAppend appends an element to a slice or array, returning an error if the
Expand Down
Loading

0 comments on commit 8b921aa

Please sign in to comment.