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 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