better_errors-creditkudos 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +8 -0
  4. data/.yardopts +1 -0
  5. data/CHANGELOG.md +3 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +128 -0
  9. data/Rakefile +13 -0
  10. data/better_errors-creditkudos.gemspec +28 -0
  11. data/lib/better_errors.rb +152 -0
  12. data/lib/better_errors/code_formatter.rb +63 -0
  13. data/lib/better_errors/code_formatter/html.rb +26 -0
  14. data/lib/better_errors/code_formatter/text.rb +14 -0
  15. data/lib/better_errors/error_page.rb +129 -0
  16. data/lib/better_errors/exception_extension.rb +17 -0
  17. data/lib/better_errors/middleware.rb +141 -0
  18. data/lib/better_errors/rails.rb +28 -0
  19. data/lib/better_errors/raised_exception.rb +68 -0
  20. data/lib/better_errors/repl.rb +30 -0
  21. data/lib/better_errors/repl/basic.rb +20 -0
  22. data/lib/better_errors/repl/pry.rb +78 -0
  23. data/lib/better_errors/stack_frame.rb +111 -0
  24. data/lib/better_errors/templates/main.erb +1032 -0
  25. data/lib/better_errors/templates/text.erb +21 -0
  26. data/lib/better_errors/templates/variable_info.erb +72 -0
  27. data/lib/better_errors/version.rb +3 -0
  28. data/spec/better_errors/code_formatter_spec.rb +92 -0
  29. data/spec/better_errors/error_page_spec.rb +122 -0
  30. data/spec/better_errors/middleware_spec.rb +180 -0
  31. data/spec/better_errors/raised_exception_spec.rb +72 -0
  32. data/spec/better_errors/repl/basic_spec.rb +18 -0
  33. data/spec/better_errors/repl/pry_spec.rb +40 -0
  34. data/spec/better_errors/repl/shared_examples.rb +18 -0
  35. data/spec/better_errors/stack_frame_spec.rb +157 -0
  36. data/spec/better_errors/support/my_source.rb +20 -0
  37. data/spec/better_errors_spec.rb +73 -0
  38. data/spec/spec_helper.rb +5 -0
  39. data/spec/without_binding_of_caller.rb +9 -0
  40. metadata +136 -0
