-
Notifications
You must be signed in to change notification settings - Fork 7.1k
/
templates.go
143 lines (122 loc) · 3.61 KB
/
templates.go
1
2
3
4
5
6
7
8
9
10
11
12
13
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package templates
import (
"bytes"
"html/template"
"io"
"path/filepath"
"sync"
"github.com/fsnotify/fsnotify"
)
// Container represents a set of templates that can be render
type Container struct {
templates *template.Template
mutex sync.RWMutex
stop chan struct{}
stopped chan struct{}
watch bool
}
// Data contains the data used to populate the template variables, it has Props
// that can be of any type and HTML that only can be `template.HTML` types.
type Data struct {
Props map[string]interface{}
HTML map[string]template.HTML
}
// NewFromTemplates creates a new templates container using a
// `template.Template` object
func NewFromTemplate(templates *template.Template) *Container {
return &Container{templates: templates}
}
// New creates a new templates container scanning a directory.
func New(directory string) (*Container, error) {
c := &Container{}
htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html"))
if err != nil {
return nil, err
}
c.templates = htmlTemplates
return c, nil
}
// NewWithWatcher creates a new templates container scanning a directory and
// watch the directory filesystem changes to apply them to the loaded
// templates. This function returns the container and an errors channel to pass
// all errors that can happen during the watch process, or an regular error if
// we fail to create the templates or the watcher. The caller must consume the
// returned errors channel to ensure not blocking the watch process.
func NewWithWatcher(directory string) (*Container, <-chan error, error) {
htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html"))
if err != nil {
return nil, nil, err
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, nil, err
}
err = watcher.Add(directory)
if err != nil {
watcher.Close()
return nil, nil, err
}
c := &Container{
templates: htmlTemplates,
watch: true,
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
errors := make(chan error)
go func() {
defer close(errors)
defer close(c.stopped)
defer watcher.Close()
for {
select {
case <-c.stop:
return
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
if htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html")); err != nil {
errors <- err
} else {
c.mutex.Lock()
c.templates = htmlTemplates
c.mutex.Unlock()
}
}
case err := <-watcher.Errors:
errors <- err
}
}
}()
return c, errors, nil
}
// Close stops the templates watcher of the container in case you have created
// it with watch parameter set to true
func (c *Container) Close() {
c.mutex.RLock()
defer c.mutex.RUnlock()
if c.watch {
close(c.stop)
<-c.stopped
}
}
// RenderToString renders the template referenced with the template name using
// the data provided and return a string with the result
func (c *Container) RenderToString(templateName string, data Data) (string, error) {
var text bytes.Buffer
if err := c.Render(&text, templateName, data); err != nil {
return "", err
}
return text.String(), nil
}
// RenderToString renders the template referenced with the template name using
// the data provided and write it to the writer provided
func (c *Container) Render(w io.Writer, templateName string, data Data) error {
c.mutex.RLock()
htmlTemplates := c.templates
c.mutex.RUnlock()
if err := htmlTemplates.ExecuteTemplate(w, templateName, data); err != nil {
return err
}
return nil
}