diff --git a/README.md b/README.md index 6b60513..fd5bb03 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ You can track our progress towards Sprout v1.0 by following the documentation pa - [Creating a Handler](#creating-a-handler) - [Customizing the Handler](#customizing-the-handler) - [Working with Registries](#working-with-registries) + - [Working with Registries Groups](#working-with-registries-groups) - [Building Function Maps](#building-function-maps) - [Working with Templates](#working-with-templates) - [Usage: Quick Example (code only)](#usage-quick-example) @@ -118,6 +119,23 @@ handler.AddRegistries( ) ``` +### Working with Registries Groups +In some cases, you can use a group of registries to add multiple registries at once. + +You can retrieve all built-ins registries groups under [Registry Groups](https://docs.atom.codes/sprout/groups/list-of-all-registry-groups). + +```go +import ( + "github.com/go-sprout/sprout/group/all" +) + +//... + +handler.AddGroup( + all.RegistryGroup(), +) +``` + ### Building Function Maps To use Sprout with templating engines like `html/template` or `text/template`, you need to build the function map: diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index ade0830..23e4195 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -12,6 +12,7 @@ ## Features * [Loader System (Registry)](features/loader-system-registry.md) +* [Loader System (Registry Group)](features/loader-system-registry-group.md) * [Function Aliases](features/function-aliases.md) * [Function Notices](features/function-notices.md) * [Safe Functions](features/safe-functions.md) @@ -40,10 +41,17 @@ * [Time](registries/time.md) * [Uniqueid](registries/uniqueid.md) +## Groups + +* [List of all registry groups](groups/list-of-all-registry-groups.md) +* [All](groups/all.md) +* [Hermetic](groups/hermetic.md) + ## Advanced * [How to create a handler](advanced/how-to-create-a-handler.md) * [How to create a registry](advanced/how-to-create-a-registry.md) +* [How to create a registry group](advanced/how-to-create-a-registry-group.md) ## Links diff --git a/docs/advanced/how-to-create-a-registry-group.md b/docs/advanced/how-to-create-a-registry-group.md new file mode 100644 index 0000000..a947a38 --- /dev/null +++ b/docs/advanced/how-to-create-a-registry-group.md @@ -0,0 +1,38 @@ +# How to create a registry group + +## File Naming Conventions + +* `{{registry_group_name}}.go`: This file defines the registry group, including key components like structs, interfaces, constants, and variables. +* `{{registry_group_name}}_test.go`: Includes tests for the registry group to ensure it functions as expected. + +{% hint style="info" %} +This structure ensures consistency and maintainability across different registries, making it easier for developers to contribute and collaborate effectively.\ +\ +For the rest of conventions please read [templating-conventions.md](../introduction/templating-conventions.md "mention"). +{% endhint %} + + +## Creating a Registry Group + +1. **New Repository**: You can start by creating a new repository on your GitHub account or organization. This will house your registry group functions. +2. **Contributing to Official Registry**: To add a new registry group to the official registry, submit a pull request (PR) after a discussion inside an issue. The official registry groups are organized under the `group/` folder. + +To start, in your `{{registry_group_name}}.go` file, create a function called `RegistryGroup` respecting the following signature: + +```go +func RegistryGroup() *sprout.RegistryGroup { + return sprout.NewRegistryGroup( + // Add your registries here + // registry.NewRegistry(), + ) +} +``` + +You can also create a group directly in your codebase without the need to create a new package. This is useful when you want to group registries that are specific to a project or a use case. +```go +group := sprout.NewRegistryGroup(registry.NewRegistry()) + +handler.AddGroups(group) +``` + +Once your group is defined, you can start using it in your projects. 🎉 diff --git a/docs/advanced/how-to-create-a-registry.md b/docs/advanced/how-to-create-a-registry.md index 324b03e..478f27a 100644 --- a/docs/advanced/how-to-create-a-registry.md +++ b/docs/advanced/how-to-create-a-registry.md @@ -1,85 +1,85 @@ -# How to create a registry - -## File Naming Conventions - -- `{{registry_name}}.go`: This file defines the registry, including key components like structs, interfaces, constants, and variables. -- `functions.go`: Contains the implementation of exported functions, making them accessible to other developers. -- `functions_test.go`: Includes tests for the exported functions to ensure they function as expected. -- `helpers.go`: Contains internal helper functions that support the registry but are not exposed for public use. -- `helpers_test.go`: Holds tests for the helper functions to validate their reliability. - -{% hint style="info" %} -This structure ensures consistency and maintainability across different registries, making it easier for developers to contribute and collaborate effectively.\ -\ -For the rest of conventions please read [templating-conventions.md](../introduction/templating-conventions.md "mention"). -{% endhint %} - -## Creating a Registry - -1. **New Repository**: You can start by creating a new repository on your GitHub account or organization. This will house your registry functions. -2. **Contributing to Official Registry**: To add your functions to the official registry, submit a pull request (PR). The official registries are organized under the `registry/` folder. - -{% hint style="info" %} -You can found an example of a registry under `registry/_example`. -{% endhint %} - -To start, in your `{{registry_name}}.go` file, start by creating a struct that implements the `Registry` interface. This struct will manage your custom functions and connect them to the handler. - -```go -package ownregistry - -import ( - "github.com/go-sprout/sprout" -) - -// OwnRegistry struct implements the Registry interface, embedding the Handler to access shared functionalities. -type OwnRegistry struct { - handler sprout.Handler // Embedding Handler for shared functionality -} - -// NewRegistry initializes and returns a new instance of your registry. -func NewRegistry() *OwnRegistry { - return &OwnRegistry{} -} - -// UID provides a unique identifier for your registry. -func (or *OwnRegistry) UID() string { - return "organization/repo.ownregistry" // Ensure this identifier is unique and uses lowercase, prefixed by your handler/repo separated with a dot. -} - -// LinkHandler connects the Handler to your registry, enabling runtime functionalities. -func (or *OwnRegistry) LinkHandler(fh sprout.Handler) error { - or.handler = fh - return nil -} - -// RegisterFunctions adds the provided functions into the given function map. -// This method is called by an Handler to register all functions of a registry. -func (or *OwnRegistry) RegisterFunctions(funcsMap sprout.FunctionMap) { - // Example of registering a function - sprout.AddFunction(funcsMap, "yourFunction", or.YourFunction) -} - -// OPTIONAL: Your registry don't needs to register aliases to work. -// RegisterAliases adds the provided aliases into the given alias map. -// method is called by an Handler to register all aliases of a registry. -func (or *OwnRegistry) RegisterAliases(aliasMap sprout.FunctionAliasMap) error { - // Example of registering an alias - sprout.AddAlias(aliasMap, "yourFunction", "yourAlias") -} -``` - -After create your registry structure and implement the `Registry` interface, you can start to define your functions in `functions.go`, you can access all features of the handler through - -```go -// YourFunction is an example function that returns a string and an error. -func (or *OwnRegistry) YourFunction() (string, error) { - return "Hello, World!", nil -} -``` - -{% hint style="danger" %} -**Important:** Make sure to write tests for your functions in `functions_test.go` to validate their functionality. -{% endhint %} - -Once your registry is defined and functions are implemented, you can start using it in your projects. 🎉 +# How to create a registry + +## File Naming Conventions + +- `{{registry_name}}.go`: This file defines the registry, including key components like structs, interfaces, constants, and variables. +- `functions.go`: Contains the implementation of exported functions, making them accessible to other developers. +- `functions_test.go`: Includes tests for the exported functions to ensure they function as expected. +- `helpers.go`: Contains internal helper functions that support the registry but are not exposed for public use. +- `helpers_test.go`: Holds tests for the helper functions to validate their reliability. + +{% hint style="info" %} +This structure ensures consistency and maintainability across different registries, making it easier for developers to contribute and collaborate effectively.\ +\ +For the rest of conventions please read [templating-conventions.md](../introduction/templating-conventions.md "mention"). +{% endhint %} + +## Creating a Registry + +1. **New Repository**: You can start by creating a new repository on your GitHub account or organization. This will house your registry functions. +2. **Contributing to Official Registry**: To add your functions to the official registry, submit a pull request (PR). The official registries are organized under the `registry/` folder. + +{% hint style="info" %} +You can found an example of a registry under `registry/_example`. +{% endhint %} + +To start, in your `{{registry_name}}.go` file, start by creating a struct that implements the `Registry` interface. This struct will manage your custom functions and connect them to the handler. + +```go +package ownregistry + +import ( + "github.com/go-sprout/sprout" +) + +// OwnRegistry struct implements the Registry interface, embedding the Handler to access shared functionalities. +type OwnRegistry struct { + handler sprout.Handler // Embedding Handler for shared functionality +} + +// NewRegistry initializes and returns a new instance of your registry. +func NewRegistry() *OwnRegistry { + return &OwnRegistry{} +} + +// UID provides a unique identifier for your registry. +func (or *OwnRegistry) UID() string { + return "organization/repo.ownregistry" // Ensure this identifier is unique and uses lowercase, prefixed by your handler/repo separated with a dot. +} + +// LinkHandler connects the Handler to your registry, enabling runtime functionalities. +func (or *OwnRegistry) LinkHandler(fh sprout.Handler) error { + or.handler = fh + return nil +} + +// RegisterFunctions adds the provided functions into the given function map. +// This method is called by an Handler to register all functions of a registry. +func (or *OwnRegistry) RegisterFunctions(funcsMap sprout.FunctionMap) { + // Example of registering a function + sprout.AddFunction(funcsMap, "yourFunction", or.YourFunction) +} + +// OPTIONAL: Your registry don't needs to register aliases to work. +// RegisterAliases adds the provided aliases into the given alias map. +// method is called by an Handler to register all aliases of a registry. +func (or *OwnRegistry) RegisterAliases(aliasMap sprout.FunctionAliasMap) error { + // Example of registering an alias + sprout.AddAlias(aliasMap, "yourFunction", "yourAlias") +} +``` + +After create your registry structure and implement the `Registry` interface, you can start to define your functions in `functions.go`, you can access all features of the handler through + +```go +// YourFunction is an example function that returns a string and an error. +func (or *OwnRegistry) YourFunction() (string, error) { + return "Hello, World!", nil +} +``` + +{% hint style="danger" %} +**Important:** Make sure to write tests for your functions in `functions_test.go` to validate their functionality. +{% endhint %} + +Once your registry is defined and functions are implemented, you can start using it in your projects. 🎉 \ No newline at end of file diff --git a/docs/features/function-safe.md b/docs/features/function-safe.md deleted file mode 100644 index 3c9940a..0000000 --- a/docs/features/function-safe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -description: >- - When you don't want to stop the execution of your template when an error occurs, - you can use the function-safe feature. ---- diff --git a/docs/features/loader-system-registry-group.md b/docs/features/loader-system-registry-group.md new file mode 100644 index 0000000..24aa80f --- /dev/null +++ b/docs/features/loader-system-registry-group.md @@ -0,0 +1,41 @@ +--- +description: 'Managing multiple registries can be simplified with RegistryGroup feature.' +--- + +# Loader System (Registry Groups) + +## Introduction + +The `RegistryGroup` struct enables the grouping of multiple registries, simplifying their management and enhancing modularity within the `Handler`. This approach promotes cleaner code organization and facilitates the reuse of registry configurations across different projects. + +{% hint style="info" %} +**Considerations** + +Using a `RegistryGroup` can help you manage multiple registries. But can lead to registering undesired functions if you are not careful. Be sure to review the functions in each registry to avoid conflicts or unexpected behavior. +{% endhint %} + +## How to use a registry group + +If you have creted a `RegistryGroup` or want to use a built-in group, you can add it to your handler using the `AddGroups` method: + +```go +err := handler.AddGroups(group1, group2) +if err != nil { + // Handle error +} +``` +You can also use the option to add registries when initializing the handler: + +```go +handler := sprout.New( + sprout.WithGroups(group1, group2), +) +``` + +In this example, `group1` and `group2` are added to the `handler`, allowing all registries within these groups to be registered collectively. + + + +## How to create a registry group + +To show how to create your own registry group, go to [how-to-create-a-registry-group.md](../advanced/how-to-create-a-registry-group.md "mention") diff --git a/docs/features/loader-system-registry.md b/docs/features/loader-system-registry.md index 8d220d5..0a4f08a 100644 --- a/docs/features/loader-system-registry.md +++ b/docs/features/loader-system-registry.md @@ -53,6 +53,10 @@ handler := sprout.New( This code sets up your project to utilize the functions from your custom registry, making it easy to integrate and extend functionality. +## You have too many registries to manage? + +You can use the [loader-system-registry-group.md](./loader-system-registry-group.md) feature to manage multiple registries at once or use built-in groups. + ## How to create a registry To show how to create your own registry, go to [how-to-create-a-registry.md](../advanced/how-to-create-a-registry.md "mention") diff --git a/docs/groups/all.md b/docs/groups/all.md new file mode 100644 index 0000000..dfc0fa7 --- /dev/null +++ b/docs/groups/all.md @@ -0,0 +1,35 @@ +--- +description: >- + The All registry group includes all the registries available in Sprout, + excluding deprecated and experimental registries. +--- + +# All + +{% hint style="info" %} +You can easily import group from the `all` group by including the following import statement in your code + +```go +import "github.com/go-sprout/sprout/group/all" +``` +{% endhint %} + +### List of registries + +* [**checksum**](checksum.md): Tools to generate and verify checksums for data integrity. +* [**conversion**](conversion.md): Functions to convert between different data types within templates. +* [**encoding**](encoding.md): Methods for encoding and decoding data in various formats. +* [**env**](env.md): Access and manipulate environment variables within templates. +* [**filesystem**](filesystem.md): Functions for interacting with the file system. +* [**maps**](maps.md): Tools to manipulate and interact with map data structures. +* [**network**](network.md): Functions to interact with network resources. +* [**numeric**](numeric.md): Utilities for numerical operations and calculations. +* [**random**](random.md): Functions to generate random numbers, strings, and other data. +* [**reflect**](reflect.md): Tools to inspect and manipulate data types using reflection. +* [**regexp**](regexp.md): Regular expression functions for pattern matching and string manipulation. +* [**semver**](semver.md): Functions to handle semantic versioning and comparison. +* [**slices**](slices.md): Utilities for slice operations, including filtering, sorting, and transforming. +* [**std**](std.md): Standard functions for common operations. +* [**strings**](strings.md): Functions for string manipulation, including formatting, splitting, and joining. +* [**time**](time.md): Tools to handle dates, times, and time-related calculations. +* [**uniqueid**](uniqueid.md): Functions to generate unique identifiers, such as UUIDs. diff --git a/docs/groups/hermetic.md b/docs/groups/hermetic.md new file mode 100644 index 0000000..ab87176 --- /dev/null +++ b/docs/groups/hermetic.md @@ -0,0 +1,33 @@ +--- +description: >- + The Hermetic registry group includes all the registries available in Sprout, + excluding registries that depend on external services or are influenced by the + environment where the application is running. +--- + +# All + +{% hint style="info" %} +You can easily import group from the `all` group by including the following import statement in your code + +```go +import "github.com/go-sprout/sprout/group/hermetic" +``` +{% endhint %} + +### List of registries + +* [**checksum**](checksum.md): Tools to generate and verify checksums for data integrity. +* [**conversion**](conversion.md): Functions to convert between different data types within templates. +* [**encoding**](encoding.md): Methods for encoding and decoding data in various formats. +* [**filesystem**](filesystem.md): Functions for interacting with the file system. +* [**maps**](maps.md): Tools to manipulate and interact with map data structures. +* [**numeric**](numeric.md): Utilities for numerical operations and calculations. +* [**reflect**](reflect.md): Tools to inspect and manipulate data types using reflection. +* [**regexp**](regexp.md): Regular expression functions for pattern matching and string manipulation. +* [**semver**](semver.md): Functions to handle semantic versioning and comparison. +* [**slices**](slices.md): Utilities for slice operations, including filtering, sorting, and transforming. +* [**std**](std.md): Standard functions for common operations. +* [**strings**](strings.md): Functions for string manipulation, including formatting, splitting, and joining. +* [**time**](time.md): Tools to handle dates, times, and time-related calculations. +* [**uniqueid**](uniqueid.md): Functions to generate unique identifiers, such as UUIDs. diff --git a/docs/groups/list-of-all-registry-groups.md b/docs/groups/list-of-all-registry-groups.md new file mode 100644 index 0000000..ee94fe4 --- /dev/null +++ b/docs/groups/list-of-all-registry-groups.md @@ -0,0 +1,21 @@ +--- +icon: list-radio +--- + +# List of all registries groups + +Every group is a collection of registries, and each registry is a collection of functions. +You can found more details about registry groups in the [loader-system-registry-group.md](../features/loader-system-registry-group.md) feature. + +### List of embed registry groups + +* [**all**](all.md): All registries available in Sprout excluding deprecated and experimental registries. +* [**hermetic**](hermetic.md): Registries don't depend on external services or influenced by the environment where the application is running. + +### Community registry groups + +{% hint style="info" %} +You can open an issue to ask to be listed here. We are a community :seedling: + +And maybe your registry will be embed on sprout directly, who know :eyes: +{% endhint %} diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index a408921..784b0a2 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -53,6 +53,14 @@ Sprout supports various customization options using handler options: ``` See more below or in dedicated page [loader-system-registry.md](../features/loader-system-registry.md "mention"). +* **Load Registry Group:**\ + You can load a group of registries directly on your handler using the `WithGroups` option: + + ```go + handler := sprout.New(sprout.WithGroups(ownregistrygroup.RegistryGroup())) + ``` + + See more below or in dedicated page [loader-system-registry-group.md](../features/loader-system-registry-group.md "mention"). * **Aliases Management:**\ You can specify your custom aliases directly on your handler: diff --git a/group.go b/group.go new file mode 100644 index 0000000..8783160 --- /dev/null +++ b/group.go @@ -0,0 +1,61 @@ +package sprout + +// RegistryGroup represents a collection of registries that can be registered +// together within a Handler. +// +// This struct simplifies the management of registries by allowing them to be grouped +// and added in bulk, enhancing modularity and organization within the Handler. +type RegistryGroup struct { + Registries []Registry +} + +// NewRegistryGroup initializes a RegistryGroup with the specified registries. +// +// Example: +// +// group := NewRegistryGroup(registry1.NewRegistry(), registry2.NewRegistry()) +func NewRegistryGroup(registries ...Registry) *RegistryGroup { + if len(registries) == 0 { + registries = make([]Registry, 0) + } + + return &RegistryGroup{ + Registries: registries, + } +} + +// AddGroups registers one or multiple RegistryGroups within the DefaultHandler. +// +// This method allows grouped registries to be added collectively. Each registry +// within each group is registered individually, simplifying large-scale registry +// addition. +// +// Example: +// +// handler.AddGroups(group1, group2) +func (dh *DefaultHandler) AddGroups(groups ...*RegistryGroup) error { + for _, group := range groups { + for _, registry := range group.Registries { + if err := dh.AddRegistry(registry); err != nil { + return err + } + } + } + return nil +} + +// WithGroups provides a HandlerOption to configure a DefaultHandler with the +// specified RegistryGroups. +// +// This option allows for the addition of grouped registries during Handler +// creation, enhancing organization and simplifying the registration of multiple +// registries in one step. +// +// Example: +// +// handler := New(WithGroups(group1, group2)) +func WithGroups(groups ...*RegistryGroup) HandlerOption[*DefaultHandler] { + return func(dh *DefaultHandler) error { + return dh.AddGroups(groups...) + } +} diff --git a/group/all/all.go b/group/all/all.go new file mode 100644 index 0000000..2aad1ad --- /dev/null +++ b/group/all/all.go @@ -0,0 +1,50 @@ +package all + +import ( + "github.com/go-sprout/sprout" + "github.com/go-sprout/sprout/registry/checksum" + "github.com/go-sprout/sprout/registry/conversion" + "github.com/go-sprout/sprout/registry/encoding" + "github.com/go-sprout/sprout/registry/env" + "github.com/go-sprout/sprout/registry/filesystem" + "github.com/go-sprout/sprout/registry/maps" + "github.com/go-sprout/sprout/registry/network" + "github.com/go-sprout/sprout/registry/numeric" + "github.com/go-sprout/sprout/registry/random" + "github.com/go-sprout/sprout/registry/reflect" + "github.com/go-sprout/sprout/registry/regexp" + "github.com/go-sprout/sprout/registry/semver" + "github.com/go-sprout/sprout/registry/slices" + "github.com/go-sprout/sprout/registry/std" + "github.com/go-sprout/sprout/registry/strings" + "github.com/go-sprout/sprout/registry/time" + "github.com/go-sprout/sprout/registry/uniqueid" +) + +// all.RegistryGroup is a group of all registries available in Sprout excluding +// deprecated and experimental registries. +// +// Included registries: checksum, conversion, encoding, env, filesystem, maps, +// network, numeric, random, reflect, regexp, semver, slices, std, strings, time, +// uniqueid. +func RegistryGroup() *sprout.RegistryGroup { + return sprout.NewRegistryGroup( + checksum.NewRegistry(), + conversion.NewRegistry(), + encoding.NewRegistry(), + env.NewRegistry(), + filesystem.NewRegistry(), + maps.NewRegistry(), + network.NewRegistry(), + numeric.NewRegistry(), + random.NewRegistry(), + reflect.NewRegistry(), + regexp.NewRegistry(), + semver.NewRegistry(), + slices.NewRegistry(), + std.NewRegistry(), + strings.NewRegistry(), + time.NewRegistry(), + uniqueid.NewRegistry(), + ) +} diff --git a/group/all/all_test.go b/group/all/all_test.go new file mode 100644 index 0000000..5c9366d --- /dev/null +++ b/group/all/all_test.go @@ -0,0 +1,34 @@ +package all_test + +import ( + "testing" + + "github.com/go-sprout/sprout/group/all" + "github.com/go-sprout/sprout/pesticide" +) + +func TestRegistryGroup(t *testing.T) { + tc := pesticide.GroupTestCase{ + RegistriesUIDs: []string{ + "go-sprout/sprout.checksum", + "go-sprout/sprout.conversion", + "go-sprout/sprout.encoding", + "go-sprout/sprout.env", + "go-sprout/sprout.filesystem", + "go-sprout/sprout.maps", + "go-sprout/sprout.network", + "go-sprout/sprout.numeric", + "go-sprout/sprout.random", + "go-sprout/sprout.reflect", + "go-sprout/sprout.regexp", + "go-sprout/sprout.semver", + "go-sprout/sprout.slices", + "go-sprout/sprout.std", + "go-sprout/sprout.strings", + "go-sprout/sprout.time", + "go-sprout/sprout.uniqueid", + }, + } + + pesticide.RunGroupTest(t, all.RegistryGroup(), tc) +} diff --git a/group/hermetic/hermetic.go b/group/hermetic/hermetic.go new file mode 100644 index 0000000..ec84adb --- /dev/null +++ b/group/hermetic/hermetic.go @@ -0,0 +1,43 @@ +package hermetic + +import ( + "github.com/go-sprout/sprout" + "github.com/go-sprout/sprout/registry/checksum" + "github.com/go-sprout/sprout/registry/conversion" + "github.com/go-sprout/sprout/registry/encoding" + "github.com/go-sprout/sprout/registry/filesystem" + "github.com/go-sprout/sprout/registry/maps" + "github.com/go-sprout/sprout/registry/numeric" + "github.com/go-sprout/sprout/registry/reflect" + "github.com/go-sprout/sprout/registry/regexp" + "github.com/go-sprout/sprout/registry/semver" + "github.com/go-sprout/sprout/registry/slices" + "github.com/go-sprout/sprout/registry/std" + "github.com/go-sprout/sprout/registry/strings" + "github.com/go-sprout/sprout/registry/time" + "github.com/go-sprout/sprout/registry/uniqueid" +) + +// hermetic.RegistryGroup is a group of all registries don't depend on external services +// or influenced by the environment where the application is running. +// +// Included registries: checksum, conversion, encoding, filesystem, maps, numeric, +// reflect, regexp, semver, slices, std, strings, time, uniqueid. +func RegistryGroup() *sprout.RegistryGroup { + return sprout.NewRegistryGroup( + checksum.NewRegistry(), + conversion.NewRegistry(), + encoding.NewRegistry(), + filesystem.NewRegistry(), + maps.NewRegistry(), + numeric.NewRegistry(), + reflect.NewRegistry(), + regexp.NewRegistry(), + semver.NewRegistry(), + slices.NewRegistry(), + std.NewRegistry(), + strings.NewRegistry(), + time.NewRegistry(), + uniqueid.NewRegistry(), + ) +} diff --git a/group/hermetic/hermetic_test.go b/group/hermetic/hermetic_test.go new file mode 100644 index 0000000..e554a67 --- /dev/null +++ b/group/hermetic/hermetic_test.go @@ -0,0 +1,31 @@ +package hermetic_test + +import ( + "testing" + + "github.com/go-sprout/sprout/group/hermetic" + "github.com/go-sprout/sprout/pesticide" +) + +func TestRegistryGroup(t *testing.T) { + tc := pesticide.GroupTestCase{ + RegistriesUIDs: []string{ + "go-sprout/sprout.checksum", + "go-sprout/sprout.conversion", + "go-sprout/sprout.encoding", + "go-sprout/sprout.filesystem", + "go-sprout/sprout.maps", + "go-sprout/sprout.numeric", + "go-sprout/sprout.reflect", + "go-sprout/sprout.regexp", + "go-sprout/sprout.semver", + "go-sprout/sprout.slices", + "go-sprout/sprout.std", + "go-sprout/sprout.strings", + "go-sprout/sprout.time", + "go-sprout/sprout.uniqueid", + }, + } + + pesticide.RunGroupTest(t, hermetic.RegistryGroup(), tc) +} diff --git a/group_test.go b/group_test.go new file mode 100644 index 0000000..d8a52e2 --- /dev/null +++ b/group_test.go @@ -0,0 +1,152 @@ +package sprout + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestNewRegistryGroup(t *testing.T) { + group := NewRegistryGroup() + + require.NotNil(t, group) + require.NotNil(t, group.Registries) +} + +func TestDefaultHandler_AddGroups(t *testing.T) { + mockRegistry1 := new(MockRegistry) + mockRegistry1.On("UID").Return("mockRegistry1") + mockRegistry1.On("LinkHandler", mock.Anything).Return() + mockRegistry1.On("RegisterFunctions", mock.Anything).Return() + + mockRegistry2 := new(MockRegistry) + mockRegistry2.On("UID").Return("mockRegistry2") + mockRegistry2.On("LinkHandler", mock.Anything).Return() + mockRegistry2.On("RegisterFunctions", mock.Anything).Return() + + group1 := NewRegistryGroup(mockRegistry1) + group2 := NewRegistryGroup(mockRegistry2) + + dh := &DefaultHandler{ + cachedFuncsMap: make(FunctionMap), + } + + err := dh.AddGroups(group1, group2) + require.NoError(t, err) + + mockRegistry1.AssertExpectations(t) + mockRegistry2.AssertExpectations(t) + + require.Len(t, dh.registries, 2, "Both registries should be added to the DefaultHandler") + assert.Contains(t, dh.registries, mockRegistry1, "First registry should match mockRegistry1") + assert.Contains(t, dh.registries, mockRegistry2, "Second registry should match mockRegistry2") + + mockRegistry1.AssertCalled(t, "LinkHandler", dh) + mockRegistry1.AssertCalled(t, "RegisterFunctions", dh.cachedFuncsMap) + mockRegistry2.AssertCalled(t, "LinkHandler", dh) + mockRegistry2.AssertCalled(t, "RegisterFunctions", dh.cachedFuncsMap) +} + +func TestDefaultHandler_AddGroups_Error(t *testing.T) { + mockRegistry := new(MockRegistry) + mockRegistry.linkHandlerMustCrash = true + mockRegistry.On("UID").Return("mockRegistry") + mockRegistry.On("LinkHandler", mock.Anything).Return(errMock) + + group1 := NewRegistryGroup(mockRegistry) + + dh := &DefaultHandler{ + cachedFuncsMap: make(FunctionMap), + } + + err := dh.AddGroups(group1) + require.ErrorIs(t, err, errMock, "Error should match the mock error") + + mockRegistry.AssertCalled(t, "LinkHandler", dh) + mockRegistry.AssertNotCalled(t, "RegisterFunctions", mock.Anything) +} + +func TestDefaultHandler_AddGroups_MultiplesTimes(t *testing.T) { + mockRegistry := new(MockRegistry) + mockRegistry.On("UID").Return("mockRegistry1") + mockRegistry.On("LinkHandler", mock.Anything).Return() + mockRegistry.On("RegisterFunctions", mock.Anything).Return() + + group1 := NewRegistryGroup(mockRegistry, mockRegistry) + group2 := NewRegistryGroup(mockRegistry, mockRegistry) + + dh := &DefaultHandler{ + cachedFuncsMap: make(FunctionMap), + } + + err := dh.AddGroups(group1, group2) + require.NoError(t, err) + + err = dh.AddGroups(group1) + require.NoError(t, err) + + mockRegistry.AssertExpectations(t) + + require.Len(t, dh.registries, 1, "Only one registry should be added to the DefaultHandler") + assert.Contains(t, dh.registries, mockRegistry, "Registry should match mockRegistry") + + mockRegistry.AssertCalled(t, "LinkHandler", dh) + mockRegistry.AssertCalled(t, "RegisterFunctions", dh.cachedFuncsMap) + mockRegistry.AssertNumberOfCalls(t, "LinkHandler", 1) + mockRegistry.AssertNumberOfCalls(t, "RegisterFunctions", 1) +} + +func TestWithGroups(t *testing.T) { + mockRegistry1 := new(MockRegistry) + mockRegistry1.On("UID").Return("mockRegistry1") + mockRegistry1.On("LinkHandler", mock.Anything).Return() + mockRegistry1.On("RegisterFunctions", mock.Anything).Return() + + mockRegistry2 := new(MockRegistry) + mockRegistry2.On("UID").Return("mockRegistry2") + mockRegistry2.On("LinkHandler", mock.Anything).Return() + mockRegistry2.On("RegisterFunctions", mock.Anything).Return() + + group1 := NewRegistryGroup(mockRegistry1) + group2 := NewRegistryGroup(mockRegistry2) + + dh := &DefaultHandler{ + cachedFuncsMap: make(FunctionMap), + } + + err := WithGroups(group1, group2)(dh) + require.NoError(t, err) + + mockRegistry1.AssertExpectations(t) + mockRegistry2.AssertExpectations(t) + + require.Len(t, dh.registries, 2, "Both registries should be added to the DefaultHandler") + assert.Contains(t, dh.registries, mockRegistry1, "First registry should match mockRegistry1") + assert.Contains(t, dh.registries, mockRegistry2, "Second registry should match mockRegistry2") + + mockRegistry1.AssertCalled(t, "LinkHandler", dh) + mockRegistry1.AssertCalled(t, "RegisterFunctions", dh.cachedFuncsMap) + mockRegistry2.AssertCalled(t, "LinkHandler", dh) + mockRegistry2.AssertCalled(t, "RegisterFunctions", dh.cachedFuncsMap) +} + +func TestWithGroups_Error(t *testing.T) { + mockRegistry := new(MockRegistry) + mockRegistry.linkHandlerMustCrash = true + mockRegistry.On("UID").Return("mockRegistry") + mockRegistry.On("LinkHandler", mock.Anything).Return(errMock) + + group1 := NewRegistryGroup(mockRegistry) + + dh := &DefaultHandler{ + cachedFuncsMap: make(FunctionMap), + } + + err := WithGroups(group1)(dh) + require.ErrorIs(t, err, errMock, "Error should match the mock error") + + mockRegistry.AssertCalled(t, "LinkHandler", dh) + mockRegistry.AssertNotCalled(t, "RegisterFunctions", mock.Anything) +} diff --git a/handler.go b/handler.go index a9e0efa..50a04e8 100644 --- a/handler.go +++ b/handler.go @@ -2,6 +2,7 @@ package sprout import ( "log/slog" + "slices" gostrings "strings" "golang.org/x/text/cases" @@ -64,7 +65,15 @@ type DefaultHandler struct { // RegisterHandler registers a single FunctionRegistry implementation (e.g., a handler) // into the FunctionHandler's internal function registry. This method allows for integrating // additional functions into the template processing environment. +// This function prevents duplicate registry registration by checking the UID +// of the registry. func (dh *DefaultHandler) AddRegistry(reg Registry) error { + if slices.ContainsFunc(dh.registries, func(r Registry) bool { + return r.UID() == reg.UID() + }) { + return nil + } + dh.registries = append(dh.registries, reg) if err := reg.LinkHandler(dh); err != nil { diff --git a/handler_test.go b/handler_test.go index 2b816d7..626036f 100644 --- a/handler_test.go +++ b/handler_test.go @@ -94,6 +94,29 @@ func TestDefaultHandler_AddRegistries_Error(t *testing.T) { mockRegistry.AssertNotCalled(t, "RegisterFunctions", mock.Anything) } +func TestDefaultHandler_AddRegistry_MultipleTimes(t *testing.T) { + mockRegistry := new(MockRegistry) + mockRegistry.On("UID").Return("mockRegistry") + mockRegistry.On("LinkHandler", mock.Anything).Return() + mockRegistry.On("RegisterFunctions", mock.Anything).Return() + + dh := &DefaultHandler{ + cachedFuncsMap: make(FunctionMap), + } + + err := dh.AddRegistry(mockRegistry) + require.NoError(t, err, "AddRegistry should not return an error") + + err = dh.AddRegistry(mockRegistry) + require.NoError(t, err, "AddRegistry should not return an error") + + require.Len(t, dh.registries, 1, "Registry should be added to the DefaultHandler") + assert.Contains(t, dh.registries, mockRegistry, "Registry should match the mock registry") + + mockRegistry.AssertNumberOfCalls(t, "LinkHandler", 1) + mockRegistry.AssertNumberOfCalls(t, "RegisterFunctions", 1) +} + // TestDefaultHandler_AddRegistries_Error tests the AddRegistry method of DefaultHandler when the registry returns an error. func TestDefaultHandler_AddRegistry_Error_RegisterFuiesctions(t *testing.T) { mockRegistry := new(MockRegistry) diff --git a/pesticide/group_test_helpers.go b/pesticide/group_test_helpers.go new file mode 100644 index 0000000..0669e83 --- /dev/null +++ b/pesticide/group_test_helpers.go @@ -0,0 +1,27 @@ +package pesticide + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/go-sprout/sprout" +) + +type GroupTestCase struct { + RegistriesUIDs []string +} + +func RunGroupTest(t *testing.T, rg *sprout.RegistryGroup, tc GroupTestCase) { + t.Helper() + + require.NotNil(t, rg, "RegistryGroup should not be nil") + require.NotNil(t, rg.Registries, "RegistryGroup.Registries should not be nil") + + require.Len(t, rg.Registries, len(tc.RegistriesUIDs), "The number of registries should match the number of expected registries") + + for _, registry := range rg.Registries { + assert.Contains(t, tc.RegistriesUIDs, registry.UID(), "Registry UID %s aren't excepted", registry.UID()) + } +} diff --git a/pesticide/test_helpers.go b/pesticide/template_test_helpers.go similarity index 100% rename from pesticide/test_helpers.go rename to pesticide/template_test_helpers.go