better_errors 0.0.8 → 0.1.0

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.

@@ -19,8 +19,10 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.add_development_dependency "rake"
21
21
 
22
- s.add_dependency "erubis"
23
- s.add_dependency "coderay"
24
- # optional dependency:
22
+ s.add_dependency "erubis", ">= 2.7.0"
23
+ s.add_dependency "coderay", ">= 1.0.0"
24
+
25
+ # optional dependencies:
25
26
  # s.add_dependency "binding_of_caller"
27
+ # s.add_dependency "pry"
26
28
  end
data/lib/better_errors.rb CHANGED
@@ -4,8 +4,10 @@ require "coderay"
4
4
 
5
5
  require "better_errors/version"
6
6
  require "better_errors/error_page"
7
- require "better_errors/error_frame"
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
13
  attr_accessor :application_root, :binding_of_caller_available, :logger
@@ -0,0 +1,60 @@
1
+ module BetterErrors
2
+ class CodeFormatter
3
+ FILE_TYPES = {
4
+ ".rb" => :ruby,
5
+ "" => :ruby,
6
+ ".html" => :html,
7
+ ".erb" => :erb,
8
+ ".haml" => :haml
9
+ }
10
+
11
+ attr_reader :filename, :line, :context
12
+
13
+ def initialize(filename, line, context = 5)
14
+ @filename = filename
15
+ @line = line
16
+ @context = context
17
+ end
18
+
19
+ def html
20
+ %{<div class="code">#{formatted_lines.join}</div>}
21
+ rescue Errno::ENOENT, Errno::EINVAL
22
+ source_unavailable
23
+ end
24
+
25
+ def source_unavailable
26
+ "<p>Source unavailable</p>"
27
+ end
28
+
29
+ def coderay_scanner
30
+ ext = File.extname(filename)
31
+ FILE_TYPES[ext] || :text
32
+ end
33
+
34
+ def formatted_lines
35
+ line_range.zip(highlighted_lines).map do |current_line, str|
36
+ class_name = current_line == line ? "highlight" : ""
37
+ sprintf '<pre class="%s">%5d %s</pre>', class_name, current_line, str
38
+ end
39
+ end
40
+
41
+ def highlighted_lines
42
+ CodeRay.scan(context_lines.join, coderay_scanner).div(wrap: nil).lines
43
+ end
44
+
45
+ def context_lines
46
+ range = line_range
47
+ source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
48
+ end
49
+
50
+ def source_lines
51
+ @source_lines ||= File.readlines(filename)
52
+ end
53
+
54
+ def line_range
55
+ min = [line - context, 1].max
56
+ max = [line + context, source_lines.count].min
57
+ min..max
58
+ end
59
+ end
60
+ end
@@ -1,17 +1,21 @@
1
1
  class Exception
2
- attr_reader :__better_errors_bindings_stack
3
-
4
2
  original_initialize = instance_method(:initialize)
5
3
 
6
- define_method :initialize do |*args|
7
- unless Thread.current[:__better_errors_exception_lock]
8
- Thread.current[:__better_errors_exception_lock] = true
9
- begin
10
- @__better_errors_bindings_stack = binding.callers.drop(1)
11
- ensure
12
- Thread.current[:__better_errors_exception_lock] = false
4
+ if BetterErrors.binding_of_caller_available?
5
+ define_method :initialize do |*args|
6
+ unless Thread.current[:__better_errors_exception_lock]
7
+ Thread.current[:__better_errors_exception_lock] = true
8
+ begin
9
+ @__better_errors_bindings_stack = binding.callers.drop(1)
10
+ ensure
11
+ Thread.current[:__better_errors_exception_lock] = false
12
+ end
13
13
  end
14
+ original_initialize.bind(self).call(*args)
14
15
  end
15
- original_initialize.bind(self).call(*args)
16
16
  end
17
- end
17
+
18
+ def __better_errors_bindings_stack
19
+ @__better_errors_bindings_stack || []
20
+ end
21
+ end
@@ -10,12 +10,13 @@ module BetterErrors
10
10
  Erubis::EscapedEruby.new(File.read(template_path(template_name)))
11
11
  end
12
12
 
13
- attr_reader :exception, :env
13
+ attr_reader :exception, :env, :repls
14
14
 
15
15
  def initialize(exception, env)
16
16
  @exception = real_exception(exception)
17
17
  @env = env
18
18
  @start_time = Time.now.to_f
19
+ @repls = []
19
20
  end
20
21
 
21
22
  def render(template_name = "main")
@@ -30,19 +31,22 @@ module BetterErrors
30
31
 
31
32
  def do_eval(opts)
32
33
  index = opts["index"].to_i
33
- binding = backtrace_frames[index].frame_binding
34
- return { error: "binding_of_caller unavailable" } unless binding
35
- response = begin
36
- result = binding.eval(opts["source"])
37
- { result: result.inspect }
38
- rescue Exception => e
39
- { error: (e.inspect rescue e.class.name rescue "Exception") }
40
- end
41
- response.merge(highlighted_input: CodeRay.scan(opts["source"], :ruby).div(wrap: nil))
34
+ code = opts["source"]
35
+
36
+ unless binding = backtrace_frames[index].frame_binding
37
+ return { error: "REPL unavailable in this stack frame" }
38
+ end
39
+
40
+ result, prompt =
41
+ (@repls[index] ||= REPL.provider.new(binding)).send_input(code)
42
+
43
+ { result: result,
44
+ prompt: prompt,
45
+ highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil) }
42
46
  end
