From 8919d64ca1fc2658ac5f86ff64e001ed68b16358 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Fri, 3 Jun 2022 10:40:54 +0200 Subject: [PATCH] #13: Both html/template and text/template support added - Switched the previous implementation to support text/template as well as html/template - Added SetBody*Template() methods to use a template directly as message body - Added AddAlternative*Template() methods to use a template directly as alternative message part --- msg.go | 117 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/msg.go b/msg.go index 289346a..bd02a10 100644 --- a/msg.go +++ b/msg.go @@ -5,7 +5,7 @@ import ( "context" "errors" "fmt" - "html/template" + ht "html/template" "io" "math/rand" "mime" @@ -14,6 +14,7 @@ import ( "os/exec" "path/filepath" "syscall" + tt "text/template" "time" ) @@ -390,10 +391,7 @@ func (m *Msg) GetRecipients() ([]string, error) { // SetBodyString sets the body of the message. func (m *Msg) SetBodyString(ct ContentType, b string, o ...PartOption) { buf := bytes.NewBufferString(b) - w := func(w io.Writer) (int64, error) { - nb, err := w.Write(buf.Bytes()) - return int64(nb), err - } + w := writeFuncFromBuffer(buf) m.SetBodyWriter(ct, w, o...) } @@ -404,13 +402,34 @@ func (m *Msg) SetBodyWriter(ct ContentType, w func(io.Writer) (int64, error), o m.parts = []*Part{p} } +// SetBodyHTMLTemplate sets the body of the message from a given html/template.Template pointer +// The content type will be set to text/html automatically +func (m *Msg) SetBodyHTMLTemplate(t *ht.Template, d interface{}, o ...PartOption) error { + buf := bytes.Buffer{} + if err := t.Execute(&buf, d); err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + w := writeFuncFromBuffer(&buf) + m.SetBodyWriter(TypeTextHTML, w, o...) + return nil +} + +// SetBodyTextTemplate sets the body of the message from a given text/template.Template pointer +// The content type will be set to text/plain automatically +func (m *Msg) SetBodyTextTemplate(t *tt.Template, d interface{}, o ...PartOption) error { + buf := bytes.Buffer{} + if err := t.Execute(&buf, d); err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + w := writeFuncFromBuffer(&buf) + m.SetBodyWriter(TypeTextPlain, w, o...) + return nil +} + // AddAlternativeString sets the alternative body of the message. func (m *Msg) AddAlternativeString(ct ContentType, b string, o ...PartOption) { buf := bytes.NewBufferString(b) - w := func(w io.Writer) (int64, error) { - nb, err := w.Write(buf.Bytes()) - return int64(nb), err - } + w := writeFuncFromBuffer(buf) m.AddAlternativeWriter(ct, w, o...) } @@ -421,6 +440,30 @@ func (m *Msg) AddAlternativeWriter(ct ContentType, w func(io.Writer) (int64, err m.parts = append(m.parts, p) } +// AddAlternativeHTMLTemplate sets the alternative body of the message to a html/template.Template output +// The content type will be set to text/html automatically +func (m *Msg) AddAlternativeHTMLTemplate(t *ht.Template, d interface{}, o ...PartOption) error { + buf := bytes.Buffer{} + if err := t.Execute(&buf, d); err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + w := writeFuncFromBuffer(&buf) + m.AddAlternativeWriter(TypeTextHTML, w, o...) + return nil +} + +// AddAlternativeTextTemplate sets the alternative body of the message to a text/template.Template output +// The content type will be set to text/plain automatically +func (m *Msg) AddAlternativeTextTemplate(t *tt.Template, d interface{}, o ...PartOption) error { + buf := bytes.Buffer{} + if err := t.Execute(&buf, d); err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + w := writeFuncFromBuffer(&buf) + m.AddAlternativeWriter(TypeTextPlain, w, o...) + return nil +} + // AttachFile adds an attachment File to the Msg func (m *Msg) AttachFile(n string, o ...FileOption) { f := fileFromFS(n) @@ -436,9 +479,19 @@ func (m *Msg) AttachReader(n string, r io.Reader, o ...FileOption) { m.attachments = m.appendFile(m.attachments, f, o...) } -// AttachTemplate adds the output of a template.Template pointer as File attachment to the Msg -func (m *Msg) AttachTemplate(n string, t *template.Template, d interface{}, o ...FileOption) error { - f, err := fileFromTemplate(n, t, d) +// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg +func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error { + f, err := fileFromHTMLTemplate(n, t, d) + if err != nil { + return fmt.Errorf("failed to attach template: %w", err) + } + m.attachments = m.appendFile(m.attachments, f, o...) + return nil +} + +// AttachTextTemplate adds the output of a text/template.Template pointer as File attachment to the Msg +func (m *Msg) AttachTextTemplate(n string, t *tt.Template, d interface{}, o ...FileOption) error { + f, err := fileFromTextTemplate(n, t, d) if err != nil { return fmt.Errorf("failed to attach template: %w", err) } @@ -461,9 +514,19 @@ func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) { m.embeds = m.appendFile(m.embeds, f, o...) } -// EmbedTemplate adds the output of a template.Template pointer as embedded File to the Msg -func (m *Msg) EmbedTemplate(n string, t *template.Template, d interface{}, o ...FileOption) error { - f, err := fileFromTemplate(n, t, d) +// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg +func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error { + f, err := fileFromHTMLTemplate(n, t, d) + if err != nil { + return fmt.Errorf("failed to embed template: %w", err) + } + m.embeds = m.appendFile(m.embeds, f, o...) + return nil +} + +// EmbedTextTemplate adds the output of a text/template.Template pointer as embedded File to the Msg +func (m *Msg) EmbedTextTemplate(n string, t *tt.Template, d interface{}, o ...FileOption) error { + f, err := fileFromTextTemplate(n, t, d) if err != nil { return fmt.Errorf("failed to embed template: %w", err) } @@ -689,8 +752,18 @@ func fileFromReader(n string, r io.Reader) *File { } } -// fileFromTemplate returns a File pointer form a given template.Template -func fileFromTemplate(n string, t *template.Template, d interface{}) (*File, error) { +// fileFromHTMLTemplate returns a File pointer form a given html/template.Template +func fileFromHTMLTemplate(n string, t *ht.Template, d interface{}) (*File, error) { + buf := bytes.Buffer{} + if err := t.Execute(&buf, d); err != nil { + return nil, fmt.Errorf("failed to execute template: %w", err) + } + f := fileFromReader(n, &buf) + return f, nil +} + +// fileFromTextTemplate returns a File pointer form a given text/template.Template +func fileFromTextTemplate(n string, t *tt.Template, d interface{}) (*File, error) { buf := bytes.Buffer{} if err := t.Execute(&buf, d); err != nil { return nil, fmt.Errorf("failed to execute template: %w", err) @@ -710,3 +783,13 @@ func getEncoder(e Encoding) mime.WordEncoder { return mime.QEncoding } } + +// writeFuncFromBuffer is a common method to convert a byte buffer into a writeFunc as +// often required by this library +func writeFuncFromBuffer(buf *bytes.Buffer) func(io.Writer) (int64, error) { + w := func(w io.Writer) (int64, error) { + nb, err := w.Write(buf.Bytes()) + return int64(nb), err + } + return w +}