This repository has been archived by the owner on Oct 7, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgonocular.go
157 lines (138 loc) · 4.24 KB
/
gonocular.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
package gonocular
import (
"bufio"
"html/template"
"net/http"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
)
//Renderer is the template rendering interface
type Renderer interface {
RenderHTML(wr http.ResponseWriter, data interface{})
}
var templateFactory = newDevRenderer
//DevModeRenderer is responsible for rendering templates and when an error occurs
//will output the error details
type DevModeRenderer struct {
templateFiles []string
}
//ProductionRenderer is responsible for only rendering the template and will
//panic in the occurrence of an error. This is equivalent to template.Must(...
type ProductionRenderer struct {
template *template.Template
}
//TemplateBuilder is responsible for building the collection of template files.
type TemplateBuilder struct {
templateFiles []string
}
//DevMode changes the template behavior to include error details.
//This is the default behavior.
func DevMode() {
templateFactory = newDevRenderer
}
//ProductionMode makes the template behavior panic only when there is
//an error. This is equivalent to calling template.Must(...
func ProductionMode() {
templateFactory = newProductionRenderer
}
//TemplateFiles is the list of file paths relative from the callers code
//file.
func TemplateFiles(filenames ...string) *TemplateBuilder {
templates := make([]string, 0, 10)
for _, file := range filenames {
file := filePathRelativeFromCaller(2, file)
templates = append(templates, file)
}
builder := &TemplateBuilder{templates}
return builder
}
func filePathRelativeFromCaller(skip int, file string) string {
_, filename, _, _ := runtime.Caller(skip)
dir := path.Dir(filename)
filePath := path.Join(dir, file)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
filePath, err = filepath.Abs(file)
}
return filePath
}
//Template will return the Dev or Production Renderer implementation based on
//current settings
func (v *TemplateBuilder) Template() Renderer {
return templateFactory(v)
}
func newDevRenderer(tb *TemplateBuilder) Renderer {
return &DevModeRenderer{tb.templateFiles}
}
func newProductionRenderer(tb *TemplateBuilder) Renderer {
t := template.Must(template.ParseFiles(tb.templateFiles...))
return &ProductionRenderer{t}
}
//RenderHTML will render an html/template file and panic if there is an error
func (r *ProductionRenderer) RenderHTML(wr http.ResponseWriter, data interface{}) {
renderTemplate(r.template, wr, data)
}
func renderTemplate(t *template.Template, wr http.ResponseWriter, data interface{}) {
wr.Header().Set("Content-Type", "text/html")
t.Execute(wr, data)
}
//ParseError is details the error template uses to render the error page
type ParseError struct {
FileName string
ErrorMessage string
LineNumber int
}
//SourceLine is the detail about a single line in a template file used for
//rendering the error page.
type SourceLine struct {
Line int
HasError bool
Text string
}
//SourceLines will parse out the error details of a template error.
func (p *ParseError) SourceLines() []*SourceLine {
lines := make([]*SourceLine, 0, 10)
file, err := os.Open(p.FileName)
if err != nil {
return lines
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineCount := 0
for scanner.Scan() {
hasError := lineCount == p.LineNumber
sl := &SourceLine{lineCount, hasError, scanner.Text()}
lines = append(lines, sl)
lineCount++
}
return lines
}
//RenderHTML will render an html/template or in the case of an error will display
//an errorpage with the error details.
func (r *DevModeRenderer) RenderHTML(wr http.ResponseWriter, data interface{}) {
t, err := template.ParseFiles(r.templateFiles...)
if err == nil {
renderTemplate(t, wr, data)
} else {
errorFile := filePathRelativeFromCaller(1, "error.html")
et := template.Must(template.ParseFiles(errorFile))
errorMessage := err.Error()
errorParts := strings.Split(errorMessage, ":")
parseError := &ParseError{}
fileName := strings.TrimSpace(errorParts[1])
for _, file := range r.templateFiles {
if strings.HasSuffix(file, fileName) {
parseError.FileName = file
}
}
parseError.ErrorMessage = errorMessage
if len(errorParts) > 2 {
parseError.LineNumber, _ = strconv.Atoi(errorParts[2])
}
wr.WriteHeader(http.StatusInternalServerError)
renderTemplate(et, wr, parseError)
}
}