better_errors 1.1.0 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe0694430a61558b1ad22a156922e9a162b02352
4
- data.tar.gz: 6804d151297037d1eb778143d3eb807fe7573e9d
3
+ metadata.gz: 5ae01bfc76b45f6af1df26c7268229088f8de251
4
+ data.tar.gz: f95f617f84e01f1641bf193d735d45d85360b768
5
5
  SHA512:
6
- metadata.gz: 3d0c58b00e4b6965e8dfd3ae3ded8a9efbfde40f8878854cf3ed86b46a5b344e647a73e24f541de858ea1f9f1b6b6c5b29145896b73870ac3eb34219edafb9db
7
- data.tar.gz: 5fc01d0bd170e712c28d0efde81fa177eff86b6b36862bef1217749e1b39778c6e97e1899bed429f66dfaecfee45de843328935fe746402508892657232cf7aa
6
+ metadata.gz: 4881b5a147d85c9c7ed116a8838e45745bd95958df71dea8934e0b5601fd32cde2a016bbc419a855f9e29dd8e6bab322fcae8d676a78ede120d134809dac29b7
7
+ data.tar.gz: 6fb75468216fc264250e8f6726d47689025352441701cb83b2c125b541ac389c491da6ba8b3eeded2759b99be47f5c0cd103b54315122d7a33905261d02d4cca
data/.gitignore CHANGED
@@ -1,6 +1,8 @@
1
1
  *.gem
2
- tmp
3
- Gemfile.lock
4
- coverage
5
- .yardoc
6
- doc
2
+ /tmp
3
+ /Gemfile.lock
4
+ /coverage
5
+ /.yardoc
6
+ /doc
7
+ /.bundle
8
+ /vendor/gems
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
+ sudo: false
1
2
  language: ruby
3
+ cache: bundler
2
4
  rvm:
5
+ - 2.1.0
3
6
  - 2.0.0
4
- - 1.9.3
5
- - 1.9.2
6
- - jruby-1.7.4
data/Gemfile CHANGED
@@ -3,9 +3,9 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem "rake"
6
+ gem "rack"
6
7
  gem "rspec", "2.14.1"
7
8
  gem "binding_of_caller", platforms: :ruby
8
9
  gem "pry", "0.9.12"
9
- gem "simplecov"
10
10
  gem "yard"
