Skip to content

Commit

Permalink
refactor: migrate all functions under function handler 🌱✨ (#14)
Browse files Browse the repository at this point in the history
## Description
This pull request represents a significant milestone in our ongoing
project to migrate all functionalities from `sprig` to the new `sprout`
function handler. This transition allows for a phased approach, setting
the stage for subsequent enhancements.


## Changes
- Refactored all functions to be methods of the `FunctionHandler`
struct.
- Improved test coverage to ensure all functions perform as expected.
- Categorized functions into appropriate groups: `misc`, `urls`,
`semver`, `maps`, `slices`, `regexp`, `conversion`, `network`, `crypto`,
`time`, `filesystem`, `encoding`, `numeric`, `strings`, and `random`.


## Fixes #13 #15 #16 #17 #18 #19 #20 #21 #22 #23 #24 #25 #26

## Checklist
- [x] I have read the **CONTRIBUTING.md** document.
- [x] My code follows the code style of this project. (This pull request
define it :trollface:)
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [ ] I have updated the documentation accordingly.
- [x] This change requires a change to the documentation on the website.

## Additional Information
This pull request consolidates all necessary migration tasks into a
single update due to the complexity and interdependencies of the changes
involved. This strategic choice accelerates our transition without
compromising the integrity of our functionalities.
  • Loading branch information
42atomys authored May 9, 2024
1 parent 15366c6 commit f253487
Show file tree
Hide file tree
Showing 71 changed files with 8,118 additions and 5,064 deletions.
1 change: 1 addition & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ github_checks:
ignore:
- benchmark/**
- docs/**
- sprig_functions_not_included_in_sprout.go
comment:
behavior: new
require_changes: true
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,19 @@ To see all the benchmarks, please refer to the [benchmarks](benchmarks/README.md
goos: linux
goarch: amd64
pkg: sprout_benchmarks
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkSprig-12 1 3869134593 ns/op 45438616 B/op 24098 allocs/op
BenchmarkSprout-12 1 1814126036 ns/op 38284040 B/op 11627 allocs/op
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
BenchmarkSprig-16 1 2152506421 ns/op 44671136 B/op 21938 allocs/op
BenchmarkSprout-16 1 1020721871 ns/op 37916984 B/op 11173 allocs/op
PASS
ok sprout_benchmarks 5.910s
ok sprout_benchmarks 3.720s
```

**Time improvement**: 53.1%
**Memory improvement**: 15.7%
**Time improvement**: ((2152506421 - 1020721871) / 2152506421) * 100 = 52.6%
**Memory improvement**: ((44671136 - 37916984) / 44671136) * 100 = 15.1%

So, Sprout v0.2 is approximately 53.1% faster and uses 15.7% less memory than Sprig v3.2.3. 🚀
So, Sprout v0.3 is approximately 52.6% faster and uses 15.1% less memory than Sprig v3.2.3. 🚀

You can see the full benchmark results [here](benchmarks/README.md).

## Development Philosophy (Currently in reflexion to create our)

Expand Down
101 changes: 101 additions & 0 deletions SPRIG_TO_SPROUT_CHANGES_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Migration Notes for Sprout Library
This document outlines the key differences and migration changes between the
Sprig and Sprout libraries. The changes are designed to enhance stability and
usability in the Sprout library.

This document will help contributors and maintainers understand the changes made
between the fork date and version 1.0.0 of the Sprout library.

It will be updated to reflect changes in future versions of the library.

This document will also assist in creating the migration guide when version 1.0 is ready.


## Error Handling Enhancements
### General Error Handling
In Sprig, errors within certain functions cause a panic.
In contrast, Sprout opts for returning nil or an empty value, improving safety
and predictability.

**Old Behavior (Sprig)**: Triggers a panic on error
```go
if err != nil {
panic("deepCopy error: " + err.Error())
}
```

**New Behavior (Sprout)**: Returns nil or an empty value on error
```go
if err != nil {
return nil, err
}
```

Methods that previously caused a panic in Sprig :
- DeepCopy
- MustDeepCopy
- ToRawJson
- Append
- Prepend
- Concat
- Chunk
- Uniq
- Compact
- Slice
- Without
- Rest
- Initial
- Reverse
- First
- Last
- Has
- Dig
- RandAlphaNumeric
- RandAlpha
- RandAscii
- RandNumeric
- RandBytes

## Function-Specific Changes

### MustDeepCopy

- **Sprig**: Accepts `nil` input, causing an internal panic.
- **Sprout**: Returns `nil` if input is `nil`, avoiding panic.

## Rand Functions

- **Sprig**: Causes an internal panic if the length parameter is zero.
- **Sprout**: Returns an empty string if the length is zero, ensuring stability.

## DateAgo

- **Sprig**: Does not support int32 and *time.Time; returns "0s".
- **Sprout**: Supports int32 and *time.Time and returns the correct duration.

## DateRound
- **Sprig**: Returns a corrected duration in positive form, even for negative inputs.
- **Sprout**: Accurately returns the duration, preserving the sign of the input.

## Base32Decode / Base64Decode
- **Sprig**: Decoding functions return the error string when the input is not a valid base64 encoded string.
- **Sprout**: Decoding functions return an empty string if the input is not a valid base64 encoded string, simplifying error handling.

## Dig
> Consider the example dictionary defined as follows:
> ```go
> dict := map[string]any{
> "a": map[string]any{
> "b": 2,
> },
> }
> ```
- **Sprig**: Previously, the `dig` function would return the last map in the access chain.
```go
{{ $dict | dig "a" "b" }} // Output: map[b:2]
```
- **Sprout**: Now, the `dig` function returns the final object in the chain, regardless of its type (map, array, string, etc.).
```go
{{ $dict | dig "a" "b" }} // Output: 2
```
55 changes: 40 additions & 15 deletions alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,42 @@ type FunctionAliasMap map[string][]string
// The following functions are provided for backwards compatibility with the
// original sprig methods. They are not recommended for use in new code.
var bc_registerSprigFuncs = FunctionAliasMap{
"dateModify": {"date_modify"}, //! Deprecated: Should use dateModify instead
"dateInZone": {"date_in_zone"}, //! Deprecated: Should use dateInZone instead
"mustDateModify": {"must_date_modify"}, //! Deprecated: Should use mustDateModify instead
"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.
"max": {"biggest"},
"dateModify": {"date_modify"}, //! Deprecated: Should use dateModify instead
"dateInZone": {"date_in_zone"}, //! Deprecated: Should use dateInZone instead
"mustDateModify": {"must_date_modify"}, //! Deprecated: Should use mustDateModify instead
"ellipsis": {"abbrev"}, //! Deprecated: Should use ellipsis instead
"ellipsisBoth": {"abbrevboth"}, //! Deprecated: Should use ellipsisBoth instead
"trimAll": {"trimall"}, //! Deprecated: Should use trimAll 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.
"max": {"biggest"}, //! Deprecated: Should use max instead
"toUpper": {"upper", "toupper", "uppercase"}, //! Deprecated: Should use toUpper instead
"toLower": {"lower", "tolower", "lowercase"}, //! Deprecated: Should use toLower instead
"add": {"addf"}, //! Deprecated: Should use add instead
"add1": {"add1f"}, //! Deprecated: Should use add1 instead
"sub": {"subf"}, //! Deprecated: Should use sub instead
"toTitleCase": {"title", "titlecase"}, //! Deprecated: Should use toTitleCase instead
"toCamelCase": {"camel", "camelcase"}, //! Deprecated: Should use toCamelCase instead
"toSnakeCase": {"snake", "snakecase"}, //! Deprecated: Should use toSnakeCase instead
"toKebabCase": {"kebab", "kebabcase"}, //! Deprecated: Should use toKebabCase instead
"swapCase": {"swapcase"}, //! Deprecated: Should use swapCase instead
"base64Encode": {"b64enc"}, //! Deprecated: Should use base64Encode instead
"base64Decode": {"b64dec"}, //! Deprecated: Should use base64Decode instead
"base32Encode": {"b32enc"}, //! Deprecated: Should use base32Encode instead
"base32Decode": {"b32dec"}, //! Deprecated: Should use base32Decode instead
"pathBase": {"base"}, //! Deprecated: Should use pathBase instead
"pathDir": {"dir"}, //! Deprecated: Should use pathDir instead
"pathExt": {"ext"}, //! Deprecated: Should use pathExt instead
"pathClean": {"clean"}, //! Deprecated: Should use pathClean instead
"pathIsAbs": {"isAbs"}, //! Deprecated: Should use pathIsAbs instead
"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 Expand Up @@ -89,19 +114,19 @@ func WithAliases(aliases FunctionAliasMap) FunctionHandlerOption {
// It should be called after all aliases have been added through the WithAlias
// option and before the function map is used to ensure all aliases are properly
// registered.
func (p *FunctionHandler) registerAliases() {
func (fh *FunctionHandler) registerAliases() {
// BACKWARDS COMPATIBILITY
// Register the sprig function aliases
for originalFunction, aliases := range bc_registerSprigFuncs {
for _, alias := range aliases {
p.funcMap[alias] = p.funcMap[originalFunction]
fh.funcMap[alias] = fh.funcMap[originalFunction]
}
}
//\ BACKWARDS COMPATIBILITY

for originalFunction, aliases := range p.funcsAlias {
for originalFunction, aliases := range fh.funcsAlias {
for _, alias := range aliases {
p.funcMap[alias] = p.funcMap[originalFunction]
fh.funcMap[alias] = fh.funcMap[originalFunction]
}
}
}
2 changes: 1 addition & 1 deletion alias_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestAliasesInTemplate(t *testing.T) {
WithAlias(originalFuncName, alias1, alias2)(handler)

// Create a template with the aliases.
result, err := runTemplate(t, handler, `{{originalFunc}} {{alias1}} {{alias2}}`)
result, err := runTemplate(t, handler, `{{originalFunc}} {{alias1}} {{alias2}}`, nil)
assert.NoError(t, err)
assert.Equal(t, "cheese cheese cheese", result)
}
23 changes: 21 additions & 2 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Benchmarks outputs

## Sprig v3.2.3 vs Sprout v0.1
## Sprig v3.2.3 vs Sprout v0.2
```
go test -count=1 -bench ^Benchmark -benchmem -cpuprofile cpu.out -memprofile mem.out
goos: linux
Expand All @@ -16,4 +16,23 @@ ok sprout_benchmarks 5.910s
**Time improvement**: ((3869134593 - 1814126036) / 3869134593) * 100 = 53.1%
**Memory improvement**: ((45438616 - 38284040) / 45438616) * 100 = 15.7%

So, Sprout v0.1 is approximately 53.1% faster and uses 15.7% less memory than Sprig v3.2.3.
So, Sprout v0.2 is approximately 53.1% faster and uses 15.7% less memory than Sprig v3.2.3.

## Sprig v3.2.3 vs Sprout v0.3

```
go test -count=1 -bench ^Benchmark -benchmem -cpuprofile cpu.out -memprofile mem.out
goos: linux
goarch: amd64
pkg: sprout_benchmarks
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
BenchmarkSprig-16 1 2152506421 ns/op 44671136 B/op 21938 allocs/op
BenchmarkSprout-16 1 1020721871 ns/op 37916984 B/op 11173 allocs/op
PASS
ok sprout_benchmarks 3.720s
```

**Time improvement**: ((2152506421 - 1020721871) / 2152506421) * 100 = 52.6%
**Memory improvement**: ((44671136 - 37916984) / 44671136) * 100 = 15.1%

So, Sprout v0.3 is approximately 52.6% faster and uses 15.1% less memory than Sprig v3.2.3.
2 changes: 1 addition & 1 deletion benchmarks/allFunctions.sprig.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Without: {{without (list 1 2 3) 2}}
Has: {{hasKey $dict "key"}}
Slice: {{slice (list 1 2 3) 1 2}}
Concat: {{concat (list 1 2) (list 3 4)}}
Dig: {{dict "a" 1 | dig "a" ""}}
Dig: {{ dig "b" "a" (dict "a" 1 "b" (dict "a" 2)) }}
Chunk: {{list 1 2 3 4 5 | chunk 2}}

{{/* Crypt Functions */}}
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/allFunctions.sprout.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Without: {{without (list 1 2 3) 2}}
Has: {{hasKey $dict "key"}}
Slice: {{slice (list 1 2 3) 1 2}}
Concat: {{concat (list 1 2) (list 3 4)}}
Dig: {{dict "a" 1 | dig "a" ""}}
Dig: {{ dig "b" "a" (dict "a" 1 "b" (dict "a" 2)) }}
Chunk: {{list 1 2 3 4 5 | chunk 2}}