@@ -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,78 @@
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 = BetterErrors::REPL::Pry::Input.new
40
+ @output = BetterErrors::REPL::Pry::Output.new
41
+ @pry = ::Pry.new input: @input, output: @output
42
+ @pry.hooks.clear_all if defined?(@pry.hooks.clear_all)
43
+ @fiber.resume
44
+ end
45
+
46
+ def send_input(str)
47
+ local ::Pry.config, color: false, pager: false do
48
+ @fiber.resume "#{str}\n"
49
+ [@output.read_buffer, *prompt]
50
+ end
51
+ end
52
+
53
+ def prompt
54
+ if indent = @pry.instance_variable_get(:@indent) and !indent.indent_level.empty?
55
+ ["..", indent.indent_level]
56
+ else
57
+ [">>", ""]
58
+ end
59
+ rescue
60
+ [">>", ""]
61
+ end
62
+
63
+ private
64
+ def local(obj, attrs)
65
+ old_attrs = {}
66
+ attrs.each do |k, v|
67
+ old_attrs[k] = obj.send k
68
+ obj.send "#{k}=", v
69
+ end
70
+ yield
71
+ ensure
72
+ old_attrs.each do |k, v|
73
+ obj.send "#{k}=", v
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,111 @@
1
+ require "set"
2
+
3
+ module BetterErrors
4
+ # @private
5
+ class StackFrame
6
+ def self.from_exception(exception)
7
+ RaisedException.new(exception).backtrace
8
+ end
9
+
10
+ attr_reader :filename, :line, :name, :frame_binding
11
+
12
+ def initialize(filename, line, name, frame_binding = nil)
13
+ @filename = filename
14
+ @line = line
15
+ @name = name
16
+ @frame_binding = frame_binding
17
+
18
+ set_pretty_method_name if frame_binding
19
+ end
20
+
21
+ def application?
22
+ if root = BetterErrors.application_root
23
+ filename.index(root) == 0 && filename.index("#{root}/vendor") != 0
24
+ end
25
+ end
26
+
27
+ def application_path
28
+ filename[(BetterErrors.application_root.length+1)..-1]
29
+ end
30
+
31
+ def gem?
32
+ Gem.path.any? { |path| filename.index(path) == 0 }
33
+ end
34
+
35
+ def gem_path
36
+ if path = Gem.path.detect { |p| filename.index(p) == 0 }
37
+ gem_name_and_version, path = filename.sub("#{path}/gems/", "").split("/", 2)
38
+ /(?<gem_name>.+)-(?<gem_version>[\w.]+)/ =~ gem_name_and_version
39
+ "#{gem_name} (#{gem_version}) #{path}"
40
+ end
41
+ end
42
+
43
+ def class_name
44
+ @class_name
45
+ end
46
+
47
+ def method_name
48
+ @method_name || @name
49
+ end
50
+
51
+ def context
52
+ if gem?
53
+ :gem
54
+ elsif application?
55
+ :application
56
+ else
57
+ :dunno
58
+ end
59
+ end
60
+
61
+ def pretty_path
62
+ case context
63
+ when :application; application_path
64
+ when :gem; gem_path
65
+ else filename
66
+ end
67
+ end
68
+
69
+ def local_variables
70
+ return {} unless frame_binding
71
+ frame_binding.eval("local_variables").each_with_object({}) do |name, hash|
72
+ if defined?(frame_binding.local_variable_get)
73
+ hash[name] = frame_binding.local_variable_get(name)
74
+ else
75
+ hash[name] = frame_binding.eval(name.to_s)
76
+ end
77
+ end
78
+ end
79
+
80
+ def instance_variables
81
+ return {} unless frame_binding
82
+ Hash[visible_instance_variables.map { |x|
83
+ [x, frame_binding.eval(x.to_s)]
84
+ }]
85
+ end
86
+
87
+ def visible_instance_variables
88
+ frame_binding.eval("instance_variables") - BetterErrors.ignored_instance_variables
89
+ end
90
+
91
+ def to_s
92
+ "#{pretty_path}:#{line}:in `#{name}'"
93
+ end
94
+
95
+ private
96
+ def set_pretty_method_name
97
+ name =~ /\A(block (\([^)]+\) )?in )?/
98
+ recv = frame_binding.eval("self")
99
+
100
+ return unless method_name = frame_binding.eval("::Kernel.__method__")
101
+
102
+ if Module === recv
103
+ @class_name = "#{$1}#{recv}"
104
+ @method_name = ".#{method_name}"
105
+ else
106
+ @class_name = "#{$1}#{Kernel.instance_method(:class).bind(recv).call}"
107
+ @method_name = "##{method_name}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,1032 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= exception_type %> at <%= request_path %></title>
5
+ </head>
6
+ <body>
7
+ <%# Stylesheets are placed in the <body> for Turbolinks compatibility. %>
8
+ <style>
9
+ /* Basic reset */
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ }
14
+
15
+ table {
16
+ width: 100%;
17
+ border-collapse: collapse;
18
+ }
19
+
20
+ th, td {
21
+ vertical-align: top;
22
+ text-align: left;
23
+ }
24
+
25
+ textarea {
26
+ resize: none;
27
+ }
28
+
29
+ body {
30
+ font-size: 10pt;
31
+ }
32
+
33
+ body, td, input, textarea {
34
+ font-family: helvetica neue, lucida grande, sans-serif;
35
+ line-height: 1.5;
36
+ color: #333;
37
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
38
+ }
39
+
40
+ html {
41
+ background: #f0f0f5;
42
+ }
43
+
44
+ .clearfix::after{
45
+ clear: both;
46
+ content: ".";
47
+ display: block;
48
+ height: 0;
49
+ visibility: hidden;
50
+ }
51
+
52
+ /* ---------------------------------------------------------------------
53
+ * Basic layout
54
+ * --------------------------------------------------------------------- */
55
+
56
+ /* Small */
57
+ @media screen and (max-width: 1100px) {
58
+ html {
59
+ overflow-y: scroll;
60
+ }
61
+
62
+ body {
63
+ margin: 0 20px;
64
+ }
65
+
66
+ header.exception {
67
+ margin: 0 -20px;
68
+ }
69
+
70
+ nav.sidebar {
71
+ padding: 0;
72
+ margin: 20px 0;
73
+ }
74
+
75
+ ul.frames {
76
+ max-height: 200px;
77
+ overflow: auto;
78
+ }
79
+ }
80
+
81
+ /* Wide */
82
+ @media screen and (min-width: 1100px) {
83
+ header.exception {
84
+ position: fixed;
85
+ top: 0;
86
+ left: 0;
87
+ right: 0;
88
+ }
89
+
90
+ nav.sidebar,
91
+ .frame_info {
92
+ position: fixed;
93
+ top: 95px;
94
+ bottom: 0;
95
+
96
+ box-sizing: border-box;
97
+
98
+ overflow-y: auto;
99
+ overflow-x: hidden;
100
+ }
101
+
102
+ nav.sidebar {
103
+ width: 40%;
104
+ left: 20px;
105
+ top: 115px;
106
+ bottom: 20px;
107
+ }
108
+
109
+ .frame_info {
110
+ right: 0;
111
+ left: 40%;
112
+
113
+ padding: 20px;
114
+ padding-left: 10px;
115
+ margin-left: 30px;
116
+ }
117
+ }
118
+
119
+ nav.sidebar {
120
+ background: #d3d3da;
121
+ border-top: solid 3px #a33;
122
+ border-bottom: solid 3px #a33;
123
+ border-radius: 4px;
124
+ box-shadow: 0 0 6px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
125
+ }
126
+
127
+ /* ---------------------------------------------------------------------
128
+ * Header
129
+ * --------------------------------------------------------------------- */
130
+
131
+ header.exception {
132
+ padding: 18px 20px;
133
+
134
+ height: 59px;
135
+ min-height: 59px;
136
+
137
+ overflow: hidden;
138
+
139
+ background-color: #20202a;
140
+ color: #aaa;
141
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
142
+ font-weight: 200;
143
+ box-shadow: inset 0 -5px 3px -3px rgba(0, 0, 0, 0.05), inset 0 -1px 0 rgba(0, 0, 0, 0.05);
144
+
145
+ -webkit-text-smoothing: antialiased;
146
+ }
147
+
148
+ /* Heading */
149
+ header.exception h2 {
150
+ font-weight: 200;
151
+ font-size: 11pt;
152
+ }
153
+
154
+ header.exception h2,
155
+ header.exception p {
156
+ line-height: 1.4em;
157
+ overflow: hidden;
158
+ white-space: pre;
159
+ text-overflow: ellipsis;
160
+ }
161
+
162
+ header.exception h2 strong {
163
+ font-weight: 700;
164
+ color: #d55;
165
+ }
166
+
167
+ header.exception p {
168
+ font-weight: 200;
169
+ font-size: 20pt;
170
+ color: white;
171
+ }
172
+
173
+ header.exception:hover {
174
+ height: auto;
175
+ z-index: 2;
176
+ }
177
+
178
+ header.exception:hover h2,
179
+ header.exception:hover p {
180
+ padding-right: 20px;
181
+ overflow-y: auto;
182
+ word-wrap: break-word;
183
+ white-space: pre-wrap;
184
+ height: auto;
185
+ max-height: 7.5em;
186
+ }
187
+
188
+ @media screen and (max-width: 1100px) {
189
+ header.exception {
190
+ height: auto;
191
+ }
192
+
193
+ header.exception h2,
194
+ header.exception p {
195
+ padding-right: 20px;
196
+ overflow-y: auto;
197
+ word-wrap: break-word;
198
+ height: auto;
199
+ max-height: 7em;
200
+ }
201
+ }
202
+
203
+ <%#
204
+ /* Light theme */
205
+ header.exception {
206
+ text-shadow: 0 1px 0 rgba(250, 250, 250, 0.6);
207
+ background: rgba(200,100,50,0.10);
208
+ color: #977;
209
+ }
210
+ header.exception h2 strong {
211
+ color: #533;
212
+ }
213
+ header.exception p {
214
+ color: #744;
215
+ }
216
+ %>
217
+
218
+ /* ---------------------------------------------------------------------
219
+ * Navigation
220
+ * --------------------------------------------------------------------- */
221
+
222
+ nav.tabs {
223
+ border-bottom: solid 1px #ddd;
224
+
225
+ background-color: #eee;
226
+ text-align: center;
227
+
228
+ padding: 6px;
229
+
230
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
231
+ }
232
+
233
+ nav.tabs a {
234
+ display: inline-block;
235
+
236
+ height: 22px;
237
+ line-height: 22px;
238
+ padding: 0 10px;
239
+
240
+ text-decoration: none;
241
+ font-size: 8pt;
242
+ font-weight: bold;
243
+
244
+ color: #999;
245
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
246
+ }
247
+
248
+ nav.tabs a.selected {
249
+ color: white;
250
+ background: rgba(0, 0, 0, 0.5);
251
+ border-radius: 16px;
252
+ box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.1);
253
+ text-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 1px 0 rgba(0, 0, 0, 0.4);
254
+ }
255
+
256
+ nav.tabs a.disabled {
257
+ text-decoration: line-through;
258
+ text-shadow: none;
259
+ cursor: default;
260
+ }
261
+
262
+ /* ---------------------------------------------------------------------
263
+ * Sidebar
264
+ * --------------------------------------------------------------------- */
265
+
266
+ ul.frames {
267
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
268
+ }
269
+
270
+ /* Each item */
271
+ ul.frames li {
272
+ background-color: #f8f8f8;
273
+ background: -webkit-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
274
+ background: -moz-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
275
+ background: linear-gradient(top, #f8f8f8 80%, #f0f0f0);
276
+ box-shadow: inset 0 -1px 0 #e2e2e2;
277
+ padding: 7px 20px;
278
+
279
+ cursor: pointer;
280
+ overflow: hidden;
281
+ }
282
+
283
+ ul.frames .name,
284
+ ul.frames .location {
285
+ overflow: hidden;
286
+ height: 1.5em;
287
+
288
+ white-space: nowrap;
289
+ word-wrap: none;
290
+ text-overflow: ellipsis;
291
+ }
292
+
293
+ ul.frames .method {
294
+ color: #966;
295
+ }
296
+
297
+ ul.frames .location {
298
+ font-size: 0.85em;
299
+ font-weight: 400;
300
+ color: #999;
301
+ }
302
+
303
+ ul.frames .line {
304
+ font-weight: bold;
305
+ }
306
+
307
+ /* Selected frame */
308
+ ul.frames li.selected {
309
+ background: #38a;
310
+ box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), inset 0 2px 0 rgba(255, 255, 255, 0.01), inset 0 -1px 0 rgba(0, 0, 0, 0.1);
311
+ }
312
+
313
+ ul.frames li.selected .name,
314
+ ul.frames li.selected .method,
315
+ ul.frames li.selected .location {
316
+ color: white;
317
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
318
+ }
319
+
320
+ ul.frames li.selected .location {
321
+ opacity: 0.6;
322
+ }
323
+
324
+ /* Iconography */
325
+ ul.frames li {
326
+ padding-left: 60px;
327
+ position: relative;
328
+ }
329
+
330
+ ul.frames li .icon {
331
+ display: block;
332
+ width: 20px;
333
+ height: 20px;
334
+ line-height: 20px;
335
+ border-radius: 15px;
336
+
337
+ text-align: center;
338
+
339
+ background: white;
340
+ border: solid 2px #ccc;
341
+
342
+ font-size: 9pt;
343
+ font-weight: 200;
344
+ font-style: normal;
345
+
346
+ position: absolute;
347
+ top: 14px;
348
+ left: 20px;
349
+ }
350
+
351
+ ul.frames .icon.application {
352
+ background: #808090;
353
+ border-color: #555;
354
+ }
355
+
356
+ ul.frames .icon.application:before {
357
+ content: 'A';
358
+ color: white;
359
+ text-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
360
+ }
361
+
362
+ /* Responsiveness -- flow to single-line mode */
363
+ @media screen and (max-width: 1100px) {
364
+ ul.frames li {
365
+ padding-top: 6px;
366
+ padding-bottom: 6px;
367
+ padding-left: 36px;
368
+ line-height: 1.3;
369
+ }
370
+
371
+ ul.frames li .icon {
372
+ width: 11px;
373
+ height: 11px;
374
+ line-height: 11px;
375
+
376
+ top: 7px;
377
+ left: 10px;
378
+ font-size: 5pt;
379
+ }
380
+
381
+ ul.frames .name,
382
+ ul.frames .location {
383
+ display: inline-block;
384
+ line-height: 1.3;
385
+ height: 1.3em;
386
+ }
387
+
388
+ ul.frames .name {
389
+ margin-right: 10px;
390
+ }
391
+ }
392
+
393
+ /* ---------------------------------------------------------------------
394
+ * Monospace
395
+ * --------------------------------------------------------------------- */
396
+
397
+ pre, code, .repl input, .repl .prompt span, textarea, .code_linenums {
398
+ font-family: menlo, lucida console, monospace;
399
+ font-size: 8pt;
400
+ }
401
+
402
+ /* ---------------------------------------------------------------------
403
+ * Display area
404
+ * --------------------------------------------------------------------- */
405
+
406
+ .trace_info {
407
+ background: #fff;
408
+ padding: 6px;
409
+ border-radius: 3px;
410
+ margin-bottom: 2px;
411
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.03), 1px 1px 0 rgba(0, 0, 0, 0.05), -1px 1px 0 rgba(0, 0, 0, 0.05), 0 0 0 4px rgba(0, 0, 0, 0.04);
412
+ }
413
+
414
+ .code_block{
415
+ background: #f1f1f1;
416
+ border-left: 1px solid #ccc;
417
+ }
418
+
419
+ /* Titlebar */
420
+ .trace_info .title {
421
+ background: #f1f1f1;
422
+
423
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
424
+ overflow: hidden;
425
+ padding: 6px 10px;
426
+
427
+ border: solid 1px #ccc;
428
+ border-bottom: 0;
429
+
430
+ border-top-left-radius: 2px;
431
+ border-top-right-radius: 2px;
432
+ }
433
+
434
+ .trace_info .title .name,
435
+ .trace_info .title .location {
436
+ font-size: 9pt;
437
+ line-height: 26px;
438
+ height: 26px;
439
+ overflow: hidden;
440
+ }
441
+
442
+ .trace_info .title .location {
443
+ float: left;
444
+ font-weight: bold;
445
+ font-size: 10pt;
446
+ }
447
+
448
+ .trace_info .title .location a {
449
+ color:inherit;
450
+ text-decoration:none;
451
+ border-bottom:1px solid #aaaaaa;
452
+ }
453
+
454
+ .trace_info .title .location a:hover {
455
+ border-color:#666666;
456
+ }
457
+
458
+ .trace_info .title .name {
459
+ float: right;
460
+ font-weight: 200;
461
+ }
462
+
463
+ .code, .console, .unavailable {
464
+ background: #fff;
465
+ padding: 5px;
466
+
467
+ box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
468
+ }
469
+
470
+ .code_linenums{
471
+ background:#f1f1f1;
472
+ padding-top:10px;
473
+ padding-bottom:9px;
474
+ float:left;
475
+ }
476
+
477
+ .code_linenums span{
478
+ display:block;
479
+ padding:0 12px;
480
+ }
481
+
482
+ .code {
483
+ margin-bottom: -1px;
484
+ border-top-left-radius:2px;
485
+ padding: 10px 0;
486
+ overflow: auto;
487
+ }
488
+
489
+ .code pre{
490
+ padding-left:12px;
491
+ min-height:16px;
492
+ }
493
+
494
+ /* Source unavailable */
495
+ p.unavailable {
496
+ padding: 20px 0 40px 0;
497
+ text-align: center;
498
+ color: #b99;
499
+ font-weight: bold;
500
+ }
501
+
502
+ p.unavailable:before {
503
+ content: '\00d7';
504
+ display: block;
505
+
506
+ color: #daa;
507
+
508
+ text-align: center;
509
+ font-size: 40pt;
510
+ font-weight: normal;
511
+ margin-bottom: -10px;
512
+ }
513
+
514
+ @-webkit-keyframes highlight {
515
+ 0% { background: rgba(220, 30, 30, 0.3); }
516
+ 100% { background: rgba(220, 30, 30, 0.1); }
517
+ }
518
+ @-moz-keyframes highlight {
519
+ 0% { background: rgba(220, 30, 30, 0.3); }
520
+ 100% { background: rgba(220, 30, 30, 0.1); }
521
+ }
522
+ @keyframes highlight {
523
+ 0% { background: rgba(220, 30, 30, 0.3); }
524
+ 100% { background: rgba(220, 30, 30, 0.1); }
525
+ }
526
+
527
+ .code .highlight, .code_linenums .highlight {
528
+ background: rgba(220, 30, 30, 0.1);
529
+ -webkit-animation: highlight 400ms linear 1;
530
+ -moz-animation: highlight 400ms linear 1;
531
+ animation: highlight 400ms linear 1;
532
+ }
533
+
534
+ /* REPL shell */
535
+ .console {
536
+ padding: 0 1px 10px 1px;
537
+ border-bottom-left-radius: 2px;
538
+ border-bottom-right-radius: 2px;
539
+ }
540
+
541
+ .console pre {
542
+ padding: 10px 10px 0 10px;
543
+ max-height: 400px;
544
+ overflow-x: none;
545
+ overflow-y: auto;
546
+ margin-bottom: -3px;
547
+ word-wrap: break-word;
548
+ white-space: pre-wrap;
549
+ }
550
+
551
+ /* .prompt > span + input */
552
+ .console .prompt {
553
+ display: table;
554
+ width: 100%;
555
+ }
556
+
557
+ .console .prompt span,
558
+ .console .prompt input {
559
+ display: table-cell;
560
+ }
561
+
562
+ .console .prompt span {
563
+ width: 1%;
564
+ padding-right: 5px;
565
+ padding-left: 10px;
566
+ }
567
+
568
+ .console .prompt input {
569
+ width: 99%;
570
+ }
571
+
572
+ /* Input box */
573
+ .console input,
574
+ .console input:focus {
575
+ outline: 0;
576
+ border: 0;
577
+ padding: 0;
578
+ background: transparent;
579
+ margin: 0;
580
+ }
581
+
582
+ /* Hint text */
583
+ .hint {
584
+ margin: 15px 0 20px 0;
585
+ font-size: 8pt;
586
+ color: #8080a0;
587
+ padding-left: 20px;
588
+ }
589
+
590
+ .hint:before {
591
+ content: '\25b2';
592
+ margin-right: 5px;
593
+ opacity: 0.5;
594
+ }
595
+
596
+ /* ---------------------------------------------------------------------
597
+ * Variable infos
598
+ * --------------------------------------------------------------------- */
599
+
600
+ .sub {
601
+ padding: 10px 0;
602
+ margin: 10px 0;
603
+ }
604
+
605
+ .sub:before {
606
+ content: '';
607
+ display: block;
608
+ width: 100%;
609
+ height: 4px;
610
+
611
+ border-radius: 2px;
612
+ background: rgba(0, 150, 200, 0.05);
613
+ box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.7), inset 0 0 0 1px rgba(0, 0, 0, 0.04), inset 2px 2px 2px rgba(0, 0, 0, 0.07);
614
+ }
615
+
616
+ .sub h3 {
617
+ color: #39a;
618
+ font-size: 1.1em;
619
+ margin: 10px 0;
620
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
621
+
622
+ -webkit-font-smoothing: antialiased;
623
+ }
624
+
625
+ .sub .inset {
626
+ overflow-y: auto;
627
+ }
628
+
629
+ .sub table {
630
+ table-layout: fixed;
631
+ }
632
+
633
+ .sub table td {
634
+ border-top: dotted 1px #ddd;
635
+ padding: 7px 1px;
636
+ }
637
+
638
+ .sub table td.name {
639
+ width: 150px;
640
+
641
+ font-weight: bold;
642
+ font-size: 0.8em;
643
+ padding-right: 20px;
644
+
645
+ word-wrap: break-word;
646
+ }
647
+
648
+ .sub table td pre {
649
+ max-height: 15em;
650
+ overflow-y: auto;
651
+ }
652
+
653
+ .sub table td pre {
654
+ width: 100%;
655
+
656
+ word-wrap: break-word;
657
+ white-space: normal;
658
+ }
659
+
660
+ /* "(object doesn't support inspect)" */
661
+ .sub .unsupported {
662
+ font-family: sans-serif;
663
+ color: #777;
664
+ }
665
+
666
+ /* ---------------------------------------------------------------------
667
+ * Scrollbar
668
+ * --------------------------------------------------------------------- */
669
+
670
+ nav.sidebar::-webkit-scrollbar,
671
+ .inset pre::-webkit-scrollbar,
672
+ .console pre::-webkit-scrollbar,
673
+ .code::-webkit-scrollbar {
674
+ width: 10px;
675
+ height: 10px;
676
+ }
677
+
678
+ .inset pre::-webkit-scrollbar-thumb,
679
+ .console pre::-webkit-scrollbar-thumb,
680
+ .code::-webkit-scrollbar-thumb {
681
+ background: #ccc;
682
+ border-radius: 5px;
683
+ }
684
+
685
+ nav.sidebar::-webkit-scrollbar-thumb {
686
+ background: rgba(0, 0, 0, 0.0);
687
+ border-radius: 5px;
688
+ }
689
+
690
+ nav.sidebar:hover::-webkit-scrollbar-thumb {
691
+ background-color: #999;
692
+ background: -webkit-linear-gradient(left, #aaa, #999);
693
+ }
694
+
695
+ .console pre:hover::-webkit-scrollbar-thumb,
696
+ .inset pre:hover::-webkit-scrollbar-thumb,
697
+ .code:hover::-webkit-scrollbar-thumb {
698
+ background: #888;
699
+ }
700
+ </style>
701
+
702
+ <%# IE8 compatibility crap %>
703
+ <script>
704
+ (function() {
705
+ var elements = ["section", "nav", "header", "footer", "audio"];
706
+ for (var i = 0; i < elements.length; i++) {
707
+ document.createElement(elements[i]);
708
+ }
709
+ })();
710
+ </script>
711
+
712
+ <%#
713
+ If Rails's Turbolinks is used, the Better Errors page is probably
714
+ rendered in the host app's layout. Let's empty out the styles of the
715
+ host app.
716
+ %>
717
+ <script>
718
+ if (window.Turbolinks) {
719
+ for(var i=0; i < document.styleSheets.length; i++) {
720
+ if(document.styleSheets[i].href)
721
+ document.styleSheets[i].disabled = true;
722
+ }
723
+ document.addEventListener("page:restore", function restoreCSS(e) {
724
+ for(var i=0; i < document.styleSheets.length; i++) {
725
+ document.styleSheets[i].disabled = false;
726
+ }
727
+ document.removeEventListener("page:restore", restoreCSS, false);
728
+ });
729
+ }
730
+ </script>
731
+
732
+ <div class='top'>
733
+ <header class="exception">
734
+ <h2><strong><%= exception_type %></strong> <span>at <%= request_path %></span></h2>
735
+ <p><%= exception_message %></p>
736
+ </header>
737
+ </div>
738
+
739
+ <section class="backtrace">
740
+ <nav class="sidebar">
741
+ <nav class="tabs">
742
+ <a href="#" id="application_frames">Application Frames</a>
743
+ <a href="#" id="all_frames">All Frames</a>
744
+ </nav>
745
+ <ul class="frames">
746
+ <% backtrace_frames.each_with_index do |frame, index| %>
747
+ <li class="<%= frame.context %>" data-context="<%= frame.context %>" data-index="<%= index %>">
748
+ <span class='stroke'></span>
749
+ <i class="icon <%= frame.context %>"></i>
750
+ <div class="info">
751
+ <div class="name">
752
+ <strong><%= frame.class_name %></strong><span class='method'><%= frame.method_name %></span>
753
+ </div>
754
+ <div class="location">
755
+ <span class="filename"><%= frame.pretty_path %></span>, line <span class="line"><%= frame.line %></span>
756
+ </div>
757
+ </div>
758
+ </li>
759
+ <% end %>
760
+ </ul>
761
+ </nav>
762
+
763
+ <% backtrace_frames.each_with_index do |frame, index| %>
764
+ <div class="frame_info" id="frame_info_<%= index %>" style="display:none;"></div>
765
+ <% end %>
766
+ </section>
767
+ </body>
768
+ <script>
769
+ (function() {
770
+
771
+ var OID = "<%= id %>";
772
+
773
+ var previousFrame = null;
774
+ var previousFrameInfo = null;
775
+ var allFrames = document.querySelectorAll("ul.frames li");
776
+ var allFrameInfos = document.querySelectorAll(".frame_info");
777
+
778
+ function apiCall(method, opts, cb) {
779
+ var req = new XMLHttpRequest();
780
+ req.open("POST", <%== uri_prefix.gsub("<", "&lt;").inspect %> + "/__better_errors/" + OID + "/" + method, true);
781
+ req.setRequestHeader("Content-Type", "application/json");
782
+ req.send(JSON.stringify(opts));
783
+ req.onreadystatechange = function() {
784
+ if(req.readyState == 4) {
785
+ var res = JSON.parse(req.responseText);
786
+ cb(res);
787
+ }
788
+ };
789
+ }
790
+
791
+ function escapeHTML(html) {
792
+ return html.replace(/&/, "&amp;").replace(/</g, "&lt;");
793
+ }
794
+
795
+ function REPL(index) {
796
+ this.index = index;
797
+
798
+ var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands"));
799
+ if(previousCommands === null) {
800
+ localStorage.setItem("better_errors_previous_commands", JSON.stringify([]));
801
+ previousCommands = [];
802
+ }
803
+
804
+ this.previousCommandOffset = previousCommands.length;
805
+ }
806
+
807
+ REPL.all = [];
808
+
809
+ REPL.prototype.install = function(containerElement) {
810
+ this.container = containerElement;
811
+
812
+ this.promptElement = this.container.querySelector(".prompt span");
813
+ this.inputElement = this.container.querySelector("input");
814
+ this.outputElement = this.container.querySelector("pre");
815
+
816
+ var self = this;
817
+ this.inputElement.onkeydown = function(ev) {
818
+ self.onKeyDown(ev);
819
+ };
820
+
821
+ this.setPrompt(">>");
822
+
823
+ REPL.all[this.index] = this;
824
+ }
825
+
826
+ REPL.prototype.focus = function() {
827
+ this.inputElement.focus();
828
+ };
829
+
830
+ REPL.prototype.setPrompt = function(prompt) {
831
+ this._prompt = prompt;
832
+ this.promptElement.innerHTML = escapeHTML(prompt);
833
+ };
834
+
835
+ REPL.prototype.getInput = function() {
836
+ return this.inputElement.value;
837
+ };
838
+
839
+ REPL.prototype.setInput = function(text) {
840
+ this.inputElement.value = text;
841
+
842
+ if(this.inputElement.setSelectionRange) {
843
+ // set cursor to end of input
844
+ this.inputElement.setSelectionRange(text.length, text.length);
845
+ }
846
+ };
847
+
848
+ REPL.prototype.writeRawOutput = function(output) {
849
+ this.outputElement.innerHTML += output;
850
+ this.outputElement.scrollTop = this.outputElement.scrollHeight;
851
+ };
852
+
853
+ REPL.prototype.writeOutput = function(output) {
854
+ this.writeRawOutput(escapeHTML(output));
855
+ };
856
+
857
+ REPL.prototype.sendInput = function(line) {
858
+ var self = this;
859
+ apiCall("eval", { "index": this.index, source: line }, function(response) {
860
+ if(response.error) {
861
+ self.writeOutput(response.error + "\n");
862
+ }
863
+ self.writeOutput(self._prompt + " ");
864
+ self.writeRawOutput(response.highlighted_input + "\n");
865
+ self.writeOutput(response.result);
866
+ self.setPrompt(response.prompt);
867
+ self.setInput(response.prefilled_input);
868
+ });
869
+ };
870
+
871
+ REPL.prototype.onEnterKey = function() {
872
+ var text = this.getInput();
873
+ if(text != "" && text !== undefined) {
874
+ var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands"));
875
+ this.previousCommandOffset = previousCommands.push(text);
876
+ if(previousCommands.length > 100) {
877
+ previousCommands.splice(0, 1);
878
+ this.previousCommandOffset -= 1;
879
+ }
880
+ localStorage.setItem("better_errors_previous_commands", JSON.stringify(previousCommands));
881
+ }
882
+ this.setInput("");
883
+ this.sendInput(text);
884
+ };
885
+
886
+ REPL.prototype.onNavigateHistory = function(direction) {
887
+ this.previousCommandOffset += direction;
888
+ var previousCommands = JSON.parse(localStorage.getItem("better_errors_previous_commands"));
889
+
890
+ if(this.previousCommandOffset < 0) {
891
+ this.previousCommandOffset = -1;
892
+ this.setInput("");
893
+ return;
894
+ }
895
+
896
+ if(this.previousCommandOffset >= previousCommands.length) {
897
+ this.previousCommandOffset = previousCommands.length;
898
+ this.setInput("");
899
+ return;
900
+ }
901
+
902
+ this.setInput(previousCommands[this.previousCommandOffset]);
903
+ };
904
+
905
+ REPL.prototype.onKeyDown = function(ev) {
906
+ if(ev.keyCode == 13) {
907
+ this.onEnterKey();
908
+ } else if(ev.keyCode == 38 || (ev.ctrlKey && ev.keyCode == 80)) {
909
+ // the user pressed the up arrow or Ctrl-P
910
+ this.onNavigateHistory(-1);
911
+ ev.preventDefault();
912
+ return false;
913
+ } else if(ev.keyCode == 40 || (ev.ctrlKey && ev.keyCode == 78)) {
914
+ // the user pressed the down arrow or Ctrl-N
915
+ this.onNavigateHistory(1);
916
+ ev.preventDefault();
917
+ return false;
918
+ }
919
+ };
920
+
921
+ function switchTo(el) {
922
+ if(previousFrameInfo) previousFrameInfo.style.display = "none";
923
+ previousFrameInfo = el;
924
+
925
+ el.style.display = "block";
926
+
927
+ var replInput = el.querySelector('.console input');
928
+ if (replInput) replInput.focus();
929
+ }
930
+
931
+ function selectFrameInfo(index) {
932
+ var el = allFrameInfos[index];
933
+ if(el) {
934
+ if (el.loaded) {
935
+ return switchTo(el);
936
+ }
937
+
938
+ apiCall("variables", { "index": index }, function(response) {
939
+ el.loaded = true;
940
+ if(response.error) {
941
+ el.innerHTML = "<span class='error'>" + escapeHTML(response.error) + "</span>";
942
+ } else {
943
+ el.innerHTML = response.html;
944
+
945
+ var repl = el.querySelector(".repl .console");
946
+ if(repl) {
947
+ new REPL(index).install(repl);
948
+ }
949
+
950
+ switchTo(el);
951
+ }
952
+ });
953
+ }
954
+ }
955
+
956
+ for(var i = 0; i < allFrames.length; i++) {
957
+ (function(i, el) {
958
+ var el = allFrames[i];
959
+ el.onclick = function() {
960
+ if(previousFrame) {
961
+ previousFrame.className = "";
962
+ }
963
+ el.className = "selected";
964
+ previousFrame = el;
965
+
966
+ selectFrameInfo(el.attributes["data-index"].value);
967
+ };
968
+ })(i);
969
+ }
970
+
971
+ // Click the first application frame
972
+ (
973
+ document.querySelector(".frames li.application") ||
974
+ document.querySelector(".frames li")
975
+ ).onclick();
976
+
977
+ // This is the second query performed for frames; maybe the 'allFrames' list
978
+ // currently used and this list can be better used to avoid the repetition:
979
+ var applicationFramesCount = document.querySelectorAll(
980
+ "ul.frames li[data-context=application]"
981
+ ).length;
982
+
983
+ var applicationFramesButtonIsInstalled = false;
984
+ var applicationFramesButton = document.getElementById("application_frames");
985
+ var allFramesButton = document.getElementById("all_frames");
986
+
987
+ // The application frames button only needs to be bound if
988
+ // there are actually any application frames to look at.
989
+ var installApplicationFramesButton = function() {
990
+ applicationFramesButton.onclick = function() {
991
+ allFramesButton.className = "";
992
+ applicationFramesButton.className = "selected";
993
+ for(var i = 0; i < allFrames.length; i++) {
994
+ if(allFrames[i].attributes["data-context"].value == "application") {
995
+ allFrames[i].style.display = "block";
996
+ } else {
997
+ allFrames[i].style.display = "none";
998
+ }
999
+ }
1000
+ return false;
1001
+ };
1002
+
1003
+ applicationFramesButtonIsInstalled = true;
1004
+ }
1005
+
1006
+ allFramesButton.onclick = function() {
1007
+ if(applicationFramesButtonIsInstalled) {
1008
+ applicationFramesButton.className = "";
1009
+ }
1010
+
1011
+ allFramesButton.className = "selected";
1012
+ for(var i = 0; i < allFrames.length; i++) {
1013
+ allFrames[i].style.display = "block";
1014
+ }
1015
+ return false;
1016
+ };
1017
+
1018
+ // If there are no application frames, select the 'All Frames'
1019
+ // tab by default.
1020
+ if(applicationFramesCount > 0) {
1021
+ installApplicationFramesButton();
1022
+ applicationFramesButton.onclick();
1023
+ } else {
1024
+ applicationFramesButton.className = "disabled";
1025
+ applicationFramesButton.title = "No application frames available";
1026
+ allFramesButton.onclick();
1027
+ }
1028
+ })();
1029
+ </script>
1030
+ </html>
1031
+
1032
+ <!-- generated by Better Errors in <%= Time.now.to_f - @start_time %> seconds -->