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 receivers\n", len(cfg.Receivers))
fmt.Printf(" - %d templates\n", len(cfg.Templates)) fmt.Printf(" - %d templates\n", len(cfg.Templates))
if len(cfg.Templates) > 0 { if len(cfg.Templates) > 0 {
_, err = template.FromGlobs(cfg.Templates...) _, err = template.FromGlobs(cfg.Templates)
if err != nil { if err != nil {
fmt.Printf(" FAILED: %s\n", err) fmt.Printf(" FAILED: %s\n", err)
failed++ failed++

View File

@ -107,7 +107,7 @@ func configureTemplateRenderCmd(cc *kingpin.CmdClause) {
} }
func (c *templateRenderCmd) render(ctx context.Context, _ *kingpin.ParseContext) error { 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 { if err != nil {
return err return err
} }

View File

@ -419,7 +419,7 @@ func run() int {
configLogger, configLogger,
) )
configCoordinator.Subscribe(func(conf *config.Config) error { configCoordinator.Subscribe(func(conf *config.Config) error {
tmpl, err = template.FromGlobs(conf.Templates...) tmpl, err = template.FromGlobs(conf.Templates)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to parse templates") 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 return nil, false, err
} }
tmpl, err := template.FromGlobs() tmpl, err := template.FromGlobs([]string{})
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }

View File

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

View File

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

View File

@ -14,8 +14,10 @@
package template package template
import ( import (
tmplhtml "html/template"
"net/url" "net/url"
"testing" "testing"
tmpltext "text/template"
"time" "time"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -277,7 +279,7 @@ func TestData(t *testing.T) {
} }
func TestTemplateExpansion(t *testing.T) { func TestTemplateExpansion(t *testing.T) {
tmpl, err := FromGlobs() tmpl, err := FromGlobs([]string{})
require.NoError(t, err) require.NoError(t, err)
for _, tc := range []struct { 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)
})
}
}