better_errors 2.0.0 → 2.8.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.
- checksums.yaml +5 -5
- data/.coveralls.yml +1 -0
- data/.gitignore +3 -0
- data/.travis.yml +96 -2
- data/CHANGELOG.md +1 -1
- data/Gemfile +2 -7
- data/LICENSE.txt +1 -1
- data/README.md +99 -39
- data/better_errors.gemspec +23 -4
- data/gemfiles/pry010.gemfile +9 -0
- data/gemfiles/pry011.gemfile +8 -0
- data/gemfiles/pry09.gemfile +8 -0
- data/gemfiles/rack.gemfile +7 -0
- data/gemfiles/rack_boc.gemfile +8 -0
- data/gemfiles/rails42.gemfile +9 -0
- data/gemfiles/rails42_boc.gemfile +10 -0
- data/gemfiles/rails42_haml.gemfile +10 -0
- data/gemfiles/rails50.gemfile +8 -0
- data/gemfiles/rails50_boc.gemfile +9 -0
- data/gemfiles/rails50_haml.gemfile +9 -0
- data/gemfiles/rails51.gemfile +8 -0
- data/gemfiles/rails51_boc.gemfile +9 -0
- data/gemfiles/rails51_haml.gemfile +9 -0
- data/gemfiles/rails52.gemfile +8 -0
- data/gemfiles/rails52_boc.gemfile +9 -0
- data/gemfiles/rails52_haml.gemfile +9 -0
- data/gemfiles/rails60.gemfile +7 -0
- data/gemfiles/rails60_boc.gemfile +8 -0
- data/gemfiles/rails60_haml.gemfile +8 -0
- data/lib/better_errors/code_formatter/html.rb +1 -1
- data/lib/better_errors/code_formatter.rb +7 -7
- data/lib/better_errors/error_page.rb +56 -15
- data/lib/better_errors/inspectable_value.rb +45 -0
- data/lib/better_errors/middleware.rb +96 -16
- data/lib/better_errors/raised_exception.rb +13 -3
- data/lib/better_errors/repl/basic.rb +3 -3
- data/lib/better_errors/repl/pry.rb +18 -8
- data/lib/better_errors/repl.rb +6 -4
- data/lib/better_errors/stack_frame.rb +33 -8
- data/lib/better_errors/templates/main.erb +71 -34
- data/lib/better_errors/templates/text.erb +2 -2
- data/lib/better_errors/templates/variable_info.erb +32 -23
- data/lib/better_errors/version.rb +1 -1
- data/lib/better_errors.rb +21 -3
- metadata +118 -35
- data/Rakefile +0 -13
- data/spec/better_errors/code_formatter_spec.rb +0 -92
- data/spec/better_errors/error_page_spec.rb +0 -76
- data/spec/better_errors/middleware_spec.rb +0 -154
- data/spec/better_errors/raised_exception_spec.rb +0 -52
- data/spec/better_errors/repl/basic_spec.rb +0 -18
- data/spec/better_errors/repl/pry_spec.rb +0 -40
- data/spec/better_errors/repl/shared_examples.rb +0 -18
- data/spec/better_errors/stack_frame_spec.rb +0 -157
- data/spec/better_errors/support/my_source.rb +0 -20
- data/spec/better_errors_spec.rb +0 -73
- data/spec/spec_helper.rb +0 -5
- data/spec/without_binding_of_caller.rb +0 -9
@@ -11,9 +11,9 @@ module BetterErrors
|
|
11
11
|
".erb" => :erb,
|
12
12
|
".haml" => :haml
|
13
13
|
}
|
14
|
-
|
14
|
+
|
15
15
|
attr_reader :filename, :line, :context
|
16
|
-
|
16
|
+
|
17
17
|
def initialize(filename, line, context = 5)
|
18
18
|
@filename = filename
|
19
19
|
@line = line
|
@@ -29,7 +29,7 @@ module BetterErrors
|
|
29
29
|
def formatted_code
|
30
30
|
formatted_lines.join
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def coderay_scanner
|
34
34
|
ext = File.extname(filename)
|
35
35
|
FILE_TYPES[ext] || :text
|
@@ -40,20 +40,20 @@ module BetterErrors
|
|
40
40
|
yield (current_line == line), current_line, str
|
41
41
|
}
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
def highlighted_lines
|
45
45
|
CodeRay.scan(context_lines.join, coderay_scanner).div(wrap: nil).lines
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
def context_lines
|
49
49
|
range = line_range
|
50
50
|
source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
def source_lines
|
54
54
|
@source_lines ||= File.readlines(filename)
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
def line_range
|
58
58
|
min = [line - context, 1].max
|
59
59
|
max = [line + context, source_lines.count].min
|
@@ -10,7 +10,7 @@ module BetterErrors
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.template(template_name)
|
13
|
-
|
13
|
+
Erubi::Engine.new(File.read(template_path(template_name)), escape: true)
|
14
14
|
end
|
15
15
|
|
16
16
|
attr_reader :exception, :env, :repls
|
@@ -26,8 +26,13 @@ module BetterErrors
|
|
26
26
|
@id ||= SecureRandom.hex(8)
|
27
27
|
end
|
28
28
|
|
29
|
-
def render(template_name = "main")
|
30
|
-
self.class.template(template_name).
|
29
|
+
def render(template_name = "main", csrf_token = nil)
|
30
|
+
binding.eval(self.class.template(template_name).src)
|
31
|
+
rescue => e
|
32
|
+
# Fix the backtrace, which doesn't identify the template that failed (within Better Errors).
|
33
|
+
# We don't know the line number, so just injecting the template path has to be enough.
|
34
|
+
e.backtrace.unshift "#{self.class.template_path(template_name)}:0"
|
35
|
+
raise
|
31
36
|
end
|
32
37
|
|
33
38
|
def do_variables(opts)
|
@@ -41,23 +46,39 @@ module BetterErrors
|
|
41
46
|
index = opts["index"].to_i
|
42
47
|
code = opts["source"]
|
43
48
|
|
44
|
-
unless binding = backtrace_frames[index].frame_binding
|
49
|
+
unless (binding = backtrace_frames[index].frame_binding)
|
45
50
|
return { error: "REPL unavailable in this stack frame" }
|
46
51
|
end
|
47
52
|
|
48
|
-
|
49
|
-
(@repls[index] ||= REPL.provider.new(binding)).send_input(code)
|
53
|
+
@repls[index] ||= REPL.provider.new(binding, exception)
|
50
54
|
|
51
|
-
|
52
|
-
prompt: prompt,
|
53
|
-
prefilled_input: prefilled_input,
|
54
|
-
highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil) }
|
55
|
+
eval_and_respond(index, code)
|
55
56
|
end
|
56
57
|
|
57
58
|
def backtrace_frames
|
58
59
|
exception.backtrace
|
59
60
|
end
|
60
61
|
|
62
|
+
def exception_type
|
63
|
+
exception.type
|
64
|
+
end
|
65
|
+
|
66
|
+
def exception_message
|
67
|
+
exception.message.strip.gsub(/(\r?\n\s*\r?\n)+/, "\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
def active_support_actions
|
71
|
+
return [] unless defined?(ActiveSupport::ActionableError)
|
72
|
+
|
73
|
+
ActiveSupport::ActionableError.actions(exception.type)
|
74
|
+
end
|
75
|
+
|
76
|
+
def action_dispatch_action_endpoint
|
77
|
+
return unless defined?(ActionDispatch::ActionableExceptions)
|
78
|
+
|
79
|
+
ActionDispatch::ActionableExceptions.endpoint
|
80
|
+
end
|
81
|
+
|
61
82
|
def application_frames
|
62
83
|
backtrace_frames.select(&:application?)
|
63
84
|
end
|
@@ -66,7 +87,8 @@ module BetterErrors
|
|
66
87
|
application_frames.first || backtrace_frames.first
|
67
88
|
end
|
68
89
|
|
69
|
-
|
90
|
+
private
|
91
|
+
|
70
92
|
def editor_url(frame)
|
71
93
|
BetterErrors.editor[frame.filename, frame.line]
|
72
94
|
end
|
@@ -100,11 +122,30 @@ module BetterErrors
|
|
100
122
|
end
|
101
123
|
|
102
124
|
def inspect_value(obj)
|
103
|
-
|
104
|
-
|
105
|
-
|
125
|
+
if BetterErrors.ignored_classes.include? obj.class.name
|
126
|
+
"<span class='unsupported'>(Instance of ignored class. "\
|
127
|
+
"#{obj.class.name ? "Remove #{CGI.escapeHTML(obj.class.name)} from" : "Modify"}"\
|
128
|
+
" BetterErrors.ignored_classes if you need to see it.)</span>"
|
129
|
+
else
|
130
|
+
InspectableValue.new(obj).to_html
|
131
|
+
end
|
132
|
+
rescue BetterErrors::ValueLargerThanConfiguredMaximum
|
133
|
+
"<span class='unsupported'>(Object too large. "\
|
134
|
+
"#{obj.class.name ? "Modify #{CGI.escapeHTML(obj.class.name)}#inspect or a" : "A"}"\
|
135
|
+
"djust BetterErrors.maximum_variable_inspect_size if you need to see it.)</span>"
|
106
136
|
rescue Exception => e
|
107
|
-
"<span class='unsupported'>(exception was raised in inspect)</span>"
|
137
|
+
"<span class='unsupported'>(exception #{CGI.escapeHTML(e.class.to_s)} was raised in inspect)</span>"
|
138
|
+
end
|
139
|
+
|
140
|
+
def eval_and_respond(index, code)
|
141
|
+
result, prompt, prefilled_input = @repls[index].send_input(code)
|
142
|
+
|
143
|
+
{
|
144
|
+
highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil),
|
145
|
+
prefilled_input: prefilled_input,
|
146
|
+
prompt: prompt,
|
147
|
+
result: result
|
148
|
+
}
|
108
149
|
end
|
109
150
|
end
|
110
151
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "objspace" rescue nil
|
3
|
+
|
4
|
+
module BetterErrors
|
5
|
+
class ValueLargerThanConfiguredMaximum < StandardError; end
|
6
|
+
|
7
|
+
class InspectableValue
|
8
|
+
def initialize(value)
|
9
|
+
@original_value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_html
|
13
|
+
raise ValueLargerThanConfiguredMaximum unless value_small_enough_to_inspect?
|
14
|
+
value_as_html
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :original_value
|
20
|
+
|
21
|
+
def value_as_html
|
22
|
+
@value_as_html ||= CGI.escapeHTML(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def value
|
26
|
+
@value ||= begin
|
27
|
+
if original_value.respond_to? :inspect
|
28
|
+
original_value.inspect
|
29
|
+
else
|
30
|
+
original_value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def value_small_enough_to_inspect?
|
36
|
+
return true if BetterErrors.maximum_variable_inspect_size.nil?
|
37
|
+
|
38
|
+
if defined?(ObjectSpace) && defined?(ObjectSpace.memsize_of) && ObjectSpace.memsize_of(value)
|
39
|
+
ObjectSpace.memsize_of(value) <= BetterErrors.maximum_variable_inspect_size
|
40
|
+
else
|
41
|
+
value_as_html.length <= BetterErrors.maximum_variable_inspect_size
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "json"
|
2
2
|
require "ipaddr"
|
3
|
+
require "securerandom"
|
3
4
|
require "set"
|
4
5
|
require "rack"
|
5
6
|
|
@@ -33,12 +34,14 @@ module BetterErrors
|
|
33
34
|
# Adds an address to the set of IP addresses allowed to access Better
|
34
35
|
# Errors.
|
35
36
|
def self.allow_ip!(addr)
|
36
|
-
ALLOWED_IPS << IPAddr.new(addr)
|
37
|
+
ALLOWED_IPS << (addr.is_a?(IPAddr) ? addr : IPAddr.new(addr))
|
37
38
|
end
|
38
39
|
|
39
40
|
allow_ip! "127.0.0.0/8"
|
40
41
|
allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
|
41
42
|
|
43
|
+
CSRF_TOKEN_COOKIE_NAME = 'BetterErrors-CSRF-Token'
|
44
|
+
|
42
45
|
# A new instance of BetterErrors::Middleware
|
43
46
|
#
|
44
47
|
# @param app The Rack app/middleware to wrap with Better Errors
|
@@ -72,7 +75,7 @@ module BetterErrors
|
|
72
75
|
def better_errors_call(env)
|
73
76
|
case env["PATH_INFO"]
|
74
77
|
when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
|
75
|
-
internal_call
|
78
|
+
internal_call(env, $~[:id], $~[:method])
|
76
79
|
when %r{/__better_errors/?\z}
|
77
80
|
show_error_page env
|
78
81
|
else
|
@@ -89,53 +92,130 @@ module BetterErrors
|
|
89
92
|
end
|
90
93
|
|
91
94
|
def show_error_page(env, exception=nil)
|
95
|
+
request = Rack::Request.new(env)
|
96
|
+
csrf_token = request.cookies[CSRF_TOKEN_COOKIE_NAME] || SecureRandom.uuid
|
97
|
+
|
92
98
|
type, content = if @error_page
|
93
99
|
if text?(env)
|
94
100
|
[ 'plain', @error_page.render('text') ]
|
95
101
|
else
|
96
|
-
[ 'html', @error_page.render ]
|
102
|
+
[ 'html', @error_page.render('main', csrf_token) ]
|
97
103
|
end
|
98
104
|
else
|
99
105
|
[ 'html', no_errors_page ]
|
100
106
|
end
|
101
107
|
|
102
108
|
status_code = 500
|
103
|
-
if defined?
|
109
|
+
if defined?(ActionDispatch::ExceptionWrapper) && exception
|
104
110
|
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
|
105
111
|
end
|
106
112
|
|
107
|
-
|
113
|
+
response = Rack::Response.new(content, status_code, { "Content-Type" => "text/#{type}; charset=utf-8" })
|
114
|
+
|
115
|
+
unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
116
|
+
response.set_cookie(CSRF_TOKEN_COOKIE_NAME, value: csrf_token, httponly: true, same_site: :strict)
|
117
|
+
end
|
118
|
+
|
119
|
+
# In older versions of Rack, the body returned here is actually a Rack::BodyProxy which seems to be a bug.
|
120
|
+
# (It contains status, headers and body and does not act like an array of strings.)
|
121
|
+
# Since we already have status code and body here, there's no need to use the ones in the Rack::Response.
|
122
|
+
(_status_code, headers, _body) = response.finish
|
123
|
+
[status_code, headers, [content]]
|
108
124
|
end
|
109
125
|
|
110
126
|
def text?(env)
|
111
127
|
env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
|
112
|
-
|
128
|
+
!env["HTTP_ACCEPT"].to_s.include?('html')
|
113
129
|
end
|
114
130
|
|
115
131
|
def log_exception
|
116
132
|
return unless BetterErrors.logger
|
117
133
|
|
118
|
-
message = "\n#{@error_page.
|
119
|
-
|
120
|
-
message << " #{frame}\n"
|
121
|
-
end
|
134
|
+
message = "\n#{@error_page.exception_type} - #{@error_page.exception_message}:\n"
|
135
|
+
message += backtrace_frames.map { |frame| " #{frame}\n" }.join
|
122
136
|
|
123
137
|
BetterErrors.logger.fatal message
|
124
138
|
end
|
125
139
|
|
126
|
-
def
|
127
|
-
if
|
128
|
-
|
140
|
+
def backtrace_frames
|
141
|
+
if defined?(Rails) && defined?(Rails.backtrace_cleaner)
|
142
|
+
Rails.backtrace_cleaner.clean @error_page.backtrace_frames.map(&:to_s)
|
143
|
+
else
|
144
|
+
@error_page.backtrace_frames
|
129
145
|
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def internal_call(env, id, method)
|
149
|
+
return not_found_json_response unless %w[variables eval].include?(method)
|
150
|
+
return no_errors_json_response unless @error_page
|
151
|
+
return invalid_error_json_response if id != @error_page.id
|
152
|
+
|
153
|
+
request = Rack::Request.new(env)
|
154
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
155
|
+
|
156
|
+
request.body.rewind
|
157
|
+
body = JSON.parse(request.body.read)
|
158
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME] == body['csrfToken']
|
130
159
|
|
131
|
-
|
132
|
-
|
133
|
-
|
160
|
+
return not_acceptable_json_response unless request.content_type == 'application/json'
|
161
|
+
|
162
|
+
response = @error_page.send("do_#{method}", body)
|
163
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(response)]]
|
134
164
|
end
|
135
165
|
|
136
166
|
def no_errors_page
|
137
167
|
"<h1>No errors</h1><p>No errors have been recorded yet.</p><hr>" +
|
138
168
|
"<code>Better Errors v#{BetterErrors::VERSION}</code>"
|
139
169
|
end
|
170
|
+
|
171
|
+
def no_errors_json_response
|
172
|
+
explanation = if defined? Middleman
|
173
|
+
"Middleman reloads all dependencies for each request, " +
|
174
|
+
"which breaks Better Errors."
|
175
|
+
elsif defined?(Shotgun) && defined?(Hanami)
|
176
|
+
"Hanami is likely running with code-reloading enabled, which is the default. " +
|
177
|
+
"You can disable this by running hanami with the `--no-code-reloading` option."
|
178
|
+
elsif defined? Shotgun
|
179
|
+
"The shotgun gem causes everything to be reloaded for every request. " +
|
180
|
+
"You can disable shotgun in the Gemfile temporarily to use Better Errors."
|
181
|
+
else
|
182
|
+
"The application has been restarted since this page loaded, " +
|
183
|
+
"or the framework is reloading all gems before each request "
|
184
|
+
end
|
185
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
186
|
+
error: 'No exception information available',
|
187
|
+
explanation: explanation,
|
188
|
+
)]]
|
189
|
+
end
|
190
|
+
|
191
|
+
def invalid_error_json_response
|
192
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
193
|
+
error: "Session expired",
|
194
|
+
explanation: "This page was likely opened from a previous exception, " +
|
195
|
+
"and the exception is no longer available in memory.",
|
196
|
+
)]]
|
197
|
+
end
|
198
|
+
|
199
|
+
def invalid_csrf_token_json_response
|
200
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
201
|
+
error: "Invalid CSRF Token",
|
202
|
+
explanation: "The browser session might have been cleared, " +
|
203
|
+
"or something went wrong.",
|
204
|
+
)]]
|
205
|
+
end
|
206
|
+
|
207
|
+
def not_found_json_response
|
208
|
+
[404, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
209
|
+
error: "Not found",
|
210
|
+
explanation: "Not a recognized internal call.",
|
211
|
+
)]]
|
212
|
+
end
|
213
|
+
|
214
|
+
def not_acceptable_json_response
|
215
|
+
[406, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
216
|
+
error: "Request not acceptable",
|
217
|
+
explanation: "The internal request did not match an acceptable content type.",
|
218
|
+
)]]
|
219
|
+
end
|
140
220
|
end
|
141
221
|
end
|
@@ -5,6 +5,7 @@ module BetterErrors
|
|
5
5
|
|
6
6
|
def initialize(exception)
|
7
7
|
if exception.respond_to?(:original_exception) && exception.original_exception
|
8
|
+
# This supports some specific Rails exceptions, and is not intended to act the same as `#cause`.
|
8
9
|
exception = exception.original_exception
|
9
10
|
end
|
10
11
|
|
@@ -34,8 +35,13 @@ module BetterErrors
|
|
34
35
|
|
35
36
|
def setup_backtrace_from_bindings
|
36
37
|
@backtrace = exception.__better_errors_bindings_stack.map { |binding|
|
37
|
-
|
38
|
-
|
38
|
+
if binding.respond_to?(:source_location) # Ruby >= 2.6
|
39
|
+
file = binding.source_location[0]
|
40
|
+
line = binding.source_location[1]
|
41
|
+
else
|
42
|
+
file = binding.eval "__FILE__"
|
43
|
+
line = binding.eval "__LINE__"
|
44
|
+
end
|
39
45
|
name = binding.frame_description
|
40
46
|
StackFrame.new(file, line, name, binding)
|
41
47
|
}
|
@@ -51,7 +57,11 @@ module BetterErrors
|
|
51
57
|
|
52
58
|
def massage_syntax_error
|
53
59
|
case exception.class.to_s
|
54
|
-
when "
|
60
|
+
when "ActionView::Template::Error"
|
61
|
+
if exception.respond_to?(:file_name) && exception.respond_to?(:line_number)
|
62
|
+
backtrace.unshift(StackFrame.new(exception.file_name, exception.line_number.to_i, "view template"))
|
63
|
+
end
|
64
|
+
when "Haml::SyntaxError", "Sprockets::Coffeelint::Error"
|
55
65
|
if /\A(.+?):(\d+)/ =~ exception.backtrace.first
|
56
66
|
backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
|
57
67
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module BetterErrors
|
2
2
|
module REPL
|
3
3
|
class Basic
|
4
|
-
def initialize(binding)
|
4
|
+
def initialize(binding, _exception)
|
5
5
|
@binding = binding
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
def send_input(str)
|
9
9
|
[execute(str), ">>", ""]
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
private
|
13
13
|
def execute(str)
|
14
14
|
"=> #{@binding.eval(str).inspect}\n"
|
@@ -9,30 +9,34 @@ module BetterErrors
|
|
9
9
|
Fiber.yield
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
class Output
|
14
14
|
def initialize
|
15
15
|
@buffer = ""
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def puts(*args)
|
19
19
|
args.each do |arg|
|
20
20
|
@buffer << "#{arg.chomp}\n"
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def tty?
|
25
25
|
false
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def read_buffer
|
29
29
|
@buffer
|
30
30
|
ensure
|
31
31
|
@buffer = ""
|
32
32
|
end
|
33
|
+
|
34
|
+
def print(*args)
|
35
|
+
@buffer << args.join(' ')
|
36
|
+
end
|
33
37
|
end
|
34
|
-
|
35
|
-
def initialize(binding)
|
38
|
+
|
39
|
+
def initialize(binding, exception)
|
36
40
|
@fiber = Fiber.new do
|
37
41
|
@pry.repl binding
|
38
42
|
end
|
@@ -40,9 +44,15 @@ module BetterErrors
|
|
40
44
|
@output = BetterErrors::REPL::Pry::Output.new
|
41
45
|
@pry = ::Pry.new input: @input, output: @output
|
42
46
|
@pry.hooks.clear_all if defined?(@pry.hooks.clear_all)
|
47
|
+
store_last_exception exception
|
43
48
|
@fiber.resume
|
44
49
|
end
|
45
|
-
|
50
|
+
|
51
|
+
def store_last_exception(exception)
|
52
|
+
return unless defined? ::Pry::LastException
|
53
|
+
@pry.instance_variable_set(:@last_exception, ::Pry::LastException.new(exception.exception))
|
54
|
+
end
|
55
|
+
|
46
56
|
def send_input(str)
|
47
57
|
local ::Pry.config, color: false, pager: false do
|
48
58
|
@fiber.resume "#{str}\n"
|
@@ -59,7 +69,7 @@ module BetterErrors
|
|
59
69
|
rescue
|
60
70
|
[">>", ""]
|
61
71
|
end
|
62
|
-
|
72
|
+
|
63
73
|
private
|
64
74
|
def local(obj, attrs)
|
65
75
|
old_attrs = {}
|
data/lib/better_errors/repl.rb
CHANGED
@@ -9,19 +9,21 @@ module BetterErrors
|
|
9
9
|
def self.provider
|
10
10
|
@provider ||= const_get detect[:const]
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def self.provider=(prov)
|
14
14
|
@provider = prov
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def self.detect
|
18
18
|
PROVIDERS.find { |prov|
|
19
19
|
test_provider prov
|
20
20
|
}
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def self.test_provider(provider)
|
24
|
-
require
|
24
|
+
# We must load this file instead of `require`ing it, since during our tests we want the file
|
25
|
+
# to be reloaded. In practice, this will only be called once, so `require` is not necessary.
|
26
|
+
load "#{provider[:impl]}.rb"
|
25
27
|
true
|
26
28
|
rescue LoadError
|
27
29
|
false
|
@@ -33,7 +33,7 @@ module BetterErrors
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def gem_path
|
36
|
-
if path = Gem.path.detect { |
|
36
|
+
if path = Gem.path.detect { |p| filename.index(p) == 0 }
|
37
37
|
gem_name_and_version, path = filename.sub("#{path}/gems/", "").split("/", 2)
|
38
38
|
/(?<gem_name>.+)-(?<gem_version>[\w.]+)/ =~ gem_name_and_version
|
39
39
|
"#{gem_name} (#{gem_version}) #{path}"
|
@@ -68,15 +68,27 @@ module BetterErrors
|
|
68
68
|
|
69
69
|
def local_variables
|
70
70
|
return {} unless frame_binding
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
|
72
|
+
lv = frame_binding.eval("local_variables")
|
73
|
+
return {} unless lv
|
74
|
+
|
75
|
+
lv.each_with_object({}) do |name, hash|
|
76
|
+
# Ruby 2.2's local_variables will include the hidden #$! variable if
|
77
|
+
# called from within a rescue context. This is not a valid variable name,
|
78
|
+
# so the local_variable_get method complains. This should probably be
|
79
|
+
# considered a bug in Ruby itself, but we need to work around it.
|
80
|
+
next if name == :"\#$!"
|
81
|
+
|
82
|
+
hash[name] = local_variable(name)
|
77
83
|
end
|
78
84
|
end
|
79
85
|
|
86
|
+
def local_variable(name)
|
87
|
+
get_local_variable(name) || eval_local_variable(name)
|
88
|
+
rescue NameError => ex
|
89
|
+
"#{ex.class}: #{ex.message}"
|
90
|
+
end
|
91
|
+
|
80
92
|
def instance_variables
|
81
93
|
return {} unless frame_binding
|
82
94
|
Hash[visible_instance_variables.map { |x|
|
@@ -85,7 +97,10 @@ module BetterErrors
|
|
85
97
|
end
|
86
98
|
|
87
99
|
def visible_instance_variables
|
88
|
-
frame_binding.eval("instance_variables")
|
100
|
+
iv = frame_binding.eval("instance_variables")
|
101
|
+
return {} unless iv
|
102
|
+
|
103
|
+
iv - BetterErrors.ignored_instance_variables
|
89
104
|
end
|
90
105
|
|
91
106
|
def to_s
|
@@ -107,5 +122,15 @@ module BetterErrors
|
|
107
122
|
@method_name = "##{method_name}"
|
108
123
|
end
|
109
124
|
end
|
125
|
+
|
126
|
+
def get_local_variable(name)
|
127
|
+
if defined?(frame_binding.local_variable_get)
|
128
|
+
frame_binding.local_variable_get(name)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def eval_local_variable(name)
|
133
|
+
frame_binding.eval(name.to_s)
|
134
|
+
end
|
110
135
|
end
|
111
136
|
end
|