43
47
 
44
48
  def backtrace_frames
45
- @backtrace_frames ||= ErrorFrame.from_exception(exception)
49
+ @backtrace_frames ||= StackFrame.from_exception(exception)
46
50
  end
47
51
 
48
52
  private
@@ -58,47 +62,8 @@ module BetterErrors
58
62
  env["REQUEST_PATH"]
59
63
  end
60
64
 
61
- def coderay_scanner_for_ext(ext)
62
- case ext
63
- when "rb"; :ruby
64
- when "html"; :html
65
- when "erb"; :erb
66
- when "haml"; :haml
67
- else :text
68
- end
69
- end
70
-
71
- def file_extension(filename)
72
- filename.split(".").last
73
- end
74
-
75
- def code_extract(frame, lines_of_context = 5)
76
- lines = File.readlines(frame.filename)
77
- min_line = [1, frame.line - lines_of_context].max - 1
78
- max_line = [frame.line + lines_of_context, lines.count + 1].min - 1
79
- raise Errno::EINVAL if min_line > lines.length
80
- [min_line, max_line, lines[min_line..max_line].join]
81
- end
82
-
83
65
  def highlighted_code_block(frame)
84
- ext = file_extension(frame.filename)
85
- scanner = coderay_scanner_for_ext(ext)
86
- min_line, max_line, code = code_extract(frame)
87
- highlighted_code = CodeRay.scan(code, scanner).div wrap: nil
88
- "".tap do |html|
89
- html << "<div class='code'>"
90
- highlighted_code.each_line.each_with_index do |str, index|
91
- if min_line + index + 1 == frame.line
92
- html << "<pre class='highlight'>"
93
- else
94
- html << "<pre>"
95
- end
96
- html << sprintf("%5d", min_line + index + 1) << " " << str << "</pre>"
97
- end
98
- html << "</div>"
99
- end
100
- rescue Errno::ENOENT, Errno::EINVAL
101
- "<p>Source unavailable</p>"
66
+ CodeFormatter.new(frame.filename, frame.line).html
102
67
  end
103
68
  end
104
69
  end
@@ -8,8 +8,11 @@ module BetterErrors
8
8
  end
9
9
 
10
10
  def call(env)
11
- if env["PATH_INFO"] =~ %r{/__better_errors/(?<oid>\d+)/(?<method>\w+)}
11
+ case env["PATH_INFO"]
12
+ when %r{\A/__better_errors/(?<oid>\d+)/(?<method>\w+)\z}
12
13
  internal_call env, $~
14
+ when %r{\A/__better_errors/?\z}
15
+ show_error_page env
13
16
  else
14
17
  app_call env
15
18
  end
@@ -21,9 +24,13 @@ module BetterErrors
21
24
  rescue Exception => ex
22
25
  @error_page = @handler.new ex, env
23
26
  log_exception
27
+ show_error_page(env)
28
+ end
29
+
30
+ def show_error_page(env)
24
31
  [500, { "Content-Type" => "text/html; charset=utf-8" }, [@error_page.render]]
25
32
  end
26
-
33
+
27
34
  def log_exception
28
35
  return unless BetterErrors.logger
29
36
 
