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.
- data/better_errors.gemspec +5 -3
- data/lib/better_errors.rb +3 -1
- data/lib/better_errors/code_formatter.rb +60 -0
- data/lib/better_errors/core_ext/exception.rb +15 -11
- data/lib/better_errors/error_page.rb +16 -51
- data/lib/better_errors/middleware.rb +9 -2
- data/lib/better_errors/repl.rb +27 -0
- data/lib/better_errors/repl/basic.rb +20 -0
- data/lib/better_errors/repl/pry.rb +67 -0
- data/lib/better_errors/{error_frame.rb → stack_frame.rb} +16 -7
- data/lib/better_errors/templates/main.erb +152 -63
- data/lib/better_errors/version.rb +1 -1
- data/spec/better_errors/code_formatter_spec.rb +51 -0
- data/spec/better_errors/error_page_spec.rb +0 -22
- data/spec/better_errors/repl/basic_spec.rb +32 -0
- data/spec/better_errors/{error_frame_spec.rb → stack_frame_spec.rb} +8 -8
- metadata +17 -9
data/better_errors.gemspec
CHANGED
@@ -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
|
-
|
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/
|
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
|
-
|
7
|
-
|
8
|
-
Thread.current[:__better_errors_exception_lock]
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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 ||=
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
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|
|
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">>>
|
252
|
+
<div class="prompt"><span>>></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
|
270
|
+
var OID = <%== object_id.to_s.inspect %>;
|
270
271
|
|
271
|
-
var
|
272
|
+
var previousFrame = null;
|
272
273
|
var previousFrameInfo = null;
|
273
|
-
var
|
274
|
-
var
|
275
|
-
|
276
|
-
|
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/" +
|
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(/&/, "&").replace(/</g, "<");
|
294
292
|
}
|
295
293
|
|
296
|
-
function
|
297
|
-
|
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 =
|
422
|
+
previousFrameInfo = allFrameInfos[index];
|
314
423
|
previousFrameInfo.style.display = "block";
|
315
424
|
|
316
|
-
|
317
|
-
|
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 <
|
323
|
-
(function(
|
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(
|
326
|
-
|
434
|
+
if(previousFrame) {
|
435
|
+
previousFrame.className = "";
|
327
436
|
}
|
328
437
|
el.className = "selected";
|
329
|
-
|
438
|
+
previousFrame = el;
|
330
439
|
|
331
440
|
selectFrameInfo(el.attributes["data-index"].value);
|
332
441
|
};
|
333
|
-
var
|
334
|
-
|
335
|
-
|
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
|
446
|
+
})(i);
|
353
447
|
}
|
354
448
|
|
355
|
-
|
356
|
-
|
449
|
+
allFrames[0].click();
|
450
|
+
|
451
|
+
var applicationFramesButton = document.getElementById("application_frames");
|
452
|
+
var allFramesButton = document.getElementById("all_frames");
|
357
453
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
for(var i = 0; i <
|
362
|
-
if(
|
363
|
-
|
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
|
-
|
461
|
+
allFrames[i].style.display = "none";
|
366
462
|
}
|
367
463
|
}
|
368
464
|
return false;
|
369
465
|
};
|
370
466
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
for(var i = 0; i <
|
375
|
-
|
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
|
-
|
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>
|
@@ -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
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
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-
|
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:
|
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:
|
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:
|
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:
|
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/
|
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/
|
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
|