{{/* Crypt Functions */}}
Expand Down
34 changes: 34 additions & 0 deletions benchmarks/comparaison_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package benchmarks_test
import (
"bytes"
"log/slog"
"sync"
"testing"
"text/template"

"github.com/42atomys/sprout"
"github.com/Masterminds/sprig/v3"
"github.com/stretchr/testify/assert"
)

/**
Expand Down Expand Up @@ -73,3 +75,35 @@ func BenchmarkSprout(b *testing.B) {
buf.Reset()
}
}

func TestCompareSprigAndSprout(t *testing.T) {
wg := sync.WaitGroup{}

wg.Add(2)

var bufSprig, bufSprout bytes.Buffer

go func() {
defer wg.Done()

tmplSprig, err := template.New("compare").Funcs(sprig.FuncMap()).ParseFiles("compare.tmpl")
assert.NoError(t, err)

err = tmplSprig.ExecuteTemplate(&bufSprig, "compare.tmpl", nil)
assert.NoError(t, err)
}()

go func() {
defer wg.Done()

tmplSprout, err := template.New("compare").Funcs(sprout.FuncMap()).ParseGlob("compare.tmpl")
assert.NoError(t, err)

err = tmplSprout.ExecuteTemplate(&bufSprout, "compare.tmpl", nil)
assert.NoError(t, err)
}()

wg.Wait()
// sprig is expected and sprout is actual
assert.Equal(t, bufSprig.String(), bufSprout.String())
}
Loading

0 comments on commit f253487

Please sign in to comment.