@@ -0,0 +1,27 @@
1
+ module BetterErrors
2
+ module REPL
3
+ PROVIDERS = [
4
+ { impl: "better_errors/repl/pry",
5
+ const: :Pry },
6
+ { impl: "better_errors/repl/basic",
7
+ const: :Basic },
8
+ ]
9
+
10
+ def self.provider
11
+ @provider ||= const_get detect[:const]
12
+ end
13
+
14
+ def self.detect
15
+ PROVIDERS.find do |prov|
16
+ test_provider prov
17
+ end
18
+ end
19
+
20
+ def self.test_provider(provider)
21
+ require provider[:impl]
22
+ true
23
+ rescue LoadError
24
+ false
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ module BetterErrors
2
+ module REPL
3
+ class Basic
4
+ def initialize(binding)
5
+ @binding = binding
6
+ end
7
+
8
+ def send_input(str)
9
+ [execute(str), ">>"]
10
+ end
11
+
12
+ private
13
+ def execute(str)
14
+ "=> #{@binding.eval(str).inspect}\n"
15
+ rescue Exception => e
16
+ "!! #{e.inspect rescue e.class.to_s rescue "Exception"}\n"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,67 @@
1
+ require "fiber"
2
+ require "pry"
3
+
4
+ module BetterErrors
5
+ module REPL
6
+ class Pry
7
+ class Input
8
+ def readline
9
+ Fiber.yield
10
+ end
11
+ end
12
+
13
+ class Output
14
+ def initialize
15
+ @buffer = ""
16
+ end
17
+
18
+ def puts(*args)
19
+ args.each do |arg|
20
+ @buffer << "#{arg.chomp}\n"
21
+ end
22
+ end
23
+
24
+ def tty?
25
+ false
26
+ end
27
+
28
+ def read_buffer
29
+ @buffer
30
+ ensure
31
+ @buffer = ""
32
+ end
33
+ end
34
+
35
+ def initialize(binding)
36
+ @fiber = Fiber.new do
37
+ @pry.repl binding
38
+ end
39
+ @input = Input.new
40
+ @output = Output.new
41
+ @pry = ::Pry.new input: @input, output: @output
42
+ @pry.hooks.clear_all
43
+ @continued_expression = false
44
+ @pry.hooks.add_hook :after_read, "better_errors hacky hook" do
45
+ @continued_expression = false
46
+ end
47
+ @fiber.resume
48
+ end
49
+
50
+ def pry_indent
51
+ @pry.instance_variable_get(:@indent)
52
+ end
53
+
54
+ def send_input(str)
55
+ old_pry_config_color = ::Pry.config.color
56
+ ::Pry.config.color = false
57
+ @continued_expression = true
58
+ @fiber.resume "#{str}\n"
59
+ # TODO - indent with `pry_indent.current_prefix`
60
+ # TODO - use proper pry prompt
61
+ [@output.read_buffer, @continued_expression ? ".." : ">>"]
62
+ ensure
63
+ ::Pry.config.color = old_pry_config_color
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,12 +1,18 @@
1
1
  module BetterErrors
2
- class ErrorFrame
2
+ class StackFrame
3
3
  def self.from_exception(exception)
4
+ idx_offset = 0
4
5
  exception.backtrace.each_with_index.map { |frame, idx|
5
- next unless frame =~ /\A(.*):(\d*):in `(.*)'\z/
6
- if BetterErrors.binding_of_caller_available?
7
- b = exception.__better_errors_bindings_stack[idx]
6
+ frame_binding = exception.__better_errors_bindings_stack[idx - idx_offset]
7
+ md = /\A(?<file>.*):(?<line>\d*):in `(?<name>.*)'\z/.match(frame)
8
+
9
+ # prevent mismatching frames in the backtrace with the binding stack
10
+ if frame_binding and frame_binding.eval("__FILE__") != md[:file]
11
+ idx_offset += 1
12
+ frame_binding = nil
8
13
  end
9
- ErrorFrame.new($1, $2.to_i, $3, b)
14
+
15
+ StackFrame.new(md[:file], md[:line].to_i, md[:name], frame_binding)
10
16
  }.compact
11
17
  end
12
18
 
@@ -22,7 +28,8 @@ module BetterErrors
22
28
  end
23
29
 
24
30
  def application?
25
- starts_with? filename, BetterErrors.application_root if BetterErrors.application_root
31
+ root = BetterErrors.application_root
32
+ starts_with? filename, root if root
26
33
  end
27
34
 
28
35
  def application_path
@@ -73,7 +80,9 @@ module BetterErrors
73
80
 
74
81
  def instance_variables
75
82
  return {} unless frame_binding
76
- Hash[frame_binding.eval("instance_variables").map { |x| [x, frame_binding.eval(x.to_s)] }]
83
+ Hash[frame_binding.eval("instance_variables").map { |x|
84
+ [x, frame_binding.eval(x.to_s)]
85
+ }]
77
86
  end
78
87
 
79
88
  def to_s
@@ -105,6 +105,7 @@
105
105
  font-family:Monaco, Incosolata, Consolas, monospace;
106
106
  font-size:14px;
107
107
  margin-bottom:4px;
108
+ word-wrap:break-word;
108
109
  }
109
110
  .location {
110
111
  color:#888888;
@@ -248,7 +249,7 @@
248
249
  <h3>REPL</h3>
249
250
  <div class="console">
250
251
  <pre></pre>
251
- <div class="prompt">&gt;&gt; <input/></div>
252
+ <div class="prompt"><span>&gt;&gt;</span> <input/></div>
252
253
  </div>
253
254
  </div>
254
255
 
@@ -266,19 +267,16 @@
266
267
  </body>
267
268
  <script>
268
269
  (function() {
269
- var oid = <%== object_id.to_s.inspect %>;
270
+ var OID = <%== object_id.to_s.inspect %>;
270
271
 
271
- var previous = null;
272
+ var previousFrame = null;
272
273
  var previousFrameInfo = null;
273
- var frames = document.querySelectorAll("ul.frames li");
274
- var frameInfos = document.querySelectorAll(".frame_info");
275
-
276
- var header = document.querySelector("header");
277
- var headerHeight = header.offsetHeight;
278
-
279
- apiCall = function(method, opts, cb) {
274
+ var allFrames = document.querySelectorAll("ul.frames li");
275
+ var allFrameInfos = document.querySelectorAll(".frame_info");
276
+
277
+ function apiCall(method, opts, cb) {
280
278
  var req = new XMLHttpRequest();
281
- req.open("POST", "/__better_errors/" + oid + "/" + method, true);
279
+ req.open("POST", "/__better_errors/" + OID + "/" + method, true);
282
280
  req.setRequestHeader("Content-Type", "application/json");
283
281
  req.send(JSON.stringify(opts));
284
282
  req.onreadystatechange = function() {
@@ -293,9 +291,116 @@
293
291
  return html.replace(/&/, "&amp;").replace(/</g, "&lt;");
294
292
  }
295
293
 
296
- function selectFrameInfo(index) {
297
- var el = document.getElementById("frame_info_" + index);
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
+ }
298
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];
299
404
  var varInfo = el.querySelector(".variable_info");
300
405
  if(varInfo && varInfo.innerHTML == "") {
301
406
  apiCall("variables", { "index": index }, function(response) {
@@ -306,85 +411,69 @@
306
411
  }
307
412
  });
308
413
  }
414
+ }
415
+
416
+ function selectFrameInfo(index) {
417
+ populateVariableInfo(index);
309
418
 
310
419
  if(previousFrameInfo) {
311
420
  previousFrameInfo.style.display = "none";
312
421
  }
313
- previousFrameInfo = el;
422
+ previousFrameInfo = allFrameInfos[index];
314
423
  previousFrameInfo.style.display = "block";
315
424
 
316
- var replInput = el.querySelector(".repl input");
317
- if(replInput) {
318
- replInput.focus();
425
+ if(REPL.all[index]) {
426
+ REPL.all[index].focus();
319
427
  }
320
428
  }
321
429
 
322
- for(var i = 0; i < frames.length; i++) {
323
- (function(index, el, frameInfo) {
430
+ for(var i = 0; i < allFrames.length; i++) {
431
+ (function(i, el) {
432
+ var el = allFrames[i];
324
433
  el.onclick = function() {
325
- if(previous) {
326
- previous.className = "";
434
+ if(previousFrame) {
435
+ previousFrame.className = "";
327
436
  }
328
437
  el.className = "selected";
329
- previous = el;
438
+ previousFrame = el;
330
439
 
331
440
  selectFrameInfo(el.attributes["data-index"].value);
332
441
  };
333
- var replPre = frameInfo.querySelector(".repl pre");
334
- var replInput = frameInfo.querySelector(".repl input");
335
- if(replInput) {
336
- replInput.onkeydown = function(ev) {
337
- if(ev.keyCode == 13) {
338
- var text = replInput.value;
339
- replInput.value = "";
340
- apiCall("eval", { "index": index, source: text }, function(response) {
341
- replPre.innerHTML += ">> " + response.highlighted_input + "\n";
342
- if(response.error) {
343
- replPre.innerHTML += "!! " + escapeHTML(response.error) + "\n";
344
- } else {
345
- replPre.innerHTML += "=> " + escapeHTML(response.result) + "\n";
346
- }
347
- replPre.scrollTop = replPre.scrollHeight;
348
- });
349
- }
350
- };
442
+ var repl = allFrameInfos[i].querySelector(".repl .console");
443
+ if(repl) {
444
+ new REPL(i).install(repl);
351
445
  }
352
- })(i, frames[i], frameInfos[i]);
446
+ })(i);
353
447
  }
354
448
 
355
- var applicationFrames = document.getElementById("application_frames");
356
- var allFrames = document.getElementById("all_frames");
449
+ allFrames[0].click();
450
+
451
+ var applicationFramesButton = document.getElementById("application_frames");
452
+ var allFramesButton = document.getElementById("all_frames");
357
453
 
358
- applicationFrames.onclick = function() {
359
- allFrames.className = "";
360
- applicationFrames.className = "selected";
361
- for(var i = 0; i < frames.length; i++) {
362
- if(frames[i].attributes["data-context"].value == "application") {
363
- frames[i].style.display = "block";
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";
364
460
  } else {
365
- frames[i].style.display = "none";
461
+ allFrames[i].style.display = "none";
366
462
  }
367
463
  }
368
464
  return false;
369
465
  };
370
466
 
371
- allFrames.onclick = function() {
372
- applicationFrames.className = "";
373
- allFrames.className = "selected";
374
- for(var i = 0; i < frames.length; i++) {
375
- frames[i].style.display = "block";
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";
376
472
  }
377
473
  return false;
378
474
  };
379
475
 
380
- applicationFrames.click();
381
-
382
- for(var i = 0; i < frames.length; i++) {
383
- if(frames[i].attributes["data-context"].value == "application") {
384
- frames[i].click();
385
- break;
386
- }
387
- }
476
+ applicationFramesButton.click();
388
477
  })();
389
478
  </script>
390
479
  </html>
@@ -1,3 +1,3 @@
1
1
  module BetterErrors
2
- VERSION = "0.0.8"
2
+ VERSION = "0.1.0"
3
3
  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
@@ -30,28 +30,6 @@ module BetterErrors
30
30
  response.should include("ZeroDivisionError")
31
31
  end
32
32
 
33
- context "when showing source code" do
34
- before do
35
- exception.stub!(:backtrace).and_return([
36
- "#{File.expand_path("../support/my_source.rb", __FILE__)}:8:in `some_method'"
37
- ])
38
- end
39
-
40
- it "should show the line where the exception was raised" do
41
- response.should include("8 eight")
42
- end
43
-
44
- it "should show five lines of context" do
45
- response.should include("3 three")
46
- response.should include("13 thirteen")
47
- end
48
-
49
- it "should not show more than five lines of context" do
50
- response.should_not include("2 two")
51
- response.should_not include("14 fourteen")
52
- end
53
- end
54
-
55
33
  context "variable inspection" do
56
34
  let(:exception) { empty_binding.eval("raise") rescue $! }
57
35
 
@@ -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
@@ -1,24 +1,24 @@
1
1
  require "spec_helper"
2
2
 
3
3
  module BetterErrors
4
- describe ErrorFrame do
4
+ describe StackFrame do
5
5
  context "#application?" do
6
6
  it "should be true for application filenames" do
7
7
  BetterErrors.stub!(:application_root).and_return("/abc/xyz")
8
- frame = ErrorFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
8
+ frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
9
9
 
10
10
  frame.application?.should be_true
11
11
  end
12
12
 
13
13
  it "should be false for everything else" do
14
14
  BetterErrors.stub!(:application_root).and_return("/abc/xyz")
15
- frame = ErrorFrame.new("/abc/nope", 123, "foo")
15
+ frame = StackFrame.new("/abc/nope", 123, "foo")
16
16
 
17
17
  frame.application?.should be_false
18
18
  end
19
19
 
20
20
  it "should not care if no application_root is set" do
21
- frame = ErrorFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
21
+ frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
22
22
 
23
23
  frame.application?.should be_false
24
24
  end
@@ -27,14 +27,14 @@ module BetterErrors
27
27
  context "#gem?" do
28
28
  it "should be true for gem filenames" do
29
29
  Gem.stub!(:path).and_return(["/abc/xyz"])
30
- frame = ErrorFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
30
+ frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
31
31
 
32
32
  frame.gem?.should be_true
33
33
  end
34
34
 
35
35
  it "should be false for everything else" do
36
36
  Gem.stub!(:path).and_return(["/abc/xyz"])
37
- frame = ErrorFrame.new("/abc/nope", 123, "foo")
37
+ frame = StackFrame.new("/abc/nope", 123, "foo")
38
38
 
39
39
  frame.gem?.should be_false
40
40
  end
@@ -43,7 +43,7 @@ module BetterErrors
43
43
  context "#application_path" do
44
44
  it "should chop off the application root" do
45
45
  BetterErrors.stub!(:application_root).and_return("/abc/xyz")
46
- frame = ErrorFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
46
+ frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
47
47
 
48
48
  frame.application_path.should == "app/controllers/crap_controller.rb"
49
49
  end
@@ -52,7 +52,7 @@ module BetterErrors
52
52
  context "#gem_path" do
53
53
  it "should chop of the gem path and stick (gem) there" do
54
54
  Gem.stub!(:path).and_return(["/abc/xyz"])
55
- frame = ErrorFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
55
+ frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
56
56
 
57
57
  frame.gem_path.should == "(gem) whatever-1.2.3/lib/whatever.rb"
58
58
  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.8
4
+ version: 0.1.0
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-10 00:00:00.000000000 Z
12
+ date: 2012-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ! '>='
36
36
  - !ruby/object:Gem::Version
37
- version: '0'
37
+ version: 2.7.0
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
- version: '0'
45
+ version: 2.7.0
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: coderay
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 1.0.0
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,7 +58,7 @@ dependencies:
58
58
  requirements:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 1.0.0
62
62
  description: Provides a better error page for Rails and other Rack apps. Includes
63
63
  source code inspection, a live REPL and local/instance variable inspection for all
64
64
  stack frames.
@@ -75,17 +75,23 @@ files:
75
75
  - Rakefile
76
76
  - better_errors.gemspec
77
77
  - lib/better_errors.rb
78
+ - lib/better_errors/code_formatter.rb
78
79
  - lib/better_errors/core_ext/exception.rb
79
- - lib/better_errors/error_frame.rb
80
80
  - lib/better_errors/error_page.rb
81
81
  - lib/better_errors/middleware.rb
82
82
  - lib/better_errors/rails.rb
83
+ - lib/better_errors/repl.rb
84
+ - lib/better_errors/repl/basic.rb
85
+ - lib/better_errors/repl/pry.rb
86
+ - lib/better_errors/stack_frame.rb
83
87
  - lib/better_errors/templates/main.erb
84
88
  - lib/better_errors/templates/variable_info.erb
85
89
  - lib/better_errors/version.rb
86
- - spec/better_errors/error_frame_spec.rb
90
+ - spec/better_errors/code_formatter_spec.rb
87
91
  - spec/better_errors/error_page_spec.rb
88
92
  - spec/better_errors/middleware_spec.rb
93
+ - spec/better_errors/repl/basic_spec.rb
94
+ - spec/better_errors/stack_frame_spec.rb
89
95
  - spec/better_errors/support/my_source.rb
90
96
  - spec/spec_helper.rb
91
97
  homepage: https://github.com/charliesome/better_errors
@@ -114,8 +120,10 @@ signing_key:
114
120
  specification_version: 3
115
121
  summary: Better error page for Rails and other Rack apps
116
122
  test_files:
117
- - spec/better_errors/error_frame_spec.rb
123
+ - spec/better_errors/code_formatter_spec.rb
118
124
  - spec/better_errors/error_page_spec.rb
119
125
  - spec/better_errors/middleware_spec.rb
126
+ - spec/better_errors/repl/basic_spec.rb
127
+ - spec/better_errors/stack_frame_spec.rb
120
128
  - spec/better_errors/support/my_source.rb
121
129
  - spec/spec_helper.rb