Repository: go-sprout/sprout (go)

Issue:
bug: multiple build occur growing wrapped function
### Describe the bug

I discover during the test of v0.6.0-rc1 something crazy, when we `handler.Build()` on a fresh handler all are good, but if we turn on safe mode and call `.Build()` multiple times, the wrapper will wrap again causing a wrapping loop (and also a memory grow).

### To Reproduce

1. Define a sprout handler
2. Enable safe mode (`sprout.WithSafeFunctions()`)
3. Execute multiple time `handler.Build()`
4. Show wrapped function growing N times (N are the count of `.Build` call)

### Expected behavior

When we re-call Build on an already builded function map, nothing must happend.

### Code Snippet

_No response_

### Version of software

v0.6.0-rc1

### Additional context

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Pull request description:
fix: resolve issues with the v0.6.0-rc.1
## Description
This pull requests grabs returns for the `v0.6.0-rc.1` (Release Candidate 1) and resolve issues related to it. 

## Changes
- Add `Raw` prefix to `Functions` and `Aliases` function to be more comprehensible and prevent confusion with `Funcs()` from sprig.
- Remove deprecated `NewFunctionhandler` labelled as deprecated in v0.2.0 to prepare the `v1.0.0`
- Ensure a built function map cannot be rebuild and cause memory growing
- Prevent to creating an alias on a no-existant function name

## Fixes #66, #67 

## Checklist
- [x] I have read the **CONTRIBUTING.md** document.
- [x] My code follows the code style of this project.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] I have updated the documentation accordingly.
- [x] This change requires a change to the documentation on the website.


