feat: add template.FromGlobsWithAdditionalFuncs (#3174)

* refactor: add Options to the template.FromGlob function to allow customizing the Template

Signed-off-by: Martin Chodur <m.chodur@seznam.cz>
This commit is contained in:
Martin Chodur 2022-12-16 15:13:13 +01:00 committed by GitHub
parent d9c847e38b
commit 26cbd6bd86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 10 deletions

View File

@ -84,7 +84,7 @@ func CheckConfig(args []string) error {
fmt.Printf(" - %d receivers\n", len(cfg.Receivers))
fmt.Printf(" - %d templates\n", len(cfg.Templates))
if len(cfg.Templates) > 0 {
_, err = template.FromGlobs(cfg.Templates...)
_, err = template.FromGlobs(cfg.Templates)
if err != nil {
fmt.Printf(" FAILED: %s\n", err)
failed++

View File

@ -107,7 +107,7 @@ func configureTemplateRenderCmd(cc *kingpin.CmdClause) {
}
func (c *templateRenderCmd) render(ctx context.Context, _ *kingpin.ParseContext) error {
tmpl, err := template.FromGlobs(c.templateFilesGlobs...)
tmpl, err := template.FromGlobs(c.templateFilesGlobs)
if err != nil {
return err
}

View File

@ -419,7 +419,7 @@ func run() int {
configLogger,
)
configCoordinator.Subscribe(func(conf *config.Config) error {
tmpl, err = template.FromGlobs(conf.Templates...)
tmpl, err = template.FromGlobs(conf.Templates)
if err != nil {
return errors.Wrap(err, "failed to parse templates")
}

View File

@ -183,7 +183,7 @@ func notifyEmailWithContext(ctx context.Context, cfg *config.EmailConfig, server
return nil, false, err
}
tmpl, err := template.FromGlobs()
tmpl, err := template.FromGlobs([]string{})
if err != nil {
return nil, false, err
}

View File

@ -128,7 +128,7 @@ func DefaultRetryCodes() []int {
// CreateTmpl returns a ready-to-use template.
func CreateTmpl(t *testing.T) *template.Template {
tmpl, err := template.FromGlobs()
tmpl, err := template.FromGlobs([]string{})
require.NoError(t, err)
tmpl.ExternalURL, _ = url.Parse("http://am")
return tmpl

View File

@ -42,16 +42,24 @@ type Template struct {
ExternalURL *url.URL
}
// Option is generic modifier of the text and html templates used by a Template.
type Option func(text *tmpltext.Template, html *tmplhtml.Template)
// FromGlobs calls ParseGlob on all path globs provided and returns the
// resulting Template.
func FromGlobs(paths ...string) (*Template, error) {
// resulting Template. Options allows customization of the text and html templates in given order.
// The DefaultFuncs have precedence over any added custom functions.
func FromGlobs(paths []string, options ...Option) (*Template, error) {
t := &Template{
text: tmpltext.New("").Option("missingkey=zero"),
html: tmplhtml.New("").Option("missingkey=zero"),
}
t.text = t.text.Funcs(tmpltext.FuncMap(DefaultFuncs))
t.html = t.html.Funcs(tmplhtml.FuncMap(DefaultFuncs))
for _, o := range options {
o(t.text, t.html)
}
t.text.Funcs(tmpltext.FuncMap(DefaultFuncs))
t.html.Funcs(tmplhtml.FuncMap(DefaultFuncs))
defaultTemplates := []string{"default.tmpl", "email.tmpl"}

View File

@ -14,8 +14,10 @@
package template
import (
tmplhtml "html/template"
"net/url"
"testing"
tmpltext "text/template"
"time"
"github.com/prometheus/common/model"
@ -277,7 +279,7 @@ func TestData(t *testing.T) {
}
func TestTemplateExpansion(t *testing.T) {
tmpl, err := FromGlobs()
tmpl, err := FromGlobs([]string{})
require.NoError(t, err)
for _, tc := range []struct {
@ -387,3 +389,67 @@ func TestTemplateExpansion(t *testing.T) {
})
}
}
func TestTemplateExpansionWithOptions(t *testing.T) {
testOptionWithAdditionalFuncs := func(funcs FuncMap) Option {
return func(text *tmpltext.Template, html *tmplhtml.Template) {
text.Funcs(tmpltext.FuncMap(funcs))
html.Funcs(tmplhtml.FuncMap(funcs))
}
}
for _, tc := range []struct {
options []Option
title string
in string
data interface{}
html bool
exp string
fail bool
}{
{
title: "Test custom function",
options: []Option{testOptionWithAdditionalFuncs(FuncMap{"printFoo": func() string { return "foo" }})},
in: `{{ printFoo }}`,
exp: "foo",
},
{
title: "Test Default function with additional function added",
options: []Option{testOptionWithAdditionalFuncs(FuncMap{"printFoo": func() string { return "foo" }})},
in: `{{ toUpper "test" }}`,
exp: "TEST",
},
{
title: "Test custom function is overridden by the DefaultFuncs",
options: []Option{testOptionWithAdditionalFuncs(FuncMap{"toUpper": func(s string) string { return "foo" }})},
in: `{{ toUpper "test" }}`,
exp: "TEST",
},
{
title: "Test later Option overrides the previous",
options: []Option{
testOptionWithAdditionalFuncs(FuncMap{"printFoo": func() string { return "foo" }}),
testOptionWithAdditionalFuncs(FuncMap{"printFoo": func() string { return "bar" }}),
},
in: `{{ printFoo }}`,
exp: "bar",
},
} {
tc := tc
t.Run(tc.title, func(t *testing.T) {
tmpl, err := FromGlobs([]string{}, tc.options...)
require.NoError(t, err)
f := tmpl.ExecuteTextString
if tc.html {
f = tmpl.ExecuteHTMLString
}
got, err := f(tc.in, tc.data)
if tc.fail {
require.NotNil(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.exp, got)
})
}
}