5
5
package templates
6
6
7
7
import (
8
+ "bytes"
8
9
"context"
10
+ "fmt"
11
+ "regexp"
12
+ "strconv"
13
+ "strings"
9
14
10
15
"code.gitea.io/gitea/modules/log"
11
16
"code.gitea.io/gitea/modules/setting"
@@ -14,7 +19,14 @@ import (
14
19
"github.com/unrolled/render"
15
20
)
16
21
17
- var rendererKey interface {} = "templatesHtmlRendereer"
22
+ var (
23
+ rendererKey interface {} = "templatesHtmlRenderer"
24
+
25
+ templateError = regexp .MustCompile (`^template: (.*):([0-9]+): (.*)` )
26
+ notDefinedError = regexp .MustCompile (`^template: (.*):([0-9]+): function "(.*)" not defined` )
27
+ unexpectedError = regexp .MustCompile (`^template: (.*):([0-9]+): unexpected "(.*)" in operand` )
28
+ expectedEndError = regexp .MustCompile (`^template: (.*):([0-9]+): expected end; found (.*)` )
29
+ )
18
30
19
31
// HTMLRenderer returns the current html renderer for the context or creates and stores one within the context for future use
20
32
func HTMLRenderer (ctx context.Context ) (context.Context , * render.Render ) {
@@ -32,6 +44,25 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
32
44
}
33
45
log .Log (1 , log .DEBUG , "Creating " + rendererType + " HTML Renderer" )
34
46
47
+ compilingTemplates := true
48
+ defer func () {
49
+ if ! compilingTemplates {
50
+ return
51
+ }
52
+
53
+ panicked := recover ()
54
+ if panicked == nil {
55
+ return
56
+ }
57
+
58
+ // OK try to handle the panic...
59
+ err , ok := panicked .(error )
60
+ if ok {
61
+ handlePanicError (err )
62
+ }
63
+ log .Fatal ("PANIC: Unable to compile templates: %v\n Stacktrace:\n %s" , panicked , log .Stack (2 ))
64
+ }()
65
+
35
66
renderer := render .New (render.Options {
36
67
Extensions : []string {".tmpl" },
37
68
Directory : "templates" ,
@@ -42,6 +73,7 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
42
73
IsDevelopment : false ,
43
74
DisableHTTPErrorRendering : true ,
44
75
})
76
+ compilingTemplates = false
45
77
if ! setting .IsProd {
46
78
watcher .CreateWatcher (ctx , "HTML Templates" , & watcher.CreateWatcherOpts {
47
79
PathsCallback : walkTemplateFiles ,
@@ -50,3 +82,141 @@ func HTMLRenderer(ctx context.Context) (context.Context, *render.Render) {
50
82
}
51
83
return context .WithValue (ctx , rendererKey , renderer ), renderer
52
84
}
85
+
86
+ func handlePanicError (err error ) {
87
+ wrapFatal (handleNotDefinedPanicError (err ))
88
+ wrapFatal (handleUnexpected (err ))
89
+ wrapFatal (handleExpectedEnd (err ))
90
+ wrapFatal (handleGenericTemplateError (err ))
91
+ }
92
+
93
+ func wrapFatal (format string , args ... interface {}) {
94
+ if format == "" {
95
+ return
96
+ }
97
+ log .Fatal (format , args ... )
98
+ }
99
+
100
+ func handleGenericTemplateError (err error ) (string , []interface {}) {
101
+ groups := templateError .FindStringSubmatch (err .Error ())
102
+ if len (groups ) != 4 {
103
+ return "" , nil
104
+ }
105
+
106
+ templateName , lineNumberStr , message := groups [1 ], groups [2 ], groups [3 ]
107
+
108
+ filename , assetErr := GetAssetFilename ("templates/" + templateName + ".tmpl" )
109
+ if assetErr != nil {
110
+ return "" , nil
111
+ }
112
+
113
+ lineNumber , _ := strconv .Atoi (lineNumberStr )
114
+
115
+ line := getLineFromAsset (templateName , lineNumber , "" )
116
+
117
+ return "PANIC: Unable to compile templates due to: %s in template file %s at line %d:\n %s\n Stacktrace:\n %s" , []interface {}{message , filename , lineNumber , log .NewColoredValue (line , log .Reset ), log .Stack (2 )}
118
+ }
119
+
120
+ func handleNotDefinedPanicError (err error ) (string , []interface {}) {
121
+ groups := notDefinedError .FindStringSubmatch (err .Error ())
122
+ if len (groups ) != 4 {
123
+ return "" , nil
124
+ }
125
+
126
+ templateName , lineNumberStr , functionName := groups [1 ], groups [2 ], groups [3 ]
127
+
128
+ functionName , _ = strconv .Unquote (`"` + functionName + `"` )
129
+
130
+ filename , assetErr := GetAssetFilename ("templates/" + templateName + ".tmpl" )
131
+ if assetErr != nil {
132
+ return "" , nil
133
+ }
134
+
135
+ lineNumber , _ := strconv .Atoi (lineNumberStr )
136
+
137
+ line := getLineFromAsset (templateName , lineNumber , functionName )
138
+
139
+ return "PANIC: Unable to compile templates due to undefined function %q in template file %s at line %d:\n %s\n Stacktrace:\n %s" , []interface {}{functionName , filename , lineNumber , log .NewColoredValue (line , log .Reset ), log .Stack (2 )}
140
+ }
141
+
142
+ func handleUnexpected (err error ) (string , []interface {}) {
143
+ groups := unexpectedError .FindStringSubmatch (err .Error ())
144
+ if len (groups ) != 4 {
145
+ return "" , nil
146
+ }
147
+
148
+ templateName , lineNumberStr , unexpected := groups [1 ], groups [2 ], groups [3 ]
149
+ unexpected , _ = strconv .Unquote (`"` + unexpected + `"` )
150
+
151
+ filename , assetErr := GetAssetFilename ("templates/" + templateName + ".tmpl" )
152
+ if assetErr != nil {
153
+ return "" , nil
154
+ }
155
+
156
+ lineNumber , _ := strconv .Atoi (lineNumberStr )
157
+
158
+ line := getLineFromAsset (templateName , lineNumber , unexpected )
159
+
160
+ return "PANIC: Unable to compile templates due to unexpected %q in template file %s at line %d:\n %s\n Stacktrace:\n %s" , []interface {}{unexpected , filename , lineNumber , log .NewColoredValue (line , log .Reset ), log .Stack (2 )}
161
+ }
162
+
163
+ func handleExpectedEnd (err error ) (string , []interface {}) {
164
+ groups := expectedEndError .FindStringSubmatch (err .Error ())
165
+ if len (groups ) != 4 {
166
+ return "" , nil
167
+ }
168
+
169
+ templateName , lineNumberStr , unexpected := groups [1 ], groups [2 ], groups [3 ]
170
+
171
+ filename , assetErr := GetAssetFilename ("templates/" + templateName + ".tmpl" )
172
+ if assetErr != nil {
173
+ return "" , nil
174
+ }
175
+
176
+ lineNumber , _ := strconv .Atoi (lineNumberStr )
177
+
178
+ line := getLineFromAsset (templateName , lineNumber , unexpected )
179
+
180
+ return "PANIC: Unable to compile templates due to missing end with unexpected %q in template file %s at line %d:\n %s\n Stacktrace:\n %s" , []interface {}{unexpected , filename , lineNumber , log .NewColoredValue (line , log .Reset ), log .Stack (2 )}
181
+ }
182
+
183
+ func getLineFromAsset (templateName string , lineNumber int , functionName string ) string {
184
+ bs , err := GetAsset ("templates/" + templateName + ".tmpl" )
185
+ if err != nil {
186
+ return fmt .Sprintf ("(unable to read template file: %v)" , err )
187
+ }
188
+
189
+ sb := & strings.Builder {}
190
+ start := 0
191
+ var lineBs []byte
192
+ for i := 0 ; i < lineNumber && start < len (bs ); i ++ {
193
+ end := bytes .IndexByte (bs [start :], '\n' )
194
+ if end < 0 {
195
+ end = len (bs )
196
+ } else {
197
+ end += start
198
+ }
199
+ lineBs = bs [start :end ]
200
+ if lineNumber - i < 4 {
201
+ _ , _ = sb .Write (lineBs )
202
+ _ = sb .WriteByte ('\n' )
203
+ }
204
+ start = end + 1
205
+ }
206
+
207
+ if functionName != "" {
208
+ idx := strings .Index (string (lineBs ), functionName )
209
+ for i := range lineBs [:idx ] {
210
+ if lineBs [i ] != '\t' {
211
+ lineBs [i ] = ' '
212
+ }
213
+ }
214
+ _ , _ = sb .Write (lineBs [:idx ])
215
+ if idx >= 0 {
216
+ _ , _ = sb .WriteString (strings .Repeat ("^" , len (functionName )))
217
+ }
218
+ _ = sb .WriteByte ('\n' )
219
+ }
220
+
221
+ return strings .Repeat ("-" , 70 ) + "\n " + sb .String () + strings .Repeat ("-" , 70 )
222
+ }
0 commit comments