sprout

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2024 License: MIT Imports: 9 Imported by: 10

README

Sprout Logo

[!NOTE] Sprout is an evolved variant of the Masterminds/sprig library, reimagined for modern Go versions. It introduces fresh functionalities and commits to maintaining the library, picking up where Sprig left off. Notably, Sprig had not seen updates for two years and was not compatible beyond Golang 1.13, necessitating the creation of Sprout.

Motivation

Sprout was born out of the need for a modernized, maintained, and performant template function library. Sprig, the predecessor to Sprout, had not seen updates for two years and was not optimized for later versions of Golang. Sprout aims to fill this gap by providing a library that is actively maintained, compatible with the latest Go versions, and optimized for performance.

Roadmap to Sprout v1.0

You can track our progress towards Sprout v1.0 by following the documentation page here.

Table of Contents

Transitioning from Sprig

Sprout provide a package sprigin to provide a drop-in replacement for Sprig in the v1.0, with the same function names and behavior. To use Sprout in your project, simply replace the Sprig import with sprigin:

[!IMPORTANT] The sprigin package is a temporary solution to provide backward compatibility with Sprig. We recommend updating your code to use the Sprout package directly to take advantage of the new features and improvements.

A complete guide are available in the documentation.

import (
-  "github.com/Masterminds/sprig/v3"
+  "github.com/go-sprout/sprout/sprigin"
)

tpl := template.Must(
  template.New("base").
-   Funcs(sprig.FuncMap()).
+   Funcs(sprigin.FuncMap()).
    ParseGlob("*.tmpl")
)

Usage

Creating a Handler

A handler in Sprout is responsible for managing the function registries and functions. The DefaultHandler is the primary implementation provided by Sprout.

import "github.com/go-sprout/sprout"

handler := sprout.New()

Customizing the Handler

Sprout supports various customization options using handler options:

handler := sprout.New(
  // Add your logger to the handler to log errors and debug information using the
  // standard slog package or any other logger that implements the slog.Logger interface.
  // By default, Sprout uses a slog.TextHandler.
  sprout.WithLogger(slogLogger),
  // Set the alias for a function. By default, Sprout use alias for some functions for backward compatibility with Sprig.
  sprout.WithAlias("hello", "hi"),
)

Working with Registries

Registries in Sprout are groups of functions that can be added to a handler. They help organize functions and optimize template performance.

You can retrieve all built-ins registries and functions under Registries.

import (
  "github.com/go-sprout/sprout/registry/conversion" // toString, toInt, toBool, ...
  "github.com/go-sprout/sprout/registry/std" // default, empty, any, all, ...
)

//...

handler.AddRegistries(
  conversion.NewRegistry(),
  std.NewRegistry(),
)

Building Function Maps

To use Sprout with templating engines like html/template or text/template, you need to build the function map:

funcs := handler.Build()
tpl := template.New("example").Funcs(funcs).Parse(`{{ hello }}`)

Working with Templates

Once your function map is ready, you can use it to render templates:

tpl, err := template.New("example").Funcs(funcs).Parse(`{{ myFunc }}`)
if err != nil {
    log.Fatal(err)
}
tpl.Execute(os.Stdout, nil)

This will render the template with all functions and aliases available.

Usage: Quick Example

Here is a quick example of how to use Sprout with the text/template package:

package main

import (
	"os"
	"text/template"

	"github.com/go-sprout/sprout"
	"github.com/go-sprout/sprout/registry/std"
)

func main() {
	handler := sprout.New()
	handler.AddRegistry(std.NewRegistry())

	tpl := template.Must(
    template.New("example").Funcs(handler.Build()).Parse(`{{ hello }}`),
  )
	tpl.Execute(os.Stdout, nil)
}

Performence Benchmarks

To see all the benchmarks, please refer to the benchmarks directory.

Sprig v3.2.3 vs Sprout v0.5

goos: linux
goarch: amd64
pkg: sprout_benchmarks
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
BenchmarkSprig-16              1        2991811373 ns/op        50522680 B/op      32649 allocs/op
BenchmarkSprout-16             1        1638797544 ns/op        42171152 B/op      18061 allocs/op
PASS
ok      sprout_benchmarks       4.921s

Time improvement: ((2991811373 - 1638797544) / 2991811373) * 100 = 45.3% Memory improvement: ((50522680 - 42171152) / 50522680) * 100 = 16.5%

So, Sprout v0.5 is approximately 45.3% faster and uses 16.5% less memory than Sprig v3.2.3. 🚀

