2017-03-25 18:27:06 +00:00
package hermes
2017-03-26 17:11:30 +00:00
import (
"bytes"
"github.com/Masterminds/sprig"
"github.com/imdario/mergo"
2017-05-31 18:36:16 +00:00
"github.com/jaytaylor/html2text"
"github.com/russross/blackfriday"
2017-03-26 17:11:30 +00:00
"html/template"
)
2017-03-28 16:16:20 +00:00
// Hermes is an instance of the hermes email generator
2017-03-26 17:11:30 +00:00
type Hermes struct {
Theme Theme
2017-03-28 16:16:20 +00:00
TextDirection TextDirection
2017-03-26 17:11:30 +00:00
Product Product
}
2017-03-28 16:16:20 +00:00
// Theme is an interface to implement when creating a new theme
type Theme interface {
Name ( ) string // The name of the theme
HTMLTemplate ( ) string // The golang template for HTML emails
PlainTextTemplate ( ) string // The golang templte for plain text emails (can be basic HTML)
}
2017-05-31 18:36:16 +00:00
// TextDirection of the text in HTML email
2017-03-28 16:16:20 +00:00
type TextDirection string
2017-05-31 18:36:16 +00:00
var templateFuncs = template . FuncMap {
"url" : func ( s string ) template . URL {
return template . URL ( s )
} ,
}
2017-03-28 16:16:20 +00:00
// TDLeftToRight is the text direction from left to right (default)
const TDLeftToRight TextDirection = "ltr"
// TDRightToLeft is the text direction from right to left
const TDRightToLeft TextDirection = "rtl"
// Product represents your company product (brand)
// Appears in header & footer of e-mails
2017-03-26 17:11:30 +00:00
type Product struct {
2017-05-13 11:43:57 +00:00
Name string
Link string // e.g. https://matcornic.github.io
Logo string // e.g. https://matcornic.github.io/img/logo.png
Copyright string // Copyright © 2017 Hermes. All rights reserved.
2017-05-14 09:52:11 +00:00
TroubleText string // TroubleText is the sentence at the end of the email for users having trouble with the button (default to `If you’ re having trouble with the button '{ACTION}', copy and paste the URL below into your web browser.`)
2017-03-26 17:11:30 +00:00
}
2017-03-28 16:16:20 +00:00
// Email is the email containing a body
2017-03-26 17:11:30 +00:00
type Email struct {
Body Body
}
2017-05-31 18:36:16 +00:00
// Markdown is a HTML template (a string) representing Markdown content
// https://en.wikipedia.org/wiki/Markdown
type Markdown template . HTML
2017-03-28 16:16:20 +00:00
// Body is the body of the email, containing all interesting data
2017-03-26 17:11:30 +00:00
type Body struct {
2017-05-31 18:36:16 +00:00
Name string // The name of the contacted person
Intros [ ] string // Intro sentences, first displayed in the email
Dictionary [ ] Entry // A list of key+value (useful for displaying parameters/settings/personal info)
Table Table // Table is an table where you can put data (pricing grid, a bill, and so on)
Actions [ ] Action // Actions are a list of actions that the user will be able to execute via a button click
Outros [ ] string // Outro sentences, last displayed in the email
Greeting string // Greeting for the contacted person (default to 'Hi')
Signature string // Signature for the contacted person (default to 'Yours truly')
Title string // Title replaces the greeting+name when set
FreeMarkdown Markdown // Free markdown content that replaces all content other than header and footer
}
// ToHTML converts Markdown to HTML
func ( c Markdown ) ToHTML ( ) template . HTML {
2017-12-12 19:25:45 +00:00
return template . HTML ( blackfriday . Run ( [ ] byte ( string ( c ) ) ) )
2017-03-26 17:11:30 +00:00
}
2017-03-28 16:16:20 +00:00
// Entry is a simple entry of a map
// Allows using a slice of entries instead of a map
// Because Golang maps are not ordered
2017-03-26 17:11:30 +00:00
type Entry struct {
Key string
Value string
}
2017-03-28 16:16:20 +00:00
// Table is an table where you can put data (pricing grid, a bill, and so on)
type Table struct {
Data [ ] [ ] Entry // Contains data
Columns Columns // Contains meta-data for display purpose (width, alignement)
}
// Columns contains meta-data for the different columns
type Columns struct {
CustomWidth map [ string ] string
CustomAlignement map [ string ] string
}
// Action is an action the user can do on the email (click on a button)
2017-03-26 17:11:30 +00:00
type Action struct {
Instructions string
Button Button
}
2017-03-28 16:16:20 +00:00
// Button defines an action to launch
2017-03-26 17:11:30 +00:00
type Button struct {
Color string
Text string
Link string
}
2017-03-28 16:16:20 +00:00
// Template is the struct given to Golang templating
// Root object in a template is this struct
2017-03-26 17:11:30 +00:00
type Template struct {
Hermes Hermes
Email Email
}
func setDefaultEmailValues ( e * Email ) error {
2017-03-28 16:16:20 +00:00
// Default values of an email
2017-03-26 17:11:30 +00:00
defaultEmail := Email {
Body : Body {
2017-03-28 16:16:20 +00:00
Intros : [ ] string { } ,
Dictionary : [ ] Entry { } ,
Outros : [ ] string { } ,
Signature : "Yours truly" ,
Greeting : "Hi" ,
2017-03-26 17:11:30 +00:00
} ,
}
2017-03-28 16:16:20 +00:00
// Merge the given email with default one
// Default one overrides all zero values
2017-03-26 17:11:30 +00:00
return mergo . Merge ( e , defaultEmail )
}
2017-03-28 16:16:20 +00:00
// default values of the engine
2017-03-26 17:11:30 +00:00
func setDefaultHermesValues ( h * Hermes ) error {
defaultTextDirection := TDLeftToRight
defaultHermes := Hermes {
2017-03-28 16:16:20 +00:00
Theme : new ( Default ) ,
2017-03-26 17:11:30 +00:00
TextDirection : defaultTextDirection ,
Product : Product {
2017-05-13 11:43:57 +00:00
Name : "Hermes" ,
Copyright : "Copyright © 2017 Hermes. All rights reserved." ,
2017-05-14 09:52:11 +00:00
TroubleText : "If you’ re having trouble with the button '{ACTION}', copy and paste the URL below into your web browser." ,
2017-03-26 17:11:30 +00:00
} ,
}
2017-04-01 20:34:29 +00:00
// Merge the given hermes engine configuration with default one
2017-03-28 16:16:20 +00:00
// Default one overrides all zero values
2017-03-26 17:11:30 +00:00
err := mergo . Merge ( h , defaultHermes )
if err != nil {
return err
}
if h . TextDirection != TDLeftToRight && h . TextDirection != TDRightToLeft {
h . TextDirection = defaultTextDirection
}
return nil
}
2017-03-28 16:16:20 +00:00
// GenerateHTML generates the email body from data to an HTML Reader
// This is for modern email clients
func ( h * Hermes ) GenerateHTML ( email Email ) ( string , error ) {
err := setDefaultHermesValues ( h )
if err != nil {
return "" , err
}
return h . generateTemplate ( email , h . Theme . HTMLTemplate ( ) )
2017-03-26 17:11:30 +00:00
}
2017-03-28 16:16:20 +00:00
// GeneratePlainText generates the email body from data
// This is for old email clients
func ( h * Hermes ) GeneratePlainText ( email Email ) ( string , error ) {
err := setDefaultHermesValues ( h )
if err != nil {
return "" , err
}
2017-05-31 18:36:16 +00:00
template , err := h . generateTemplate ( email , h . Theme . PlainTextTemplate ( ) )
if err != nil {
return "" , err
}
return html2text . FromString ( template , html2text . Options { PrettyTables : true } )
2017-03-26 17:11:30 +00:00
}
2017-03-28 16:16:20 +00:00
func ( h * Hermes ) generateTemplate ( email Email , tplt string ) ( string , error ) {
err := setDefaultEmailValues ( & email )
2017-03-26 17:11:30 +00:00
if err != nil {
2017-03-28 16:16:20 +00:00
return "" , err
2017-03-26 17:11:30 +00:00
}
2017-03-28 16:16:20 +00:00
// Generate the email from Golang template
// Allow usage of simple function from sprig : https://github.com/Masterminds/sprig
2017-05-31 18:36:16 +00:00
t , err := template . New ( "hermes" ) . Funcs ( sprig . FuncMap ( ) ) . Funcs ( templateFuncs ) . Parse ( tplt )
2017-03-26 17:11:30 +00:00
if err != nil {
2017-03-28 16:16:20 +00:00
return "" , err
2017-03-26 17:11:30 +00:00
}
var b bytes . Buffer
t . Execute ( & b , Template { * h , email } )
2017-03-28 16:16:20 +00:00
return b . String ( ) , nil
2017-03-26 17:11:30 +00:00
}