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!\n %v\n \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,168 @@ 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 .FatalWithSkip (1 , 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!\n %s in template file %s at line %d:\n \n %s\n Stacktrace:\n \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!\n Undefined function %q in template file %s at line %d:\n \n %s" , []interface {}{functionName , filename , lineNumber , log .NewColoredValue (line , log .Reset )}
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!\n Unexpected %q in template file %s at line %d:\n \n %s" , []interface {}{unexpected , filename , lineNumber , log .NewColoredValue (line , log .Reset )}
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!\n Missing end with unexpected %q in template file %s at line %d:\n \n %s" , []interface {}{unexpected , filename , lineNumber , log .NewColoredValue (line , log .Reset )}
181
+ }
182
+
183
+ const dashSeparator = "----------------------------------------------------------------------\n "
184
+
185
+ func getLineFromAsset (templateName string , targetLineNum int , target string ) string {
186
+ bs , err := GetAsset ("templates/" + templateName + ".tmpl" )
187
+ if err != nil {
188
+ return fmt .Sprintf ("(unable to read template file: %v)" , err )
189
+ }
190
+
191
+ sb := & strings.Builder {}
192
+
193
+ // Write the header
194
+ sb .WriteString (dashSeparator )
195
+
196
+ var lineBs []byte
197
+
198
+ // Iterate through the lines from the asset file to find the target line
199
+ for start , currentLineNum := 0 , 1 ; currentLineNum <= targetLineNum && start < len (bs ); currentLineNum ++ {
200
+ // Find the next new line
201
+ end := bytes .IndexByte (bs [start :], '\n' )
202
+
203
+ // adjust the end to be a direct pointer in to []byte
204
+ if end < 0 {
205
+ end = len (bs )
206
+ } else {
207
+ end += start
208
+ }
209
+
210
+ // set lineBs to the current line []byte
211
+ lineBs = bs [start :end ]
212
+
213
+ // move start to after the current new line position
214
+ start = end + 1
215
+
216
+ // Write 2 preceding lines + the target line
217
+ if targetLineNum - currentLineNum < 3 {
218
+ _ , _ = sb .Write (lineBs )
219
+ _ = sb .WriteByte ('\n' )
220
+ }
221
+ }
222
+
223
+ // If there is a provided target to look for in the line add a pointer to it
224
+ // e.g. ^^^^^^^
225
+ if target != "" {
226
+ idx := bytes .Index (lineBs , []byte (target ))
227
+
228
+ if idx >= 0 {
229
+ // take the current line and replace preceding text with whitespace (except for tab)
230
+ for i := range lineBs [:idx ] {
231
+ if lineBs [i ] != '\t' {
232
+ lineBs [i ] = ' '
233
+ }
234
+ }
235
+
236
+ // write the preceding "space"
237
+ _ , _ = sb .Write (lineBs [:idx ])
238
+
239
+ // Now write the ^^ pointer
240
+ _ , _ = sb .WriteString (strings .Repeat ("^" , len (target )))
241
+ _ = sb .WriteByte ('\n' )
242
+ }
243
+ }
244
+
245
+ // Finally write the footer
246
+ sb .WriteString (dashSeparator )
247
+
248
+ return sb .String ()
249
+ }
0 commit comments