better_errors 0.0.2 → 0.0.3
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.
Potentially problematic release.
This version of better_errors might be problematic. Click here for more details.
- data/README.md +2 -1
- data/lib/better_errors/core_ext/exception.rb +1 -4
- data/lib/better_errors/error_frame.rb +11 -2
- data/lib/better_errors/error_page.rb +24 -6
- data/lib/better_errors/middleware.rb +22 -2
- data/lib/better_errors/{error_page.erb → templates/main.erb} +89 -14
- data/lib/better_errors/templates/variable_info.erb +13 -0
- data/lib/better_errors/version.rb +1 -1
- metadata +4 -3
data/README.md
CHANGED
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
Better Errors replaces the standard Rails error page with a much better and more useful error page. It is also usable outside of Rails.
|
4
4
|
|
5
|
-

|
6
6
|
|
7
7
|
## Features
|
8
8
|
|
9
9
|
* Full stack trace
|
10
10
|
* Source code inspection for all stack frames (with highlighting)
|
11
11
|
* Local and instance variable inspection
|
12
|
+
* Ruby console on every stack frame
|
12
13
|
|
13
14
|
## Installation
|
14
15
|
|
@@ -7,10 +7,7 @@ class Exception
|
|
7
7
|
unless Thread.current[:__better_errors_exception_lock]
|
8
8
|
Thread.current[:__better_errors_exception_lock] = true
|
9
9
|
begin
|
10
|
-
@__better_errors_bindings_stack =
|
11
|
-
2.upto(caller.size) do |index|
|
12
|
-
@__better_errors_bindings_stack << binding.of_caller(index) rescue break
|
13
|
-
end
|
10
|
+
@__better_errors_bindings_stack = binding.callers.drop(1)
|
14
11
|
ensure
|
15
12
|
Thread.current[:__better_errors_exception_lock] = false
|
16
13
|
end
|
@@ -3,7 +3,9 @@ module BetterErrors
|
|
3
3
|
def self.from_exception(exception)
|
4
4
|
exception.backtrace.each_with_index.map { |frame, idx|
|
5
5
|
next unless frame =~ /\A(.*):(\d*):in `(.*)'\z/
|
6
|
-
|
6
|
+
if BetterErrors.binding_of_caller_available?
|
7
|
+
b = exception.__better_errors_bindings_stack[idx]
|
8
|
+
end
|
7
9
|
ErrorFrame.new($1, $2.to_i, $3, b)
|
8
10
|
}.compact
|
9
11
|
end
|
@@ -59,7 +61,14 @@ module BetterErrors
|
|
59
61
|
|
60
62
|
def local_variables
|
61
63
|
return {} unless frame_binding
|
62
|
-
|
64
|
+
frame_binding.eval("local_variables").each_with_object({}) do |name, hash|
|
65
|
+
begin
|
66
|
+
hash[name] = frame_binding.eval(name.to_s)
|
67
|
+
rescue NameError => e
|
68
|
+
# local_variables sometimes returns broken variables.
|
69
|
+
# https://bugs.ruby-lang.org/issues/7536
|
70
|
+
end
|
71
|
+
end
|
63
72
|
end
|
64
73
|
|
65
74
|
def instance_variables
|
@@ -2,12 +2,12 @@ require "json"
|
|
2
2
|
|
3
3
|
module BetterErrors
|
4
4
|
class ErrorPage
|
5
|
-
def self.template_path
|
6
|
-
|
5
|
+
def self.template_path(template_name)
|
6
|
+
File.expand_path("../templates/#{template_name}.erb", __FILE__)
|
7
7
|
end
|
8
8
|
|
9
|
-
def self.template
|
10
|
-
Erubis::EscapedEruby.new(File.read(template_path))
|
9
|
+
def self.template(template_name)
|
10
|
+
Erubis::EscapedEruby.new(File.read(template_path(template_name)))
|
11
11
|
end
|
12
12
|
|
13
13
|
attr_reader :exception, :env
|
@@ -15,10 +15,28 @@ module BetterErrors
|
|
15
15
|
def initialize(exception, env)
|
16
16
|
@exception = real_exception(exception)
|
17
17
|
@env = env
|
18
|
+
@start_time = Time.now.to_f
|
18
19
|
end
|
19
20
|
|
20
|
-
def render
|
21
|
-
self.class.template.result binding
|
21
|
+
def render(template_name = "main")
|
22
|
+
self.class.template(template_name).result binding
|
23
|
+
end
|
24
|
+
|
25
|
+
def do_variables(opts)
|
26
|
+
index = opts["index"].to_i
|
27
|
+
@frame = backtrace_frames[index]
|
28
|
+
{ html: render("variable_info") }
|
29
|
+
end
|
30
|
+
|
31
|
+
def do_eval(opts)
|
32
|
+
index = opts["index"].to_i
|
33
|
+
response = begin
|
34
|
+
result = backtrace_frames[index].frame_binding.eval(opts["source"])
|
35
|
+
{ result: result.inspect }
|
36
|
+
rescue Exception => e
|
37
|
+
{ error: (e.inspect rescue e.class.name rescue "Exception") }
|
38
|
+
end
|
39
|
+
response.merge(highlighted_input: CodeRay.scan(opts["source"], :ruby).div(wrap: nil))
|
22
40
|
end
|
23
41
|
|
24
42
|
private
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "json"
|
2
|
+
|
1
3
|
module BetterErrors
|
2
4
|
class Middleware
|
3
5
|
def initialize(app, handler = ErrorPage)
|
@@ -6,10 +8,28 @@ module BetterErrors
|
|
6
8
|
end
|
7
9
|
|
8
10
|
def call(env)
|
11
|
+
if env["REQUEST_PATH"] =~ %r{\A/__better_errors/(?<oid>\d+)/(?<method>\w+)\z}
|
12
|
+
internal_call env, $~
|
13
|
+
else
|
14
|
+
app_call env
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def app_call(env)
|
9
20
|
@app.call env
|
10
21
|
rescue Exception => ex
|
11
|
-
error_page = @handler.new ex, env
|
12
|
-
[500, { "Content-Type" => "text/html; charset=utf-8" }, [error_page.render]]
|
22
|
+
@error_page = @handler.new ex, env
|
23
|
+
[500, { "Content-Type" => "text/html; charset=utf-8" }, [@error_page.render]]
|
24
|
+
end
|
25
|
+
|
26
|
+
def internal_call(env, opts)
|
27
|
+
if opts[:oid].to_i != @error_page.object_id
|
28
|
+
return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]]
|
29
|
+
end
|
30
|
+
|
31
|
+
response = @error_page.send("do_#{opts[:method]}", JSON.parse(env["rack.input"].read))
|
32
|
+
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
|
13
33
|
end
|
14
34
|
end
|
15
35
|
end
|
@@ -120,6 +120,7 @@
|
|
120
120
|
font-weight:normal;
|
121
121
|
border-top:1px solid #cccccc;
|
122
122
|
padding-top:16px;
|
123
|
+
margin-bottom:16px;
|
123
124
|
}
|
124
125
|
.var_table {
|
125
126
|
border-collapse:collapse;
|
@@ -133,6 +134,33 @@
|
|
133
134
|
.var_table tr {
|
134
135
|
border-bottom:1px solid #cccccc;
|
135
136
|
}
|
137
|
+
.repl .console {
|
138
|
+
background-color:#ffffff;
|
139
|
+
padding:2px 4px;
|
140
|
+
border:1px solid #d0d0d0;
|
141
|
+
margin-bottom:8px;
|
142
|
+
font-family:monospace;
|
143
|
+
}
|
144
|
+
.repl pre {
|
145
|
+
font-size:12px;
|
146
|
+
white-space:pre-wrap;
|
147
|
+
max-height:400px;
|
148
|
+
overflow:auto;
|
149
|
+
}
|
150
|
+
.repl .prompt {
|
151
|
+
font-size:12px;
|
152
|
+
position:relative;
|
153
|
+
}
|
154
|
+
.repl input {
|
155
|
+
font-size:12px;
|
156
|
+
border:none;
|
157
|
+
font-family:monospace;
|
158
|
+
outline:none;
|
159
|
+
padding:0px;
|
160
|
+
position:absolute;
|
161
|
+
left:20px;
|
162
|
+
right:0px;
|
163
|
+
}
|
136
164
|
</style>
|
137
165
|
</head>
|
138
166
|
<body>
|
@@ -177,19 +205,15 @@
|
|
177
205
|
<div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
|
178
206
|
<%== highlighted_code_block frame %>
|
179
207
|
|
180
|
-
<
|
181
|
-
|
182
|
-
|
183
|
-
<
|
184
|
-
|
185
|
-
|
208
|
+
<div class="repl">
|
209
|
+
<h3>REPL</h3>
|
210
|
+
<div class="console">
|
211
|
+
<pre></pre>
|
212
|
+
<div class="prompt">>> <input/></div>
|
213
|
+
</div>
|
214
|
+
</div>
|
186
215
|
|
187
|
-
<
|
188
|
-
<table class="var_table">
|
189
|
-
<% frame.instance_variables.each do |name, value| %>
|
190
|
-
<tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
|
191
|
-
<% end %>
|
192
|
-
</table>
|
216
|
+
<div class="variable_info"></div>
|
193
217
|
</div>
|
194
218
|
<% end %>
|
195
219
|
<div style="clear:both"></div>
|
@@ -197,25 +221,58 @@
|
|
197
221
|
</body>
|
198
222
|
<script>
|
199
223
|
(function() {
|
224
|
+
var oid = <%== object_id.to_s.inspect %>;
|
225
|
+
|
200
226
|
var previous = null;
|
201
227
|
var previousFrameInfo = null;
|
202
228
|
var frames = document.querySelectorAll("ul.frames li");
|
229
|
+
var frameInfos = document.querySelectorAll(".frame_info");
|
203
230
|
|
204
231
|
var header = document.querySelector("header");
|
205
232
|
var headerHeight = header.offsetHeight;
|
206
233
|
|
234
|
+
apiCall = function(method, opts, cb) {
|
235
|
+
var req = new XMLHttpRequest();
|
236
|
+
req.open("POST", "/__better_errors/" + oid + "/" + method, true);
|
237
|
+
req.setRequestHeader("Content-Type", "application/json");
|
238
|
+
req.send(JSON.stringify(opts));
|
239
|
+
req.onreadystatechange = function() {
|
240
|
+
if(req.readyState == 4) {
|
241
|
+
var res = JSON.parse(req.responseText);
|
242
|
+
cb(res);
|
243
|
+
}
|
244
|
+
};
|
245
|
+
}
|
246
|
+
|
247
|
+
function escapeHTML(html) {
|
248
|
+
return html.replace(/&/, "&").replace(/</g, "<");
|
249
|
+
}
|
250
|
+
|
207
251
|
function selectFrameInfo(index) {
|
208
252
|
var el = document.getElementById("frame_info_" + index);
|
209
253
|
|
254
|
+
var varInfo = el.querySelector(".variable_info");
|
255
|
+
if(varInfo.innerHTML == "") {
|
256
|
+
apiCall("variables", { "index": index }, function(response) {
|
257
|
+
if(response.error) {
|
258
|
+
varInfo.innerHTML = "<span class='error'>" + escapeHTML(response.error) + "</span>";
|
259
|
+
} else {
|
260
|
+
varInfo.innerHTML = response.html;
|
261
|
+
}
|
262
|
+
});
|
263
|
+
}
|
264
|
+
|
210
265
|
if(previousFrameInfo) {
|
211
266
|
previousFrameInfo.style.display = "none";
|
212
267
|
}
|
213
268
|
previousFrameInfo = el;
|
214
269
|
previousFrameInfo.style.display = "block";
|
270
|
+
|
271
|
+
el.querySelector(".repl input").focus();
|
215
272
|
}
|
216
273
|
|
217
274
|
for(var i = 0; i < frames.length; i++) {
|
218
|
-
(function(el) {
|
275
|
+
(function(index, el, frameInfo) {
|
219
276
|
el.onclick = function() {
|
220
277
|
if(previous) {
|
221
278
|
previous.className = "";
|
@@ -225,10 +282,28 @@
|
|
225
282
|
|
226
283
|
selectFrameInfo(el.attributes["data-index"].value);
|
227
284
|
};
|
228
|
-
|
285
|
+
var replPre = frameInfo.querySelector(".repl pre");
|
286
|
+
var replInput = frameInfo.querySelector(".repl input");
|
287
|
+
replInput.onkeydown = function(ev) {
|
288
|
+
if(ev.keyCode == 13) {
|
289
|
+
var text = replInput.value;
|
290
|
+
replInput.value = "";
|
291
|
+
apiCall("eval", { "index": index, source: text }, function(response) {
|
292
|
+
replPre.innerHTML += ">> " + response.highlighted_input + "\n";
|
293
|
+
if(response.error) {
|
294
|
+
replPre.innerHTML += "!! " + escapeHTML(response.error) + "\n";
|
295
|
+
} else {
|
296
|
+
replPre.innerHTML += "=> " + escapeHTML(response.result) + "\n";
|
297
|
+
}
|
298
|
+
replPre.scrollTop = replPre.offsetHeight;
|
299
|
+
});
|
300
|
+
}
|
301
|
+
};
|
302
|
+
})(i, frames[i], frameInfos[i]);
|
229
303
|
}
|
230
304
|
|
231
305
|
document.querySelector(".frames li:first-child").click();
|
232
306
|
})();
|
233
307
|
</script>
|
234
308
|
</html>
|
309
|
+
<!-- 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>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: better_errors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -75,10 +75,11 @@ files:
|
|
75
75
|
- lib/better_errors.rb
|
76
76
|
- lib/better_errors/core_ext/exception.rb
|
77
77
|
- lib/better_errors/error_frame.rb
|
78
|
-
- lib/better_errors/error_page.erb
|
79
78
|
- lib/better_errors/error_page.rb
|
80
79
|
- lib/better_errors/middleware.rb
|
81
80
|
- lib/better_errors/rails.rb
|
81
|
+
- lib/better_errors/templates/main.erb
|
82
|
+
- lib/better_errors/templates/variable_info.erb
|
82
83
|
- lib/better_errors/version.rb
|
83
84
|
- spec/better_errors/error_frame_spec.rb
|
84
85
|
- spec/better_errors/error_page_spec.rb
|