-
Notifications
You must be signed in to change notification settings - Fork 0
/
contour.go
206 lines (170 loc) · 4.36 KB
/
contour.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package contour
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"text/template"
)
// layoutKey defines a method to embed a template within a layout
const layoutKey = "body"
// Engine defines the template engine
type Engine struct {
cfg *config
dir string
fs http.FileSystem
ext string
layout string
funcLock sync.RWMutex
funcMap map[string]interface{}
Flashes *Flash
Templates *template.Template
}
// New returns a new template engine with a default configuration
func New(dir, ext string) *Engine {
engine := &Engine{
cfg: &config{},
dir: dir,
ext: ext,
layout: layoutKey,
funcMap: make(map[string]interface{}),
Flashes: NewFlash(),
}
engine.AddFunc(engine.layout, func() error {
return errors.New("include called unexpectedly")
})
engine.AddFunc("flash", func(key string) []string {
if len(key) > 0 {
return engine.Flashes.Get(key)
}
return engine.Flashes.All()
})
return engine
}
// NewFS returns a template engine with a default configuration and given file system
func NewFS(fs http.FileSystem, ext string) *Engine {
e := New("/", ext)
e.fs = fs
return e
}
// Layout sets a new method to embed a template within a layout
func (e *Engine) Layout(key string) *Engine {
e.layout = key
return e
}
// Reload if set to true the templates are reloading on each render,
// use it when you're in development and you don't want to restart
// the application when you edit a template file.
func (e *Engine) Reload(enabled bool) *Engine {
e.cfg.reload = enabled
return e
}
// AddFunc adds the function to the template's function map.
// It is legal to overwrite elements of the default actions
func (e *Engine) AddFunc(name string, fn interface{}) *Engine {
e.funcLock.Lock()
e.funcMap[name] = fn
e.funcLock.Unlock()
return e
}
// AddFuncMap adds the functions from a map to the template's function map.
// It is legal to overwrite elements of the default actions
func (e *Engine) AddFuncMap(m map[string]interface{}) *Engine {
e.funcLock.Lock()
for name, fn := range m {
e.funcMap[name] = fn
}
e.funcLock.Unlock()
return e
}
// Load parses the templates to the engine.
func (e *Engine) Load() error {
if e.cfg.loaded {
return nil
}
e.funcLock.Lock()
defer e.funcLock.Unlock()
e.Templates = template.New(e.dir)
e.Templates.Funcs(e.funcMap)
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip file if it's a directory or has no file info
if info == nil || info.IsDir() {
return nil
}
// Skip file if it does not equal the given template extension
if filepath.Ext(path) != e.ext {
return nil
}
// Get the relative file path
rel, err := filepath.Rel(e.dir, path)
if err != nil {
return err
}
name := filepath.ToSlash(rel)
name = strings.TrimSuffix(name, e.ext)
buf, err := readFile(e.fs, path)
if err != nil {
return err
}
// Create new template associated with the current one
// This enable use to invoke other templates {{ template .. }}
_, err = e.Templates.New(name).Parse(string(buf))
if err != nil {
return err
}
return err
}
// set config to loaded so that all templates have been parsed
e.cfg.loaded = true
if e.fs != nil {
info, err := stat(e.fs, e.dir)
if err != nil {
return walkFn(e.dir, nil, err)
}
return walk(e.fs, e.dir, info, walkFn)
}
return filepath.Walk(e.dir, walkFn)
}
// Render renders a template with given name using provided data and layout template
func (e *Engine) Render(w io.Writer, name string, data interface{}, layout ...string) error {
if e.cfg.ShouldReload() {
if e.cfg.reload {
e.cfg.loaded = false
}
if err := e.Load(); err != nil {
return err
}
}
tmpl := e.Templates.Lookup(name)
if tmpl == nil {
return fmt.Errorf("render: template not found: %s", name)
}
// Ensure flashes are cleared after rendering
defer e.Flashes.Clear()
// Got the layout
if len(layout) > 0 && layout[0] != "" {
lay := e.Templates.Lookup(layout[0])
if lay == nil {
return fmt.Errorf("render: layout not found: %s", layout[0])
}
e.funcLock.Lock()
defer e.funcLock.Unlock()
lay.Funcs(map[string]interface{}{
e.layout: func() string {
if err := tmpl.Execute(w, data); err != nil {
return err.Error()
}
return ""
},
})
return lay.Execute(w, data)
}
return tmpl.Execute(w, data)
}