11
11
  gem "kramdown"
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Charlie Somerville
1
+ Copyright (c) 2012-2015 Charlie Somerville
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Better Errors [![Build Status](https://travis-ci.org/charliesome/better_errors.png)](https://travis-ci.org/charliesome/better_errors) [![Code Climate](https://codeclimate.com/github/charliesome/better_errors.png)](https://codeclimate.com/github/charliesome/better_errors)
1
+ # Better Errors [![Gem Version](https://img.shields.io/gem/v/better_errors.svg)](https://rubygems.org/gems/better_errors) [![Build Status](https://travis-ci.org/charliesome/better_errors.svg)](https://travis-ci.org/charliesome/better_errors) [![Code Climate](https://img.shields.io/codeclimate/github/charliesome/better_errors.svg)](https://codeclimate.com/github/charliesome/better_errors)
2
2
 
3
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
- ![image](http://i.imgur.com/6zBGAAb.png)
5
+ ![image](https://i.imgur.com/6zBGAAb.png)
6
6
 
7
7
  ## Features
8
8
 
@@ -21,7 +21,7 @@ group :development do
21
21
  end
22
22
  ```
23
23
 
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 by [@banisterfiend](http://twitter.com/banisterfiend) to your Gemfile:
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 by [@banisterfiend](https://twitter.com/banisterfiend) to your Gemfile:
25
25
 
26
26
  ```ruby
27
27
  gem "binding_of_caller"
@@ -29,6 +29,8 @@ gem "binding_of_caller"
29
29
 
30
30
  This is an optional dependency however, and Better Errors will work without it.
31
31
 
32
+ _Note: If you discover that Better Errors isn't working - particularly after upgrading from version 0.5.0 or less - be sure to set `config.consider_all_requests_local = true` in `config/environments/development.rb`._
33
+
32
34
  ## Security
33
35
 
34
36
  **NOTE:** It is *critical* you put better\_errors in the **development** section. **Do NOT run better_errors in production, or on Internet facing hosts.**
@@ -75,6 +77,11 @@ get "/" do
75
77
  end
76
78
  ```
77
79
 
80
+ ### Plain text
81
+
82
+ Better Errors will render a plain text error page when the request is an
83
+ `XMLHttpRequest` or when the `Accept` header does *not* include 'html'.
84
+
78
85
  ### Unicorn, Puma, and other multi-worker servers
79
86
 
80
87
  Better Errors works by leaving a lot of context in server process memory. If
data/Rakefile CHANGED
@@ -1,4 +1,13 @@
1
1
  require "bundler/gem_tasks"
2
- task :default => :spec
3
- task :test => :spec
4
- task :spec do sh 'bundle exec rspec' end
2
+ require "rspec/core/rake_task"
3
+
4
+ namespace :test do
5
+ RSpec::Core::RakeTask.new(:with_binding_of_caller)
6
+
7
+ without_task = RSpec::Core::RakeTask.new(:without_binding_of_caller)
8
+ without_task.ruby_opts = "-I spec -r without_binding_of_caller"
9
+
10
+ task :all => [:with_binding_of_caller, :without_binding_of_caller]
11
+ end
12
+
13
+ task :default => "test:all"
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'better_errors/version'
@@ -17,10 +16,11 @@ Gem::Specification.new do |s|
17
16
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
17
  s.require_paths = ["lib"]
19
18
 
20
- s.required_ruby_version = ">= 1.9.2"
21
-
19
+ s.required_ruby_version = ">= 2.0.0"
20
+
22
21
  s.add_dependency "erubis", ">= 2.6.6"
23
22
  s.add_dependency "coderay", ">= 1.0.0"
23
+ s.add_dependency "rack", ">= 0.9.0"
24
24
 
25
25
  # optional dependencies:
26
26
  # s.add_dependency "binding_of_caller"
@@ -11,7 +11,7 @@ module BetterErrors
11
11
  sprintf '<pre class="%s">%s</pre>', class_name, str
12
12
  }
13
13
  end
14
-
14
+
15
15
  def formatted_nums
16
16
  each_line_of(highlighted_lines) { |highlight, current_line, str|
17
17
  class_name = highlight ? "highlight" : ""
@@ -11,9 +11,9 @@ module BetterErrors
11
11
  ".erb" => :erb,
12
12
  ".haml" => :haml
13
13
  }
14
-
14
+
15
15
  attr_reader :filename, :line, :context
16
-
16
+
17
17
  def initialize(filename, line, context = 5)
18
18
  @filename = filename
19
19
  @line = line
@@ -29,7 +29,7 @@ module BetterErrors
29
29
  def formatted_code
30
30
  formatted_lines.join
31
31
  end
32
-
32
+
33
33
  def coderay_scanner
34
34
  ext = File.extname(filename)
35
35
  FILE_TYPES[ext] || :text
@@ -40,20 +40,20 @@ module BetterErrors
40
40
  yield (current_line == line), current_line, str
41
41
  }
42
42
  end
43
-
43
+
44
44
  def highlighted_lines
45
45
  CodeRay.scan(context_lines.join, coderay_scanner).div(wrap: nil).lines
46
46
  end
47
-
47
+
48
48
  def context_lines
49
49
  range = line_range
50
50
  source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
51
51
  end
52
-
52
+
53
53
  def source_lines
54
54
  @source_lines ||= File.readlines(filename)
55
55
  end
56
-
56
+
57
57
  def line_range
58
58
  min = [line - context, 1].max
59
59
  max = [line + context, source_lines.count].min
@@ -1,5 +1,6 @@
1
1
  require "cgi"
2
2
  require "json"
3
+ require "securerandom"
3
4
 
4
5
  module BetterErrors
5
6
  # @private
@@ -7,42 +8,46 @@ module BetterErrors
7
8
  def self.template_path(template_name)
8
9
  File.expand_path("../templates/#{template_name}.erb", __FILE__)
9
10
  end
10
-
11
+
11
12
  def self.template(template_name)
12
13
  Erubis::EscapedEruby.new(File.read(template_path(template_name)))
13
14
  end
14
-
15
+
15
16
  attr_reader :exception, :env, :repls
16
-
17
+
17
18
  def initialize(exception, env)
18
- @exception = real_exception(exception)
19
+ @exception = RaisedException.new(exception)
19
20
  @env = env
20
21
  @start_time = Time.now.to_f
21
22
  @repls = []
22
23
  end
23
-
24
+
25
+ def id
26
+ @id ||= SecureRandom.hex(8)
27
+ end
28
+
24
29
  def render(template_name = "main")
25
30
  self.class.template(template_name).result binding
26
31
  end
27
-
32
+
28
33
  def do_variables(opts)
29
34
  index = opts["index"].to_i
30
35
  @frame = backtrace_frames[index]
31
36
  @var_start_time = Time.now.to_f
32
37
  { html: render("variable_info") }
33
38
  end
34
-
39
+
35
40
  def do_eval(opts)
36
41
  index = opts["index"].to_i
37
42
  code = opts["source"]
38
-
43
+
39
44
  unless binding = backtrace_frames[index].frame_binding
40
45
  return { error: "REPL unavailable in this stack frame" }
41
46
  end
42
-
47
+
43
48
  result, prompt, prefilled_input =
44
49
  (@repls[index] ||= REPL.provider.new(binding)).send_input(code)
45
-
50
+
46
51
  { result: result,
47
52
  prompt: prompt,
48
53
  prefilled_input: prefilled_input,
@@ -50,22 +55,22 @@ module BetterErrors
50
55
  end
51
56
 
52
57
  def backtrace_frames
53
- @backtrace_frames ||= StackFrame.from_exception(exception)
58
+ exception.backtrace
54
59
  end
55
60
 
56
61
  def application_frames
57
- backtrace_frames.select { |frame| frame.context == :application }
62
+ backtrace_frames.select(&:application?)
58
63
  end
59
64
 
60
65
  def first_frame
61
- backtrace_frames.detect { |frame| frame.context == :application } || backtrace_frames.first
66
+ application_frames.first || backtrace_frames.first
62
67
  end
63
-
68
+
64
69
  private
65
70
  def editor_url(frame)
66
71
  BetterErrors.editor[frame.filename, frame.line]
67
72
  end
68
-
73
+
69
74
  def rack_session
70
75
  env['rack.session']
71
76
  end
@@ -77,31 +82,15 @@ module BetterErrors
77
82
  def uri_prefix
78
83
  env["SCRIPT_NAME"] || ""
79
84
  end
80
-
81
- def exception_message
82
- if exception.is_a?(SyntaxError) && exception.message =~ /\A.*:\d*: (.*)$/
83
- $1
84
- else
85
- exception.message
86
- end
87
- end
88
85
 
89
- def real_exception(exception)
90
- if exception.respond_to?(:original_exception) && exception.original_exception.is_a?(Exception)
91
- exception.original_exception
92
- else
93
- exception
94
- end
95
- end
96
-
97
86
  def request_path
98
87
  env["PATH_INFO"]
99
88
  end
100
-
89
+
101
90
  def html_formatted_code_block(frame)
102
91
  CodeFormatter::HTML.new(frame.filename, frame.line).output
103
92
  end
104
-
93
+
105
94
  def text_formatted_code_block(frame)
106
95
  CodeFormatter::Text.new(frame.filename, frame.line).output
107
96
  end
@@ -114,7 +103,7 @@ module BetterErrors
114
103
  CGI.escapeHTML(obj.inspect)
115
104
  rescue NoMethodError
116
105
  "<span class='unsupported'>(object doesn't support inspect)</span>"
117
- rescue Exception => e
106
+ rescue Exception
118
107
  "<span class='unsupported'>(exception was raised in inspect)</span>"
119
108
  end
120
109
  end
@@ -0,0 +1,17 @@
1
+ module BetterErrors
2
+ module ExceptionExtension
3
+ prepend_features Exception
4
+
5
+ def set_backtrace(*)
6
+ if caller_locations.none? { |loc| loc.path == __FILE__ }
7
+ @__better_errors_bindings_stack = ::Kernel.binding.callers.drop(1)
8
+ end
9
+
10
+ super
11
+ end
12
+
13
+ def __better_errors_bindings_stack
14
+ @__better_errors_bindings_stack || []
15
+ end
16
+ end
17
+ end
@@ -1,6 +1,7 @@
1
1
  require "json"
2
2
  require "ipaddr"
3
3
  require "set"
4
+ require "rack"
4
5
 
5
6
  module BetterErrors
6
7
  # Better Errors' error handling middleware. Including this in your middleware
@@ -62,16 +63,15 @@ module BetterErrors
62
63
  private
63
64
 
64
65
  def allow_ip?(env)
65
- # REMOTE_ADDR is not in the rack spec, so some application servers do
66
- # not provide it.
67
- return true unless env["REMOTE_ADDR"] and !env["REMOTE_ADDR"].strip.empty?
68
- ip = IPAddr.new env["REMOTE_ADDR"].split("%").first
66
+ request = Rack::Request.new(env)
67
+ return true unless request.ip and !request.ip.strip.empty?
68
+ ip = IPAddr.new request.ip.split("%").first
69
69
  ALLOWED_IPS.any? { |subnet| subnet.include? ip }
70
70
  end
71
71
 
72
72
  def better_errors_call(env)
73
73
  case env["PATH_INFO"]
74
- when %r{/__better_errors/(?<oid>-?\d+)/(?<method>\w+)\z}
74
+ when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
75
75
  internal_call env, $~
76
76
  when %r{/__better_errors/?\z}
77
77
  show_error_page env
@@ -115,7 +115,7 @@ module BetterErrors
115
115
  def log_exception
116
116
  return unless BetterErrors.logger
117
117
 
118
- message = "\n#{@error_page.exception.class} - #{@error_page.exception.message}:\n"
118
+ message = "\n#{@error_page.exception.type} - #{@error_page.exception.message}:\n"
119
119
  @error_page.backtrace_frames.each do |frame|
120
120
  message << " #{frame}\n"
121
121
  end
@@ -124,7 +124,7 @@ module BetterErrors
124
124
  end
125
125
 
126
126
  def internal_call(env, opts)
127
- if opts[:oid].to_i != @error_page.object_id
127
+ if opts[:id] != @error_page.id
128
128
  return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]]
129
129
  end
130
130
 
@@ -0,0 +1,66 @@
1
+ # @private
2
+ module BetterErrors
3
+ class RaisedException
4
+ attr_reader :exception, :message, :backtrace
5
+
6
+ def initialize(exception)
7
+ if exception.respond_to?(:original_exception) && exception.original_exception
8
+ exception = exception.original_exception
9
+ end
10
+
11
+ @exception = exception
12
+ @message = exception.message
13
+
14
+ setup_backtrace
15
+ massage_syntax_error
16
+ end
17
+
18
+ def type
19
+ exception.class
20
+ end
21
+
22
+ private
23
+ def has_bindings?
24
+ exception.respond_to?(:__better_errors_bindings_stack) && exception.__better_errors_bindings_stack.any?
25
+ end
26
+
27
+ def setup_backtrace
28
+ if has_bindings?
29
+ setup_backtrace_from_bindings
30
+ else
31
+ setup_backtrace_from_backtrace
32
+ end
33
+ end
34
+
35
+ def setup_backtrace_from_bindings
36
+ @backtrace = exception.__better_errors_bindings_stack.map { |binding|
37
+ file = binding.eval "__FILE__"
38
+ line = binding.eval "__LINE__"
39
+ name = binding.frame_description
40
+ StackFrame.new(file, line, name, binding)
41
+ }
42
+ end
43
+
44
+ def setup_backtrace_from_backtrace
45
+ @backtrace = (exception.backtrace || []).map { |frame|
46
+ if /\A(?<file>.*?):(?<line>\d+)(:in `(?<name>.*)')?/ =~ frame
47
+ StackFrame.new(file, line.to_i, name)
48
+ end
49
+ }.compact
50
+ end
51
+
52
+ def massage_syntax_error
53
+ case exception.class.to_s
54
+ when "Haml::SyntaxError", "Sprockets::Coffeelint::Error"
55
+ if /\A(.+?):(\d+)/ =~ exception.backtrace.first
56
+ backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
57
+ end
58
+ when "SyntaxError"
59
+ if /\A(.+?):(\d+): (.*)/m =~ exception.message
60
+ backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
61
+ @message = $3
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -4,11 +4,11 @@ module BetterErrors
4
4
  def initialize(binding)
5
5
  @binding = binding
6
6
  end
7
-
7
+
8
8
  def send_input(str)
9
9
  [execute(str), ">>", ""]
10
10
  end
11
-
11
+
12
12
  private
13
13
  def execute(str)
14
14
  "=> #{@binding.eval(str).inspect}\n"
@@ -9,40 +9,40 @@ module BetterErrors
9
9
  Fiber.yield
10
10
  end
11
11
  end
12
-
12
+
13
13
  class Output
14
14
  def initialize
15
15
  @buffer = ""
16
16
  end
17
-
17
+
18
18
  def puts(*args)
19
19
  args.each do |arg|
20
20
  @buffer << "#{arg.chomp}\n"
21
21
  end
22
22
  end
23
-
23
+
24
24
  def tty?
25
25
  false
26
26
  end
27
-
27
+
28
28
  def read_buffer
29
29
  @buffer
30
30
  ensure
31
31
  @buffer = ""
32
32
  end
33
33
  end
34
-
34
+
35
35
  def initialize(binding)
36
36
  @fiber = Fiber.new do
37
37
  @pry.repl binding
38
38
  end
39
- @input = Input.new
40
- @output = Output.new
39
+ @input = BetterErrors::REPL::Pry::Input.new
40
+ @output = BetterErrors::REPL::Pry::Output.new
41
41
  @pry = ::Pry.new input: @input, output: @output
42
42
  @pry.hooks.clear_all if defined?(@pry.hooks.clear_all)
43
43
  @fiber.resume
44
44
  end
45
-
45
+
46
46
  def send_input(str)
47
47
  local ::Pry.config, color: false, pager: false do
48
48
  @fiber.resume "#{str}\n"
@@ -59,7 +59,7 @@ module BetterErrors
59
59
  rescue
60
60
  [">>", ""]
61
61
  end
62
-
62
+
63
63
  private
64
64
  def local(obj, attrs)
65
65
  old_attrs = {}
@@ -9,17 +9,17 @@ module BetterErrors
9
9
  def self.provider
10
10
  @provider ||= const_get detect[:const]
11
11
  end
12
-
12
+
13
13
  def self.provider=(prov)
14
14
  @provider = prov
15
15
  end
16
-
16
+
17
17
  def self.detect
18
18
  PROVIDERS.find { |prov|
19
19
  test_provider prov
20
20
  }
21
21
  end
22
-
22
+
23
23
  def self.test_provider(provider)
24
24
  require provider[:impl]
25
25
  true
@@ -4,49 +4,7 @@ module BetterErrors
4
4
  # @private
5
5
  class StackFrame
6
6
  def self.from_exception(exception)
7
- if has_binding_stack?(exception)
8
- list = exception.__better_errors_bindings_stack.map { |binding|
9
- file = binding.eval "__FILE__"
10
- line = binding.eval "__LINE__"
11
- name = binding.frame_description
12
- StackFrame.new(file, line, name, binding)
13
- }
14
- else
15
- list = (exception.backtrace || []).map { |frame|
16
- next unless md = /\A(?<file>.*?):(?<line>\d+)(:in `(?<name>.*)')?/.match(frame)
17
- StackFrame.new(md[:file], md[:line].to_i, md[:name])
18
- }.compact
19
- end
20
-
21
- if syntax_error?(exception)
22
- if trace = exception.backtrace and trace.first =~ /\A(.*):(\d+)/
23
- list.unshift StackFrame.new($1, $2.to_i, "")
24
- end
25
- end
26
-
27
- list
28
- end
29
-
30
- def self.syntax_error_classes
31
- # Better Errors may be loaded before some of the gems that provide these
32
- # classes, so we lazily set up the set of syntax error classes at runtime
33
- # after everything has hopefully had a chance to load.
34
- #
35
- @syntax_error_classes ||= begin
36
- class_names = %w[
37
- Haml::SyntaxError
38
- ]
39
-
40
- Set.new(class_names.map { |klass| eval(klass) rescue nil }.compact)
41
- end
42
- end
43
-
44
- def self.syntax_error?(exception)
45
- exception.is_a?(SyntaxError) || syntax_error_classes.include?(exception.class)
46
- end
47
-
48
- def self.has_binding_stack?(exception)
49
- exception.respond_to?(:__better_errors_bindings_stack) && exception.__better_errors_bindings_stack.any?
7
+ RaisedException.new(exception).backtrace
50
8
  end
51
9
 
52
10
  attr_reader :filename, :line, :name, :frame_binding
@@ -75,7 +33,7 @@ module BetterErrors
75
33
  end
76
34
 
77
35
  def gem_path
78
- if path = Gem.path.detect { |path| filename.index(path) == 0 }
36
+ if path = Gem.path.detect { |p| filename.index(p) == 0 }
79
37
  gem_name_and_version, path = filename.sub("#{path}/gems/", "").split("/", 2)
80
38
  /(?<gem_name>.+)-(?<gem_version>[\w.]+)/ =~ gem_name_and_version
81
39
  "#{gem_name} (#{gem_version}) #{path}"
@@ -111,15 +69,10 @@ module BetterErrors
111
69
  def local_variables
112
70
  return {} unless frame_binding
113
71
  frame_binding.eval("local_variables").each_with_object({}) do |name, hash|
114
- begin
115
- if defined?(frame_binding.local_variable_get)
116
- hash[name] = frame_binding.local_variable_get(name)
117
- else
118
- hash[name] = frame_binding.eval(name.to_s)
119
- end
120
- rescue NameError => e
121
- # local_variables sometimes returns broken variables.
122
- # https://bugs.ruby-lang.org/issues/7536
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)
123
76
  end
124
77
  end
125
78
  end
@@ -141,14 +94,12 @@ module BetterErrors
141
94
 
142
95
  private
143
96
  def set_pretty_method_name
144
- return if RUBY_VERSION < "2.0.0"
145
-
146
97
  name =~ /\A(block (\([^)]+\) )?in )?/
147
98
  recv = frame_binding.eval("self")
148
99
 
149
100
  return unless method_name = frame_binding.eval("::Kernel.__method__")
150
101
 
151
- if Kernel.instance_method(:is_a?).bind(recv).call Module
102
+ if Module === recv
152
103
  @class_name = "#{$1}#{recv}"
153
104
  @method_name = ".#{method_name}"
154
105
  else