better_errors 0.0.4 → 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 +25 -14
- data/better_errors.gemspec +7 -5
- 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 +19 -48
- data/lib/better_errors/middleware.rb +20 -1
- 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} +21 -8
- data/lib/better_errors/templates/main.erb +168 -62
- 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 +21 -12
data/README.md
CHANGED
|
@@ -1,33 +1,31 @@
|
|
|
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
|
-
*
|
|
12
|
+
* Live REPL on every stack frame
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
16
|
Add this line to your application's Gemfile (under the **development** group):
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Or install it yourself as:
|
|
25
|
-
|
|
26
|
-
$ gem install better_errors
|
|
18
|
+
```ruby
|
|
19
|
+
group :development do
|
|
20
|
+
gem "better_errors"
|
|
21
|
+
end
|
|
22
|
+
```
|
|
27
23
|
|
|
28
|
-
If you would like to use Better Errors' **advanced features
|
|
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:
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
```ruby
|
|
27
|
+
gem "binding_of_caller"
|
|
28
|
+
```
|
|
31
29
|
|
|
32
30
|
This is an optional dependency however, and Better Errors will work without it.
|
|
33
31
|
|
|
@@ -51,6 +49,19 @@ get "/" do
|
|
|
51
49
|
end
|
|
52
50
|
```
|
|
53
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
|
+
|
|
54
65
|
## Contributing
|
|
55
66
|
|
|
56
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,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,13 +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
|
-
|
|
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)
|
|
40
50
|
end
|
|
41
51
|
|
|
42
52
|
private
|
|
@@ -51,48 +61,9 @@ module BetterErrors
|
|
|
51
61
|
def request_path
|
|
52
62
|
env["REQUEST_PATH"]
|
|
53
63
|
end
|
|
54
|
-
|
|
55
|
-
def backtrace_frames
|
|
56
|
-
@backtrace_frames ||= ErrorFrame.from_exception(exception)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def coderay_scanner_for_ext(ext)
|
|
60
|
-
case ext
|
|
61
|
-
when "rb"; :ruby
|
|
62
|
-
when "html"; :html
|
|
63
|
-
when "erb"; :erb
|
|
64
|
-
when "haml"; :haml
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def file_extension(filename)
|
|
69
|
-
filename.split(".").last
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def code_extract(frame, lines_of_context = 5)
|
|
73
|
-
lines = File.readlines(frame.filename)
|
|
74
|
-
min_line = [1, frame.line - lines_of_context].max - 1
|
|
75
|
-
max_line = [frame.line + lines_of_context, lines.count + 1].min - 1
|
|
76
|
-
[min_line, max_line, lines[min_line..max_line].join]
|
|
77
|
-
end
|
|
78
64
|
|
|
79
65
|
def highlighted_code_block(frame)
|
|
80
|
-
|
|
81
|
-
scanner = coderay_scanner_for_ext(ext)
|
|
82
|
-
min_line, max_line, code = code_extract(frame)
|
|
83
|
-
highlighted_code = CodeRay.scan(code, scanner).div wrap: nil
|
|
84
|
-
"".tap do |html|
|
|
85
|
-
html << "<div class='code'>"
|
|
86
|
-
highlighted_code.each_line.each_with_index do |str, index|
|
|
87
|
-
if min_line + index + 1 == frame.line
|
|
88
|
-
html << "<pre class='highlight'>"
|
|
89
|
-
else
|
|
90
|
-
html << "<pre>"
|
|
91
|
-
end
|
|
92
|
-
html << sprintf("%5d", min_line + index + 1) << " " << str << "</pre>"
|
|
93
|
-
end
|
|
94
|
-
html << "</div>"
|
|
95
|
-
end
|
|
66
|
+
CodeFormatter.new(frame.filename, frame.line).html
|
|
96
67
|
end
|
|
97
68
|
end
|
|
98
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
|
|
@@ -20,8 +23,24 @@ module BetterErrors
|
|
|
20
23
|
@app.call env
|
|
21
24
|
rescue Exception => ex
|
|
22
25
|
@error_page = @handler.new ex, env
|
|
26
|
+
log_exception
|
|
27
|
+
show_error_page(env)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def show_error_page(env)
|
|
23
31
|
[500, { "Content-Type" => "text/html; charset=utf-8" }, [@error_page.render]]
|
|
24
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
|
|
25
44
|
|
|
26
45
|
def internal_call(env, opts)
|
|
27
46
|
if opts[:oid].to_i != @error_page.object_id
|
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,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,14 +80,20 @@ 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
|
+
}]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_s
|
|
89
|
+
"#{pretty_path}:#{line}:in `#{name}'"
|
|
77
90
|
end
|
|
78
91
|
|
|
79
92
|
private
|
|
80
93
|
def set_pretty_method_name
|
|
81
94
|
name =~ /\A(block (\([^)]+\) )?in )?/
|
|
82
95
|
recv = frame_binding.eval("self")
|
|
83
|
-
method = frame_binding.eval("__method__")
|
|
96
|
+
return unless method = frame_binding.eval("__method__")
|
|
84
97
|
@name = if recv.is_a? Module
|
|
85
98
|
"#{$1}#{recv}.#{method}"
|
|
86
99
|
else
|
|
@@ -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;
|
|
@@ -243,15 +244,22 @@
|
|
|
243
244
|
<div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
|
|
244
245
|
<%== highlighted_code_block frame %>
|
|
245
246
|
|
|
246
|
-
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
<
|
|
250
|
-
|
|
247
|
+
<% if BetterErrors.binding_of_caller_available? %>
|
|
248
|
+
<div class="repl">
|
|
249
|
+
<h3>REPL</h3>
|
|
250
|
+
<div class="console">
|
|
251
|
+
<pre></pre>
|
|
252
|
+
<div class="prompt"><span>>></span> <input/></div>
|
|
253
|
+
</div>
|
|
251
254
|
</div>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
|
|
256
|
+
<div class="variable_info"></div>
|
|
257
|
+
<% else %>
|
|
258
|
+
<h3>Advanced features unavailable</h3>
|
|
259
|
+
<p class="error">
|
|
260
|
+
You must add <code>gem "binding_of_caller"</code> to your Gemfile to enable the REPL and local/instance variable inspection.
|
|
261
|
+
</p>
|
|
262
|
+
<% end %>
|
|
255
263
|
</div>
|
|
256
264
|
<% end %>
|
|
257
265
|
<div style="clear:both"></div>
|
|
@@ -259,19 +267,16 @@
|
|
|
259
267
|
</body>
|
|
260
268
|
<script>
|
|
261
269
|
(function() {
|
|
262
|
-
var
|
|
270
|
+
var OID = <%== object_id.to_s.inspect %>;
|
|
263
271
|
|
|
264
|
-
var
|
|
272
|
+
var previousFrame = null;
|
|
265
273
|
var previousFrameInfo = null;
|
|
266
|
-
var
|
|
267
|
-
var
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
var headerHeight = header.offsetHeight;
|
|
271
|
-
|
|
272
|
-
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) {
|
|
273
278
|
var req = new XMLHttpRequest();
|
|
274
|
-
req.open("POST", "/__better_errors/" +
|
|
279
|
+
req.open("POST", "/__better_errors/" + OID + "/" + method, true);
|
|
275
280
|
req.setRequestHeader("Content-Type", "application/json");
|
|
276
281
|
req.send(JSON.stringify(opts));
|
|
277
282
|
req.onreadystatechange = function() {
|
|
@@ -286,11 +291,118 @@
|
|
|
286
291
|
return html.replace(/&/, "&").replace(/</g, "<");
|
|
287
292
|
}
|
|
288
293
|
|
|
289
|
-
function
|
|
290
|
-
|
|
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
|
+
}
|
|
291
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];
|
|
292
404
|
var varInfo = el.querySelector(".variable_info");
|
|
293
|
-
if(varInfo.innerHTML == "") {
|
|
405
|
+
if(varInfo && varInfo.innerHTML == "") {
|
|
294
406
|
apiCall("variables", { "index": index }, function(response) {
|
|
295
407
|
if(response.error) {
|
|
296
408
|
varInfo.innerHTML = "<span class='error'>" + escapeHTML(response.error) + "</span>";
|
|
@@ -299,75 +411,69 @@
|
|
|
299
411
|
}
|
|
300
412
|
});
|
|
301
413
|
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function selectFrameInfo(index) {
|
|
417
|
+
populateVariableInfo(index);
|
|
302
418
|
|
|
303
419
|
if(previousFrameInfo) {
|
|
304
420
|
previousFrameInfo.style.display = "none";
|
|
305
421
|
}
|
|
306
|
-
previousFrameInfo =
|
|
422
|
+
previousFrameInfo = allFrameInfos[index];
|
|
307
423
|
previousFrameInfo.style.display = "block";
|
|
308
424
|
|
|
309
|
-
|
|
425
|
+
if(REPL.all[index]) {
|
|
426
|
+
REPL.all[index].focus();
|
|
427
|
+
}
|
|
310
428
|
}
|
|
311
429
|
|
|
312
|
-
for(var i = 0; i <
|
|
313
|
-
(function(
|
|
430
|
+
for(var i = 0; i < allFrames.length; i++) {
|
|
431
|
+
(function(i, el) {
|
|
432
|
+
var el = allFrames[i];
|
|
314
433
|
el.onclick = function() {
|
|
315
|
-
if(
|
|
316
|
-
|
|
434
|
+
if(previousFrame) {
|
|
435
|
+
previousFrame.className = "";
|
|
317
436
|
}
|
|
318
437
|
el.className = "selected";
|
|
319
|
-
|
|
438
|
+
previousFrame = el;
|
|
320
439
|
|
|
321
440
|
selectFrameInfo(el.attributes["data-index"].value);
|
|
322
441
|
};
|
|
323
|
-
var
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
replInput.value = "";
|
|
329
|
-
apiCall("eval", { "index": index, source: text }, function(response) {
|
|
330
|
-
replPre.innerHTML += ">> " + response.highlighted_input + "\n";
|
|
331
|
-
if(response.error) {
|
|
332
|
-
replPre.innerHTML += "!! " + escapeHTML(response.error) + "\n";
|
|
333
|
-
} else {
|
|
334
|
-
replPre.innerHTML += "=> " + escapeHTML(response.result) + "\n";
|
|
335
|
-
}
|
|
336
|
-
replPre.scrollTop = replPre.offsetHeight;
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
})(i, frames[i], frameInfos[i]);
|
|
442
|
+
var repl = allFrameInfos[i].querySelector(".repl .console");
|
|
443
|
+
if(repl) {
|
|
444
|
+
new REPL(i).install(repl);
|
|
445
|
+
}
|
|
446
|
+
})(i);
|
|
341
447
|
}
|
|
342
448
|
|
|
343
|
-
|
|
449
|
+
allFrames[0].click();
|
|
344
450
|
|
|
345
|
-
var
|
|
346
|
-
var
|
|
451
|
+
var applicationFramesButton = document.getElementById("application_frames");
|
|
452
|
+
var allFramesButton = document.getElementById("all_frames");
|
|
347
453
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
for(var i = 0; i <
|
|
352
|
-
if(
|
|
353
|
-
|
|
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";
|
|
354
460
|
} else {
|
|
355
|
-
|
|
461
|
+
allFrames[i].style.display = "none";
|
|
356
462
|
}
|
|
357
463
|
}
|
|
358
464
|
return false;
|
|
359
465
|
};
|
|
360
466
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
for(var i = 0; i <
|
|
365
|
-
|
|
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";
|
|
366
472
|
}
|
|
367
473
|
return false;
|
|
368
474
|
};
|
|
369
475
|
|
|
370
|
-
|
|
476
|
+
applicationFramesButton.click();
|
|
371
477
|
})();
|
|
372
478
|
</script>
|
|
373
479
|
</html>
|
data/lib/better_errors.rb
CHANGED
|
@@ -4,11 +4,13 @@ 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
|
-
attr_accessor :application_root, :binding_of_caller_available
|
|
13
|
+
attr_accessor :application_root, :binding_of_caller_available, :logger
|
|
12
14
|
|
|
13
15
|
alias_method :binding_of_caller_available?, :binding_of_caller_available
|
|
14
16
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
module BetterErrors
|
|
4
|
+
describe CodeFormatter do
|
|
5
|
+
let(:filename) { File.expand_path("../support/my_source.rb", __FILE__) }
|
|
6
|
+
|
|
7
|
+
let(:formatter) { CodeFormatter.new(filename, 8) }
|
|
8
|
+
|
|
9
|
+
it "should pick an appropriate scanner" do
|
|
10
|
+
formatter.coderay_scanner.should == :ruby
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "should show 5 lines of context" do
|
|
14
|
+
formatter.line_range.should == (3..13)
|
|
15
|
+
|
|
16
|
+
formatter.context_lines.should == [
|
|
17
|
+
"three\n",
|
|
18
|
+
"four\n",
|
|
19
|
+
"five\n",
|
|
20
|
+
"six\n",
|
|
21
|
+
"seven\n",
|
|
22
|
+
"eight\n",
|
|
23
|
+
"nine\n",
|
|
24
|
+
"ten\n",
|
|
25
|
+
"eleven\n",
|
|
26
|
+
"twelve\n",
|
|
27
|
+
"thirteen\n"
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should highlight the erroring line" do
|
|
32
|
+
formatter.html.should =~ /highlight.*eight/
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "should work when the line is right on the edge" do
|
|
36
|
+
formatter = CodeFormatter.new(filename, 20)
|
|
37
|
+
formatter.line_range.should == (15..20)
|
|
38
|
+
formatter.html.should_not == formatter.source_unavailable
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should not barf when the lines don't make any sense" do
|
|
42
|
+
formatter = CodeFormatter.new(filename, 999)
|
|
43
|
+
formatter.html.should == formatter.source_unavailable
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should not barf when the file doesn't exist" do
|
|
47
|
+
formatter = CodeFormatter.new("fkdguhskd7e l", 1)
|
|
48
|
+
formatter.html.should == formatter.source_unavailable
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -7,6 +7,16 @@ module BetterErrors
|
|
|
7
7
|
let(:error_page) { ErrorPage.new exception, { "REQUEST_PATH" => "/some/path" } }
|
|
8
8
|
|
|
9
9
|
let(:response) { error_page.render }
|
|
10
|
+
|
|
11
|
+
let(:empty_binding) {
|
|
12
|
+
local_a = :value_for_local_a
|
|
13
|
+
local_b = :value_for_local_b
|
|
14
|
+
|
|
15
|
+
@inst_c = :value_for_inst_c
|
|
16
|
+
@inst_d = :value_for_inst_d
|
|
17
|
+
|
|
18
|
+
binding
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
it "should include the error message" do
|
|
12
22
|
response.should include("you divided by zero you silly goose!")
|
|
@@ -20,26 +30,31 @@ module BetterErrors
|
|
|
20
30
|
response.should include("ZeroDivisionError")
|
|
21
31
|
end
|
|
22
32
|
|
|
23
|
-
context "
|
|
24
|
-
|
|
25
|
-
exception.stub!(:backtrace).and_return([
|
|
26
|
-
"#{File.expand_path("../support/my_source.rb", __FILE__)}:8:in `some_method'"
|
|
27
|
-
])
|
|
28
|
-
end
|
|
33
|
+
context "variable inspection" do
|
|
34
|
+
let(:exception) { empty_binding.eval("raise") rescue $! }
|
|
29
35
|
|
|
30
|
-
it "should show
|
|
31
|
-
|
|
36
|
+
it "should show local variables" do
|
|
37
|
+
html = error_page.do_variables("index" => 0)[:html]
|
|
38
|
+
html.should include("local_a")
|
|
39
|
+
html.should include(":value_for_local_a")
|
|
40
|
+
html.should include("local_b")
|
|
41
|
+
html.should include(":value_for_local_b")
|
|
32
42
|
end
|
|
33
43
|
|
|
34
|
-
it "should show
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
response.should_not include("2 two")
|
|
41
|
-
response.should_not include("14 fourteen")
|
|
44
|
+
it "should show instance variables" do
|
|
45
|
+
html = error_page.do_variables("index" => 0)[:html]
|
|
46
|
+
html.should include("inst_c")
|
|
47
|
+
html.should include(":value_for_inst_c")
|
|
48
|
+
html.should include("inst_d")
|
|
49
|
+
html.should include(":value_for_inst_d")
|
|
42
50
|
end
|
|
43
51
|
end
|
|
52
|
+
|
|
53
|
+
it "should not die if the source file is not a real filename" do
|
|
54
|
+
exception.stub!(:backtrace).and_return([
|
|
55
|
+
"<internal:prelude>:10:in `spawn_rack_application'"
|
|
56
|
+
])
|
|
57
|
+
response.should include("Source unavailable")
|
|
58
|
+
end
|
|
44
59
|
end
|
|
45
60
|
end
|
|
@@ -21,6 +21,14 @@ module BetterErrors
|
|
|
21
21
|
|
|
22
22
|
headers["Content-Type"].should == "text/html; charset=utf-8"
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
it "should log the exception" do
|
|
26
|
+
logger = Object.new
|
|
27
|
+
logger.should_receive :fatal
|
|
28
|
+
BetterErrors.stub!(:logger).and_return(logger)
|
|
29
|
+
|
|
30
|
+
app.call({})
|
|
31
|
+
end
|
|
24
32
|
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
@@ -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,8 +58,10 @@ dependencies:
|
|
|
58
58
|
requirements:
|
|
59
59
|
- - ! '>='
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
62
|
-
description:
|
|
61
|
+
version: 1.0.0
|
|
62
|
+
description: Provides a better error page for Rails and other Rack apps. Includes
|
|
63
|
+
source code inspection, a live REPL and local/instance variable inspection for all
|
|
64
|
+
stack frames.
|
|
63
65
|
email:
|
|
64
66
|
- charlie@charliesomerville.com
|
|
65
67
|
executables: []
|
|
@@ -73,17 +75,23 @@ files:
|
|
|
73
75
|
- Rakefile
|
|
74
76
|
- better_errors.gemspec
|
|
75
77
|
- lib/better_errors.rb
|
|
78
|
+
- lib/better_errors/code_formatter.rb
|
|
76
79
|
- lib/better_errors/core_ext/exception.rb
|
|
77
|
-
- lib/better_errors/error_frame.rb
|
|
78
80
|
- lib/better_errors/error_page.rb
|
|
79
81
|
- lib/better_errors/middleware.rb
|
|
80
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
|
|
81
87
|
- lib/better_errors/templates/main.erb
|
|
82
88
|
- lib/better_errors/templates/variable_info.erb
|
|
83
89
|
- lib/better_errors/version.rb
|
|
84
|
-
- spec/better_errors/
|
|
90
|
+
- spec/better_errors/code_formatter_spec.rb
|
|
85
91
|
- spec/better_errors/error_page_spec.rb
|
|
86
92
|
- spec/better_errors/middleware_spec.rb
|
|
93
|
+
- spec/better_errors/repl/basic_spec.rb
|
|
94
|
+
- spec/better_errors/stack_frame_spec.rb
|
|
87
95
|
- spec/better_errors/support/my_source.rb
|
|
88
96
|
- spec/spec_helper.rb
|
|
89
97
|
homepage: https://github.com/charliesome/better_errors
|
|
@@ -110,11 +118,12 @@ rubyforge_project:
|
|
|
110
118
|
rubygems_version: 1.8.24
|
|
111
119
|
signing_key:
|
|
112
120
|
specification_version: 3
|
|
113
|
-
summary: Better
|
|
121
|
+
summary: Better error page for Rails and other Rack apps
|
|
114
122
|
test_files:
|
|
115
|
-
- spec/better_errors/
|
|
123
|
+
- spec/better_errors/code_formatter_spec.rb
|
|
116
124
|
- spec/better_errors/error_page_spec.rb
|
|
117
125
|
- spec/better_errors/middleware_spec.rb
|
|
126
|
+
- spec/better_errors/repl/basic_spec.rb
|
|
127
|
+
- spec/better_errors/stack_frame_spec.rb
|
|
118
128
|
- spec/better_errors/support/my_source.rb
|
|
119
129
|
- spec/spec_helper.rb
|
|
120
|
-
has_rdoc:
|