You can see the full benchmark results here.

Development Philosophy (Currently in reflexion to create our)

Our approach to extending and refining Sprout was guided by several key principles:

  • Build on the principles of simplicity, flexibility, and consistency.
  • Empower developers to create robust templates without sacrificing performance or usability.
  • Adheres strictly to Go's templating conventions, ensuring a seamless experience for those familiar with Go's native tools.
  • Naming conventions across functions are standardized for predictability and ease of use.
  • Emphasizes error handling, preferring safe defaults over panics.
  • Provide a clear and comprehensive documentation to help users understand the library and its features.
  • Maintain a high level of code quality, ensuring that the library is well-tested, performant, and reliable.
  • Continuously improve and optimize the library to meet the needs of the community.
  • Avoids any external dependencies within template functions, ensuring all operations are self-contained and reliable.
  • Performance is a key consideration, with a focus on optimizing the library for speed and efficiency.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrConvertFailed = errors.New("failed to convert")

ErrConvertFailed is an error message when converting a value to another type fails. You can create a new error message using NewErrConvertFailed.

Functions

func AddAlias added in v0.5.0

func AddAlias(aliasMap FunctionAliasMap, originalFunction string, aliases ...string)

AddAlias adds an alias for the original function name. The original function name must already exist in the FunctionHandler's function map. If the original function name does not exist, the alias is not added.

func AddFunction added in v0.5.0

func AddFunction(funcsMap FunctionMap, name string, function any)

AddFunction adds a new function under the specified name to the given registry. If the function name already exists in the registry, this method does nothing to prevent accidental overwriting of existing registered functions.

func AddNotice added in v0.6.0

func AddNotice(notices *[]FunctionNotice, notice *FunctionNotice)

AddNotice adds a new function notice to the given function

func AssignAliases added in v0.5.0

func AssignAliases(h Handler)

AssignAliases assigns all aliases defined in the handler to their original functions. This function is used to ensure that all aliases are properly associated with their original functions in the handler instance.

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 AssignNotices added in v0.6.0

func AssignNotices(h Handler)

AssignNotices assigns all notices defined in the handler to their original functions. This function is used to ensure that all notices are properly associated with their original functions in the handler instance.

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 AssignSafeFuncs added in v0.6.0

func AssignSafeFuncs(handler Handler)

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 ErrRecoverPanic added in v0.6.0

func ErrRecoverPanic(err *error, errMessage string)

ErrRecoverPanic are an utility function to recover panic from a function and set the error message to unsure no panic is thrown in the template engine.

This is very useful when you are calling a function that might panic and you want to catch the panic and return an error message instead like when you use an external package that might panic (e.g.: yaml package).

func NewErrConvertFailed added in v0.6.0

func NewErrConvertFailed(typ string, value any, err error) error

NewErrConvertFailed is an utility function to create a new error message when converting a value to another type fails. Th error generated will contain the type of the value, the value itself, and the error message.

You can check if the error is an ErrConvertFailed using errors.Is.

Types

type DefaultHandler added in v0.5.0

type DefaultHandler struct {
	// contains filtered or unexported fields
}

DefaultHandler manages function execution with configurable error handling and logging.

func New added in v0.5.0

New creates and returns a new instance of DefaultHandler with optional configurations.

The DefaultHandler is initialized with default values, including an error handling strategy, a logger, and empty function maps. You can customize the DefaultHandler instance by passing in one or more HandlerOption functions, which apply specific configurations to the handler.

Example usage:

logger := slog.New(slog.NewTextHandler(os.Stdout))
handler := New(
    WithLogger(logger),
    WithRegistries(myRegistry),
)

In the above example, the DefaultHandler is created with a custom logger and a specific registry.

func (*DefaultHandler) AddRegistries added in v0.5.0

func (dh *DefaultHandler) AddRegistries(registries ...Registry) error

RegisterHandlers registers multiple FunctionRegistry implementations into the FunctionHandler's internal function registry. This method simplifies the process of adding multiple sets of functionalities into the template engine at once.

func (*DefaultHandler) AddRegistry added in v0.5.0

func (dh *DefaultHandler) AddRegistry(reg Registry) error

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.

func (*DefaultHandler) Build added in v0.5.0

func (dh *DefaultHandler) Build() FunctionMap

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. 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 replaces the github.com/Masterminds/sprig.FuncMap, github.com/Masterminds/sprig.TxtFuncMap and github.com/Masterminds/sprig.HtmlFuncMap from sprig

