-
Notifications
You must be signed in to change notification settings - Fork 13
/
tcache.go
220 lines (190 loc) · 5.49 KB
/
tcache.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package handler
import (
"fmt"
"html/template"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
gopi "github.com/djthorpe/gopi/v3"
multierror "github.com/hashicorp/go-multierror"
)
/////////////////////////////////////////////////////////////////////
// TYPES
type TemplateCache struct {
sync.RWMutex
gopi.Unit
gopi.Logger
folder *string
t map[string]tcached
}
type tcached struct {
os.FileInfo
*template.Template
}
/////////////////////////////////////////////////////////////////////
// LIFECYCLE
func (this *TemplateCache) Define(cfg gopi.Config) error {
this.folder = cfg.FlagString("http.templates", "", "Path to HTML Templates")
return nil
}
func (this *TemplateCache) New(gopi.Config) error {
this.Require(this.Logger)
// Where there is no templates argument provided, the unit does
// not activate
if *this.folder == "" {
return nil
}
// Check folder argument
if stat, err := os.Stat(*this.folder); os.IsNotExist(err) {
return gopi.ErrNotFound.WithPrefix(*this.folder)
} else if err != nil {
return gopi.ErrBadParameter.WithPrefix(*this.folder)
} else if stat.IsDir() == false {
return gopi.ErrBadParameter.WithPrefix(*this.folder)
}
// Read all templates in from a folder, every template needs to
// be parsed without error
files, err := ioutil.ReadDir(*this.folder)
if err != nil {
return err
}
// Create cache of templates
this.t = make(map[string]tcached, len(files))
// Read templates into cache. Collect parse errors
// into results
var result error
for _, file := range files {
if strings.HasPrefix(file.Name(), ".") {
continue
}
if file.Mode().IsRegular() == false {
continue
}
path := filepath.Join(*this.folder, file.Name())
if tmpl, err := template.New(file.Name()).Funcs(this.funcmap()).ParseFiles(path); err != nil {
result = multierror.Append(result, err)
} else {
tmpl = tmpl.Funcs(this.funcmap())
key := tmpl.Name()
this.t[key] = tcached{file, tmpl}
this.Debugf("Parsed template: %q", tmpl.Name())
}
}
// Return any errors
return result
}
/////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
// Get returns a template keyed by name. If the template has been
// updated on the filesystem, then it is reparsed.
func (this *TemplateCache) Get(name string) (*template.Template, time.Time, error) {
t := this.get(name)
path := filepath.Join(*this.folder, name)
// No template exists with this name, return nil
if t.Template == nil {
return nil, time.Time{}, gopi.ErrNotFound.WithPrefix(name)
}
// Check case where template has not changed
info, err := os.Stat(path)
if err != nil {
return t.Template, time.Time{}, err
} else if info.ModTime() == t.ModTime() {
return t.Template, t.ModTime(), nil
}
// Re-parse template and return it
tmpl, err := template.New(name).Funcs(this.funcmap()).ParseFiles(path)
if err == nil {
this.Debugf("Reparsed template: %q", tmpl.Name())
this.set(name, tmpl, info)
return tmpl, info.ModTime(), nil
} else {
this.Debugf("Parse Error: %q: %v", name, err)
return t.Template, t.ModTime(), err
}
}
// Modified returns the modification time of a template by name
// or returns zero-time if the template does not exist
func (this *TemplateCache) Modified(name string) time.Time {
t := this.get(name)
if t.Template == nil {
return time.Time{}
} else {
return t.ModTime()
}
}
/////////////////////////////////////////////////////////////////////
// STRINGIFY
func (this *TemplateCache) String() string {
str := "<http.templatecache"
if *this.folder != "" {
str += fmt.Sprintf(" folder=%q", *this.folder)
}
for k := range this.t {
str += " tmpl=" + strconv.Quote(k)
}
return str + ">"
}
/////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
func (this *TemplateCache) get(name string) tcached {
this.RWMutex.RLock()
defer this.RWMutex.RUnlock()
if this.t == nil {
return tcached{}
} else if t, exists := this.t[name]; exists == false {
return tcached{}
} else {
return t
}
}
func (this *TemplateCache) set(name string, t *template.Template, info os.FileInfo) {
this.RWMutex.Lock()
defer this.RWMutex.Unlock()
this.t[name] = tcached{info, t}
}
func (this *TemplateCache) funcmap() template.FuncMap {
return template.FuncMap{
"ssi": funcSSI,
}
}
func funcSSI(cmd string, args ...string) template.HTML {
switch cmd {
case "block":
if len(args) == 1 {
return template.HTML(fmt.Sprintf("<!--# block name=%q -->", args[0]))
}
case "endblock", "else", "endif":
if len(args) == 0 {
return template.HTML(fmt.Sprintf("<!--# %v -->", cmd))
}
case "echo":
if len(args) == 1 {
return template.HTML(fmt.Sprintf("<!--# var name=%q -->", args[0]))
} else if len(args) == 2 {
return template.HTML(fmt.Sprintf("<!--# var name=%q default=%q -->", args[0], args[1]))
}
case "if", "elif":
if len(args) == 1 {
return template.HTML(fmt.Sprintf("<!--# %v expr=%q -->", cmd, args[0]))
}
case "include":
if len(args) == 1 {
return template.HTML(fmt.Sprintf("<!--# %v virtual=%q -->", cmd, args[0]))
}
if len(args) == 2 && args[1] == "wait" {
return template.HTML(fmt.Sprintf("<!--# %v virtual=%q wait=%q -->", cmd, args[0], "yes"))
}
case "set":
if len(args) == 1 {
return template.HTML(fmt.Sprintf("<!--# set var=%q value=%q -->", args[0], ""))
}
if len(args) == 2 {
return template.HTML(fmt.Sprintf("<!--# set var=%q value=%q -->", args[0], args[1]))
}
}
return template.HTML(fmt.Sprintf("[an error occurred while processing the directive: %q ]", cmd))
}