better_errors 0.0.1
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/.gitignore +3 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/better_errors.gemspec +26 -0
- data/lib/better_errors.rb +31 -0
- data/lib/better_errors/core_ext/exception.rb +20 -0
- data/lib/better_errors/error_frame.rb +72 -0
- data/lib/better_errors/error_page.erb +244 -0
- data/lib/better_errors/error_page.rb +82 -0
- data/lib/better_errors/middleware.rb +15 -0
- data/lib/better_errors/rails.rb +10 -0
- data/lib/better_errors/version.rb +3 -0
- data/spec/better_errors/error_frame_spec.rb +61 -0
- data/spec/better_errors/error_page_spec.rb +45 -0
- data/spec/better_errors/middleware_spec.rb +26 -0
- data/spec/better_errors/support/my_source.rb +20 -0
- data/spec/spec_helper.rb +3 -0
- metadata +119 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Charlie Somerville
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Better Errors
|
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.
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'better_errors'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install better_errors
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
If you're using Rails, there's nothing else you need to do.
|
24
|
+
|
25
|
+
If you're not using Rails, you need to insert `BetterErrors::Middleware` into your middleware stack, and optionally set `BetterErrors.application_root` if you'd like Better Errors to abbreviate filenames within your application.
|
26
|
+
|
27
|
+
Here's an example using Sinatra:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require "sinatra"
|
31
|
+
require "better_errors"
|
32
|
+
|
33
|
+
use BetterErrors::Middleware
|
34
|
+
BetterErrors.application_root = File.expand_path("..", __FILE__)
|
35
|
+
|
36
|
+
get "/" do
|
37
|
+
raise "oops"
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
1. Fork it
|
44
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
45
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
46
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
47
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'better_errors/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "better_errors"
|
8
|
+
s.version = BetterErrors::VERSION
|
9
|
+
s.authors = ["Charlie Somerville"]
|
10
|
+
s.email = ["charlie@charliesomerville.com"]
|
11
|
+
s.description = %q{Better Errors gives Rails a better error page.}
|
12
|
+
s.summary = %q{Better Errors gives Rails a better error page}
|
13
|
+
s.homepage = "https://github.com/charliesome/better_errors"
|
14
|
+
s.license = "MIT"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency "rake"
|
21
|
+
|
22
|
+
s.add_dependency "erubis"
|
23
|
+
s.add_dependency "coderay"
|
24
|
+
# optional dependency:
|
25
|
+
# s.add_dependency "binding_of_caller"
|
26
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "pp"
|
2
|
+
require "erubis"
|
3
|
+
require "coderay"
|
4
|
+
|
5
|
+
require "better_errors/version"
|
6
|
+
require "better_errors/error_page"
|
7
|
+
require "better_errors/error_frame"
|
8
|
+
require "better_errors/middleware"
|
9
|
+
|
10
|
+
class << BetterErrors
|
11
|
+
attr_accessor :application_root, :binding_of_caller_available
|
12
|
+
|
13
|
+
alias_method :binding_of_caller_available?, :binding_of_caller_available
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
require "binding_of_caller"
|
18
|
+
BetterErrors.binding_of_caller_available = true
|
19
|
+
rescue LoadError => e
|
20
|
+
BetterErrors.binding_of_caller_available = false
|
21
|
+
end
|
22
|
+
|
23
|
+
if BetterErrors.binding_of_caller_available?
|
24
|
+
require "better_errors/core_ext/exception"
|
25
|
+
else
|
26
|
+
warn "BetterErrors: binding_of_caller gem unavailable, cannot display local variables on error pages."
|
27
|
+
warn "Add 'binding_of_caller' to your Gemfile to make this warning go away."
|
28
|
+
warn ""
|
29
|
+
end
|
30
|
+
|
31
|
+
require "better_errors/rails" if defined? Rails::Railtie
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Exception
|
2
|
+
attr_reader :__better_errors_bindings_stack
|
3
|
+
|
4
|
+
original_initialize = instance_method(:initialize)
|
5
|
+
|
6
|
+
define_method :initialize do |*args|
|
7
|
+
unless Thread.current[:__better_errors_exception_lock]
|
8
|
+
Thread.current[:__better_errors_exception_lock] = true
|
9
|
+
begin
|
10
|
+
@__better_errors_bindings_stack = []
|
11
|
+
2.upto(caller.size) do |index|
|
12
|
+
@__better_errors_bindings_stack << binding.of_caller(index) rescue break
|
13
|
+
end
|
14
|
+
ensure
|
15
|
+
Thread.current[:__better_errors_exception_lock] = false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
original_initialize.bind(self).call(*args)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module BetterErrors
|
2
|
+
class ErrorFrame
|
3
|
+
def self.from_exception(exception)
|
4
|
+
exception.backtrace.each_with_index.map { |frame, idx|
|
5
|
+
next unless frame =~ /\A(.*):(\d*):in `(.*)'\z/
|
6
|
+
ErrorFrame.new($1, $2.to_i, $3, exception.__better_errors_bindings_stack[idx])
|
7
|
+
}.compact
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :filename, :line, :name, :frame_binding
|
11
|
+
|
12
|
+
def initialize(filename, line, name, frame_binding)
|
13
|
+
@filename = filename
|
14
|
+
@line = line
|
15
|
+
@name = name
|
16
|
+
@frame_binding = frame_binding
|
17
|
+
end
|
18
|
+
|
19
|
+
def application?
|
20
|
+
starts_with? filename, BetterErrors.application_root if BetterErrors.application_root
|
21
|
+
end
|
22
|
+
|
23
|
+
def application_path
|
24
|
+
filename[(BetterErrors.application_root.length+1)..-1]
|
25
|
+
end
|
26
|
+
|
27
|
+
def gem?
|
28
|
+
Gem.path.any? { |path| starts_with? filename, path }
|
29
|
+
end
|
30
|
+
|
31
|
+
def gem_path
|
32
|
+
Gem.path.each do |path|
|
33
|
+
if starts_with? filename, path
|
34
|
+
return filename.gsub("#{path}/gems/", "(gem) ")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def context
|
40
|
+
if application?
|
41
|
+
:application
|
42
|
+
elsif gem?
|
43
|
+
:gem
|
44
|
+
else
|
45
|
+
:dunno
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def pretty_path
|
50
|
+
case context
|
51
|
+
when :application; application_path
|
52
|
+
when :gem; gem_path
|
53
|
+
else filename
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def local_variables
|
58
|
+
return {} unless frame_binding
|
59
|
+
Hash[frame_binding.eval("local_variables").map { |x| [x, frame_binding.eval(x.to_s)] }]
|
60
|
+
end
|
61
|
+
|
62
|
+
def instance_variables
|
63
|
+
return {} unless frame_binding
|
64
|
+
Hash[frame_binding.eval("instance_variables").map { |x| [x, frame_binding.eval(x.to_s)] }]
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def starts_with?(haystack, needle)
|
69
|
+
haystack[0, needle.length] == needle
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= exception.class %> at <%= request_path %></title>
|
5
|
+
<style>
|
6
|
+
* {
|
7
|
+
margin:0;
|
8
|
+
padding:0;
|
9
|
+
}
|
10
|
+
body {
|
11
|
+
font-family:Verdana, sans-serif;
|
12
|
+
margin:0;
|
13
|
+
font-size:13px;
|
14
|
+
}
|
15
|
+
header {
|
16
|
+
padding:32px 32px;
|
17
|
+
border-bottom:1px solid #ea6756;
|
18
|
+
background-color: #ffe5e7;
|
19
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffe5e7), to(#ffb2b8)); /* Safari 4+, Chrome */
|
20
|
+
background-image: -webkit-linear-gradient(top, #ffe5e7, #ffb2b8); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
21
|
+
background-image: -moz-linear-gradient(top, #ffe5e7, #ffb2b8); /* Firefox 3.6-15 */
|
22
|
+
background-image: -o-linear-gradient(top, #ffe5e7, #ffb2b8); /* Opera 11.10-12.00 */
|
23
|
+
background-image: linear-gradient(to bottom, #ffe5e7, #ffb2b8); /* Firefox 16+, IE10, Opera 12.50+ */
|
24
|
+
}
|
25
|
+
h2 {
|
26
|
+
font-weight:normal;
|
27
|
+
margin:0;
|
28
|
+
margin-bottom:16px;
|
29
|
+
}
|
30
|
+
h2 span {
|
31
|
+
color:#666666;
|
32
|
+
}
|
33
|
+
header table {
|
34
|
+
border-collapse:collapse;
|
35
|
+
}
|
36
|
+
header table th {
|
37
|
+
text-align:right;
|
38
|
+
font-weight:bold;
|
39
|
+
padding-right:12px;
|
40
|
+
}
|
41
|
+
header table th, header table td {
|
42
|
+
padding:4px 6px;
|
43
|
+
}
|
44
|
+
.frames {
|
45
|
+
width:50%;
|
46
|
+
float:left;
|
47
|
+
}
|
48
|
+
.frames li {
|
49
|
+
list-style:none;
|
50
|
+
display:block;
|
51
|
+
padding:12px;
|
52
|
+
overflow:hidden;
|
53
|
+
cursor:pointer;
|
54
|
+
background-color: #f4f7ff;
|
55
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#f4f7ff), to(#e4ebfe)); /* Safari 4+, Chrome */
|
56
|
+
background-image: -webkit-linear-gradient(top, #f4f7ff, #e4ebfe); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
57
|
+
background-image: -moz-linear-gradient(top, #f4f7ff, #e4ebfe); /* Firefox 3.6-15 */
|
58
|
+
background-image: -o-linear-gradient(top, #f4f7ff, #e4ebfe); /* Opera 11.10-12.00 */
|
59
|
+
background-image: linear-gradient(to bottom, #f4f7ff, #e4ebfe); /* Firefox 16+, IE10, Opera 12.50+ */
|
60
|
+
border-bottom:1px solid #cccccc;
|
61
|
+
}
|
62
|
+
.frames li:hover, .frames li.selected {
|
63
|
+
background-color: #e4ebfe;
|
64
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#e4ebfe), to(#c2d3fe)); /* Safari 4+, Chrome */
|
65
|
+
background-image: -webkit-linear-gradient(top, #e4ebfe, #c2d3fe); /* Chrome 10+, Safari 5.1+, iOS 5+ */
|
66
|
+
background-image: -moz-linear-gradient(top, #e4ebfe, #c2d3fe); /* Firefox 3.6-15 */
|
67
|
+
background-image: -o-linear-gradient(top, #e4ebfe, #c2d3fe); /* Opera 11.10-12.00 */
|
68
|
+
background-image: linear-gradient(to bottom, #e4ebfe, #c2d3fe); /* Firefox 16+, IE10, Opera 12.50+ */
|
69
|
+
}
|
70
|
+
.frames .name, .frame_info h2.name {
|
71
|
+
font-family:Monaco, Incosolata, Consolas, monospace;
|
72
|
+
font-size:14px;
|
73
|
+
margin-bottom:4px;
|
74
|
+
}
|
75
|
+
.location {
|
76
|
+
color:#888888;
|
77
|
+
}
|
78
|
+
.location .filename, .location .line {
|
79
|
+
color:#333333;
|
80
|
+
}
|
81
|
+
.frame_info {
|
82
|
+
background-color:#efefef;
|
83
|
+
border-bottom:1px solid #cccccc;
|
84
|
+
float:left;
|
85
|
+
width:50%;
|
86
|
+
}
|
87
|
+
.frame_info > * {
|
88
|
+
margin:16px;
|
89
|
+
}
|
90
|
+
.frame_info h2 {
|
91
|
+
font-family:Monaco, Incosolata, Consolas, monospace;
|
92
|
+
}
|
93
|
+
.frame_info .code {
|
94
|
+
font-family:Monaco, Incosolata, Consolas, monospace;
|
95
|
+
background-color:#ffffff;
|
96
|
+
border:1px solid #cccccc;
|
97
|
+
border-collapse:collapse;
|
98
|
+
overflow:auto;
|
99
|
+
}
|
100
|
+
.frame_info .code pre:nth-child(2n+1) {
|
101
|
+
background-color:#f9f9f9;
|
102
|
+
}
|
103
|
+
.frame_info .code pre:nth-child(2n) {
|
104
|
+
background-color:#f3f3f3;
|
105
|
+
}
|
106
|
+
.frame_info .code pre.highlight {
|
107
|
+
background-color:#ffeaaa;
|
108
|
+
}
|
109
|
+
.frame_info .code .line {
|
110
|
+
text-align:right;
|
111
|
+
padding-right:12px;
|
112
|
+
padding-left:0px;
|
113
|
+
}
|
114
|
+
code {
|
115
|
+
background-color:#f0f0f0;
|
116
|
+
padding:2px 4px;
|
117
|
+
border:1px solid #d0d0d0;
|
118
|
+
}
|
119
|
+
h3 {
|
120
|
+
font-weight:normal;
|
121
|
+
border-top:1px solid #cccccc;
|
122
|
+
padding-top:16px;
|
123
|
+
}
|
124
|
+
.var_table {
|
125
|
+
border-collapse:collapse;
|
126
|
+
}
|
127
|
+
.var_table td {
|
128
|
+
padding:8px;
|
129
|
+
}
|
130
|
+
.var_table td.name {
|
131
|
+
font-style:italic;
|
132
|
+
}
|
133
|
+
.var_table tr {
|
134
|
+
border-bottom:1px solid #cccccc;
|
135
|
+
}
|
136
|
+
</style>
|
137
|
+
</head>
|
138
|
+
<body>
|
139
|
+
<header>
|
140
|
+
<h2><%= exception.class %> <span>at <%= request_path %></span></h2>
|
141
|
+
<table>
|
142
|
+
<tr>
|
143
|
+
<th>Message</th>
|
144
|
+
<td><%= exception.message %></td>
|
145
|
+
</tr>
|
146
|
+
<% if backtrace_frames.any? %>
|
147
|
+
<tr>
|
148
|
+
<th>File</th>
|
149
|
+
<td><%= backtrace_frames.first.filename %></td>
|
150
|
+
</tr>
|
151
|
+
<tr>
|
152
|
+
<th>Line</th>
|
153
|
+
<td><%= backtrace_frames.first.line %></td>
|
154
|
+
</tr>
|
155
|
+
<% end %>
|
156
|
+
</table>
|
157
|
+
</header>
|
158
|
+
<section class="backtrace">
|
159
|
+
<ul class="frames">
|
160
|
+
<% backtrace_frames.each_with_index do |frame, index| %>
|
161
|
+
<li
|
162
|
+
data-context="<%= frame.context %>"
|
163
|
+
data-full-filename="<%= frame.filename %>"
|
164
|
+
data-filename="<%= frame.pretty_path %>"
|
165
|
+
data-line="<%= frame.line %>"
|
166
|
+
data-name="<%= frame.name %>"
|
167
|
+
data-index="<%= index %>"
|
168
|
+
>
|
169
|
+
<div class="name"><%= frame.name %></div>
|
170
|
+
<div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
|
171
|
+
</li>
|
172
|
+
<% end %>
|
173
|
+
</ul>
|
174
|
+
<% backtrace_frames.each_with_index do |frame, index| %>
|
175
|
+
<div class="frame_info" id="frame_info_<%= index %>" style="display:none;">
|
176
|
+
<h2 class="name"><%= frame.name %></h2>
|
177
|
+
<div class="location"><span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span></div>
|
178
|
+
<%== highlighted_code_block frame %>
|
179
|
+
|
180
|
+
<h3>Local Variables</h3>
|
181
|
+
<table class="var_table">
|
182
|
+
<% frame.local_variables.each do |name, value| %>
|
183
|
+
<tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
|
184
|
+
<% end %>
|
185
|
+
</table>
|
186
|
+
|
187
|
+
<h3>Instance Variables</h3>
|
188
|
+
<table class="var_table">
|
189
|
+
<% frame.instance_variables.each do |name, value| %>
|
190
|
+
<tr><td class="name"><%= name %></td><td><pre><%= value.inspect %></pre></td></tr>
|
191
|
+
<% end %>
|
192
|
+
</table>
|
193
|
+
</div>
|
194
|
+
<% end %>
|
195
|
+
<div style="clear:both"></div>
|
196
|
+
</section>
|
197
|
+
</body>
|
198
|
+
<script>
|
199
|
+
(function() {
|
200
|
+
var previous = null;
|
201
|
+
var previousFrameInfo = null;
|
202
|
+
var frames = document.querySelectorAll("ul.frames li");
|
203
|
+
|
204
|
+
var header = document.querySelector("header");
|
205
|
+
var headerHeight = header.offsetHeight;
|
206
|
+
|
207
|
+
function selectFrameInfo(index) {
|
208
|
+
var el = document.getElementById("frame_info_" + index);
|
209
|
+
|
210
|
+
if(previousFrameInfo) {
|
211
|
+
previousFrameInfo.style.display = "none";
|
212
|
+
}
|
213
|
+
previousFrameInfo = el;
|
214
|
+
previousFrameInfo.style.display = "block";
|
215
|
+
}
|
216
|
+
|
217
|
+
function updateMarginTop() {
|
218
|
+
return;
|
219
|
+
if(previousFrameInfo) {
|
220
|
+
previousFrameInfo.style.marginTop = Math.max(window.scrollY - headerHeight, 0) + "px";
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
window.onscroll = updateMarginTop;
|
225
|
+
|
226
|
+
for(var i = 0; i < frames.length; i++) {
|
227
|
+
(function(el) {
|
228
|
+
el.onclick = function() {
|
229
|
+
if(previous) {
|
230
|
+
previous.className = "";
|
231
|
+
}
|
232
|
+
el.className = "selected";
|
233
|
+
previous = el;
|
234
|
+
|
235
|
+
selectFrameInfo(el.attributes["data-index"].value);
|
236
|
+
updateMarginTop();
|
237
|
+
};
|
238
|
+
})(frames[i]);
|
239
|
+
}
|
240
|
+
|
241
|
+
document.querySelector(".frames li:first-child").click();
|
242
|
+
})();
|
243
|
+
</script>
|
244
|
+
</html>
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module BetterErrors
|
4
|
+
class ErrorPage
|
5
|
+
def self.template_path
|
6
|
+
__FILE__.gsub(/\.rb$/, ".erb")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.template
|
10
|
+
Erubis::EscapedEruby.new(File.read(template_path))
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :exception, :env
|
14
|
+
|
15
|
+
def initialize(exception, env)
|
16
|
+
@exception = real_exception(exception)
|
17
|
+
@env = env
|
18
|
+
end
|
19
|
+
|
20
|
+
def render
|
21
|
+
self.class.template.result binding
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def real_exception(exception)
|
26
|
+
loop do
|
27
|
+
case exception
|
28
|
+
when ActionView::Template::Error; exception = exception.original_exception
|
29
|
+
else
|
30
|
+
return exception
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def request_path
|
36
|
+
env["REQUEST_PATH"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def backtrace_frames
|
40
|
+
@backtrace_frames ||= ErrorFrame.from_exception(exception)
|
41
|
+
end
|
42
|
+
|
43
|
+
def coderay_scanner_for_ext(ext)
|
44
|
+
case ext
|
45
|
+
when "rb"; :ruby
|
46
|
+
when "html"; :html
|
47
|
+
when "erb"; :erb
|
48
|
+
when "haml"; :haml
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def file_extension(filename)
|
53
|
+
filename.split(".").last
|
54
|
+
end
|
55
|
+
|
56
|
+
def code_extract(frame, lines_of_context = 5)
|
57
|
+
lines = File.readlines(frame.filename)
|
58
|
+
min_line = [1, frame.line - lines_of_context].max - 1
|
59
|
+
max_line = [frame.line + lines_of_context, lines.count + 1].min - 1
|
60
|
+
[min_line, max_line, lines[min_line..max_line].join]
|
61
|
+
end
|
62
|
+
|
63
|
+
def highlighted_code_block(frame)
|
64
|
+
ext = file_extension(frame.filename)
|
65
|
+
scanner = coderay_scanner_for_ext(ext)
|
66
|
+
min_line, max_line, code = code_extract(frame)
|
67
|
+
highlighted_code = CodeRay.scan(code, scanner).div wrap: nil
|
68
|
+
"".tap do |html|
|
69
|
+
html << "<div class='code'>"
|
70
|
+
highlighted_code.each_line.each_with_index do |str, index|
|
71
|
+
if min_line + index + 1 == frame.line
|
72
|
+
html << "<pre class='highlight'>"
|
73
|
+
else
|
74
|
+
html << "<pre>"
|
75
|
+
end
|
76
|
+
html << sprintf("%5d", min_line + index + 1) << " " << str << "</pre>"
|
77
|
+
end
|
78
|
+
html << "</div>"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BetterErrors
|
2
|
+
class Middleware
|
3
|
+
def initialize(app, handler = ErrorPage)
|
4
|
+
@app = app
|
5
|
+
@handler = handler
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
@app.call env
|
10
|
+
rescue Exception => ex
|
11
|
+
error_page = @handler.new ex, env
|
12
|
+
[500, { "Content-Type" => "text/html; charset=utf-8" }, [error_page.render]]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module BetterErrors
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "better_errors.configure_rails_initialization" do
|
4
|
+
middleware = Rails.application.middleware
|
5
|
+
middleware.use BetterErrors::Middleware
|
6
|
+
|
7
|
+
BetterErrors.application_root = Rails.root.to_s
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module BetterErrors
|
4
|
+
describe ErrorFrame do
|
5
|
+
context "#application?" do
|
6
|
+
it "should be true for application filenames" do
|
7
|
+
BetterErrors.stub!(:application_root).and_return("/abc/xyz")
|
8
|
+
frame = ErrorFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
|
9
|
+
|
10
|
+
frame.application?.should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should be false for everything else" do
|
14
|
+
BetterErrors.stub!(:application_root).and_return("/abc/xyz")
|
15
|
+
frame = ErrorFrame.new("/abc/nope", 123, "foo")
|
16
|
+
|
17
|
+
frame.application?.should be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not care if no application_root is set" do
|
21
|
+
frame = ErrorFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
|
22
|
+
|
23
|
+
frame.application?.should be_false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#gem?" do
|
28
|
+
it "should be true for gem filenames" do
|
29
|
+
Gem.stub!(:path).and_return(["/abc/xyz"])
|
30
|
+
frame = ErrorFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
|
31
|
+
|
32
|
+
frame.gem?.should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be false for everything else" do
|
36
|
+
Gem.stub!(:path).and_return(["/abc/xyz"])
|
37
|
+
frame = ErrorFrame.new("/abc/nope", 123, "foo")
|
38
|
+
|
39
|
+
frame.gem?.should be_false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#application_path" do
|
44
|
+
it "should chop off the application root" do
|
45
|
+
BetterErrors.stub!(:application_root).and_return("/abc/xyz")
|
46
|
+
frame = ErrorFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
|
47
|
+
|
48
|
+
frame.application_path.should == "app/controllers/crap_controller.rb"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "#gem_path" do
|
53
|
+
it "should chop of the gem path and stick (gem) there" do
|
54
|
+
Gem.stub!(:path).and_return(["/abc/xyz"])
|
55
|
+
frame = ErrorFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
|
56
|
+
|
57
|
+
frame.gem_path.should == "(gem) whatever-1.2.3/lib/whatever.rb"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module BetterErrors
|
4
|
+
describe ErrorPage do
|
5
|
+
let(:exception) { raise ZeroDivisionError, "you divided by zero you silly goose!" rescue $! }
|
6
|
+
|
7
|
+
let(:error_page) { ErrorPage.new exception, { "REQUEST_PATH" => "/some/path" } }
|
8
|
+
|
9
|
+
let(:response) { error_page.render }
|
10
|
+
|
11
|
+
it "should include the error message" do
|
12
|
+
response.should include("you divided by zero you silly goose!")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should include the request path" do
|
16
|
+
response.should include("/some/path")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should include the exception class" do
|
20
|
+
response.should include("ZeroDivisionError")
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when showing source code" do
|
24
|
+
before do
|
25
|
+
exception.stub!(:backtrace).and_return([
|
26
|
+
"#{File.expand_path("../support/my_source.rb", __FILE__)}:8:in `some_method'"
|
27
|
+
])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should show the line where the exception was raised" do
|
31
|
+
response.should include("8 eight")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should show five lines of context" do
|
35
|
+
response.should include("3 three")
|
36
|
+
response.should include("13 thirteen")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not show more than five lines of context" do
|
40
|
+
response.should_not include("2 two")
|
41
|
+
response.should_not include("14 fourteen")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module BetterErrors
|
4
|
+
describe Middleware do
|
5
|
+
it "should pass non-error responses through" do
|
6
|
+
app = Middleware.new(->env { ":)" })
|
7
|
+
app.call({}).should == ":)"
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when handling an error" do
|
11
|
+
let(:app) { Middleware.new(->env { raise "oh no :(" }) }
|
12
|
+
|
13
|
+
it "should return status 500" do
|
14
|
+
status, headers, body = app.call({})
|
15
|
+
|
16
|
+
status.should == 500
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return UTF-8 error pages" do
|
20
|
+
status, headers, body = app.call({})
|
21
|
+
|
22
|
+
headers["Content-Type"].should == "text/html; charset=utf-8"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: better_errors
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Charlie Somerville
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: erubis
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: coderay
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Better Errors gives Rails a better error page.
|
63
|
+
email:
|
64
|
+
- charlie@charliesomerville.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- better_errors.gemspec
|
75
|
+
- lib/better_errors.rb
|
76
|
+
- lib/better_errors/core_ext/exception.rb
|
77
|
+
- lib/better_errors/error_frame.rb
|
78
|
+
- lib/better_errors/error_page.erb
|
79
|
+
- lib/better_errors/error_page.rb
|
80
|
+
- lib/better_errors/middleware.rb
|
81
|
+
- lib/better_errors/rails.rb
|
82
|
+
- lib/better_errors/version.rb
|
83
|
+
- spec/better_errors/error_frame_spec.rb
|
84
|
+
- spec/better_errors/error_page_spec.rb
|
85
|
+
- spec/better_errors/middleware_spec.rb
|
86
|
+
- spec/better_errors/support/my_source.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
homepage: https://github.com/charliesome/better_errors
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.8.24
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: Better Errors gives Rails a better error page
|
113
|
+
test_files:
|
114
|
+
- spec/better_errors/error_frame_spec.rb
|
115
|
+
- spec/better_errors/error_page_spec.rb
|
116
|
+
- spec/better_errors/middleware_spec.rb
|
117
|
+
- spec/better_errors/support/my_source.rb
|
118
|
+
- spec/spec_helper.rb
|
119
|
+
has_rdoc:
|