better_errors 2.8.2 → 2.10.0.beta2
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 +4 -4
- data/.github/workflows/ci.yml +142 -0
- data/.github/workflows/release.yml +68 -0
- data/.gitignore +4 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -1
- data/README.md +2 -2
- data/better_errors.gemspec +5 -4
- data/gemfiles/pry010.gemfile +2 -1
- data/gemfiles/pry011.gemfile +2 -1
- data/gemfiles/pry09.gemfile +2 -1
- data/gemfiles/rack.gemfile +2 -1
- data/gemfiles/rack_boc.gemfile +2 -1
- data/gemfiles/rails42.gemfile +2 -1
- data/gemfiles/rails42_boc.gemfile +2 -1
- data/gemfiles/rails42_haml.gemfile +2 -1
- data/gemfiles/rails50.gemfile +2 -1
- data/gemfiles/rails50_boc.gemfile +2 -1
- data/gemfiles/rails50_haml.gemfile +2 -1
- data/gemfiles/rails51.gemfile +2 -1
- data/gemfiles/rails51_boc.gemfile +2 -1
- data/gemfiles/rails51_haml.gemfile +2 -1
- data/gemfiles/rails52.gemfile +2 -1
- data/gemfiles/rails52_boc.gemfile +2 -1
- data/gemfiles/rails52_haml.gemfile +2 -1
- data/gemfiles/rails60.gemfile +2 -1
- data/gemfiles/rails60_boc.gemfile +2 -1
- data/gemfiles/rails60_haml.gemfile +2 -1
- data/gemfiles/rails61.gemfile +8 -0
- data/gemfiles/rails61_boc.gemfile +9 -0
- data/gemfiles/rails61_haml.gemfile +9 -0
- data/lib/better_errors.rb +15 -35
- data/lib/better_errors/code_formatter.rb +16 -27
- data/lib/better_errors/code_formatter/html.rb +15 -1
- data/lib/better_errors/editor.rb +103 -0
- data/lib/better_errors/error_page.rb +33 -15
- data/lib/better_errors/error_page_style.rb +30 -0
- data/lib/better_errors/exception_hint.rb +29 -0
- data/lib/better_errors/middleware.rb +20 -4
- data/lib/better_errors/raised_exception.rb +8 -1
- data/lib/better_errors/templates/main.erb +80 -717
- data/lib/better_errors/templates/text.erb +6 -3
- data/lib/better_errors/templates/variable_info.erb +18 -15
- data/lib/better_errors/version.rb +2 -1
- metadata +30 -7
- data/.travis.yml +0 -111
data/gemfiles/rails60.gemfile
CHANGED
data/lib/better_errors.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require "pp"
|
2
2
|
require "erubi"
|
3
|
-
require "coderay"
|
4
3
|
require "uri"
|
5
4
|
|
5
|
+
require "better_errors/version"
|
6
6
|
require "better_errors/code_formatter"
|
7
7
|
require "better_errors/inspectable_value"
|
8
8
|
require "better_errors/error_page"
|
@@ -10,21 +10,9 @@ require "better_errors/middleware"
|
|
10
10
|
require "better_errors/raised_exception"
|
11
11
|
require "better_errors/repl"
|
12
12
|
require "better_errors/stack_frame"
|
13
|
-
require "better_errors/
|
13
|
+
require "better_errors/editor"
|
14
14
|
|
15
15
|
module BetterErrors
|
16
|
-
POSSIBLE_EDITOR_PRESETS = [
|
17
|
-
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
18
|
-
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
|
19
|
-
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
20
|
-
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
21
|
-
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
|
22
|
-
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
|
23
|
-
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
|
24
|
-
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
|
25
|
-
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
|
26
|
-
]
|
27
|
-
|
28
16
|
class << self
|
29
17
|
# The path to the root of the application. Better Errors uses this property
|
30
18
|
# to determine if a file in a backtrace should be considered an application
|
@@ -64,17 +52,18 @@ module BetterErrors
|
|
64
52
|
@maximum_variable_inspect_size = 100_000
|
65
53
|
@ignored_classes = ['ActionDispatch::Request', 'ActionDispatch::Response']
|
66
54
|
|
67
|
-
# Returns
|
55
|
+
# Returns an object which responds to #url, which when called with
|
56
|
+
# a filename and line number argument,
|
68
57
|
# returns a URL to open the filename and line in the selected editor.
|
69
58
|
#
|
70
59
|
# Generates TextMate URLs by default.
|
71
60
|
#
|
72
|
-
# BetterErrors.editor
|
61
|
+
# BetterErrors.editor.url("/some/file", 123)
|
73
62
|
# # => txmt://open?url=file:///some/file&line=123
|
74
63
|
#
|
75
64
|
# @return [Proc]
|
76
65
|
def self.editor
|
77
|
-
@editor
|
66
|
+
@editor ||= default_editor
|
78
67
|
end
|
79
68
|
|
80
69
|
# Configures how Better Errors generates open-in-editor URLs.
|
@@ -115,20 +104,15 @@ module BetterErrors
|
|
115
104
|
# @param [Proc] proc
|
116
105
|
#
|
117
106
|
def self.editor=(editor)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
|
107
|
+
if editor.is_a? Symbol
|
108
|
+
@editor = Editor.editor_from_symbol(editor)
|
109
|
+
raise(ArgumentError, "Symbol #{editor} is not a symbol in the list of supported errors.") unless editor
|
110
|
+
elsif editor.is_a? String
|
111
|
+
@editor = Editor.for_formatting_string(editor)
|
112
|
+
elsif editor.respond_to? :call
|
113
|
+
@editor = Editor.for_proc(editor)
|
126
114
|
else
|
127
|
-
|
128
|
-
@editor = editor
|
129
|
-
else
|
130
|
-
raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
|
131
|
-
end
|
115
|
+
raise ArgumentError, "Expected editor to be a valid editor key, a format string or a callable."
|
132
116
|
end
|
133
117
|
end
|
134
118
|
|
@@ -145,12 +129,8 @@ module BetterErrors
|
|
145
129
|
#
|
146
130
|
# @return [Symbol]
|
147
131
|
def self.default_editor
|
148
|
-
|
149
|
-
ENV["EDITOR"] =~ config[:sniff]
|
150
|
-
}[:url] || :textmate
|
132
|
+
Editor.default_editor
|
151
133
|
end
|
152
|
-
|
153
|
-
BetterErrors.editor = default_editor
|
154
134
|
end
|
155
135
|
|
156
136
|
begin
|
@@ -4,14 +4,6 @@ module BetterErrors
|
|
4
4
|
require "better_errors/code_formatter/html"
|
5
5
|
require "better_errors/code_formatter/text"
|
6
6
|
|
7
|
-
FILE_TYPES = {
|
8
|
-
".rb" => :ruby,
|
9
|
-
"" => :ruby,
|
10
|
-
".html" => :html,
|
11
|
-
".erb" => :erb,
|
12
|
-
".haml" => :haml
|
13
|
-
}
|
14
|
-
|
15
7
|
attr_reader :filename, :line, :context
|
16
8
|
|
17
9
|
def initialize(filename, line, context = 5)
|
@@ -26,13 +18,21 @@ module BetterErrors
|
|
26
18
|
source_unavailable
|
27
19
|
end
|
28
20
|
|
29
|
-
def
|
30
|
-
|
21
|
+
def line_range
|
22
|
+
min = [line - context, 1].max
|
23
|
+
max = [line + context, source_lines.count].min
|
24
|
+
min..max
|
31
25
|
end
|
32
26
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
27
|
+
def context_lines
|
28
|
+
range = line_range
|
29
|
+
source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def formatted_code
|
35
|
+
formatted_lines.join
|
36
36
|
end
|
37
37
|
|
38
38
|
def each_line_of(lines, &blk)
|
@@ -41,23 +41,12 @@ module BetterErrors
|
|
41
41
|
}
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
def context_lines
|
49
|
-
range = line_range
|
50
|
-
source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
|
44
|
+
def source
|
45
|
+
@source ||= File.read(filename)
|
51
46
|
end
|
52
47
|
|
53
48
|
def source_lines
|
54
|
-
@source_lines ||=
|
55
|
-
end
|
56
|
-
|
57
|
-
def line_range
|
58
|
-
min = [line - context, 1].max
|
59
|
-
max = [line + context, source_lines.count].min
|
60
|
-
min..max
|
49
|
+
@source_lines ||= source.lines
|
61
50
|
end
|
62
51
|
end
|
63
52
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "rouge"
|
2
|
+
|
1
3
|
module BetterErrors
|
2
4
|
# @private
|
3
5
|
class CodeFormatter::HTML < CodeFormatter
|
@@ -20,7 +22,19 @@ module BetterErrors
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def formatted_code
|
23
|
-
%{
|
25
|
+
%{
|
26
|
+
<div class="code_linenums">#{formatted_nums.join}</div>
|
27
|
+
<div class="code"><div class='code-wrapper'>#{super}</div></div>
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def rouge_lexer
|
32
|
+
Rouge::Lexer.guess(filename: filename, source: source) { Rouge::Lexers::Ruby }
|
24
33
|
end
|
34
|
+
|
35
|
+
def highlighted_lines
|
36
|
+
Rouge::Formatters::HTML.new.format(rouge_lexer.lex(context_lines.join)).lines
|
37
|
+
end
|
38
|
+
|
25
39
|
end
|
26
40
|
end
|
@@ -0,0 +1,103 @@
|
|
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
|
+
def scheme
|
88
|
+
url('/fake', 42).sub(/:.*/, ':')
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
attr_reader :url_proc
|
94
|
+
|
95
|
+
def virtual_path
|
96
|
+
@virtual_path ||= ENV['BETTER_ERRORS_VIRTUAL_PATH']
|
97
|
+
end
|
98
|
+
|
99
|
+
def host_path
|
100
|
+
@host_path ||= ENV['BETTER_ERRORS_HOST_PATH']
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
require "cgi"
|
2
2
|
require "json"
|
3
3
|
require "securerandom"
|
4
|
+
require "rouge"
|
5
|
+
require "better_errors/error_page_style"
|
4
6
|
|
5
7
|
module BetterErrors
|
6
8
|
# @private
|
7
9
|
class ErrorPage
|
10
|
+
VariableInfo = Struct.new(:frame, :editor_url, :rails_params, :rack_session, :start_time)
|
11
|
+
|
8
12
|
def self.template_path(template_name)
|
9
13
|
File.expand_path("../templates/#{template_name}.erb", __FILE__)
|
10
14
|
end
|
@@ -13,6 +17,15 @@ module BetterErrors
|
|
13
17
|
Erubi::Engine.new(File.read(template_path(template_name)), escape: true)
|
14
18
|
end
|
15
19
|
|
20
|
+
def self.render_template(template_name, locals)
|
21
|
+
locals.send(:eval, self.template(template_name).src)
|
22
|
+
rescue => e
|
23
|
+
# Fix the backtrace, which doesn't identify the template that failed (within Better Errors).
|
24
|
+
# We don't know the line number, so just injecting the template path has to be enough.
|
25
|
+
e.backtrace.unshift "#{self.template_path(template_name)}:0"
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
16
29
|
attr_reader :exception, :env, :repls
|
17
30
|
|
18
31
|
def initialize(exception, env)
|
@@ -26,20 +39,21 @@ module BetterErrors
|
|
26
39
|
@id ||= SecureRandom.hex(8)
|
27
40
|
end
|
28
41
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
42
|
+
def render_main(csrf_token, csp_nonce)
|
43
|
+
frame = backtrace_frames[0]
|
44
|
+
first_frame_variable_info = VariableInfo.new(frame, editor_url(frame), rails_params, rack_session, Time.now.to_f)
|
45
|
+
self.class.render_template('main', binding)
|
46
|
+
end
|
47
|
+
|
48
|
+
def render_text
|
49
|
+
self.class.render_template('text', binding)
|
36
50
|
end
|
37
51
|
|
38
52
|
def do_variables(opts)
|
39
53
|
index = opts["index"].to_i
|
40
|
-
|
41
|
-
|
42
|
-
{ html:
|
54
|
+
frame = backtrace_frames[index]
|
55
|
+
variable_info = VariableInfo.new(frame, editor_url(frame), rails_params, rack_session, Time.now.to_f)
|
56
|
+
{ html: self.class.render_template("variable_info", variable_info) }
|
43
57
|
end
|
44
58
|
|
45
59
|
def do_eval(opts)
|
@@ -67,6 +81,10 @@ module BetterErrors
|
|
67
81
|
exception.message.strip.gsub(/(\r?\n\s*\r?\n)+/, "\n")
|
68
82
|
end
|
69
83
|
|
84
|
+
def exception_hint
|
85
|
+
exception.hint
|
86
|
+
end
|
87
|
+
|
70
88
|
def active_support_actions
|
71
89
|
return [] unless defined?(ActiveSupport::ActionableError)
|
72
90
|
|
@@ -90,7 +108,7 @@ module BetterErrors
|
|
90
108
|
private
|
91
109
|
|
92
110
|
def editor_url(frame)
|
93
|
-
BetterErrors.editor
|
111
|
+
BetterErrors.editor.url(frame.filename, frame.line)
|
94
112
|
end
|
95
113
|
|
96
114
|
def rack_session
|
@@ -109,11 +127,11 @@ module BetterErrors
|
|
109
127
|
env["PATH_INFO"]
|
110
128
|
end
|
111
129
|
|
112
|
-
def html_formatted_code_block(frame)
|
130
|
+
def self.html_formatted_code_block(frame)
|
113
131
|
CodeFormatter::HTML.new(frame.filename, frame.line).output
|
114
132
|
end
|
115
133
|
|
116
|
-
def text_formatted_code_block(frame)
|
134
|
+
def self.text_formatted_code_block(frame)
|
117
135
|
CodeFormatter::Text.new(frame.filename, frame.line).output
|
118
136
|
end
|
119
137
|
|
@@ -121,7 +139,7 @@ module BetterErrors
|
|
121
139
|
str + "\n" + char*str.size
|
122
140
|
end
|
123
141
|
|
124
|
-
def inspect_value(obj)
|
142
|
+
def self.inspect_value(obj)
|
125
143
|
if BetterErrors.ignored_classes.include? obj.class.name
|
126
144
|
"<span class='unsupported'>(Instance of ignored class. "\
|
127
145
|
"#{obj.class.name ? "Remove #{CGI.escapeHTML(obj.class.name)} from" : "Modify"}"\
|
@@ -141,7 +159,7 @@ module BetterErrors
|
|
141
159
|
result, prompt, prefilled_input = @repls[index].send_input(code)
|
142
160
|
|
143
161
|
{
|
144
|
-
highlighted_input:
|
162
|
+
highlighted_input: Rouge::Formatters::HTML.new.format(Rouge::Lexers::Ruby.lex(code)),
|
145
163
|
prefilled_input: prefilled_input,
|
146
164
|
prompt: prompt,
|
147
165
|
result: result
|