better_errors 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +26 -8
- data/better_errors.gemspec +7 -5
- data/lib/better_errors/code_formatter.rb +60 -0
- data/lib/better_errors/core_ext/exception.rb +14 -13
- data/lib/better_errors/error_page.rb +36 -47
- data/lib/better_errors/middleware.rb +41 -2
- data/lib/better_errors/rails.rb +5 -4
- data/lib/better_errors/repl/basic.rb +20 -0
- data/lib/better_errors/repl/pry.rb +67 -0
- data/lib/better_errors/repl.rb +27 -0
- data/lib/better_errors/{error_frame.rb → stack_frame.rb} +30 -8
- data/lib/better_errors/templates/main.erb +480 -0
- data/lib/better_errors/templates/variable_info.erb +13 -0
- data/lib/better_errors/version.rb +1 -1
- data/lib/better_errors.rb +4 -2
- data/spec/better_errors/code_formatter_spec.rb +51 -0
- data/spec/better_errors/error_page_spec.rb +31 -16
- data/spec/better_errors/middleware_spec.rb +8 -0
- data/spec/better_errors/repl/basic_spec.rb +32 -0
- data/spec/better_errors/{error_frame_spec.rb → stack_frame_spec.rb} +8 -8
- metadata +23 -13
- data/lib/better_errors/error_page.erb +0 -234
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= exception.class %> at <%= request_path %></title>
|
|
5
|
+
<style>
|
|
6
|
+
* {
|
|
7
|
+
margin:0;
|
|
8
|
+
padding:0;
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
font-family:Verdana, sans-serif;
|
|
12
|
+
margin:0;
|
|
13
|
+
font-size:13px;
|
|
14
|
+
}
|
|
15
|
+
header {
|
|
16
|
+
padding:32px 32px;
|
|
17
|
+
border-bottom:1px solid #e64c38;
|
|
18
|
+
background-color: #ffe5e7;
|
|
19
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffe5e7), to(#ffb2b8)); /* Safari 4+, Chrome */
|
|
20
|
+
background-image: -webkit-linear-gradient(top, #ffe5e7, #ffb2b8); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
|
21
|
+
background-image: -moz-linear-gradient(top, #ffe5e7, #ffb2b8); /* Firefox 3.6-15 */
|
|
22
|
+
background-image: -o-linear-gradient(top, #ffe5e7, #ffb2b8); /* Opera 11.10-12.00 */
|
|
23
|
+
background-image: linear-gradient(to bottom, #ffe5e7, #ffb2b8); /* Firefox 16+, IE10, Opera 12.50+ */
|
|
24
|
+
}
|
|
25
|
+
h2 {
|
|
26
|
+
font-weight:normal;
|
|
27
|
+
margin:0;
|
|
28
|
+
margin-bottom:16px;
|
|
29
|
+
}
|
|
30
|
+
h2 span {
|
|
31
|
+
color:#666666;
|
|
32
|
+
}
|
|
33
|
+
header table {
|
|
34
|
+
border-collapse:collapse;
|
|
35
|
+
}
|
|
36
|
+
header table th {
|
|
37
|
+
text-align:right;
|
|
38
|
+
font-weight:bold;
|
|
39
|
+
padding-right:12px;
|
|
40
|
+
}
|
|
41
|
+
header table th, header table td {
|
|
42
|
+
padding:4px 6px;
|
|
43
|
+
}
|
|
44
|
+
nav {
|
|
45
|
+
display:block;
|
|
46
|
+
padding:12px 12px;
|
|
47
|
+
border-bottom:1px solid #000000;
|
|
48
|
+
background-color: #444444;
|
|
49
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#444444), to(#222222)); /* Safari 4+, Chrome */
|
|
50
|
+
background-image: -webkit-linear-gradient(top, #444444, #222222); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
|
51
|
+
background-image: -moz-linear-gradient(top, #444444, #222222); /* Firefox 3.6-15 */
|
|
52
|
+
background-image: -o-linear-gradient(top, #444444, #222222); /* Opera 11.10-12.00 */
|
|
53
|
+
background-image: linear-gradient(to bottom, #444444, #222222); /* Firefox 16+, IE10, Opera 12.50+ */
|
|
54
|
+
}
|
|
55
|
+
nav a {
|
|
56
|
+
color:#ffffff;
|
|
57
|
+
text-decoration:none;
|
|
58
|
+
padding:4px 16px;
|
|
59
|
+
border-radius:16px;
|
|
60
|
+
}
|
|
61
|
+
nav a:hover {
|
|
62
|
+
background-color: #666666;
|
|
63
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#666666), to(#444444)); /* Safari 4+, Chrome */
|
|
64
|
+
background-image: -webkit-linear-gradient(top, #666666, #444444); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
|
65
|
+
background-image: -moz-linear-gradient(top, #666666, #444444); /* Firefox 3.6-15 */
|
|
66
|
+
background-image: -o-linear-gradient(top, #666666, #444444); /* Opera 11.10-12.00 */
|
|
67
|
+
background-image: linear-gradient(to bottom, #666666, #444444); /* Firefox 16+, IE10, Opera 12.50+ */
|
|
68
|
+
}
|
|
69
|
+
nav a.selected {
|
|
70
|
+
color:#000000;
|
|
71
|
+
background-color: #efefef;
|
|
72
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#cfcfcf)); /* Safari 4+, Chrome */
|
|
73
|
+
background-image: -webkit-linear-gradient(top, #efefef, #cfcfcf); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
|
74
|
+
background-image: -moz-linear-gradient(top, #efefef, #cfcfcf); /* Firefox 3.6-15 */
|
|
75
|
+
background-image: -o-linear-gradient(top, #efefef, #cfcfcf); /* Opera 11.10-12.00 */
|
|
76
|
+
background-image: linear-gradient(to bottom, #efefef, #cfcfcf); /* Firefox 16+, IE10, Opera 12.50+ */
|
|
77
|
+
}
|
|
78
|
+
.frames {
|
|
79
|
+
width:50%;
|
|
80
|
+
float:left;
|
|
81
|
+
}
|
|
82
|
+
.frames li {
|
|
83
|
+
list-style:none;
|
|
84
|
+
display:block;
|
|
85
|
+
padding:12px;
|
|
86
|
+
overflow:hidden;
|
|
87
|
+
cursor:pointer;
|
|
88
|
+
background-color: #f4f7ff;
|
|
89
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#f4f7ff), to(#e4ebfe)); /* Safari 4+, Chrome */
|
|
90
|
+
background-image: -webkit-linear-gradient(top, #f4f7ff, #e4ebfe); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
|
91
|
+
background-image: -moz-linear-gradient(top, #f4f7ff, #e4ebfe); /* Firefox 3.6-15 */
|
|
92
|
+
background-image: -o-linear-gradient(top, #f4f7ff, #e4ebfe); /* Opera 11.10-12.00 */
|
|
93
|
+
background-image: linear-gradient(to bottom, #f4f7ff, #e4ebfe); /* Firefox 16+, IE10, Opera 12.50+ */
|
|
94
|
+
border-bottom:1px solid #cccccc;
|
|
95
|
+
}
|
|
96
|
+
.frames li:hover, .frames li.selected {
|
|
97
|
+
background-color: #e4ebfe;
|
|
98
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#e4ebfe), to(#c2d3fe)); /* Safari 4+, Chrome */
|
|
99
|
+
background-image: -webkit-linear-gradient(top, #e4ebfe, #c2d3fe); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
|
100
|
+
background-image: -moz-linear-gradient(top, #e4ebfe, #c2d3fe); /* Firefox 3.6-15 */
|
|
101
|
+
background-image: -o-linear-gradient(top, #e4ebfe, #c2d3fe); /* Opera 11.10-12.00 */
|
|
102
|
+
background-image: linear-gradient(to bottom, #e4ebfe, #c2d3fe); /* Firefox 16+, IE10, Opera 12.50+ */
|
|
103
|
+
}
|
|
104
|
+
.frames .name, .frame_info h2.name {
|
|
105
|
+
font-family:Monaco, Incosolata, Consolas, monospace;
|
|
106
|
+
font-size:14px;
|
|
107
|
+
margin-bottom:4px;
|
|
108
|
+
word-wrap:break-word;
|
|
109
|
+
}
|
|
110
|
+
.location {
|
|
111
|
+
color:#888888;
|
|
112
|
+
}
|
|
113
|
+
.location .filename, .location .line {
|
|
114
|
+
color:#333333;
|
|
115
|
+
}
|
|
116
|
+
.frame_info {
|
|
117
|
+
background-color:#efefef;
|
|
118
|
+
border-bottom:1px solid #cccccc;
|
|
119
|
+
float:left;
|
|
120
|
+
width:50%;
|
|
121
|
+
}
|
|
122
|
+
.frame_info > * {
|
|
123
|
+
margin:16px;
|
|
124
|
+
}
|
|
125
|
+
.frame_info h2 {
|
|
126
|
+
font-family:Monaco, Incosolata, Consolas, monospace;
|
|
127
|
+
}
|
|
128
|
+
.frame_info .code {
|
|
129
|
+
font-family:Monaco, Incosolata, Consolas, monospace;
|
|
130
|
+
background-color:#ffffff;
|
|
131
|
+
border:1px solid #cccccc;
|
|
132
|
+
border-collapse:collapse;
|
|
133
|
+
overflow:auto;
|
|
134
|
+
}
|
|
135
|
+
.frame_info .code pre:nth-child(2n+1) {
|
|
136
|
+
background-color:#f9f9f9;
|
|
137
|
+
}
|
|
138
|
+
.frame_info .code pre:nth-child(2n) {
|
|
139
|
+
background-color:#f3f3f3;
|
|
140
|
+
}
|
|
141
|
+
.frame_info .code pre.highlight {
|
|
142
|
+
background-color:#ffeaaa;
|
|
143
|
+
}
|
|
144
|
+
.frame_info .code .line {
|
|
145
|
+
text-align:right;
|
|
146
|
+
padding-right:12px;
|
|
147
|
+
padding-left:0px;
|
|
148
|
+
}
|
|
149
|
+
code {
|
|
150
|
+
background-color:#f0f0f0;
|
|
151
|
+
padding:2px 4px;
|
|
152
|
+
border:1px solid #d0d0d0;
|
|
153
|
+
}
|
|
154
|
+
h3 {
|
|
155
|
+
font-weight:normal;
|
|
156
|
+
border-top:1px solid #cccccc;
|
|
157
|
+
padding-top:16px;
|
|
158
|
+
margin-bottom:16px;
|
|
159
|
+
}
|
|
160
|
+
.var_table {
|
|
161
|
+
border-collapse:collapse;
|
|
162
|
+
}
|
|
163
|
+
.var_table td {
|
|
164
|
+
padding:8px;
|
|
165
|
+
}
|
|
166
|
+
.var_table td.name {
|
|
167
|
+
font-style:italic;
|
|
168
|
+
}
|
|
169
|
+
.var_table tr {
|
|
170
|
+
border-bottom:1px solid #cccccc;
|
|
171
|
+
}
|
|
172
|
+
.repl .console {
|
|
173
|
+
background-color:#ffffff;
|
|
174
|
+
padding:2px 4px;
|
|
175
|
+
border:1px solid #d0d0d0;
|
|
176
|
+
margin-bottom:8px;
|
|
177
|
+
font-family:monospace;
|
|
178
|
+
}
|
|
179
|
+
.repl pre {
|
|
180
|
+
font-size:12px;
|
|
181
|
+
white-space:pre-wrap;
|
|
182
|
+
max-height:400px;
|
|
183
|
+
overflow:auto;
|
|
184
|
+
}
|
|
185
|
+
.repl .prompt {
|
|
186
|
+
font-size:12px;
|
|
187
|
+
position:relative;
|
|
188
|
+
}
|
|
189
|
+
.repl input {
|
|
190
|
+
font-size:12px;
|
|
191
|
+
border:none;
|
|
192
|
+
font-family:monospace;
|
|
193
|
+
outline:none;
|
|
194
|
+
padding:0px;
|
|
195
|
+
position:absolute;
|
|
196
|
+
left:20px;
|
|
197
|
+
right:0px;
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
200
|
+
</head>
|
|
201
|
+
<body>
|
|
202
|
+
<header>
|
|
203
|
+
<h2><%= exception.class %> <span>at <%= request_path %></span></h2>
|
|
204
|
+
<table>
|
|
205
|
+
<tr>
|
|
206
|
+
<th>Message</th>
|
|
207
|
+
<td><%= exception.message %></td>
|
|
208
|
+
</tr>
|
|
209
|
+
<% if backtrace_frames.any? %>
|
|
210
|
+
<tr>
|
|
211
|
+
<th>File</th>
|
|
212
|
+
<td><%= backtrace_frames.first.filename %></td>
|
|
213
|
+
</tr>
|
|
214
|
+
<tr>
|
|
215
|
+
<th>Line</th>
|
|
216
|
+
<td><%= backtrace_frames.first.line %></td>
|
|
217
|
+
</tr>
|
|
218
|
+
<% end %>
|
|
219
|
+
</table>
|
|
220
|
+
</header>
|
|
221
|
+
<nav>
|
|
222
|
+
<a href="#" id="application_frames">Application Frames</a>
|
|
223
|
+
<a href="#" id="all_frames">All Frames</a>
|
|
224
|
+
</nav>
|
|
225
|
+
<section class="backtrace">
|
|
226
|
+
<ul class="frames">
|
|
227
|
+
<% backtrace_frames.each_with_index do |frame, index| %>
|
|
228
|
+
<li
|
|
229
|
+
data-context="<%= frame.context %>"
|
|
230
|
+
data-full-filename="<%= frame.filename %>"
|
|
231
|
+
data-filename="<%= frame.pretty_path %>"
|
|
232
|
+
data-line="<%= frame.line %>"
|
|
233
|
+
data-name="<%= frame.name %>"
|
|
234
|
+
data-index="<%= index %>"
|
|
235
|
+
>
|
|
236
|
+
<div class="name"><%= frame.name %></div>
|
|
237
|
+
<div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
|
|
238
|
+
</li>
|
|
239
|
+
<% end %>
|
|
240
|
+
</ul>
|
|
241
|
+
<% backtrace_frames.each_with_index do |frame, index| %>
|
|
242
|
+
<div class="frame_info" id="frame_info_<%= index %>" style="display:none;">
|
|
243
|
+
<h2 class="name"><%= frame.name %></h2>
|
|
244
|
+
<div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
|
|
245
|
+
<%== highlighted_code_block frame %>
|
|
246
|
+
|
|
247
|
+
<% if BetterErrors.binding_of_caller_available? %>
|
|
248
|
+
<div class="repl">
|
|
249
|
+
<h3>REPL</h3>
|
|
250
|
+
<div class="console">
|
|
251
|
+
<pre></pre>
|
|
252
|
+
<div class="prompt"><span>>></span> <input/></div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div class="variable_info"></div>
|
|
257
|
+
<% else %>
|
|
258
|
+
<h3>Advanced features unavailable</h3>
|
|
259
|
+
<p class="error">
|
|
260
|
+
You must add <code>gem "binding_of_caller"</code> to your Gemfile to enable the REPL and local/instance variable inspection.
|
|
261
|
+
</p>
|
|
262
|
+
<% end %>
|
|
263
|
+
</div>
|
|
264
|
+
<% end %>
|
|
265
|
+
<div style="clear:both"></div>
|
|
266
|
+
</section>
|
|
267
|
+
</body>
|
|
268
|
+
<script>
|
|
269
|
+
(function() {
|
|
270
|
+
var OID = <%== object_id.to_s.inspect %>;
|
|
271
|
+
|
|
272
|
+
var previousFrame = null;
|
|
273
|
+
var previousFrameInfo = null;
|
|
274
|
+
var allFrames = document.querySelectorAll("ul.frames li");
|
|
275
|
+
var allFrameInfos = document.querySelectorAll(".frame_info");
|
|
276
|
+
|
|
277
|
+
function apiCall(method, opts, cb) {
|
|
278
|
+
var req = new XMLHttpRequest();
|
|
279
|
+
req.open("POST", "/__better_errors/" + OID + "/" + method, true);
|
|
280
|
+
req.setRequestHeader("Content-Type", "application/json");
|
|
281
|
+
req.send(JSON.stringify(opts));
|
|
282
|
+
req.onreadystatechange = function() {
|
|
283
|
+
if(req.readyState == 4) {
|
|
284
|
+
var res = JSON.parse(req.responseText);
|
|
285
|
+
cb(res);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function escapeHTML(html) {
|
|
291
|
+
return html.replace(/&/, "&").replace(/</g, "<");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function REPL(index) {
|
|
295
|
+
this.index = index;
|
|
296
|
+
|
|
297
|
+
this.previousCommands = [];
|
|
298
|
+
this.previousCommandOffset = 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
REPL.all = [];
|
|
302
|
+
|
|
303
|
+
REPL.prototype.install = function(containerElement) {
|
|
304
|
+
this.container = containerElement;
|
|
305
|
+
|
|
306
|
+
this.promptElement = this.container.querySelector(".prompt span");
|
|
307
|
+
this.inputElement = this.container.querySelector("input");
|
|
308
|
+
this.outputElement = this.container.querySelector("pre");
|
|
309
|
+
|
|
310
|
+
this.inputElement.onkeydown = this.onKeyDown.bind(this);
|
|
311
|
+
|
|
312
|
+
this.setPrompt(">>");
|
|
313
|
+
|
|
314
|
+
REPL.all[this.index] = this;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
REPL.prototype.focus = function() {
|
|
318
|
+
this.inputElement.focus();
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
REPL.prototype.setPrompt = function(prompt) {
|
|
322
|
+
this._prompt = prompt;
|
|
323
|
+
this.promptElement.innerHTML = escapeHTML(prompt);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
REPL.prototype.getInput = function() {
|
|
327
|
+
return this.inputElement.value;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
REPL.prototype.setInput = function(text) {
|
|
331
|
+
this.inputElement.value = text;
|
|
332
|
+
|
|
333
|
+
if(this.inputElement.setSelectionRange) {
|
|
334
|
+
// set cursor to end of input
|
|
335
|
+
this.inputElement.setSelectionRange(text.length, text.length);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
REPL.prototype.writeRawOutput = function(output) {
|
|
340
|
+
this.outputElement.innerHTML += output;
|
|
341
|
+
this.outputElement.scrollTop = this.outputElement.scrollHeight;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
REPL.prototype.writeOutput = function(output) {
|
|
345
|
+
this.writeRawOutput(escapeHTML(output));
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
REPL.prototype.sendInput = function(line) {
|
|
349
|
+
var self = this;
|
|
350
|
+
apiCall("eval", { "index": this.index, source: line }, function(response) {
|
|
351
|
+
if(response.error) {
|
|
352
|
+
self.writeOutput(response.error + "\n");
|
|
353
|
+
}
|
|
354
|
+
self.writeOutput(self._prompt + " ");
|
|
355
|
+
self.writeRawOutput(response.highlighted_input + "\n");
|
|
356
|
+
self.writeOutput(response.result);
|
|
357
|
+
self.setPrompt(response.prompt);
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
REPL.prototype.onEnterKey = function() {
|
|
362
|
+
var text = this.getInput();
|
|
363
|
+
if(text != "" && text !== undefined) {
|
|
364
|
+
this.previousCommandOffset = this.previousCommands.push(text);
|
|
365
|
+
}
|
|
366
|
+
this.setInput("");
|
|
367
|
+
this.sendInput(text);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
REPL.prototype.onNavigateHistory = function(direction) {
|
|
371
|
+
this.previousCommandOffset += direction;
|
|
372
|
+
|
|
373
|
+
if(this.previousCommandOffset < 0) {
|
|
374
|
+
this.previousCommandOffset = -1;
|
|
375
|
+
this.setInput("");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if(this.previousCommandOffset >= this.previousCommands.length) {
|
|
380
|
+
this.previousCommandOffset = this.previousCommands.length;
|
|
381
|
+
this.setInput("");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this.setInput(this.previousCommands[this.previousCommandOffset]);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
REPL.prototype.onKeyDown = function(ev) {
|
|
389
|
+
if(ev.keyCode == 13) {
|
|
390
|
+
this.onEnterKey();
|
|
391
|
+
} else if(ev.keyCode == 38) {
|
|
392
|
+
// the user pressed the up arrow.
|
|
393
|
+
this.onNavigateHistory(-1);
|
|
394
|
+
return false;
|
|
395
|
+
} else if(ev.keyCode == 40) {
|
|
396
|
+
// the user pressed the down arrow.
|
|
397
|
+
this.onNavigateHistory(1);
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
function populateVariableInfo(index) {
|
|
403
|
+
var el = allFrameInfos[index];
|
|
404
|
+
var varInfo = el.querySelector(".variable_info");
|
|
405
|
+
if(varInfo && varInfo.innerHTML == "") {
|
|
406
|
+
apiCall("variables", { "index": index }, function(response) {
|
|
407
|
+
if(response.error) {
|
|
408
|
+
varInfo.innerHTML = "<span class='error'>" + escapeHTML(response.error) + "</span>";
|
|
409
|
+
} else {
|
|
410
|
+
varInfo.innerHTML = response.html;
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function selectFrameInfo(index) {
|
|
417
|
+
populateVariableInfo(index);
|
|
418
|
+
|
|
419
|
+
if(previousFrameInfo) {
|
|
420
|
+
previousFrameInfo.style.display = "none";
|
|
421
|
+
}
|
|
422
|
+
previousFrameInfo = allFrameInfos[index];
|
|
423
|
+
previousFrameInfo.style.display = "block";
|
|
424
|
+
|
|
425
|
+
if(REPL.all[index]) {
|
|
426
|
+
REPL.all[index].focus();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
for(var i = 0; i < allFrames.length; i++) {
|
|
431
|
+
(function(i, el) {
|
|
432
|
+
var el = allFrames[i];
|
|
433
|
+
el.onclick = function() {
|
|
434
|
+
if(previousFrame) {
|
|
435
|
+
previousFrame.className = "";
|
|
436
|
+
}
|
|
437
|
+
el.className = "selected";
|
|
438
|
+
previousFrame = el;
|
|
439
|
+
|
|
440
|
+
selectFrameInfo(el.attributes["data-index"].value);
|
|
441
|
+
};
|
|
442
|
+
var repl = allFrameInfos[i].querySelector(".repl .console");
|
|
443
|
+
if(repl) {
|
|
444
|
+
new REPL(i).install(repl);
|
|
445
|
+
}
|
|
446
|
+
})(i);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
allFrames[0].click();
|
|
450
|
+
|
|
451
|
+
var applicationFramesButton = document.getElementById("application_frames");
|
|
452
|
+
var allFramesButton = document.getElementById("all_frames");
|
|
453
|
+
|
|
454
|
+
applicationFramesButton.onclick = function() {
|
|
455
|
+
allFramesButton.className = "";
|
|
456
|
+
applicationFramesButton.className = "selected";
|
|
457
|
+
for(var i = 0; i < allFrames.length; i++) {
|
|
458
|
+
if(allFrames[i].attributes["data-context"].value == "application") {
|
|
459
|
+
allFrames[i].style.display = "block";
|
|
460
|
+
} else {
|
|
461
|
+
allFrames[i].style.display = "none";
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
allFramesButton.onclick = function() {
|
|
468
|
+
applicationFramesButton.className = "";
|
|
469
|
+
allFramesButton.className = "selected";
|
|
470
|
+
for(var i = 0; i < allFrames.length; i++) {
|
|
471
|
+
allFrames[i].style.display = "block";
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
applicationFramesButton.click();
|
|
477
|
+
})();
|
|
478
|
+
</script>
|
|
479
|
+
</html>
|
|
480
|
+
<!-- generated by Better Errors in <%= Time.now.to_f - @start_time %> seconds -->
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<h3>Local Variables</h3>
|
|
2
|
+
<table class="var_table">
|
|
3
|
+
<% @frame.local_variables.each do |name, value| %>
|
|
4
|
+
<tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
|
|
5
|
+
<% end %>
|
|
6
|
+
</table>
|
|
7
|
+
|
|
8
|
+
<h3>Instance Variables</h3>
|
|
9
|
+
<table class="var_table">
|
|
10
|
+
<% @frame.instance_variables.each do |name, value| %>
|
|
11
|
+
<tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
|
|
12
|
+
<% end %>
|
|
13
|
+
</table>
|
data/lib/better_errors.rb
CHANGED
|
@@ -4,11 +4,13 @@ require "coderay"
|
|
|
4
4
|
|
|
5
5
|
require "better_errors/version"
|
|
6
6
|
require "better_errors/error_page"
|
|
7
|
-
require "better_errors/
|
|
7
|
+
require "better_errors/stack_frame"
|
|
8
8
|
require "better_errors/middleware"
|
|
9
|
+
require "better_errors/code_formatter"
|
|
10
|
+
require "better_errors/repl"
|
|
9
11
|
|
|
10
12
|
class << BetterErrors
|
|
11
|
-
attr_accessor :application_root, :binding_of_caller_available
|
|
13
|
+
attr_accessor :application_root, :binding_of_caller_available, :logger
|
|
12
14
|
|
|
13
15
|
alias_method :binding_of_caller_available?, :binding_of_caller_available
|
|
14
16
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module BetterErrors
|
|
4
|
+
describe CodeFormatter do
|
|
5
|
+
let(:filename) { File.expand_path("../support/my_source.rb", __FILE__) }
|
|
6
|
+
|
|
7
|
+
let(:formatter) { CodeFormatter.new(filename, 8) }
|
|
8
|
+
|
|
9
|
+
it "should pick an appropriate scanner" do
|
|
10
|
+
formatter.coderay_scanner.should == :ruby
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should show 5 lines of context" do
|
|
14
|
+
formatter.line_range.should == (3..13)
|
|
15
|
+
|
|
16
|
+
formatter.context_lines.should == [
|
|
17
|
+
"three\n",
|
|
18
|
+
"four\n",
|
|
19
|
+
"five\n",
|
|
20
|
+
"six\n",
|
|
21
|
+
"seven\n",
|
|
22
|
+
"eight\n",
|
|
23
|
+
"nine\n",
|
|
24
|
+
"ten\n",
|
|
25
|
+
"eleven\n",
|
|
26
|
+
"twelve\n",
|
|
27
|
+
"thirteen\n"
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should highlight the erroring line" do
|
|
32
|
+
formatter.html.should =~ /highlight.*eight/
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should work when the line is right on the edge" do
|
|
36
|
+
formatter = CodeFormatter.new(filename, 20)
|
|
37
|
+
formatter.line_range.should == (15..20)
|
|
38
|
+
formatter.html.should_not == formatter.source_unavailable
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should not barf when the lines don't make any sense" do
|
|
42
|
+
formatter = CodeFormatter.new(filename, 999)
|
|
43
|
+
formatter.html.should == formatter.source_unavailable
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should not barf when the file doesn't exist" do
|
|
47
|
+
formatter = CodeFormatter.new("fkdguhskd7e l", 1)
|
|
48
|
+
formatter.html.should == formatter.source_unavailable
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -7,6 +7,16 @@ module BetterErrors
|
|
|
7
7
|
let(:error_page) { ErrorPage.new exception, { "REQUEST_PATH" => "/some/path" } }
|
|
8
8
|
|
|
9
9
|
let(:response) { error_page.render }
|
|
10
|
+
|
|
11
|
+
let(:empty_binding) {
|
|
12
|
+
local_a = :value_for_local_a
|
|
13
|
+
local_b = :value_for_local_b
|
|
14
|
+
|
|
15
|
+
@inst_c = :value_for_inst_c
|
|
16
|
+
@inst_d = :value_for_inst_d
|
|
17
|
+
|
|
18
|
+
binding
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
it "should include the error message" do
|
|
12
22
|
response.should include("you divided by zero you silly goose!")
|
|
@@ -20,26 +30,31 @@ module BetterErrors
|
|
|
20
30
|
response.should include("ZeroDivisionError")
|
|
21
31
|
end
|
|
22
32
|
|
|
23
|
-
context "
|
|
24
|
-
|
|
25
|
-
exception.stub!(:backtrace).and_return([
|
|
26
|
-
"#{File.expand_path("../support/my_source.rb", __FILE__)}:8:in `some_method'"
|
|
27
|
-
])
|
|
28
|
-
end
|
|
33
|
+
context "variable inspection" do
|
|
34
|
+
let(:exception) { empty_binding.eval("raise") rescue $! }
|
|
29
35
|
|
|
30
|
-
it "should show
|
|
31
|
-
|
|
36
|
+
it "should show local variables" do
|
|
37
|
+
html = error_page.do_variables("index" => 0)[:html]
|
|
38
|
+
html.should include("local_a")
|
|
39
|
+
html.should include(":value_for_local_a")
|
|
40
|
+
html.should include("local_b")
|
|
41
|
+
html.should include(":value_for_local_b")
|
|
32
42
|
end
|
|
33
43
|
|
|
34
|
-
it "should show
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
response.should_not include("2 two")
|
|
41
|
-
response.should_not include("14 fourteen")
|
|
44
|
+
it "should show instance variables" do
|
|
45
|
+
html = error_page.do_variables("index" => 0)[:html]
|
|
46
|
+
html.should include("inst_c")
|
|
47
|
+
html.should include(":value_for_inst_c")
|
|
48
|
+
html.should include("inst_d")
|
|
49
|
+
html.should include(":value_for_inst_d")
|
|
42
50
|
end
|
|
43
51
|
end
|
|
52
|
+
|
|
53
|
+
it "should not die if the source file is not a real filename" do
|
|
54
|
+
exception.stub!(:backtrace).and_return([
|
|
55
|
+
"<internal:prelude>:10:in `spawn_rack_application'"
|
|
56
|
+
])
|
|
57
|
+
response.should include("Source unavailable")
|
|
58
|
+
end
|
|
44
59
|
end
|
|
45
60
|
end
|
|
@@ -21,6 +21,14 @@ module BetterErrors
|
|
|
21
21
|
|
|
22
22
|
headers["Content-Type"].should == "text/html; charset=utf-8"
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
it "should log the exception" do
|
|
26
|
+
logger = Object.new
|
|
27
|
+
logger.should_receive :fatal
|
|
28
|
+
BetterErrors.stub!(:logger).and_return(logger)
|
|
29
|
+
|
|
30
|
+
app.call({})
|
|
31
|
+
end
|
|
24
32
|
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "better_errors/repl/basic"
|
|
3
|
+
|
|
4
|
+
module BetterErrors
|
|
5
|
+
module REPL
|
|
6
|
+
describe Basic do
|
|
7
|
+
let(:fresh_binding) {
|
|
8
|
+
local_a = 123
|
|
9
|
+
binding
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let(:repl) { Basic.new fresh_binding }
|
|
13
|
+
|
|
14
|
+
it "should evaluate ruby code in a given context" do
|
|
15
|
+
repl.send_input("local_a = 456")
|
|
16
|
+
fresh_binding.eval("local_a").should == 456
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should return a tuple of output and the new prompt" do
|
|
20
|
+
output, prompt = repl.send_input("1 + 2")
|
|
21
|
+
output.should == "=> 3\n"
|
|
22
|
+
prompt.should == ">>"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should not barf if the code throws an exception" do
|
|
26
|
+
output, prompt = repl.send_input("raise Exception")
|
|
27
|
+
output.should == "!! #<Exception: Exception>\n"
|
|
28
|
+
prompt.should == ">>"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|