From 45a5bdf7d1b46a84df0b6a39fd0eee6ca707f111 Mon Sep 17 00:00:00 2001 From: Markus Thierolf <77847348+thierolm@users.noreply.github.com> Date: Sat, 26 Feb 2022 13:08:32 +0100 Subject: [PATCH] Use go templates/sprig for messages (#2527) --- cmd/config.go | 2 +- cmd/setup.go | 5 ++++- push/config.go | 5 ----- push/hub.go | 47 +++++++++++++++++++++++++++++++++++++++++---- util/format.go | 19 +++++++++++++++++- util/format_test.go | 1 + 6 files changed, 67 insertions(+), 12 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index fee86303f..c84ff2b77 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -64,7 +64,7 @@ type typedConfig struct { } type messagingConfig struct { - Events map[string]push.EventTemplate + Events map[string]push.EventTemplateConfig Services []typedConfig } diff --git a/cmd/setup.go b/cmd/setup.go index 02f3b22c4..7b0abb7b5 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -138,7 +138,10 @@ func configureEEBus(conf map[string]interface{}) error { // setup messaging func configureMessengers(conf messagingConfig, cache *util.Cache) chan push.Event { notificationChan := make(chan push.Event, 1) - notificationHub := push.NewHub(conf.Events, cache) + notificationHub, err := push.NewHub(conf.Events, cache) + if err != nil { + log.FATAL.Fatalf("failed configuring push services: %v", err) + } for _, service := range conf.Services { impl, err := push.NewMessengerFromConfig(service.Type, service.Other) diff --git a/push/config.go b/push/config.go index 20796f5ca..fa83fb320 100644 --- a/push/config.go +++ b/push/config.go @@ -12,11 +12,6 @@ type Sender interface { Send(title, msg string) } -// EventTemplate is the push message template for an event -type EventTemplate struct { - Title, Msg string -} - var log = util.NewLogger("push") // NewMessengerFromConfig creates a new messenger diff --git a/push/hub.go b/push/hub.go index 609922652..234da2f3c 100644 --- a/push/hub.go +++ b/push/hub.go @@ -1,8 +1,11 @@ package push import ( + "strings" + "text/template" "time" + "github.com/Masterminds/sprig/v3" "github.com/evcc-io/evcc/util" ) @@ -12,6 +15,16 @@ type Event struct { Event string } +// EventTemplateConfig is the push message configuration for an event +type EventTemplateConfig struct { + Title, Msg string +} + +// EventTemplate is the push message template for an event +type EventTemplate struct { + Title, Msg *template.Template +} + // Hub subscribes to event notifications and sends them to client devices type Hub struct { definitions map[string]EventTemplate @@ -20,12 +33,32 @@ type Hub struct { } // NewHub creates push hub with definitions and receiver -func NewHub(definitions map[string]EventTemplate, cache *util.Cache) *Hub { +func NewHub(cc map[string]EventTemplateConfig, cache *util.Cache) (*Hub, error) { + definitions := make(map[string]EventTemplate) + + // instantiate all event templates + for k, v := range cc { + var def EventTemplate + var err error + + def.Title, err = template.New("out").Funcs(template.FuncMap(sprig.FuncMap())).Parse(v.Title) + if err == nil { + def.Msg, err = template.New("out").Funcs(template.FuncMap(sprig.FuncMap())).Parse(v.Msg) + } + + if err != nil { + return nil, err + } + + definitions[k] = def + } + h := &Hub{ definitions: definitions, cache: cache, } - return h + + return h, nil } // Add adds a sender to the list of senders @@ -34,7 +67,7 @@ func (h *Hub) Add(sender Sender) { } // apply applies the event template to the content to produce the actual message -func (h *Hub) apply(ev Event, template string) (string, error) { +func (h *Hub) apply(ev Event, tmpl *template.Template) (string, error) { attr := make(map[string]interface{}) // let cache catch up, refs reverted https://github.com/evcc-io/evcc/pull/445 @@ -47,7 +80,13 @@ func (h *Hub) apply(ev Event, template string) (string, error) { } } - return util.ReplaceFormatted(template, attr) + // apply data attributes to template using sprig functions + applied := new(strings.Builder) + if err := tmpl.Execute(applied, attr); err != nil { + return "", err + } + + return util.ReplaceFormatted(applied.String(), attr) } // Run is the Hub's main publishing loop diff --git a/util/format.go b/util/format.go index 71811b5ac..4e4e60a3f 100644 --- a/util/format.go +++ b/util/format.go @@ -1,10 +1,14 @@ package util import ( + "bytes" "fmt" + "html/template" "regexp" "strings" "time" + + "github.com/Masterminds/sprig/v3" ) var re = regexp.MustCompile(`\${(\w+)(:([a-zA-Z0-9%.]+))?}`) @@ -46,6 +50,20 @@ func FormatValue(format string, val interface{}) string { // ReplaceFormatted replaces all occurrences of ${key} with formatted val from the kv map func ReplaceFormatted(s string, kv map[string]interface{}) (string, error) { + // Enhanced golang template logic + tpl, err := template.New("base").Funcs(sprig.FuncMap()).Parse(s) + if err != nil { + return s, err + } + + var rs bytes.Buffer + err = tpl.Execute(&rs, kv) + if err != nil { + return s, err + } + s = rs.String() + + // Regex logic for backward compatibility wanted := make([]string, 0) for m := re.FindStringSubmatch(s); m != nil; m = re.FindStringSubmatch(s) { @@ -65,7 +83,6 @@ func ReplaceFormatted(s string, kv map[string]interface{}) (string, error) { } // return missing keys - var err error if len(wanted) > 0 { got := make([]string, 0) for k := range kv { diff --git a/util/format_test.go b/util/format_test.go index 91de2ec98..32d2f2af2 100644 --- a/util/format_test.go +++ b/util/format_test.go @@ -33,6 +33,7 @@ func TestReplace(t *testing.T) { v interface{} fmt, expected string }{ + // regex tests {"foo", true, "${foo}", "true"}, {"foo", "1", "abc${foo}${foo}", "abc11"}, {"foo", math.Pi, "${foo:%.2f}", "3.14"},