better_errors 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of better_errors might be problematic. Click here for more details.

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
- ![image](http://i.imgur.com/O9anD.png)
5
+ ![image](http://i.imgur.com/quHUZ.png)
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
- b = exception.__better_errors_bindings_stack[idx]
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
- Hash[frame_binding.eval("local_variables").map { |x| [x, frame_binding.eval(x.to_s)] }]
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
- __FILE__.gsub(/\.rb$/, ".erb")
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
- <h3>Local Variables</h3>
181
- <table class="var_table">
182
- <% frame.local_variables.each do |name, value| %>
183
- <tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
184
- <% end %>
185
- </table>
208
+ <div class="repl">
209
+ <h3>REPL</h3>
210
+ <div class="console">
211
+ <pre></pre>
212
+ <div class="prompt">&gt;&gt; <input/></div>
213
+ </div>
214
+ </div>
186
215
 
187
- <h3>Instance Variables</h3>
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(/&/, "&amp;").replace(/</g, "&lt;");
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
- })(frames[i]);
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>
@@ -1,3 +1,3 @@
1
1
  module BetterErrors
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
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.2
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-08 00:00:00.000000000 Z
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