better_errors 0.0.2 → 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.
- data/README.md +26 -8
- data/better_errors.gemspec +7 -5
- data/lib/better_errors/code_formatter.rb +60 -0
- data/lib/better_errors/core_ext/exception.rb +14 -13
- data/lib/better_errors/error_page.rb +36 -47
- data/lib/better_errors/middleware.rb +41 -2
- data/lib/better_errors/rails.rb +5 -4
- data/lib/better_errors/repl/basic.rb +20 -0
- data/lib/better_errors/repl/pry.rb +67 -0
- data/lib/better_errors/repl.rb +27 -0
- data/lib/better_errors/{error_frame.rb → stack_frame.rb} +30 -8
- data/lib/better_errors/templates/main.erb +480 -0
- data/lib/better_errors/templates/variable_info.erb +13 -0
- data/lib/better_errors/version.rb +1 -1
- data/lib/better_errors.rb +4 -2
- data/spec/better_errors/code_formatter_spec.rb +51 -0
- data/spec/better_errors/error_page_spec.rb +31 -16
- data/spec/better_errors/middleware_spec.rb +8 -0
- 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 +23 -13
- data/lib/better_errors/error_page.erb +0 -234
data/README.md
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
# Better Errors
|
|
2
2
|
|
|
3
|
-
Better Errors replaces the standard Rails error page with a much better and more useful error page. It is also usable outside of Rails.
|
|
3
|
+
Better Errors replaces the standard Rails error page with a much better and more useful error page. It is also usable outside of Rails in any Rack app as Rack middleware.
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
* Full stack trace
|
|
10
10
|
* Source code inspection for all stack frames (with highlighting)
|
|
11
11
|
* Local and instance variable inspection
|
|
12
|
+
* Live REPL on every stack frame
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
15
16
|
Add this line to your application's Gemfile (under the **development** group):
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
```ruby
|
|
19
|
+
group :development do
|
|
20
|
+
gem "better_errors"
|
|
21
|
+
end
|
|
22
|
+
```
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
If you would like to use Better Errors' **advanced features** (REPL, local/instance variable inspection, pretty stack frame names), you need to add the [`binding_of_caller`](https://github.com/banister/binding_of_caller) gem to your Gemfile:
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
```ruby
|
|
27
|
+
gem "binding_of_caller"
|
|
28
|
+
```
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
This is an optional dependency however, and Better Errors will work without it.
|
|
26
31
|
|
|
27
32
|
## Usage
|
|
28
33
|
|
|
@@ -44,6 +49,19 @@ get "/" do
|
|
|
44
49
|
end
|
|
45
50
|
```
|
|
46
51
|
|
|
52
|
+
## Compatibility
|
|
53
|
+
|
|
54
|
+
* **Supported**
|
|
55
|
+
* MRI 1.9.2, 1.9.3
|
|
56
|
+
* JRuby (1.9 mode) - *advanced features unsupported*
|
|
57
|
+
* Rubinius (1.9 mode) - *advanced features unsupported*
|
|
58
|
+
* **Coming soon**
|
|
59
|
+
* MRI 2.0.0 - the official API for grabbing caller bindings is slated for MRI 2.0.0, but it has not been implemented yet
|
|
60
|
+
|
|
61
|
+
## Known issues
|
|
62
|
+
|
|
63
|
+
* Calling `yield` from the REPL segfaults MRI 1.9.x.
|
|
64
|
+
|
|
47
65
|
## Contributing
|
|
48
66
|
|
|
49
67
|
1. Fork it
|
data/better_errors.gemspec
CHANGED
|
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
|
8
8
|
s.version = BetterErrors::VERSION
|
|
9
9
|
s.authors = ["Charlie Somerville"]
|
|
10
10
|
s.email = ["charlie@charliesomerville.com"]
|
|
11
|
-
s.description = %q{
|
|
12
|
-
s.summary = %q{Better
|
|
11
|
+
s.description = %q{Provides a better error page for Rails and other Rack apps. Includes source code inspection, a live REPL and local/instance variable inspection for all stack frames.}
|
|
12
|
+
s.summary = %q{Better error page for Rails and other Rack apps}
|
|
13
13
|
s.homepage = "https://github.com/charliesome/better_errors"
|
|
14
14
|
s.license = "MIT"
|
|
15
15
|
|
|
@@ -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
|
|
@@ -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,20 +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
|
|
13
12
|
end
|
|
14
|
-
ensure
|
|
15
|
-
Thread.current[:__better_errors_exception_lock] = false
|
|
16
13
|
end
|
|
14
|
+
original_initialize.bind(self).call(*args)
|
|
17
15
|
end
|
|
18
|
-
original_initialize.bind(self).call(*args)
|
|
19
16
|
end
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
def __better_errors_bindings_stack
|
|
19
|
+
@__better_errors_bindings_stack || []
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -2,23 +2,51 @@ require "json"
|
|
|
2
2
|
|
|
3
3
|
module BetterErrors
|
|
4
4
|
class ErrorPage
|
|
5
|
-
def self.template_path
|
|
6
|
-
|
|
5
|
+
def self.template_path(template_name)
|
|
6
|
+
File.expand_path("../templates/#{template_name}.erb", __FILE__)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def self.template
|
|
10
|
-
Erubis::EscapedEruby.new(File.read(template_path))
|
|
9
|
+
def self.template(template_name)
|
|
10
|
+
Erubis::EscapedEruby.new(File.read(template_path(template_name)))
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
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
|
+
@start_time = Time.now.to_f
|
|
19
|
+
@repls = []
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
def render
|
|
21
|
-
self.class.template.result binding
|
|
22
|
+
def render(template_name = "main")
|
|
23
|
+
self.class.template(template_name).result binding
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def do_variables(opts)
|
|
27
|
+
index = opts["index"].to_i
|
|
28
|
+
@frame = backtrace_frames[index]
|
|
29
|
+
{ html: render("variable_info") }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def do_eval(opts)
|
|
33
|
+
index = opts["index"].to_i
|
|
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) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def backtrace_frames
|
|
49
|
+
@backtrace_frames ||= StackFrame.from_exception(exception)
|
|
22
50
|
end
|
|
23
51
|
|
|
24
52
|
private
|
|
@@ -33,48 +61,9 @@ module BetterErrors
|
|
|
33
61
|
def request_path
|
|
34
62
|
env["REQUEST_PATH"]
|
|
35
63
|
end
|
|
36
|
-
|
|
37
|
-
def backtrace_frames
|
|
38
|
-
@backtrace_frames ||= ErrorFrame.from_exception(exception)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def coderay_scanner_for_ext(ext)
|
|
42
|
-
case ext
|
|
43
|
-
when "rb"; :ruby
|
|
44
|
-
when "html"; :html
|
|
45
|
-
when "erb"; :erb
|
|
46
|
-
when "haml"; :haml
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def file_extension(filename)
|
|
51
|
-
filename.split(".").last
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def code_extract(frame, lines_of_context = 5)
|
|
55
|
-
lines = File.readlines(frame.filename)
|
|
56
|
-
min_line = [1, frame.line - lines_of_context].max - 1
|
|
57
|
-
max_line = [frame.line + lines_of_context, lines.count + 1].min - 1
|
|
58
|
-
[min_line, max_line, lines[min_line..max_line].join]
|
|
59
|
-
end
|
|
60
64
|
|
|
61
65
|
def highlighted_code_block(frame)
|
|
62
|
-
|
|
63
|
-
scanner = coderay_scanner_for_ext(ext)
|
|
64
|
-
min_line, max_line, code = code_extract(frame)
|
|
65
|
-
highlighted_code = CodeRay.scan(code, scanner).div wrap: nil
|
|
66
|
-
"".tap do |html|
|
|
67
|
-
html << "<div class='code'>"
|
|
68
|
-
highlighted_code.each_line.each_with_index do |str, index|
|
|
69
|
-
if min_line + index + 1 == frame.line
|
|
70
|
-
html << "<pre class='highlight'>"
|
|
71
|
-
else
|
|
72
|
-
html << "<pre>"
|
|
73
|
-
end
|
|
74
|
-
html << sprintf("%5d", min_line + index + 1) << " " << str << "</pre>"
|
|
75
|
-
end
|
|
76
|
-
html << "</div>"
|
|
77
|
-
end
|
|
66
|
+
CodeFormatter.new(frame.filename, frame.line).html
|
|
78
67
|
end
|
|
79
68
|
end
|
|
80
69
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
1
3
|
module BetterErrors
|
|
2
4
|
class Middleware
|
|
3
5
|
def initialize(app, handler = ErrorPage)
|
|
@@ -6,10 +8,47 @@ module BetterErrors
|
|
|
6
8
|
end
|
|
7
9
|
|
|
8
10
|
def call(env)
|
|
11
|
+
case env["PATH_INFO"]
|
|
12
|
+
when %r{\A/__better_errors/(?<oid>\d+)/(?<method>\w+)\z}
|
|
13
|
+
internal_call env, $~
|
|
14
|
+
when %r{\A/__better_errors/?\z}
|
|
15
|
+
show_error_page env
|
|
16
|
+
else
|
|
17
|
+
app_call env
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def app_call(env)
|
|
9
23
|
@app.call env
|
|
10
24
|
rescue Exception => ex
|
|
11
|
-
error_page = @handler.new ex, env
|
|
12
|
-
|
|
25
|
+
@error_page = @handler.new ex, env
|
|
26
|
+
log_exception
|
|
27
|
+
show_error_page(env)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def show_error_page(env)
|
|
31
|
+
[500, { "Content-Type" => "text/html; charset=utf-8" }, [@error_page.render]]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def log_exception
|
|
35
|
+
return unless BetterErrors.logger
|
|
36
|
+
|
|
37
|
+
message = "\n#{@error_page.exception.class} - #{@error_page.exception.message}:\n"
|
|
38
|
+
@error_page.backtrace_frames.each do |frame|
|
|
39
|
+
message << " #{frame}\n"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
BetterErrors.logger.fatal message
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def internal_call(env, opts)
|
|
46
|
+
if opts[:oid].to_i != @error_page.object_id
|
|
47
|
+
return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
response = @error_page.send("do_#{opts[:method]}", JSON.parse(env["rack.input"].read))
|
|
51
|
+
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
|
|
13
52
|
end
|
|
14
53
|
end
|
|
15
54
|
end
|
data/lib/better_errors/rails.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
module BetterErrors
|
|
2
2
|
class Railtie < Rails::Railtie
|
|
3
3
|
initializer "better_errors.configure_rails_initialization" do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
unless Rails.env.production?
|
|
5
|
+
Rails.application.middleware.use BetterErrors::Middleware
|
|
6
|
+
BetterErrors.logger = Rails.logger
|
|
7
|
+
BetterErrors.application_root = Rails.root.to_s
|
|
8
|
+
end
|
|
8
9
|
end
|
|
9
10
|
end
|
|
10
11
|
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
|
|
@@ -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
|
|
@@ -1,10 +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
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
StackFrame.new(md[:file], md[:line].to_i, md[:name], frame_binding)
|
|
8
16
|
}.compact
|
|
9
17
|
end
|
|
10
18
|
|
|
@@ -20,7 +28,8 @@ module BetterErrors
|
|
|
20
28
|
end
|
|
21
29
|
|
|
22
30
|
def application?
|
|
23
|
-
|
|
31
|
+
root = BetterErrors.application_root
|
|
32
|
+
starts_with? filename, root if root
|
|
24
33
|
end
|
|
25
34
|
|
|
26
35
|
def application_path
|
|
@@ -59,19 +68,32 @@ module BetterErrors
|
|
|
59
68
|
|
|
60
69
|
def local_variables
|
|
61
70
|
return {} unless frame_binding
|
|
62
|
-
|
|
71
|
+
frame_binding.eval("local_variables").each_with_object({}) do |name, hash|
|
|
72
|
+
begin
|
|
73
|
+
hash[name] = frame_binding.eval(name.to_s)
|
|
74
|
+
rescue NameError => e
|
|
75
|
+
# local_variables sometimes returns broken variables.
|
|
76
|
+
# https://bugs.ruby-lang.org/issues/7536
|
|
77
|
+
end
|
|
78
|
+
end
|
|
63
79
|
end
|
|
64
80
|
|
|
65
81
|
def instance_variables
|
|
66
82
|
return {} unless frame_binding
|
|
67
|
-
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
|
+
}]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_s
|
|
89
|
+
"#{pretty_path}:#{line}:in `#{name}'"
|
|
68
90
|
end
|
|
69
91
|
|
|
70
92
|
private
|
|
71
93
|
def set_pretty_method_name
|
|
72
94
|
name =~ /\A(block (\([^)]+\) )?in )?/
|
|
73
95
|
recv = frame_binding.eval("self")
|
|
74
|
-
method = frame_binding.eval("__method__")
|
|
96
|
+
return unless method = frame_binding.eval("__method__")
|
|
75
97
|
@name = if recv.is_a? Module
|
|
76
98
|
"#{$1}#{recv}.#{method}"
|
|
77
99
|
else
|