Patch:
diff --git a/alias.go b/alias.go
index 37ec083..bcbdf2b 100644
--- a/alias.go
+++ b/alias.go
@@ -10,11 +10,14 @@ type FunctionAliasMap = map[string][]string
 // It should be called after all functions and aliases have been added and
 // inside the Build function in case of using a custom handler.
 func AssignAliases(h Handler) {
-	for originalFunction, aliases := range h.Aliases() {
+	for originalName, aliases := range h.RawAliases() {
+		fn, exists := h.RawFunctions()[originalName]
+		if !exists {
+			continue
+		}
+
 		for _, alias := range aliases {
-			if fn, ok := h.Functions()[originalFunction]; ok {
-				h.Functions()[alias] = fn
-			}
+			h.RawFunctions()[alias] = fn
 		}
 	}
 }
@@ -39,9 +42,9 @@ func AssignAliases(h Handler) {
 //
 //	handler := New(WithAlias("originalFunc", "alias1", "alias2"))
 func WithAlias(originalFunction string, aliases ...string) HandlerOption[*DefaultHandler] {
-	return func(p *DefaultHandler) {
+	return func(p *DefaultHandler) error {
 		if len(aliases) == 0 {
-			return
+			return nil
 		}
 
 		if _, ok := p.cachedFuncsAlias[originalFunction]; !ok {
@@ -49,6 +52,7 @@ func WithAlias(originalFunction string, aliases ...string) HandlerOption[*Defaul
 		}
 
 		p.cachedFuncsAlias[originalFunction] = append(p.cachedFuncsAlias[originalFunction], aliases...)
+		return nil
 	}
 }
 
@@ -69,7 +73,7 @@ func WithAlias(originalFunction string, aliases ...string) HandlerOption[*Defaul
 //	    "originalFunc2": {"alias2_1", "alias2_2"},
 //	}))
 func WithAliases(aliases FunctionAliasMap) HandlerOption[*DefaultHandler] {
-	return func(p *DefaultHandler) {
+	return func(p *DefaultHandler) error {
 		for originalFunction, aliasList := range aliases {
 			if _, ok := p.cachedFuncsAlias[originalFunction]; !ok {
 				p.cachedFuncsAlias[originalFunction] = make([]string, 0)
@@ -77,5 +81,6 @@ func WithAliases(aliases FunctionAliasMap) HandlerOption[*DefaultHandler] {
 
 			p.cachedFuncsAlias[originalFunction] = append(p.cachedFuncsAlias[originalFunction], aliasList...)
 		}
+		return nil
 	}
 }
diff --git a/docs/advanced/how-to-create-a-handler.md b/docs/advanced/how-to-create-a-handler.md
index 3e9bf02..8a52e34 100644
--- a/docs/advanced/how-to-create-a-handler.md
+++ b/docs/advanced/how-to-create-a-handler.md
@@ -11,8 +11,8 @@ The `Handler` interface in Sprout defines the basic methods required to manage r
 * `Logger() *slog.Logger`: Returns the logger instance used for logging.
 * `AddRegistry(registry Registry) error`: Adds a single registry to the handler.
 * `AddRegistries(registries ...Registry) error`: Adds multiple registries to the handler.
-* `Functions() FunctionMap`: Returns the map of registered functions.
-* `Aliases() FunctionAliasMap`: Returns the map of function aliases.
+* `RawFunctions() FunctionMap`: Returns the map of registered functions.
+* `RawAliases() FunctionAliasMap`: Returns the map of function aliases.
 * `Build() FunctionMap`: Builds and returns the complete function map, ready to be used in templates.
 
 ### Step 2: Create Your Custom Handler Struct
diff --git a/docs/features/loader-system-registry.md b/docs/features/loader-system-registry.md
index b67d71c..8d220d5 100644
--- a/docs/features/loader-system-registry.md
+++ b/docs/features/loader-system-registry.md
@@ -43,6 +43,14 @@ tpl := template.Must(
   )
 ```
 
+You can also use the option to add registries when initializing the handler:
+
+```go
+handler := sprout.New(
+  sprout.WithRegistries(reg1.NewRegistry(), reg2.NewRegistry()),
+)
+```
+
 This code sets up your project to utilize the functions from your custom registry, making it easy to integrate and extend functionality.
 
 ## How to create a registry
diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md
index 6b31d1d..a408921 100644
--- a/docs/introduction/getting-started.md
+++ b/docs/introduction/getting-started.md
@@ -38,19 +38,27 @@ handler := sprout.New()
 
 Sprout supports various customization options using handler options:
 
-*   **Logger Configuration:**
-
+*   **Logger Configuration:**\
     You can customize the logging behavior by providing a custom logger:
 
     ```go
     logger := slog.New(slog.NewTextHandler(os.Stdout))
     handler := sprout.New(sprout.WithLogger(logger))
     ```
+*   **Load Registry:**\
+    You can load a registry directly on your handler using the `WithRegistries` option:
+
+    ```go
+    handler := sprout.New(sprout.WithRegistries(ownregistry.NewRegistry()))
+    ```
+
+    See more below or in dedicated page [loader-system-registry.md](../features/loader-system-registry.md "mention").
 *   **Aliases Management:**\
     You can specify your custom aliases directly on your handler:
 
-    <pre class="language-go"><code class="lang-go"><strong>handler := sprout.New(sprout.WithAlias("originalFunc", "alias"))
-    </strong></code></pre>
+    ```go
+    handler := sprout.New(sprout.WithAlias("originalFunc", "alias"))
+    ```
 
     See more below or in dedicated page [function-aliases.md](../features/function-aliases.md "mention").
 *   **Notices:**\
@@ -103,6 +111,14 @@ You can also add multiple registries at once:
 handler.AddRegistries(conversion.NewRegistry(), std.NewRegistry())
 ```
 
+Or add registries directly when initializing the handler:
+
+```go
+handler := sprout.New(
+    sprout.WithRegistries(conversion.NewRegistry(), std.NewRegistry()),
+)
+```
+
 ### Function Aliases
 
 Sprout supports function aliases, allowing you to call the same function by different names.
@@ -130,7 +146,7 @@ funcs := handler.Build()
 tpl := template.New("example").Funcs(funcs).Parse(`{{ hello }}`)
 ```
 
-This prepares all registered functions and aliases for use in templates.
+This prepares all registered functions and aliases for use in templates. This also caches the function map for better performance.
 
 ### Working with Templates
 
diff --git a/handler.go b/handler.go
index 97e7d50..a9e0efa 100644
--- a/handler.go
+++ b/handler.go
@@ -25,16 +25,15 @@ type Handler interface {
 	// processing environment.
 	AddRegistry(registry Registry) error
 
-	// AddRegistries registers multiple registries into the Handler. This method
-	// simplifies the process of adding multiple sets of functionalities into the
-	// template engine at once.
-	AddRegistries(registries ...Registry) error
-
-	// Functions returns the map of registered functions managed by the Handler.
-	Functions() FunctionMap
+	// RawFunctions returns the map of registered functions without any alias,
+	// notices or other additional information. This function is useful for
+	// special cases where you need to access raw data from registries.
+	//
+	// ⚠ To access the function map for the template engine use `Build()` instead.
+	RawFunctions() FunctionMap
 
-	// Aliases returns the map of function aliases managed by the Handler.
-	Aliases() FunctionAliasMap
+	// RawAliases returns the map of function aliases managed by the Handler.
+	RawAliases() FunctionAliasMap
 
 	// Notices returns the list of function notices managed by the Handler.
 	Notices() []FunctionNotice
@@ -56,6 +55,7 @@ type DefaultHandler struct {
 	notices    []FunctionNotice
 
 	wantSafeFuncs bool
+	built         bool
 
 	cachedFuncsMap   FunctionMap
 	cachedFuncsAlias FunctionAliasMap
@@ -104,26 +104,26 @@ func (dh *DefaultHandler) AddRegistries(registries ...Registry) error {
 
 // Build retrieves the complete suite of functiosn and alias that has been configured
 // within this Handler. This handler is ready to be used with template engines
-// that accept FuncMap, such as html/template or text/template.
+// that accept FuncMap, such as html/template or text/template. It will also
+// cache the function map for future use to avoid rebuilding the function map
+// multiple times, so it is safe to call this method multiple times to retrieve
+// the same builded function map.
 //
-// NOTE: This will replace the `FuncsMap()`, `TxtFuncMap()` and `HtmlFuncMap()` from sprig
+// NOTE: This replaces the [github.com/Masterminds/sprig.FuncMap],
+// [github.com/Masterminds/sprig.TxtFuncMap] and [github.com/Masterminds/sprig.HtmlFuncMap]
+// from sprig
 func (dh *DefaultHandler) Build() FunctionMap {
+	if dh.built {
+		return dh.cachedFuncsMap
+	}
+
 	AssignAliases(dh) // Ensure all aliases are processed before returning the registry
 	AssignNotices(dh) // Ensure all notices are processed before returning the registry
-
-	// If safe functions are enabled, wrap all functions with a safe wrapper
-	// that logs any errors that occur during function execution.
 	if dh.wantSafeFuncs {
-		safeFuncs := make(FunctionMap)
-		for funcName, fn := range dh.cachedFuncsMap {
-			safeFuncs[safeFuncName(funcName)] = dh.safeWrapper(funcName, fn)
-		}
-
-		for funcName, fn := range safeFuncs {
-			dh.cachedFuncsMap[funcName] = fn
-		}
+		AssignSafeFuncs(dh) // Ensure all functions are wrapped with safe functions
 	}
 
+	dh.built = true
 	return dh.cachedFuncsMap
 }
 
@@ -137,7 +137,7 @@ func (dh *DefaultHandler) Logger() *slog.Logger {
 	return dh.logger
 }
 
-// Functions returns the map of registered functions managed by the DefaultHandler.
+// RawFunctions returns the map of registered functions managed by the DefaultHandler.
 //
 // ⚠ This function is for special cases where you need to access the function
 // map for the template engine use `Build()` instead.
@@ -145,17 +145,17 @@ func (dh *DefaultHandler) Logger() *slog.Logger {
 // This function map contains all the functions that have been added to the handler,
 // typically for use in templating engines. Each entry in the map associates a function
 // name with its corresponding implementation.
-func (dh *DefaultHandler) Functions() FunctionMap {
+func (dh *DefaultHandler) RawFunctions() FunctionMap {
 	return dh.cachedFuncsMap
 }
 
-// Aliases returns the map of function aliases managed by the DefaultHandler.
+// RawAliases returns the map of function aliases managed by the DefaultHandler.
 //
 // The alias map allows certain functions to be referenced by multiple names. This
 // can be useful in templating environments where different names might be preferred
 // for the same underlying function. The alias map associates each original function
 // name with a list of aliases that can be used interchangeably.
-func (dh *DefaultHandler) Aliases() FunctionAliasMap {
+func (dh *DefaultHandler) RawAliases() FunctionAliasMap {
 	return dh.cachedFuncsAlias
 }
 
@@ -171,22 +171,25 @@ func (dh *DefaultHandler) Notices() []FunctionNotice {
 
 // WithLogger sets the logger used by a DefaultHandler.
 func WithLogger(l *slog.Logger) HandlerOption[*DefaultHandler] {
-	return func(p *DefaultHandler) {
+	return func(p *DefaultHandler) error {
 		p.logger = l
+		return nil
 	}
 }
 
 // WithHandler updates a DefaultHandler with settings from another DefaultHandler.
 // This is useful for copying configurations between handlers.
 func WithHandler(new Handler) HandlerOption[*DefaultHandler] {
-	return func(fnh *DefaultHandler) {
+	return func(fnh *DefaultHandler) error {
 		if new == nil {
-			return
+			return nil
 		}
 
 		if fhCast, ok := new.(*DefaultHandler); ok {
 			*fnh = *fhCast
 		}
+
+		return nil
 	}
 }
 
@@ -198,19 +201,39 @@ func WithHandler(new Handler) HandlerOption[*DefaultHandler] {
 // To use a safe function, prepend `safe` to the original function name,
 // example: `safeOriginalFuncName` instead of `originalFuncName`.
 func WithSafeFuncs(enabled bool) HandlerOption[*DefaultHandler] {
-	return func(dh *DefaultHandler) {
+	return func(dh *DefaultHandler) error {
 		dh.wantSafeFuncs = enabled
+		return nil
+	}
+}
+
+// AssignSafeFuncs wraps all functions with a safe wrapper that logs any errors
+// that occur during function execution. If safe functions are enabled in the
+// DefaultHandler, this method will prepend "safe" to the function name and
+// create a safe wrapper for each function.
+//
+// E.G. all functions will have both the original function name and a safe function name:
+//
+//	originalFuncName -> SafeOriginalFuncName
+func AssignSafeFuncs(handler Handler) {
+	safeFuncs := make(FunctionMap)
+	for funcName, fn := range handler.RawFunctions() {
+		safeFuncs[safeFuncName(funcName)] = safeWrapper(handler, funcName, fn)
+	}
+
+	for funcName, fn := range safeFuncs {
+		handler.RawFunctions()[funcName] = fn
 	}
 }
 
 // safeWrapper create a safe wrapper function that calls the original function
 // and logs any errors that occur during the function call without interrupting
 // the execution of the template.
-func (dh *DefaultHandler) safeWrapper(functionName string, fn any) wrappedFunc {
+func safeWrapper(handler Handler, functionName string, fn any) wrappedFunction {
 	return func(args ...any) (any, error) {
 		out, err := runtime.SafeCall(fn, args...)
 		if err != nil {
-			dh.Logger().With("function", functionName, "error", err).Error("function call failed")
+			handler.Logger().With("function", functionName, "error", err).Error("function call failed")
 		}
 		return out, nil
 	}
diff --git a/notice.go b/notice.go
index 1a1f93c..6fe2978 100644
--- a/notice.go
+++ b/notice.go
@@ -7,12 +7,6 @@ import (
 	"github.com/go-sprout/sprout/internal/runtime"
 )
 
-// wrappedFunc is a type alias for a function that accepts a variadic number of
-// arguments of any type and returns a single result of any type along with an
-// error. This is typically used for functions that need to be wrapped with
-// additional logic, such as logging or notice handling.
-type wrappedFunc = func(args ...any) (any, error)
-
 // NoticeKind represents the type of notice that can be applied to a function.
 // It is an enumeration with different possible values that dictate how the
 // notice should behave.
@@ -91,23 +85,23 @@ func NewDebugNotice(functionName, message string) *FunctionNotice {
 // It should be called after all functions and notices have been added and
 // inside the Build function in case of using a custom handler.
 func AssignNotices(h Handler) {
-	funcs := h.Functions()
+	funcs := h.RawFunctions()
 	for _, notice := range h.Notices() {
 		for _, functionName := range notice.FunctionNames {
 			if fn, ok := funcs[functionName]; ok {
-				wrappedFn := createWrappedFunction(h, notice, functionName, fn)
+				wrappedFn := noticeWrapper(h, notice, functionName, fn)
 				funcs[functionName] = wrappedFn
 			}
 		}
 	}
 }
 
-// createWrappedFunction creates a wrapped function that logs a notice after
+// noticeWrapper creates a wrapped function that logs a notice after
 // calling the original function. The notice is logged using the handler's
 // logger instance. The wrapped function is returned as a wrappedFunc, which
 // is a type alias for a function that takes a variadic list of arguments
 // and returns an `any` result and an `error`.
-func createWrappedFunction(h Handler, notice FunctionNotice, functionName string, fn any) wrappedFunc {
+func noticeWrapper(h Handler, notice FunctionNotice, functionName string, fn any) wrappedFunction {
 	return func(args ...any) (any, error) {
 		out, err := runtime.SafeCall(fn, args...)
 		switch notice.Kind {
@@ -130,7 +124,7 @@ func createWrappedFunction(h Handler, notice FunctionNotice, functionName string
 // You can use the ApplyOnAliases method on the FunctionNotice to control
 // whether the notice should be applied to aliases.
 func WithNotices(notices ...*FunctionNotice) HandlerOption[*DefaultHandler] {
-	return func(p *DefaultHandler) {
+	return func(p *DefaultHandler) error {
 		// Preallocate the slice if we expect to append multiple notices
 		if cap(p.notices) < len(p.notices)+len(notices) {
 			newNotices := make([]FunctionNotice, len(p.notices), len(p.notices)+len(notices))
@@ -147,5 +141,7 @@ func WithNotices(notices ...*FunctionNotice) HandlerOption[*DefaultHandler] {
 			// Append the notice directly without dereferencing
 			p.notices = append(p.notices, *notice)
 		}
+
+		return nil
 	}
 }
diff --git a/registry.go b/registry.go
index c86ed58..f0bcd57 100644
--- a/registry.go
+++ b/registry.go
@@ -68,3 +68,16 @@ func AddAlias(aliasMap FunctionAliasMap, originalFunction string, aliases ...str
 func AddNotice(notices *[]FunctionNotice, notice *FunctionNotice) {
 	*notices = append(*notices, *notice)
 }
+
+// WithRegistries returns a HandlerOption that adds the provided registries to the handler.
+// This option simplifies the process of adding multiple sets of functionalities into the
+// template engine at once.
+//
+// Example:
+//
+//	handler := New(WithRegistries(myRegistry1, myRegistry2, myRegistry3))
+func WithRegistries(registries ...Registry) HandlerOption[*DefaultHandler] {
+	return func(dh *DefaultHandler) error {
+		return dh.AddRegistries(registries...)
+	}
+}
diff --git a/sprigin/sprig_backward_compatibility.go b/sprigin/sprig_backward_compatibility.go
index 2224437..d01ec9c 100644
--- a/sprigin/sprig_backward_compatibility.go
+++ b/sprigin/sprig_backward_compatibility.go
@@ -144,11 +144,11 @@ func (sh *SprigHandler) Logger() *slog.Logger {
 	return slog.New(slog.Default().Handler())
 }
 
-func (sh *SprigHandler) Functions() sprout.FunctionMap {
+func (sh *SprigHandler) RawFunctions() sprout.FunctionMap {
 	return sh.funcsMap
 }
 
-func (sh *SprigHandler) Aliases() sprout.FunctionAliasMap {
+func (sh *SprigHandler) RawAliases() sprout.FunctionAliasMap {
 	return sh.funcsAlias
 }
 
diff --git a/sprout.go b/sprout.go
index f2ab31b..88398ec 100644
--- a/sprout.go
+++ b/sprout.go
@@ -7,7 +7,13 @@ import (
 
 // HandlerOption[Handler] defines a type for functional options that configure
 // a typed Handler.
-type HandlerOption[T Handler] func(T)
+type HandlerOption[T Handler] func(T) error
+
+// wrappedFunction is a type alias for a function that accepts a variadic number of
+// arguments of any type and returns a single result of any type along with an
+// error. This is typically used for functions that need to be wrapped with
+// additional logic, such as logging or notice handling.
+type wrappedFunction = func(args ...any) (any, error)
 
 // New creates and returns a new instance of DefaultHandler with optional
 // configurations.
@@ -22,7 +28,7 @@ type HandlerOption[T Handler] func(T)
 //	logger := slog.New(slog.NewTextHandler(os.Stdout))
 //	handler := New(
 //	    WithLogger(logger),
-//	    WithRegistry(myRegistry),
+//	    WithRegistries(myRegistry),
 //	)
 //
 // In the above example, the DefaultHandler is created with a custom logger and
@@ -40,15 +46,10 @@ func New(opts ...HandlerOption[*DefaultHandler]) *DefaultHandler {
 	}
 
 	for _, opt := range opts {
-		opt(dh)
+		if err := opt(dh); err != nil {
+			dh.logger.With("error", err).Error("Failed to apply handler option")
+		}
 	}
 
 	return dh
 }
-
-// Deprecated: NewFunctionHandler creates a new function handler with the
-// default values. It is deprecated and should not be used. Use `New` instead.
-func NewFunctionHandler(opts ...HandlerOption[*DefaultHandler]) *DefaultHandler {
-	slog.Warn("NewFunctionHandler are deprecated. Use `New` instead")
-	return New(opts...)
-}


Test patch:
diff --git a/alias_test.go b/alias_test.go
index bc65068..321ffee 100644
--- a/alias_test.go
+++ b/alias_test.go
@@ -12,13 +12,13 @@ import (
 
 // TestWithAlias checks that aliases are correctly added to a function.
 func TestWithAlias(t *testing.T) {
-	handler := NewFunctionHandler()
+	handler := New()
 	originalFunc := "originalFunc"
 	alias1 := "alias1"
 	alias2 := "alias2"
 
 	// Apply the WithAlias option with two aliases.
-	WithAlias(originalFunc, alias1, alias2)(handler)
+	require.NoError(t, WithAlias(originalFunc, alias1, alias2)(handler))
 
 	// Check that the aliases were added.
 	assert.Contains(t, handler.cachedFuncsAlias, originalFunc)
@@ -28,18 +28,18 @@ func TestWithAlias(t *testing.T) {
 }
 
 func TestWithAlias_Empty(t *testing.T) {
-	handler := NewFunctionHandler()
+	handler := New()
 	originalFunc := "originalFunc"
 
 	// Apply the WithAlias option with no aliases.
-	WithAlias(originalFunc)(handler)
+	require.NoError(t, WithAlias(originalFunc)(handler))
 
 	// Check that no aliases were added.
 	assert.NotContains(t, handler.cachedFuncsAlias, originalFunc)
 }
 
 func TestWithAliases(t *testing.T) {
-	handler := NewFunctionHandler()
+	handler := New()
 	originalFunc1 := "originalFunc1"
 	alias1 := "alias1"
 	alias2 := "alias2"
@@ -47,10 +47,10 @@ func TestWithAliases(t *testing.T) {
 	alias3 := "alias3"
 
 	// Apply the WithAliases option with two sets of aliases.
-	WithAliases(FunctionAliasMap{
+	require.NoError(t, WithAliases(FunctionAliasMap{
 		originalFunc1: {alias1, alias2},
 		originalFunc2: {alias3},
-	})(handler)
+	})(handler))
 
 	// Check that the aliases were added.
 	assert.Contains(t, handler.cachedFuncsAlias, originalFunc1)
@@ -65,7 +65,7 @@ func TestWithAliases(t *testing.T) {
 
 // TestRegisterAliases checks that aliases are correctly registered in the function map.
 func TestRegisterAliases(t *testing.T) {
-	handler := NewFunctionHandler()
+	handler := New()
 	originalFunc := "originalFunc"
 	alias1 := "alias1"
 	alias2 := "alias2"
@@ -75,7 +75,7 @@ func TestRegisterAliases(t *testing.T) {
 	handler.cachedFuncsMap[originalFunc] = mockFunc
 
 	// Apply the WithAlias option and then register the aliases.
-	WithAlias(originalFunc, alias1, alias2)(handler)
+	require.NoError(t, WithAlias(originalFunc, alias1, alias2)(handler))
 	AssignAliases(handler)
 
 	// Check that the aliases are mapped to the same function as the original function in funcsRegistry.
@@ -84,7 +84,7 @@ func TestRegisterAliases(t *testing.T) {
 }
 
 func TestAliasesInTemplate(t *testing.T) {
-	handler := NewFunctionHandler()
+	handler := New()
 	originalFuncName := "originalFunc"
 	alias1 := "alias1"
 	alias2 := "alias2"
@@ -94,7 +94,7 @@ func TestAliasesInTemplate(t *testing.T) {
 	handler.cachedFuncsMap[originalFuncName] = mockFunc
 
 	// Apply the WithAlias option and then register the aliases.
-	WithAlias(originalFuncName, alias1, alias2)(handler)
+	require.NoError(t, WithAlias(originalFuncName, alias1, alias2)(handler))
 
 	// Create a template with the aliases.
 	tmpl, err := template.New("test").Funcs(handler.Build()).Parse(`{{originalFunc}} {{alias1}} {{alias2}}`)
diff --git a/handler_test.go b/handler_test.go
index 03416d7..a3b36a0 100644
--- a/handler_test.go
+++ b/handler_test.go
@@ -269,12 +269,12 @@ func TestDefaultHandler_Registries(t *testing.T) {
 	assert.Len(t, dh.registries, 2, "Registries should return the correct number of registries")
 }
 
-// TestDefaultHandler_Functions tests the Functions method of DefaultHandler.
-func TestDefaultHandler_Functions(t *testing.T) {
+// TestDefaultHandler_RawFunctions tests the Functions method of DefaultHandler.
+func TestDefaultHandler_RawFunctions(t *testing.T) {
 	funcsMap := make(FunctionMap)
 	dh := &DefaultHandler{cachedFuncsMap: funcsMap}
 
-	assert.Equal(t, funcsMap, dh.Functions(), "Functions should return the correct FunctionMap")
+	assert.Equal(t, funcsMap, dh.RawFunctions(), "Functions should return the correct FunctionMap")
 }
 
 // TestDefaultHandler_Aliases tests the Aliases method of DefaultHandler.
@@ -282,7 +282,7 @@ func TestDefaultHandler_Aliases(t *testing.T) {
 	aliasesMap := make(FunctionAliasMap)
 	dh := &DefaultHandler{cachedFuncsAlias: aliasesMap}
 
-	assert.Equal(t, aliasesMap, dh.Aliases(), "Aliases should return the correct FunctionAliasMap")
+	assert.Equal(t, aliasesMap, dh.RawAliases(), "Aliases should return the correct FunctionAliasMap")
 }
 
 // TestDefaultHandler_Build tests the Build method of DefaultHandler.
@@ -301,6 +301,9 @@ func TestDefaultHandler_Build(t *testing.T) {
 	builtFuncsMap := dh.Build()
 
 	assert.Equal(t, funcsMap, builtFuncsMap, "Build should return the correct FunctionMap")
+
+	builtFuncsMapSecond := dh.Build()
+	assert.Equal(t, builtFuncsMap, builtFuncsMapSecond, "Build should return the same FunctionMap on subsequent calls")
 }
 
 func TestDefaultHandler_safeWrapper(t *testing.T) {
@@ -311,7 +314,7 @@ func TestDefaultHandler_safeWrapper(t *testing.T) {
 	_, err := fn()
 	require.Error(t, err, "fn should return an error")
 
-	safeFn := handler.safeWrapper("fn", fn)
+	safeFn := safeWrapper(handler, "fn", fn)
 	_, safeErr := safeFn()
 	require.NoError(t, safeErr, "safeFn should not return an error")
 	assert.Equal(t, "[ERROR] function call failed\n", loggerHandler.messages.String())
diff --git a/notice_test.go b/notice_test.go
index 36c216a..bc183e4 100644
--- a/notice_test.go
+++ b/notice_test.go
@@ -41,7 +41,7 @@ func TestWithNotice(t *testing.T) {
 	notice := NewInfoNotice(originalFunc, "amazing")
 
 	// Apply the WithNotices option with one notice.
-	WithNotices(notice)(handler)
+	require.NoError(t, WithNotices(notice)(handler))
 
 	// Check that the aliases were added.
 	assert.Contains(t, handler.Notices(), *notice)
@@ -49,7 +49,7 @@ func TestWithNotice(t *testing.T) {
 
 	// Apply the WithNotices option with multiple notices.
 	notice2 := NewDeprecatedNotice(originalFunc, "oh no")
-	WithNotices(notice, notice2)(handler)
+	require.NoError(t, WithNotices(notice, notice2)(handler))
 
 	// Check that the aliases were added.
 	assert.Contains(t, handler.Notices(), *notice)
@@ -58,7 +58,7 @@ func TestWithNotice(t *testing.T) {
 
 	// Apply the WithNotices option with an empty message
 	notice3 := NewDebugNotice(originalFunc, "")
-	WithNotices(notice3)(handler)
+	require.NoError(t, WithNotices(notice3)(handler))
 
 	assert.Contains(t, handler.Notices(), *notice)
 	assert.Contains(t, handler.Notices(), *notice2)
@@ -67,7 +67,7 @@ func TestWithNotice(t *testing.T) {
 
 	// Try to apply a notice with an empty function name.
 	notice4 := &FunctionNotice{}
-	WithNotices(notice4)(handler)
+	require.NoError(t, WithNotices(notice4)(handler))
 
 	// Check that the aliases were not added.
 	assert.NotContains(t, handler.Notices(), *notice4)
@@ -91,8 +91,8 @@ func TestAssignNotices(t *testing.T) {
 	assert.Contains(t, handler.Notices(), *notice)
 	assert.Len(t, handler.notices, 1, "there should be exactly 1 notice")
 
-	require.Contains(t, handler.Functions(), originalFunc)
-	assert.NotEqual(t, reflect.ValueOf(mockFunc).Pointer(), reflect.ValueOf(handler.Functions()[originalFunc]).Pointer(), "the function should have been wrapped")
+	require.Contains(t, handler.RawFunctions(), originalFunc)
+	assert.NotEqual(t, reflect.ValueOf(mockFunc).Pointer(), reflect.ValueOf(handler.RawFunctions()[originalFunc]).Pointer(), "the function should have been wrapped")
 }
 
 func TestCreateWrappedFunction(t *testing.T) {
@@ -103,9 +103,9 @@ func TestCreateWrappedFunction(t *testing.T) {
 	mockFunc := func() string { return "cheese" }
 
 	// Create a wrapped function.
-	wrappedFunc := createWrappedFunction(handler, *NewInfoNotice(originalFunc, "amazing"), originalFunc, mockFunc)
-	wrappedFunc2 := createWrappedFunction(handler, *NewDeprecatedNotice(originalFunc, "oh no"), originalFunc, mockFunc)
-	wrappedFunc3 := createWrappedFunction(handler, *NewNotice(NoticeKindDebug, []string{originalFunc}, "Nice this function returns $out"), originalFunc, mockFunc)
+	wrappedFunc := noticeWrapper(handler, *NewInfoNotice(originalFunc, "amazing"), originalFunc, mockFunc)
+	wrappedFunc2 := noticeWrapper(handler, *NewDeprecatedNotice(originalFunc, "oh no"), originalFunc, mockFunc)
+	wrappedFunc3 := noticeWrapper(handler, *NewNotice(NoticeKindDebug, []string{originalFunc}, "Nice this function returns $out"), originalFunc, mockFunc)
 
 	// Call the wrapped function.
 	out, err := wrappedFunc()
diff --git a/registry_test.go b/registry_test.go
index d4a5258..1fa1a19 100644
--- a/registry_test.go
+++ b/registry_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 )
 
 func TestAddFunction(t *testing.T) {
@@ -53,3 +54,25 @@ func TestAddAlias(t *testing.T) {
 	AddAlias(aliasMap, "nonExistentFunc", "aliasX")
 	assert.Contains(t, aliasMap, "nonExistentFunc", "Aliases should be added under 'nonExistentFunc' even if the function doesn't exist")
 }
+
+func TestWithRegistries(t *testing.T) {
+	// Define two registries with functions and aliases
+	mockRegistry1 := new(MockRegistry)
+	mockRegistry1.On("Uid").Return("mockRegistry1")
+	mockRegistry1.On("LinkHandler", mock.Anything).Return(nil)
+	mockRegistry1.On("RegisterFunctions", mock.Anything).Return(nil)
+
+	mockRegistry2 := new(MockRegistry)
+	mockRegistry2.linkHandlerMustCrash = true
+	mockRegistry2.On("Uid").Return("mockRegistry2")
+	mockRegistry2.On("LinkHandler", mock.Anything).Return(nil)
+	mockRegistry1.On("RegisterFunctions", mock.Anything).Return(nil)
+
+	// Create a handler with the registries
+	handler := New(WithRegistries(mockRegistry1, mockRegistry2))
+	handler.Build()
+
+	// Check that the functions and aliases are present in the handler
+	assert.Contains(t, handler.registries, mockRegistry1, "Registry 1 should be added to the handler")
+	assert.Contains(t, handler.registries, mockRegistry2, "Registry 2 should be added to the handler")
+}
diff --git a/sprigin/sprig_backward_compatibility_test.go b/sprigin/sprig_backward_compatibility_test.go
index a210805..49dfc4b 100644
--- a/sprigin/sprig_backward_compatibility_test.go
+++ b/sprigin/sprig_backward_compatibility_test.go
@@ -53,8 +53,8 @@ func TestSprigHandler(t *testing.T) {
 
 	handler.Build()
 
-	assert.GreaterOrEqual(t, len(handler.Functions()), sprigFunctionCount)
-	assert.Len(t, handler.Aliases(), 37) // Hardcoded for backward compatibility
+	assert.GreaterOrEqual(t, len(handler.RawFunctions()), sprigFunctionCount)
+	assert.Len(t, handler.RawAliases(), 37) // Hardcoded for backward compatibility
 
 	assert.Len(t, handler.registries, 18) // Hardcoded for backward compatibility
 
diff --git a/sprout_test.go b/sprout_test.go
index 589e3c4..1f8aa65 100644
--- a/sprout_test.go
+++ b/sprout_test.go
@@ -6,18 +6,19 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
-func TestNewFunctionHandler_DefaultValues(t *testing.T) {
-	handler := NewFunctionHandler()
+func TestNew_DefaultValues(t *testing.T) {
+	handler := New()
 
 	assert.NotNil(t, handler)
 	assert.NotNil(t, handler.Logger)
 }
 
-func TestNewFunctionHandler_CustomValues(t *testing.T) {
+func TestNew_CustomValues(t *testing.T) {
 	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
-	handler := NewFunctionHandler(
+	handler := New(
 		WithLogger(logger),
 	)
 
@@ -29,8 +30,8 @@ func TestWithLogger(t *testing.T) {
 	logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
 	option := WithLogger(logger)
 
-	handler := NewFunctionHandler()
-	option(handler) // Apply the option
+	handler := New()
+	require.NoError(t, option(handler)) // Apply the option
 
 	assert.Equal(t, logger, handler.Logger())
 }
@@ -42,7 +43,7 @@ func TestWithParser(t *testing.T) {
 	option := WithHandler(fnHandler)
 
 	handler := New()
-	option(handler) // Apply the option
+	require.NoError(t, option(handler)) // Apply the option
 
 	assert.Equal(t, fnHandler, handler)
 }
@@ -54,7 +55,7 @@ func TestWithNilHandler(t *testing.T) {
 	option := WithHandler(nil)
 
 	beforeApply := fnHandler
-	option(beforeApply)
+	require.NoError(t, option(beforeApply)) // Apply the option
 
 	assert.Equal(t, beforeApply, fnHandler)
 }
@@ -64,13 +65,13 @@ func TestWithSafeFuncs(t *testing.T) {
 	assert.True(t, handler.wantSafeFuncs)
 
 	handler.cachedFuncsMap["test"] = func() {}
-	funcCount := len(handler.Functions())
+	funcCount := len(handler.RawFunctions())
 	handler.Build()
 
 	assert.Len(t, handler.cachedFuncsMap, funcCount*2)
 
 	var keys []string
-	for k := range handler.Functions() {
+	for k := range handler.RawFunctions() {
 		keys = append(keys, k)
 	}