func (*DefaultHandler) Logger added in v0.5.0

func (dh *DefaultHandler) Logger() *slog.Logger

Logger returns the logger instance associated with the DefaultHandler.

The logger is used for logging information, warnings, and errors that occur during the execution of functions managed by the DefaultHandler. By default, the logger is initialized with a basic text handler, but it can be customized using the WithLogger option when creating a new DefaultHandler.

func (*DefaultHandler) Notices added in v0.6.0

func (dh *DefaultHandler) Notices() []FunctionNotice

Notices returns the list of function notices managed by the DefaultHandler.

The notices list contains information about functions that have been deprecated or are otherwise subject to special handling. Each notice includes the name of the function, a message describing the notice, and the kind of notice (e.g., info or deprecated).

func (*DefaultHandler) RawAliases added in v0.6.0

func (dh *DefaultHandler) RawAliases() FunctionAliasMap

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 (*DefaultHandler) RawFunctions added in v0.6.0

func (dh *DefaultHandler) RawFunctions() FunctionMap

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.

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.

type FunctionAliasMap

type FunctionAliasMap = map[string][]string

FunctionAliasMap is a map that stores a list of aliases for each function.

type FunctionMap added in v0.5.0

type FunctionMap = template.FuncMap

FunctionMap is an alias for template.FuncMap, which maps function names to functions. This registry is used to register all template functions.

type FunctionNotice added in v0.6.0

type FunctionNotice struct {
	// FunctionNames is a list of function names to which the notice should be
	// applied. The function names are case-sensitive.
	FunctionNames []string

	// Kind is the kind of the notice
	Kind NoticeKind

	// Message is the message of the notice
	Message string
}

func NewDebugNotice added in v0.6.0

func NewDebugNotice(functionName, message string) *FunctionNotice

NewDebugNotice creates a new debug function notice with the given function name and message. The function name is case-sensitive. The message is a string that provides additional information for debugging purposes. The message can contain the "$out" placeholder, which will be replaced with the output of the function.

func NewDeprecatedNotice added in v0.6.0

func NewDeprecatedNotice(functionName, message string) *FunctionNotice

NewDeprecatedNotice creates a new deprecated function notice with the given function name and message. The function name is case-sensitive. The message is a string that describes what the user should do instead of using the deprecated function.

func NewInfoNotice added in v0.6.0

func NewInfoNotice(functionName, message string) *FunctionNotice

NewInfoNotice creates a new informational function notice with the given function name and message. The function name is case-sensitive. The message is a string that provides additional information.

func NewNotice added in v0.6.0

func NewNotice(kind NoticeKind, functionNames []string, message string) *FunctionNotice

NewNotice creates a new function notice with the given function names, kind, and message. The function names are case-sensitive. The kind should be one of the predefined NoticeKind values. The message is a string that describes the notice.

Example:

notice := NewNotice(NoticeKindDeprecated, []string{"myFunc"}, "please use myNewFunc instead")

This example creates a new notice that indicates the function "myFunc" is deprecated and should be replaced with "myNewFunc" during template rendering.

type Handler added in v0.5.0

type Handler interface {
	Logger() *slog.Logger

	// AddRegistry registers a single registry,  into the Handler.
	// This method allows for integrating additional functions into the template
	// processing environment.
	AddRegistry(registry Registry) error

	// 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

	// 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

	// Build retrieves the complete suite of functions and aliases 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.
	//
	// Build should call AssignAliases and AssignNotices to ensure that all aliases
	// and notices are properly associated with their original functions.
	Build() FunctionMap
}

Handler is the interface that wraps the basic methods of a handler to manage all registries and functions. The Handler brick is the main brick of sprout. It is used to configure and manage a cross-registry configuration and function management like a global logging system, error handling, and more. ! This interface is not meant to be implemented by the user but by the ! library itself. An user could implement it but it is not recommended.

type HandlerOption added in v0.5.0

type HandlerOption[T Handler] func(T) error

HandlerOption[Handler] defines a type for functional options that configure a typed Handler.

func WithAlias

func WithAlias(originalFunction string, aliases ...string) HandlerOption[*DefaultHandler]

WithAlias returns a FunctionOption[**DefaultHandler] that associates one or more alias names with an original function name. This allows the function to be called by any of its aliases.

originalFunction specifies the original function name to which aliases will be added. aliases is a variadic parameter that takes one or more strings as aliases for the original function.

The function does nothing if no aliases are provided. If the original function name does not already have associated aliases in the DefaultHandler, a new slice of strings is created to hold its aliases. Each provided alias is then appended to this slice.

