better_errors-creditkudos 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.
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,26 @@
1
+ module BetterErrors
2
+ # @private
3
+ class CodeFormatter::HTML < CodeFormatter
4
+ def source_unavailable
5
+ "<p class='unavailable'>Source is not available</p>"
6
+ end
7
+
8
+ def formatted_lines
9
+ each_line_of(highlighted_lines) { |highlight, current_line, str|
10
+ class_name = highlight ? "highlight" : ""
11
+ sprintf '<pre class="%s">%s</pre>', class_name, str
12
+ }
13
+ end
14
+
15
+ def formatted_nums
16
+ each_line_of(highlighted_lines) { |highlight, current_line, str|
17
+ class_name = highlight ? "highlight" : ""
18
+ sprintf '<span class="%s">%5d</span>', class_name, current_line
19
+ }
20
+ end
21
+
22
+ def formatted_code
23
+ %{<div class="code_linenums">#{formatted_nums.join}</div><div class="code">#{super}</div>}
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ module BetterErrors
2
+ # @private
3
+ class CodeFormatter::Text < CodeFormatter
4
+ def source_unavailable
5
+ "# Source is not available"
6
+ end
7
+
8
+ def formatted_lines
9
+ each_line_of(context_lines) { |highlight, current_line, str|
10
+ sprintf '%s %3d %s', (highlight ? '>' : ' '), current_line, str
11
+ }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,129 @@
1
+ require "cgi"
2
+ require "json"
3
+ require "securerandom"
4
+
5
+ module BetterErrors
6
+ # @private
7
+ class ErrorPage
8
+ def self.template_path(template_name)
9
+ File.expand_path("../templates/#{template_name}.erb", __FILE__)
10
+ end
11
+
12
+ def self.template(template_name)
13
+ Erubis::EscapedEruby.new(File.read(template_path(template_name)))
14
+ end
15
+
16
+ attr_reader :exception, :env, :repls
17
+
18
+ def initialize(exception, env)
19
+ @exception = RaisedException.new(exception)
20
+ @env = env
21
+ @start_time = Time.now.to_f
22
+ @repls = []
23
+ end
24
+
25
+ def id
26
+ @id ||= SecureRandom.hex(8)
27
+ end
28
+
29
+ def render(template_name = "main")
30
+ self.class.template(template_name).result binding
31
+ end
32
+
33
+ def do_variables(opts)
34
+ index = opts["index"].to_i
35
+ @frame = backtrace_frames[index]
36
+ @var_start_time = Time.now.to_f
37
+ { html: render("variable_info") }
38
+ end
39
+
40
+ def do_eval(opts)
41
+ index = opts["index"].to_i
42
+ code = opts["source"]
43
+
44
+ unless binding = backtrace_frames[index].frame_binding
45
+ return { error: "REPL unavailable in this stack frame" }
46
+ end
47
+
48
+ result, prompt, prefilled_input =
49
+ (@repls[index] ||= REPL.provider.new(binding)).send_input(code)
50
+
51
+ { result: result,
52
+ prompt: prompt,
53
+ prefilled_input: prefilled_input,
54
+ highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil) }
55
+ end
56
+
57
+ def backtrace_frames
58
+ exception.backtrace
59
+ end
60
+
61
+ def exception_type
62
+ exception.type
63
+ end
64
+
65
+ def exception_message
66
+ exception.message.lstrip
67
+ end
68
+
69
+ def application_frames
70
+ backtrace_frames.select(&:application?)
71
+ end
72
+
73
+ def first_frame
74
+ application_frames.first || backtrace_frames.first
75
+ end
76
+
77
+ private
78
+ def editor_url(frame)
79
+ BetterErrors.editor[frame.filename, frame.line]
80
+ end
81
+
82
+ def rack_session
83
+ env['rack.session']
84
+ end
85
+
86
+ def rails_params
87
+ env['action_dispatch.request.parameters']
88
+ end
89
+
90
+ def uri_prefix
91
+ env["SCRIPT_NAME"] || ""
92
+ end
93
+
94
+ def request_path
95
+ env["PATH_INFO"]
96
+ end
97
+
98
+ def html_formatted_code_block(frame)
99
+ CodeFormatter::HTML.new(frame.filename, frame.line).output
100
+ end
101
+
102
+ def text_formatted_code_block(frame)
103
+ CodeFormatter::Text.new(frame.filename, frame.line).output
104
+ end
105
+
106
+ def text_heading(char, str)
107
+ str + "\n" + char*str.size
108
+ end
109
+
110
+ def inspect_value(obj)
111
+ inspect_raw_value(obj)
112
+ rescue NoMethodError
113
+ "<span class='unsupported'>(object doesn't support inspect)</span>"
114
+ rescue Exception => e
115
+ binding.pry
116
+ "<span class='unsupported'>(exception #{CGI.escapeHTML(e.class.to_s)} was raised in inspect)</span>"
117
+ end
118
+
119
+ def inspect_raw_value(obj)
120
+ value = CGI.escapeHTML(obj.inspect)
121
+
122
+ if !BetterErrors.maximum_variable_inspect_size.nil? && value.length > BetterErrors.maximum_variable_inspect_size
123
+ value = "<span class='unsupported'>(object too large - modify #{CGI.escapeHTML(obj.class.to_s)}#inspect or raise BetterErrors.maximum_variable_inspect_size)</span>"
124
+ end
125
+
126
+ value
127
+ end
128
+ end
129
+ 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
@@ -0,0 +1,141 @@
1
+ require "json"
2
+ require "ipaddr"
3
+ require "set"
4
+ require "rack"
5
+
6
+ module BetterErrors
7
+ # Better Errors' error handling middleware. Including this in your middleware
8
+ # stack will show a Better Errors error page for exceptions raised below this
9
+ # middleware.
10
+ #
11
+ # If you are using Ruby on Rails, you do not need to manually insert this
12
+ # middleware into your middleware stack.
13
+ #
14
+ # @example Sinatra
15
+ # require "better_errors"
16
+ #
17
+ # if development?
18
+ # use BetterErrors::Middleware
19
+ # end
20
+ #
21
+ # @example Rack
22
+ # require "better_errors"
23
+ # if ENV["RACK_ENV"] == "development"
24
+ # use BetterErrors::Middleware
25
+ # end
26
+ #
27
+ class Middleware
28
+ # The set of IP addresses that are allowed to access Better Errors.
29
+ #
30
+ # Set to `{ "127.0.0.1/8", "::1/128" }` by default.
31
+ ALLOWED_IPS = Set.new
32
+
33
+ # Adds an address to the set of IP addresses allowed to access Better
34
+ # Errors.
35
+ def self.allow_ip!(addr)
36
+ ALLOWED_IPS << IPAddr.new(addr)
37
+ end
38
+
39
+ allow_ip! "127.0.0.0/8"
40
+ allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
41
+
42
+ # A new instance of BetterErrors::Middleware
43
+ #
44
+ # @param app The Rack app/middleware to wrap with Better Errors
45
+ # @param handler The error handler to use.
46
+ def initialize(app, handler = ErrorPage)
47
+ @app = app
48
+ @handler = handler
49
+ end
50
+
51
+ # Calls the Better Errors middleware
52
+ #
53
+ # @param [Hash] env
54
+ # @return [Array]
55
+ def call(env)
56
+ if allow_ip? env
57
+ better_errors_call env
58
+ else
59
+ @app.call env
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def allow_ip?(env)
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
+ ALLOWED_IPS.any? { |subnet| subnet.include? ip }
70
+ end
71
+
72
+ def better_errors_call(env)
73
+ case env["PATH_INFO"]
74
+ when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
75
+ internal_call env, $~
76
+ when %r{/__better_errors/?\z}
77
+ show_error_page env
78
+ else
79
+ protected_app_call env
80
+ end
81
+ end
82
+
83
+ def protected_app_call(env)
84
+ @app.call env
85
+ rescue Exception => ex
86
+ @error_page = @handler.new ex, env
87
+ log_exception
88
+ show_error_page(env, ex)
89
+ end
90
+
91
+ def show_error_page(env, exception=nil)
92
+ type, content = if @error_page
93
+ if text?(env)
94
+ [ 'plain', @error_page.render('text') ]
95
+ else
96
+ [ 'html', @error_page.render ]
97
+ end
98
+ else
99
+ [ 'html', no_errors_page ]
100
+ end
101
+
102
+ status_code = 500
103
+ if defined? ActionDispatch::ExceptionWrapper
104
+ status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
105
+ end
106
+
107
+ [status_code, { "Content-Type" => "text/#{type}; charset=utf-8" }, [content]]
108
+ end
109
+
110
+ def text?(env)
111
+ env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
112
+ !env["HTTP_ACCEPT"].to_s.include?('html')
113
+ end
114
+
115
+ def log_exception
116
+ return unless BetterErrors.logger
117
+
118
+ message = "\n#{@error_page.exception_type} - #{@error_page.exception_message}:\n"
119
+ @error_page.backtrace_frames.each do |frame|
120
+ message << " #{frame}\n"
121
+ end
122
+
123
+ BetterErrors.logger.fatal message
124
+ end
125
+
126
+ def internal_call(env, opts)
127
+ if opts[:id] != @error_page.id
128
+ return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]]
129
+ end
130
+
131
+ env["rack.input"].rewind
132
+ response = @error_page.send("do_#{opts[:method]}", JSON.parse(env["rack.input"].read))
133
+ [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
134
+ end
135
+
136
+ def no_errors_page
137
+ "<h1>No errors</h1><p>No errors have been recorded yet.</p><hr>" +
138
+ "<code>Better Errors v#{BetterErrors::VERSION}</code>"
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,28 @@
1
+ module BetterErrors
2
+ # @private
3
+ class Railtie < Rails::Railtie
4
+ initializer "better_errors.configure_rails_initialization" do
5
+ if use_better_errors?
6
+ insert_middleware
7
+ BetterErrors.logger = Rails.logger
8
+ BetterErrors.application_root = Rails.root.to_s
9
+ end
10
+ end
11
+
12
+ def insert_middleware
13
+ if defined? ActionDispatch::DebugExceptions
14
+ app.middleware.insert_after ActionDispatch::DebugExceptions, BetterErrors::Middleware
15
+ else
16
+ app.middleware.use BetterErrors::Middleware
17
+ end
18
+ end
19
+
20
+ def use_better_errors?
21
+ !Rails.env.production? and app.config.consider_all_requests_local
22
+ end
23
+
24
+ def app
25
+ Rails.application
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,68 @@
1
+ # @private
2
+ module BetterErrors
3
+ class RaisedException
4
+ attr_reader :exception, :message, :backtrace
5
+
6
+ def initialize(exception)
7
+ if exception.respond_to?(:cause)
8
+ exception = exception.cause if exception.cause
9
+ elsif exception.respond_to?(:original_exception) && exception.original_exception
10
+ exception = exception.original_exception
11
+ end
12
+
13
+ @exception = exception
14
+ @message = exception.message
15
+
16
+ setup_backtrace
17
+ massage_syntax_error
18
+ end
19
+
20
+ def type
21
+ exception.class
22
+ end
23
+
24
+ private
25
+ def has_bindings?
26
+ exception.respond_to?(:__better_errors_bindings_stack) && exception.__better_errors_bindings_stack.any?
27
+ end
28
+
29
+ def setup_backtrace
30
+ if has_bindings?
31
+ setup_backtrace_from_bindings
32
+ else
33
+ setup_backtrace_from_backtrace
34
+ end
35
+ end
36
+
37
+ def setup_backtrace_from_bindings
38
+ @backtrace = exception.__better_errors_bindings_stack.map { |binding|
39
+ file = binding.eval "__FILE__"
40
+ line = binding.eval "__LINE__"
41
+ name = binding.frame_description
42
+ StackFrame.new(file, line, name, binding)
43
+ }
44
+ end
45
+
46
+ def setup_backtrace_from_backtrace
47
+ @backtrace = (exception.backtrace || []).map { |frame|
48
+ if /\A(?<file>.*?):(?<line>\d+)(:in `(?<name>.*)')?/ =~ frame
49
+ StackFrame.new(file, line.to_i, name)
50
+ end
51
+ }.compact
52
+ end
53
+
54
+ def massage_syntax_error
55
+ case exception.class.to_s
56
+ when "Haml::SyntaxError", "Sprockets::Coffeelint::Error"
57
+ if /\A(.+?):(\d+)/ =~ exception.backtrace.first
58
+ backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
59
+ end
60
+ when "SyntaxError"
61
+ if /\A(.+?):(\d+): (.*)/m =~ exception.message
62
+ backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
63
+ @message = $3
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ module BetterErrors
2
+ # @private
3
+ module REPL
4
+ PROVIDERS = [
5
+ { impl: "better_errors/repl/basic",
6
+ const: :Basic },
7
+ ]
8
+
9
+ def self.provider
10
+ @provider ||= const_get detect[:const]
11
+ end
12
+
13
+ def self.provider=(prov)
14
+ @provider = prov
15
+ end
16
+
17
+ def self.detect
18
+ PROVIDERS.find { |prov|
19
+ test_provider prov
20
+ }
21
+ end
22
+
23
+ def self.test_provider(provider)
24
+ require provider[:impl]
25
+ true
26
+ rescue LoadError
27
+ false
28
+ end
29
+ end
30
+ end