Skip to content

Commit

Permalink
templates: Add custom template function registration (#4757)
Browse files Browse the repository at this point in the history
* Add custom template function registration

* Rename TemplateFunctions to CustomFunctions

* Add documentation

* Document CustomFunctions interface

* Preallocate custom functions map list

* Fix interface name in error message
  • Loading branch information
kroppt committed May 2, 2022
1 parent 4a223f5 commit e84e19a
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 8 deletions.
32 changes: 28 additions & 4 deletions modules/caddyhttp/templates/templates.go
Expand Up @@ -21,6 +21,7 @@ import (
"net/http"
"strconv"
"strings"
"text/template"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
Expand All @@ -36,6 +37,8 @@ func init() {
//
// ⚠️ Template functions/actions are still experimental, so they are subject to change.
//
// Custom template functions can be registered by creating a plugin module under the `http.handlers.templates.functions.*` namespace that implements the `CustomFunctions` interface.
//
// [All Sprig functions](https://masterminds.github.io/sprig/) are supported.
//
// In addition to the standard functions and the Sprig library, Caddy adds
Expand Down Expand Up @@ -249,6 +252,14 @@ type Templates struct {
// The template action delimiters. If set, must be precisely two elements:
// the opening and closing delimiters. Default: `["{{", "}}"]`
Delimiters []string `json:"delimiters,omitempty"`

customFuncs []template.FuncMap
}

// Customfunctions is the interface for registering custom template functions.
type CustomFunctions interface {
// CustomTemplateFunctions should return the mapping from custom function names to implementations.
CustomTemplateFunctions() template.FuncMap
}

// CaddyModule returns the Caddy module information.
Expand All @@ -261,6 +272,18 @@ func (Templates) CaddyModule() caddy.ModuleInfo {

// Provision provisions t.
func (t *Templates) Provision(ctx caddy.Context) error {
fnModInfos := caddy.GetModules("http.handlers.templates.functions")
customFuncs := make([]template.FuncMap, len(fnModInfos), 0)
for _, modInfo := range fnModInfos {
mod := modInfo.New()
fnMod, ok := mod.(CustomFunctions)
if !ok {
return fmt.Errorf("module %q does not satisfy the CustomFunctions interface", modInfo.ID)
}
customFuncs = append(customFuncs, fnMod.CustomTemplateFunctions())
}
t.customFuncs = customFuncs

if t.MIMETypes == nil {
t.MIMETypes = defaultMIMETypes
}
Expand Down Expand Up @@ -331,10 +354,11 @@ func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Reque
}

ctx := &TemplateContext{
Root: fs,
Req: r,
RespHeader: WrappedHeader{rr.Header()},
config: t,
Root: fs,
Req: r,
RespHeader: WrappedHeader{rr.Header()},
config: t,
CustomFuncs: t.customFuncs,
}

err := ctx.executeTemplateInBuffer(r.URL.Path, rr.Buffer())
Expand Down
14 changes: 10 additions & 4 deletions modules/caddyhttp/templates/tplcontext.go
Expand Up @@ -40,10 +40,11 @@ import (

// TemplateContext is the TemplateContext with which HTTP templates are executed.
type TemplateContext struct {
Root http.FileSystem
Req *http.Request
Args []interface{} // defined by arguments to funcInclude
RespHeader WrappedHeader
Root http.FileSystem
Req *http.Request
Args []interface{} // defined by arguments to funcInclude
RespHeader WrappedHeader
CustomFuncs []template.FuncMap // functions added by plugins

config *Templates
tpl *template.Template
Expand All @@ -62,6 +63,11 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
// add sprig library
c.tpl.Funcs(sprigFuncMap)

// add all custom functions
for _, funcMap := range c.CustomFuncs {
c.tpl.Funcs(funcMap)
}

// add our own library
c.tpl.Funcs(template.FuncMap{
"include": c.funcInclude,
Expand Down

0 comments on commit e84e19a

Please sign in to comment.