Newer
Older
package template
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"sync"
"text/template"
"git.autistici.org/ai3/go-common/mail/mdtext"
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
)
var (
// TemplateDirectory points at the directory containing templates.
TemplateDirectory = "/etc/ai/templates/mail"
// DefaultLanguage is the fallback language.
DefaultLanguage = "en"
// Global, lazily-initialized, shared template registry.
templates *template.Template
templateLoadMx sync.Mutex
// Line width of email plain text bodies.
emailLineWidth = 75
)
func init() {
if d := os.Getenv("MAIL_TEMPLATE_DIR"); d != "" {
TemplateDirectory = d
}
}
func loadTemplates() (err error) {
templateLoadMx.Lock()
defer templateLoadMx.Unlock()
if templates != nil {
return
}
templates, err = template.ParseGlob(filepath.Join(TemplateDirectory, "*.??.md"))
return
}
// SetTemplateDirectory can be used to (re)set the TemplateDirectory
// once the program has started, so it's mostly useful for tests.
func SetTemplateDirectory(d string) {
templateLoadMx.Lock()
templates = nil
TemplateDirectory = d
templateLoadMx.Unlock()
}
func findTemplate(name, lang string) *template.Template {
if lang == "" {
lang = DefaultLanguage
}
tpl := templates.Lookup(fmt.Sprintf("%s.%s.md", name, lang))
if tpl == nil && lang != DefaultLanguage {
return findTemplate(name, DefaultLanguage)
}
return tpl
}
// Template represents a templated message body.
type Template struct {
body []byte
}
// New loads a template with the specified name and language,
// and renders it with the given values.
//
// Templates are Markdown files loaded from the TemplateDirectory
// (which can be overridden at runtime by setting the environment
// variable MAIL_TEMPLATE_DIR), and must follow the <name>.<lang>.md
// naming pattern. Such templates can then be rendered to plain text
// or HTML.
//
// If a template with the desired language does not exist, we fall
// back to using DefaultLanguage.
func New(name, lang string, values map[string]interface{}) (*Template, error) {
if err := loadTemplates(); err != nil {
return nil, err
}
tpl := findTemplate(name, lang)
if tpl == nil {
return nil, errors.New("template not found")
}
var buf bytes.Buffer
if err := tpl.Execute(&buf, values); err != nil {
return nil, err
}
return &Template{
body: buf.Bytes(),
}, nil
}
// Text renders the template body to plain text.
func (t *Template) Text() []byte {
return bf.Run(t.body, bf.WithRenderer(mdtext.NewTextRenderer(emailLineWidth)))
}
// HTML renders the template body to HTML.
func (t *Template) HTML() []byte {
return bf.Run(t.body)
}