This option must be applied to a DefaultHandler using the DefaultHandler's options mechanism for the aliases to take effect.

Example:

handler := New(WithAlias("originalFunc", "alias1", "alias2"))

func WithAliases

func WithAliases(aliases FunctionAliasMap) HandlerOption[*DefaultHandler]

WithAliases returns a FunctionOption[**DefaultHandler] that configures multiple aliases for function names in a single call. It allows a batch of functions to be associated with their respective aliases, facilitating the creation of aliases for multiple functions at once.

This option must be applied to a DefaultHandler using the DefaultHandler's options mechanism for the aliases to take effect. It complements the WithAlias function by providing a means to configure multiple aliases in one operation, rather than one at a time.

Example:

handler := New(WithAliases(sprout.FunctionAliasMap{
    "originalFunc1": {"alias1_1", "alias1_2"},
    "originalFunc2": {"alias2_1", "alias2_2"},
}))

func WithHandler added in v0.5.0

func WithHandler(new Handler) HandlerOption[*DefaultHandler]

WithHandler updates a DefaultHandler with settings from another DefaultHandler. This is useful for copying configurations between handlers.

func WithLogger

func WithLogger(l *slog.Logger) HandlerOption[*DefaultHandler]

WithLogger sets the logger used by a DefaultHandler.

func WithNotices added in v0.6.0

func WithNotices(notices ...*FunctionNotice) HandlerOption[*DefaultHandler]

WithNotices is used to add one or more function notices to the handler. This option allows you to associate a notice with a function, providing information about the function's deprecation or other special handling.

The notices are applied to the original function name and its aliases. You can use the ApplyOnAliases method on the FunctionNotice to control whether the notice should be applied to aliases.

func WithRegistries added in v0.6.0

func WithRegistries(registries ...Registry) HandlerOption[*DefaultHandler]

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 WithSafeFuncs added in v0.6.0

func WithSafeFuncs(enabled bool) HandlerOption[*DefaultHandler]

WithSafeFuncs enables safe function calls in a DefaultHandler. When safe functions are enabled, the handler will wrap all functions with a safe wrapper that logs any errors that occur during function execution without interrupting the execution of the template.

To use a safe function, prepend `safe` to the original function name, example: `safeOriginalFuncName` instead of `originalFuncName`.

type NoticeKind added in v0.6.0

type NoticeKind int

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.

const (
	// NoticeKindDeprecated indicates that the function is deprecated.
	NoticeKindDeprecated NoticeKind = iota + 1
	// NoticeKindInfo indicates that the notice is informational.
	NoticeKindInfo
	// NoticeKindDebug indicates that the notice is for debugging purposes.
	// When using this kind, the notice message can contain the "$out" placeholder
	// which will be replaced with the output of the function.
	NoticeKindDebug
)

type Registry added in v0.5.0

type Registry interface {
	// Uid returns the unique name of the registry. This name is used to identify
	// the registry and in future prevent duplicate registry registration.
	// TODO: Consider implement a solution for duplicate registry registration.
	Uid() string
	// LinkHandler links the given Handler to the registry.
	// * This method help you to have access to the main handler and its
	// * functionalities, like the logger, error handling, and more.
	LinkHandler(fh Handler) error
	// RegisterFunctions adds the provided functions into the given function map.
	// This method is called by an Handler to register all functions of a registry.
	RegisterFunctions(fnMap FunctionMap) error
}

Registry is an interface that defines the method to register functions within a given Handler. The Registry brick are a way to group functions together and register them with a Handler. This is useful to split functions into different categories and to avoid having a single large file with all functions and optimize the performance of the template engine. It also allows for easy extension of the template functions by adding a new one.

type RegistryWithAlias added in v0.5.0

type RegistryWithAlias interface {
	// RegisterAliases adds the provided aliases into the given alias map.
	// This method is called by an Handler to register all aliases of a registry.
	RegisterAliases(aliasMap FunctionAliasMap) error
}

type RegistryWithNotice added in v0.6.0

type RegistryWithNotice interface {
	// RegisterNotices adds the provided notices into the given notice list.
	// This method is called by an Handler to register all notices of a registry.
	RegisterNotices(notices *[]FunctionNotice) error
}

Directories

Path Synopsis
This package are used to store all helpers functions used to validate and log deprecated functions and signatures in the project codebase.
This package are used to store all helpers functions used to validate and log deprecated functions and signatures in the project codebase.
internal
*
registry
*
env
std

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL