better_errors 0.0.8 → 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.

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