better_errors 2.4.0 → 2.9.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 (42) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +130 -0
  3. data/.github/workflows/release.yml +64 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +1 -1
  6. data/Gemfile +6 -0
  7. data/README.md +40 -8
  8. data/better_errors.gemspec +13 -2
  9. data/gemfiles/pry010.gemfile +2 -1
  10. data/gemfiles/pry011.gemfile +2 -1
  11. data/gemfiles/pry09.gemfile +2 -1
  12. data/gemfiles/rack.gemfile +2 -1
  13. data/gemfiles/rack_boc.gemfile +2 -1
  14. data/gemfiles/rails42.gemfile +3 -1
  15. data/gemfiles/rails42_boc.gemfile +3 -1
  16. data/gemfiles/rails42_haml.gemfile +3 -1
  17. data/gemfiles/rails50.gemfile +3 -1
  18. data/gemfiles/rails50_boc.gemfile +3 -1
  19. data/gemfiles/rails50_haml.gemfile +3 -1
  20. data/gemfiles/rails51.gemfile +3 -1
  21. data/gemfiles/rails51_boc.gemfile +3 -1
  22. data/gemfiles/rails51_haml.gemfile +3 -1
  23. data/gemfiles/rails52.gemfile +9 -0
  24. data/gemfiles/rails52_boc.gemfile +10 -0
  25. data/gemfiles/rails52_haml.gemfile +10 -0
  26. data/gemfiles/rails60.gemfile +8 -0
  27. data/gemfiles/rails60_boc.gemfile +9 -0
  28. data/gemfiles/rails60_haml.gemfile +9 -0
  29. data/lib/better_errors.rb +23 -32
  30. data/lib/better_errors/editor.rb +99 -0
  31. data/lib/better_errors/error_page.rb +37 -24
  32. data/lib/better_errors/exception_hint.rb +29 -0
  33. data/lib/better_errors/inspectable_value.rb +45 -0
  34. data/lib/better_errors/middleware.rb +59 -12
  35. data/lib/better_errors/raised_exception.rb +25 -4
  36. data/lib/better_errors/stack_frame.rb +25 -7
  37. data/lib/better_errors/templates/main.erb +78 -24
  38. data/lib/better_errors/templates/text.erb +5 -2
  39. data/lib/better_errors/templates/variable_info.erb +9 -2
  40. data/lib/better_errors/version.rb +1 -1
  41. metadata +37 -10
  42. data/.travis.yml +0 -68
@@ -1,7 +1,9 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "rails", "~> 5.0.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
4
5
 
5
- gem 'coveralls', require: false
6
+ gem 'simplecov', require: false
7
+ gem 'simplecov-lcov', require: false
6
8
 
7
9
  gemspec path: "../"
@@ -1,8 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "rails", "~> 5.0.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
4
5
  gem "binding_of_caller"
5
6
 
6
- gem 'coveralls', require: false
7
+ gem 'simplecov', require: false
8
+ gem 'simplecov-lcov', require: false
7
9
 
8
10
  gemspec path: "../"
@@ -1,8 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "rails", "~> 5.0.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
4
5
  gem "haml"
5
6
 
6
- gem 'coveralls', require: false
7
+ gem 'simplecov', require: false
8
+ gem 'simplecov-lcov', require: false
7
9
 
8
10
  gemspec path: "../"
@@ -1,7 +1,9 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "rails", "~> 5.1.0"
4
+ gem 'i18n', '< 1.5.2', require: false if RUBY_VERSION < '2.3'
4
5
 
5
- gem 'coveralls', require: false
6
+ gem 'simplecov', require: false
7
+ gem 'simplecov-lcov', require: false
6
8
 
7
9
  gemspec path: "../"
@@ -1,8 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "rails", "~> 5.1.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
4
5
  gem "binding_of_caller"
5
6
 
6
- gem 'coveralls', require: false
7
+ gem 'simplecov', require: false
8
+ gem 'simplecov-lcov', require: false
7
9
 
8
10
  gemspec path: "../"
@@ -1,8 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "rails", "~> 5.1.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
4
5
  gem "haml"
5
6
 
6
- gem 'coveralls', require: false
7
+ gem 'simplecov', require: false
8
+ gem 'simplecov-lcov', require: false
7
9
 
8
10
  gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 5.2.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
5
+
6
+ gem 'simplecov', require: false
7
+ gem 'simplecov-lcov', require: false
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 5.2.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
5
+ gem "binding_of_caller"
6
+
7
+ gem 'simplecov', require: false
8
+ gem 'simplecov-lcov', require: false
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 5.2.0"
4
+ gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
5
+ gem "haml"
6
+
7
+ gem 'simplecov', require: false
8
+ gem 'simplecov-lcov', require: false
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 6.0.0"
4
+
5
+ gem 'simplecov', require: false
6
+ gem 'simplecov-lcov', require: false
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 6.0.0"
4
+ gem "binding_of_caller"
5
+
6
+ gem 'simplecov', require: false
7
+ gem 'simplecov-lcov', require: false
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 6.0.0"
4
+ gem "haml"
5
+
6
+ gem 'simplecov', require: false
7
+ gem 'simplecov-lcov', require: false
8
+
9
+ gemspec path: "../"
data/lib/better_errors.rb CHANGED
@@ -3,24 +3,17 @@ require "erubi"
3
3
  require "coderay"
4
4
  require "uri"
5
5
 
6
+ require "better_errors/version"
6
7
  require "better_errors/code_formatter"
8
+ require "better_errors/inspectable_value"
7
9
  require "better_errors/error_page"
8
10
  require "better_errors/middleware"
9
11
  require "better_errors/raised_exception"
10
12
  require "better_errors/repl"
11
13
  require "better_errors/stack_frame"
12
- require "better_errors/version"
14
+ require "better_errors/editor"
13
15
 
14
16
  module BetterErrors
15
- POSSIBLE_EDITOR_PRESETS = [
16
- { symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
17
- { symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
18
- { symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
19
- { symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
20
- { symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
21
- { symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
22
- ]
23
-
24
17
  class << self
25
18
  # The path to the root of the application. Better Errors uses this property
26
19
  # to determine if a file in a backtrace should be considered an application
@@ -51,21 +44,27 @@ module BetterErrors
51
44
  # the variable won't be returned.
52
45
  # @return int
53
46
  attr_accessor :maximum_variable_inspect_size
47
+
48
+ # List of classes that are excluded from inspection.
49
+ # @return [Array]
50
+ attr_accessor :ignored_classes
54
51
  end
55
52
  @ignored_instance_variables = []
56
53
  @maximum_variable_inspect_size = 100_000
54
+ @ignored_classes = ['ActionDispatch::Request', 'ActionDispatch::Response']
57
55
 
58
- # Returns a proc, which when called with a filename and line number argument,
56
+ # Returns an object which responds to #url, which when called with
57
+ # a filename and line number argument,
59
58
  # returns a URL to open the filename and line in the selected editor.
60
59
  #
61
60
  # Generates TextMate URLs by default.
62
61
  #
63
- # BetterErrors.editor["/some/file", 123]
62
+ # BetterErrors.editor.url("/some/file", 123)
64
63
  # # => txmt://open?url=file:///some/file&line=123
65
64
  #
66
65
  # @return [Proc]
67
66
  def self.editor
68
- @editor
67
+ @editor ||= default_editor
69
68
  end
70
69
 
71
70
  # Configures how Better Errors generates open-in-editor URLs.
@@ -76,6 +75,7 @@ module BetterErrors
76
75
  # * `:textmate`, `:txmt`, `:tm`
77
76
  # * `:sublime`, `:subl`, `:st`
78
77
  # * `:macvim`
78
+ # * `:atom`
79
79
  #
80
80
  # @param [Symbol] sym
81
81
  #
@@ -105,27 +105,22 @@ module BetterErrors
105
105
  # @param [Proc] proc
106
106
  #
107
107
  def self.editor=(editor)
108
- POSSIBLE_EDITOR_PRESETS.each do |config|
109
- if config[:symbols].include?(editor)
110
- return self.editor = config[:url]
111
- end
112
- end
113
-
114
- if editor.is_a? String
115
- self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
108
+ if editor.is_a? Symbol
109
+ @editor = Editor.editor_from_symbol(editor)
110
+ raise(ArgumentError, "Symbol #{editor} is not a symbol in the list of supported errors.") unless editor
111
+ elsif editor.is_a? String
112
+ @editor = Editor.for_formatting_string(editor)
113
+ elsif editor.respond_to? :call
114
+ @editor = Editor.for_proc(editor)
116
115
  else
117
- if editor.respond_to? :call
118
- @editor = editor
119
- else
120
- raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
121
- end
116
+ raise ArgumentError, "Expected editor to be a valid editor key, a format string or a callable."
122
117
  end
123
118
  end
124
119
 
125
120
  # Enables experimental Pry support in the inline REPL
126
121
  #
127
122
  # If you encounter problems while using Pry, *please* file a bug report at
128
- # https://github.com/charliesome/better_errors/issues
123
+ # https://github.com/BetterErrors/better_errors/issues
129
124
  def self.use_pry!
130
125
  REPL::PROVIDERS.unshift const: :Pry, impl: "better_errors/repl/pry"
131
126
  end
@@ -135,12 +130,8 @@ module BetterErrors
135
130
  #
136
131
  # @return [Symbol]
137
132
  def self.default_editor
138
- POSSIBLE_EDITOR_PRESETS.detect(-> { {} }) { |config|
139
- ENV["EDITOR"] =~ config[:sniff]
140
- }[:url] || :textmate
133
+ Editor.default_editor
141
134
  end
142
-
143
- BetterErrors.editor = default_editor
144
135
  end
145
136
 
146
137
  begin
@@ -0,0 +1,99 @@
1
+ require "uri"
2
+
3
+ module BetterErrors
4
+ class Editor
5
+ KNOWN_EDITORS = [
6
+ { symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
7
+ { symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
8
+ { symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
9
+ { symbols: [:macvim, :mvim], sniff: /vim/i, url: "mvim://open?url=file://%{file_unencoded}&line=%{line}" },
10
+ { symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
11
+ { symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
12
+ { symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
13
+ { symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
14
+ { symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
15
+ ]
16
+
17
+ def self.for_formatting_string(formatting_string)
18
+ new proc { |file, line|
19
+ formatting_string % { file: URI.encode_www_form_component(file), file_unencoded: file, line: line }
20
+ }
21
+ end
22
+
23
+ def self.for_proc(url_proc)
24
+ new url_proc
25
+ end
26
+
27
+ # Automatically sniffs a default editor preset based on
28
+ # environment variables.
29
+ #
30
+ # @return [Symbol]
31
+ def self.default_editor
32
+ editor_from_environment_formatting_string ||
33
+ editor_from_environment_editor ||
34
+ editor_from_symbol(:textmate)
35
+ end
36
+
37
+ def self.editor_from_environment_editor
38
+ if ENV["BETTER_ERRORS_EDITOR"]
39
+ editor = editor_from_command(ENV["BETTER_ERRORS_EDITOR"])
40
+ return editor if editor
41
+ puts "BETTER_ERRORS_EDITOR environment variable is not recognized as a supported Better Errors editor."
42
+ end
43
+ if ENV["EDITOR"]
44
+ editor = editor_from_command(ENV["EDITOR"])
45
+ return editor if editor
46
+ puts "EDITOR environment variable is not recognized as a supported Better Errors editor. Using TextMate by default."
47
+ else
48
+ puts "Since there is no EDITOR or BETTER_ERRORS_EDITOR environment variable, using Textmate by default."
49
+ end
50
+ end
51
+
52
+ def self.editor_from_command(editor_command)
53
+ env_preset = KNOWN_EDITORS.find { |preset| editor_command =~ preset[:sniff] }
54
+ for_formatting_string(env_preset[:url]) if env_preset
55
+ end
56
+
57
+ def self.editor_from_environment_formatting_string
58
+ return unless ENV['BETTER_ERRORS_EDITOR_URL']
59
+
60
+ for_formatting_string(ENV['BETTER_ERRORS_EDITOR_URL'])
61
+ end
62
+
63
+ def self.editor_from_symbol(symbol)
64
+ KNOWN_EDITORS.each do |preset|
65
+ return for_formatting_string(preset[:url]) if preset[:symbols].include?(symbol)
66
+ end
67
+ end
68
+
69
+ def initialize(url_proc)
70
+ @url_proc = url_proc
71
+ end
72
+
73
+ def url(raw_path, line)
74
+ if virtual_path && raw_path.start_with?(virtual_path)
75
+ if host_path
76
+ file = raw_path.sub(%r{\A#{virtual_path}}, host_path)
77
+ else
78
+ file = raw_path.sub(%r{\A#{virtual_path}/}, '')
79
+ end
80
+ else
81
+ file = raw_path
82
+ end
83
+
84
+ url_proc.call(file, line)
85
+ end
86
+
87
+ private
88
+
89
+ attr_reader :url_proc
90
+
91
+ def virtual_path
92
+ @virtual_path ||= ENV['BETTER_ERRORS_VIRTUAL_PATH']
93
+ end
94
+
95
+ def host_path
96
+ @host_path ||= ENV['BETTER_ERRORS_HOST_PATH']
97
+ end
98
+ end
99
+ end
@@ -26,8 +26,13 @@ module BetterErrors
26
26
  @id ||= SecureRandom.hex(8)
27
27
  end
28
28
 
29
- def render(template_name = "main")
29
+ def render(template_name = "main", csrf_token = nil)
30
30
  binding.eval(self.class.template(template_name).src)
31
+ rescue => e
32
+ # Fix the backtrace, which doesn't identify the template that failed (within Better Errors).
33
+ # We don't know the line number, so just injecting the template path has to be enough.
34
+ e.backtrace.unshift "#{self.class.template_path(template_name)}:0"
35
+ raise
31
36
  end
32
37
 
33
38
  def do_variables(opts)
@@ -59,7 +64,23 @@ module BetterErrors
59
64
  end
60
65
 
61
66
  def exception_message
62
- exception.message.lstrip
67
+ exception.message.strip.gsub(/(\r?\n\s*\r?\n)+/, "\n")
68
+ end
69
+
70
+ def exception_hint
71
+ exception.hint
72
+ end
73
+
74
+ def active_support_actions
75
+ return [] unless defined?(ActiveSupport::ActionableError)
76
+
77
+ ActiveSupport::ActionableError.actions(exception.type)
78
+ end
79
+
80
+ def action_dispatch_action_endpoint
81
+ return unless defined?(ActionDispatch::ActionableExceptions)
82
+
83
+ ActionDispatch::ActionableExceptions.endpoint
63
84
  end
64
85
 
65
86
  def application_frames
@@ -70,9 +91,10 @@ module BetterErrors
70
91
  application_frames.first || backtrace_frames.first
71
92
  end
72
93
 
73
- private
94
+ private
95
+
74
96
  def editor_url(frame)
75
- BetterErrors.editor[frame.filename, frame.line]
97
+ BetterErrors.editor.url(frame.filename, frame.line)
76
98
  end
77
99
 
78
100
  def rack_session
@@ -104,28 +126,19 @@ module BetterErrors
104
126
  end
105
127
 
106
128
  def inspect_value(obj)
107
- inspect_raw_value(obj)
108
- rescue NoMethodError
109
- "<span class='unsupported'>(object doesn't support inspect)</span>"
110
- rescue Exception => e
111
- "<span class='unsupported'>(exception #{CGI.escapeHTML(e.class.to_s)} was raised in inspect)</span>"
112
- end
113
-
114
- def inspect_raw_value(obj)
115
- value = CGI.escapeHTML(obj.inspect)
116
-
117
- if value_small_enough_to_inspect?(value)
118
- value
129
+ if BetterErrors.ignored_classes.include? obj.class.name
130
+ "<span class='unsupported'>(Instance of ignored class. "\
131
+ "#{obj.class.name ? "Remove #{CGI.escapeHTML(obj.class.name)} from" : "Modify"}"\
132
+ " BetterErrors.ignored_classes if you need to see it.)</span>"
119
133
  else
120
- "<span class='unsupported'>(object too large. "\
121
- "Modify #{CGI.escapeHTML(obj.class.to_s)}#inspect "\
122
- "or increase BetterErrors.maximum_variable_inspect_size)</span>"
134
+ InspectableValue.new(obj).to_html
123
135
  end
124
- end
125
-
126
- def value_small_enough_to_inspect?(value)
127
- return true if BetterErrors.maximum_variable_inspect_size.nil?
128
- value.length <= BetterErrors.maximum_variable_inspect_size
136
+ rescue BetterErrors::ValueLargerThanConfiguredMaximum
137
+ "<span class='unsupported'>(Object too large. "\
138
+ "#{obj.class.name ? "Modify #{CGI.escapeHTML(obj.class.name)}#inspect or a" : "A"}"\
139
+ "djust BetterErrors.maximum_variable_inspect_size if you need to see it.)</span>"
140
+ rescue Exception => e
141
+ "<span class='unsupported'>(exception #{CGI.escapeHTML(e.class.to_s)} was raised in inspect)</span>"
129
142
  end
130
143
 
131
144
  def eval_and_respond(index, code)