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