what_weve_got_here_is_an_error_to_communicate 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Readme.md +9 -7
  4. data/experiments/formatting/other_resources +7 -0
  5. data/lib/error_to_communicate/at_exit.rb +15 -9
  6. data/lib/error_to_communicate/config.rb +55 -22
  7. data/lib/error_to_communicate/exception_info.rb +86 -31
  8. data/lib/error_to_communicate/format_terminal.rb +146 -0
  9. data/lib/error_to_communicate/heuristic/exception.rb +22 -0
  10. data/lib/error_to_communicate/heuristic/load_error.rb +57 -0
  11. data/lib/error_to_communicate/heuristic/no_method_error.rb +35 -0
  12. data/lib/error_to_communicate/heuristic/syntax_error.rb +55 -0
  13. data/lib/error_to_communicate/heuristic/wrong_number_of_arguments.rb +73 -0
  14. data/lib/error_to_communicate/heuristic.rb +54 -0
  15. data/lib/error_to_communicate/project.rb +50 -0
  16. data/lib/error_to_communicate/rspec_formatter.rb +8 -9
  17. data/lib/error_to_communicate/theme.rb +137 -0
  18. data/lib/error_to_communicate/version.rb +2 -2
  19. data/lib/error_to_communicate.rb +4 -2
  20. data/spec/acceptance/exception_spec.rb +2 -4
  21. data/spec/acceptance/load_error_spec.rb +23 -0
  22. data/spec/acceptance/name_error_spec.rb +46 -0
  23. data/spec/acceptance/no_methood_error_spec.rb +6 -8
  24. data/spec/acceptance/runtime_error_spec.rb +27 -0
  25. data/spec/acceptance/short_and_long_require_spec.rb +29 -0
  26. data/spec/acceptance/spec_helper.rb +4 -3
  27. data/spec/acceptance/syntax_error_spec.rb +32 -0
  28. data/spec/acceptance/{argument_error_spec.rb → wrong_number_of_arguments_spec.rb} +1 -1
  29. data/spec/config_spec.rb +120 -0
  30. data/spec/heuristic/exception_spec.rb +17 -0
  31. data/spec/heuristic/load_error_spec.rb +195 -0
  32. data/spec/heuristic/no_method_error_spec.rb +25 -0
  33. data/spec/heuristic/spec_helper.rb +33 -0
  34. data/spec/heuristic/wrong_number_of_arguments_spec.rb +115 -0
  35. data/spec/heuristic_spec.rb +76 -0
  36. data/spec/parsing_exception_info_spec.rb +212 -0
  37. data/spec/rspec_formatter_spec.rb +3 -1
  38. data/spec/spec_helper.rb +28 -1
  39. data/what_weve_got_here_is_an_error_to_communicate.gemspec +2 -2
  40. metadata +29 -19
  41. data/lib/error_to_communicate/format/terminal_helpers.rb +0 -97
  42. data/lib/error_to_communicate/format.rb +0 -132
  43. data/lib/error_to_communicate/parse/backtrace.rb +0 -34
  44. data/lib/error_to_communicate/parse/exception.rb +0 -21
  45. data/lib/error_to_communicate/parse/no_method_error.rb +0 -27
  46. data/lib/error_to_communicate/parse/registry.rb +0 -30
  47. data/lib/error_to_communicate/parse/wrong_number_of_arguments.rb +0 -35
  48. data/spec/parse/backtrace_spec.rb +0 -101
  49. data/spec/parse/exception_spec.rb +0 -14
  50. data/spec/parse/no_method_error_spec.rb +0 -23
  51. data/spec/parse/registered_parsers_spec.rb +0 -68
  52. data/spec/parse/spec_helper.rb +0 -23
  53. data/spec/parse/wrong_number_of_arguments_spec.rb +0 -77
@@ -0,0 +1,212 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Parsing exceptions to ExceptionInfo', einfo: true do
4
+ it 'records the class name, message, and backtrace' do
5
+ exception = FakeException.new message: 'Some message',
6
+ backtrace: ["/Users/someone/a/b/c.rb:123:in `some_method_name'"]
7
+ einfo = einfo_for exception
8
+ expect(einfo.classname ).to eq 'FakeException'
9
+ expect(einfo.message ).to eq 'Some message'
10
+ expect(einfo.backtrace.map &:linenum).to eq [123]
11
+ end
12
+
13
+ describe 'recording the actual exception' do
14
+ let(:exception) { FakeException.new }
15
+ let(:einfo) { einfo_for exception }
16
+
17
+ it 'records the exception for informational purposes' do
18
+ trap_warnings do
19
+ expect(einfo.exception).to equal exception
20
+ end
21
+ end
22
+
23
+ it 'warns the first time you try to use it (ie available for debugging, but not for development)' do
24
+ warnings = trap_warnings { einfo.exception }
25
+ expect(warnings).to match /debugging/
26
+
27
+ warnings = trap_warnings { einfo.exception }
28
+ expect(warnings).to be_empty
29
+ end
30
+ end
31
+
32
+ describe 'backtrace' do
33
+ let :exception do
34
+ FakeException.new backtrace: [
35
+ "file.rb:111:in `method1'",
36
+ "file.rb:222:in `method2'",
37
+ "file.rb:333:in `method3'",
38
+ ]
39
+ end
40
+
41
+ def backtrace_for(exception)
42
+ einfo_for(exception).backtrace
43
+ end
44
+
45
+ it 'records the linenum, and label of each backtrace location' do
46
+ locations = backtrace_for exception
47
+ expect(locations.map &:linenum).to eq [111, 222, 333]
48
+ expect(locations.map &:label).to eq %w[method1 method2 method3]
49
+ end
50
+
51
+ specify 'the successor is the parsed location that was called, or nil for the first' do
52
+ l1, l2, l3 = locations = backtrace_for(exception)
53
+ expect(locations.map &:succ).to eq [nil, l1, l2]
54
+ end
55
+
56
+ specify 'the predecessor is the parsed location from the caller, or nil for the last' do
57
+ l1, l2, l3 = locations = backtrace_for(exception)
58
+ expect(locations.map &:pred).to eq [l2, l3, nil]
59
+ end
60
+
61
+ # it 'records the absolute filepath if it can find the file'
62
+ # it 'records the relative filepath if it can find the file'
63
+ # it 'records the relative filepath if it cannot fild the file'
64
+
65
+ def assert_parses_line(line, assertions)
66
+ parsed = ErrorToCommunicate::ExceptionInfo::Location.parse(line)
67
+ assertions.each do |method_name, expected|
68
+ expected = Pathname.new expected if method_name == :path
69
+ actual = parsed.__send__ method_name
70
+ expect(actual).to eq expected
71
+ end
72
+ end
73
+
74
+ it 'records the path whether its absolute or relative' do
75
+ assert_parses_line "file.rb:111:in `method1'", path: "file.rb"
76
+ assert_parses_line "/file.rb:111:in `method1'", path: "/file.rb"
77
+ end
78
+
79
+ it 'does not get confused by numbers in directories, filenames, or method names' do
80
+ line = "/a1/b2/c3123/file123.rb:111:in `method1'"
81
+ assert_parses_line line, path: "/a1/b2/c3123/file123.rb"
82
+ assert_parses_line line, linenum: 111
83
+ assert_parses_line line, label: "method1"
84
+ end
85
+
86
+ context 'random ass colons in the middle of like files and directories and shit' do
87
+ # $ mkdir 'a:b'
88
+ # $ echo 'begin; define_method("a:b") { |arg| }; send "a:b"; rescue Exception; p $!.backtrace; end' > 'a:b/c:d.rb'
89
+
90
+ # $ chruby-exec 2.2 -- ruby -v
91
+ # > ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin13]
92
+ #
93
+ # $ chruby-exec 2.2 -- ruby 'a:b/c:d.rb'
94
+ # > ["a:b/c:d.rb:1:in `block in <main>'", "a:b/c:d.rb:1:in `<main>'"]
95
+ it 'does not get confused with MRI style results' do
96
+ line = "a:b/c:d.rb:1:in `block in <main>'"
97
+ assert_parses_line line, path: "a:b/c:d.rb"
98
+ assert_parses_line line, linenum: 1
99
+ assert_parses_line line, label: "block in <main>"
100
+ end
101
+
102
+ # $ chruby-exec rbx -- ruby -v
103
+ # > rubinius 2.5.0 (2.1.0 50777f41 2015-01-17 3.5.0 JI) [x86_64-darwin14.1.0]
104
+ #
105
+ # $ chruby-exec rbx -- ruby 'a:b/c:d.rb'
106
+ # > ["a:b/c:d.rb:1:in `__script__'",
107
+ # "kernel/delta/code_loader.rb:66:in `load_script'",
108
+ # "kernel/delta/code_loader.rb:152:in `load_script'",
109
+ # "kernel/loader.rb:645:in `script'",
110
+ # "kernel/loader.rb:799:in `main'"]
111
+ it 'does not get confused with RBX style results' do
112
+ line = "a:b/c:d.rb:1:in `__script__'"
113
+ assert_parses_line line, path: "a:b/c:d.rb"
114
+ assert_parses_line line, linenum: 1
115
+ assert_parses_line line, label: "__script__"
116
+ end
117
+
118
+ # $ chruby-exec jruby -- ruby -v
119
+ # > jruby 1.7.16 (1.9.3p392) 2014-09-25 575b395 on Java HotSpot(TM) 64-Bit Server VM 1.7.0_51-b13 +jit [darwin-x86_64]
120
+ #
121
+ # $ chruby-exec jruby -- ruby 'a:b/c:d.rb'
122
+ # > ["a:b/c:d.rb:1:in `(root)'"]
123
+ it 'does not get confused by Jruby style results' do
124
+ line = "a:b/c:d.rb:1:in `(root)'"
125
+ assert_parses_line line, path: "a:b/c:d.rb"
126
+ assert_parses_line line, linenum: 1
127
+ assert_parses_line line, label: "(root)"
128
+ end
129
+ end
130
+ end
131
+
132
+ describe 'ExceptionInfo::Location' do
133
+ def location_for(attrs)
134
+ ErrorToCommunicate::ExceptionInfo::Location.new attrs
135
+ end
136
+
137
+ it 'hashes take into account its path, linenum, and label' do
138
+ loc_base = location_for(path: 'p', linenum: 123, label: 'l')
139
+ loc_eq1 = location_for(path: 'p', linenum: 123, label: 'l')
140
+ loc_eq2 = location_for(path: 'p', linenum: 123, label: 'l', succ: loc_base, pred: loc_base)
141
+ loc_diff_path = location_for(path: 'P', linenum: 123, label: 'l')
142
+ loc_diff_linenum = location_for(path: 'p', linenum: 999, label: 'l')
143
+ loc_diff_label = location_for(path: 'p', linenum: 123, label: 'L')
144
+
145
+ expect(loc_base.hash).to eq loc_eq1.hash # same values gets same hash
146
+ expect(loc_base.hash).to eq loc_eq2.hash # succ/pred are excluded from hash
147
+ expect(loc_base.hash).to_not eq loc_diff_path.hash
148
+ expect(loc_base.hash).to_not eq loc_diff_linenum.hash
149
+ expect(loc_base.hash).to_not eq loc_diff_label.hash
150
+
151
+ # use this, just to show it works
152
+ h = {loc_base => :found}
153
+ expect( h[loc_eq1 ] ).to eq :found
154
+ expect( h[loc_eq2 ] ).to eq :found
155
+ expect( h[loc_diff_path ] ).to eq nil
156
+ expect( h[loc_diff_linenum ] ).to eq nil
157
+ expect( h[loc_diff_label ] ).to eq nil
158
+ end
159
+
160
+
161
+ # Can't directly check associations for equality or we'll end up in an infinite loop
162
+ # This was maybe a waste of time, to specify it this precisely, all I want to be able to do is compare two paths -.^
163
+ it 'is == and eql? to another location, if their paths, linenums, and labels are ==' do
164
+ # the rest are compared against this
165
+ loc1 = location_for path: 'p1', linenum: 1, label: 'l1'
166
+
167
+ # equivalent
168
+ loc1_same = location_for path: 'p1', linenum: 1, label: 'l1'
169
+ expect(loc1).to eq loc1_same
170
+ expect(loc1).to eql loc1_same
171
+
172
+ # different path
173
+ loc1_diff_path = location_for path: 'p2', linenum: 1, label: 'l1'
174
+ expect(loc1).to_not eq loc1_diff_path
175
+ expect(loc1).to_not eql loc1_diff_path
176
+
177
+ # different line
178
+ loc1_diff_line = location_for path: 'p1', linenum: 2, label: 'l1'
179
+ expect(loc1).to_not eq loc1_diff_line
180
+ expect(loc1).to_not eql loc1_diff_line
181
+
182
+ # different label
183
+ loc1_diff_label = location_for path: 'p1', linenum: 1, label: 'l2'
184
+ expect(loc1).to_not eq loc1_diff_label
185
+ expect(loc1).to_not eql loc1_diff_label
186
+
187
+ # are == when different before
188
+ loc2_before = location_for path: 'p2', linenum: 2, label: 'l2'
189
+ loc2 = location_for path: 'p1', linenum: 1, label: 'l1'
190
+ loc2.succ, loc2_before.pred = loc2_before, loc2
191
+ expect(loc1).to eq loc2
192
+ expect(loc1).to eql loc2
193
+
194
+ # are == when different after
195
+ loc3 = location_for path: 'p1', linenum: 1, label: 'l1'
196
+ loc3_after = location_for path: 'p2', linenum: 2, label: 'l2'
197
+ loc3.succ, loc3_after.pred = loc3_after, loc3
198
+ expect(loc1).to eq loc3
199
+ expect(loc1).to eql loc3
200
+ end
201
+
202
+ it 'inspects to something that isn\'t obnoxious to look at' do
203
+ linked_loc = location_for path: 'somepath', linenum: 123, label: 'somelabel'
204
+ linked_loc.succ, linked_loc.pred = linked_loc, linked_loc
205
+ expect(linked_loc.inspect).to eq '#<ExInfo::Loc somepath:123:in `somelabel\' pred:true succ:true>'
206
+
207
+ unlinked_loc = location_for path: 'anotherpath', linenum: 12, label: 'anotherlabel'
208
+ expect(unlinked_loc.inspect).to eq '#<ExInfo::Loc anotherpath:12:in `anotherlabel\' pred:false succ:false>'
209
+ end
210
+ end
211
+
212
+ end
@@ -1,7 +1,7 @@
1
1
  require 'stringio'
2
2
  require 'spec_helper'
3
3
 
4
- RSpec.describe ErrorToCommunicate::RSpecFormatter, formatter: true do
4
+ RSpec.describe ErrorToCommunicate::RSpecFormatter, rspec_formatter: true do
5
5
  let(:substring_that_would_only_be_in_full_backtrace) { 'lib/rspec/core' }
6
6
 
7
7
  def formatter_for(attributes)
@@ -53,6 +53,7 @@ RSpec.describe ErrorToCommunicate::RSpecFormatter, formatter: true do
53
53
  end
54
54
 
55
55
  it 'uses our lib to print the details of failing examples.' do
56
+ # does print
56
57
  formatter = new_formatter
57
58
  context_around_failure = this_line_of_code
58
59
  run_specs_against formatter do
@@ -60,6 +61,7 @@ RSpec.describe ErrorToCommunicate::RSpecFormatter, formatter: true do
60
61
  end
61
62
  expect(get_printed formatter).to include context_around_failure
62
63
 
64
+ # does not print
63
65
  formatter = new_formatter
64
66
  context_around_success = this_line_of_code
65
67
  run_specs_against formatter do
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  class FakeException
2
2
  attr_reader :message, :backtrace
3
3
 
4
- def initialize(attributes)
4
+ def initialize(attributes={})
5
5
  @message = attributes.fetch :message, 'default message'
6
6
  @backtrace = attributes.fetch(:backtrace, [])
7
7
  end
@@ -11,7 +11,34 @@ class FakeException
11
11
  end
12
12
  end
13
13
 
14
+ module SpecHelpers
15
+ def einfo_for(exception)
16
+ ErrorToCommunicate::ExceptionInfo.parse exception
17
+ end
18
+
19
+ def trap_warnings
20
+ initial_stderr = $stderr
21
+ mock_stderr = StringIO.new
22
+ $stderr = mock_stderr
23
+ yield
24
+ ensure
25
+ $stderr = initial_stderr
26
+ warnings = mock_stderr.string
27
+ return warnings unless $! # don't swallow exceptions
28
+ end
29
+
30
+ def capture
31
+ yield
32
+ raise 'NO EXCEPTION WAS RAISED!'
33
+ rescue Exception
34
+ return $!
35
+ end
36
+ end
37
+
38
+
14
39
  RSpec.configure do |config|
40
+ config.include SpecHelpers
41
+
15
42
  # Stop testing after first failure
16
43
  config.fail_fast = true
17
44
 
@@ -1,7 +1,7 @@
1
1
  require_relative 'lib/error_to_communicate/version'
2
2
  Gem::Specification.new do |s|
3
3
  s.name = 'what_weve_got_here_is_an_error_to_communicate'
4
- s.version = WhatWeveGotHereIsAnErrorToCommunicate::VERSION
4
+ s.version = ErrorToCommunicate::VERSION
5
5
  s.licenses = ['MIT']
6
6
  s.summary = "Readable, helpful error messages"
7
7
  s.description = "Hooks into program lifecycle to display error messages to you in a helpufl way, with inlined code, colour, and helpful heuristics about what might be the cause."
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.files = `git ls-files`.split("\n")
11
11
  s.homepage = 'https://github.com/JoshCheek/what-we-ve-got-here-is-an-error-to-communicate'
12
12
 
13
- s.add_runtime_dependency 'coderay', '~> 1.1'
13
+ s.add_runtime_dependency 'rouge', '~> 1.8'
14
14
 
15
15
  s.add_development_dependency 'rspec', '~> 3.2'
16
16
  s.add_development_dependency 'haiti', '< 0.3', '>= 0.2.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: what_weve_got_here_is_an_error_to_communicate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Cheek
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-04-08 00:00:00.000000000 Z
12
+ date: 2015-05-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: coderay
15
+ name: rouge
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '1.1'
20
+ version: '1.8'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.1'
27
+ version: '1.8'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rspec
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -110,33 +110,43 @@ files:
110
110
  - experiments/formatting/5_potential_structure_dsls.rb
111
111
  - experiments/formatting/half_thoughtout_dsl_for_toplevel_structure_of_argument_error.rb
112
112
  - experiments/formatting/haml_like_structure.rb
113
+ - experiments/formatting/other_resources
113
114
  - experiments/formatting/other_structures
114
115
  - lib/error_to_communicate.rb
115
116
  - lib/error_to_communicate/at_exit.rb
116
117
  - lib/error_to_communicate/config.rb
117
118
  - lib/error_to_communicate/exception_info.rb
118
- - lib/error_to_communicate/format.rb
119
- - lib/error_to_communicate/format/terminal_helpers.rb
120
- - lib/error_to_communicate/parse/backtrace.rb
121
- - lib/error_to_communicate/parse/exception.rb
122
- - lib/error_to_communicate/parse/no_method_error.rb
123
- - lib/error_to_communicate/parse/registry.rb
124
- - lib/error_to_communicate/parse/wrong_number_of_arguments.rb
119
+ - lib/error_to_communicate/format_terminal.rb
120
+ - lib/error_to_communicate/heuristic.rb
121
+ - lib/error_to_communicate/heuristic/exception.rb
122
+ - lib/error_to_communicate/heuristic/load_error.rb
123
+ - lib/error_to_communicate/heuristic/no_method_error.rb
124
+ - lib/error_to_communicate/heuristic/syntax_error.rb
125
+ - lib/error_to_communicate/heuristic/wrong_number_of_arguments.rb
126
+ - lib/error_to_communicate/project.rb
125
127
  - lib/error_to_communicate/rspec_formatter.rb
128
+ - lib/error_to_communicate/theme.rb
126
129
  - lib/error_to_communicate/version.rb
127
130
  - lib/what_weve_got_here_is_an_error_to_communicate.rb
128
131
  - screenshot.png
129
- - spec/acceptance/argument_error_spec.rb
130
132
  - spec/acceptance/exception_spec.rb
133
+ - spec/acceptance/load_error_spec.rb
134
+ - spec/acceptance/name_error_spec.rb
131
135
  - spec/acceptance/no_error_spec.rb
132
136
  - spec/acceptance/no_methood_error_spec.rb
137
+ - spec/acceptance/runtime_error_spec.rb
138
+ - spec/acceptance/short_and_long_require_spec.rb
133
139
  - spec/acceptance/spec_helper.rb
134
- - spec/parse/backtrace_spec.rb
135
- - spec/parse/exception_spec.rb
136
- - spec/parse/no_method_error_spec.rb
137
- - spec/parse/registered_parsers_spec.rb
138
- - spec/parse/spec_helper.rb
139
- - spec/parse/wrong_number_of_arguments_spec.rb
140
+ - spec/acceptance/syntax_error_spec.rb
141
+ - spec/acceptance/wrong_number_of_arguments_spec.rb
142
+ - spec/config_spec.rb
143
+ - spec/heuristic/exception_spec.rb
144
+ - spec/heuristic/load_error_spec.rb
145
+ - spec/heuristic/no_method_error_spec.rb
146
+ - spec/heuristic/spec_helper.rb
147
+ - spec/heuristic/wrong_number_of_arguments_spec.rb
148
+ - spec/heuristic_spec.rb
149
+ - spec/parsing_exception_info_spec.rb
140
150
  - spec/rspec_formatter_spec.rb
141
151
  - spec/spec_helper.rb
142
152
  - what_weve_got_here_is_an_error_to_communicate.gemspec
@@ -1,97 +0,0 @@
1
- module WhatWeveGotHereIsAnErrorToCommunicate
2
- module Format
3
- module TerminalHelpers
4
- def separator
5
- ("="*70) << "\n"
6
- end
7
-
8
- def color_path(str)
9
- "\e[38;5;36m#{str}\e[39m" # fg r:0, g:3, b:2 (out of 0..5)
10
- end
11
-
12
- def color_linenum(linenum)
13
- "\e[34m#{linenum}\e[39m"
14
- end
15
-
16
- def path_to_dir(from, to)
17
- to.relative_path_from(from).dirname
18
- rescue ArgumentError
19
- return to # eg rbx's core code
20
- end
21
-
22
- def white
23
- "\e[38;5;255m"
24
- end
25
-
26
- def bri_red
27
- "\e[38;5;196m"
28
- end
29
-
30
- def dim_red
31
- "\e[38;5;124m"
32
- end
33
-
34
- def none
35
- "\e[39m"
36
- end
37
-
38
- def bound_num(attributes)
39
- num = attributes.fetch :num
40
- min = attributes.fetch :min
41
- num < min ? min : num
42
- end
43
-
44
- def remove_indentation(code)
45
- indentation = code.scan(/^\s*/).min_by(&:length)
46
- code.gsub(/^#{indentation}/, "")
47
- end
48
-
49
- def prefix_linenos_to(code, start_linenum)
50
- lines = code.lines
51
- max_linenum = lines.count + start_linenum - 1 # 1 to translate to indexes
52
- linenum_width = max_linenum.to_s.length + 1 # 1 for the colon
53
- lines.zip(start_linenum..max_linenum)
54
- .map { |line, num|
55
- formatted_num = "#{num}:".ljust(linenum_width)
56
- color_linenum(formatted_num) << " " << line
57
- }.join("")
58
- end
59
-
60
- def add_message_to(code, offset, message)
61
- lines = code.lines
62
- lines[offset].chomp! << " " << message << "\n"
63
- lines.join("")
64
- end
65
-
66
- def highlight_text(code, index, text)
67
- lines = code.lines
68
- return code unless lines[index]
69
- lines[index].gsub!(text, "\e[7m#{text}\e[27m") # invert
70
- lines.join("")
71
- end
72
-
73
- def indent(str, indentation_str)
74
- str.gsub /^/, indentation_str
75
- end
76
-
77
- def screaming_red(text)
78
- return "" if text.empty?
79
- "\e[38;5;255;48;5;88m #{text} \e[39;49m" # bright white on medium red
80
- end
81
-
82
- def underline(str)
83
- "\e[4m#{str}\e[24m"
84
- end
85
-
86
- def color_filename(str)
87
- "\e[38;5;49;1m#{str}\e[39m" # fg r:0, g:5, b:3 (out of 0..5)
88
- end
89
-
90
- def desaturate(str)
91
- nocolor = str.gsub(/\e\[[\d;]+?m/, "")
92
- allgray = nocolor.gsub(/^(.*?)\n?$/, "\e[38;5;240m\\1\e[39m\n")
93
- allgray
94
- end
95
- end
96
- end
97
- end
@@ -1,132 +0,0 @@
1
- require 'coderay'
2
- require 'pathname'
3
- require 'error_to_communicate/format/terminal_helpers'
4
-
5
- module WhatWeveGotHereIsAnErrorToCommunicate
6
- extend Format::TerminalHelpers
7
- def self.format(info)
8
- cwd = Dir.pwd
9
-
10
- # FIXME:
11
- # Something else should set this?
12
- # I'd say heuristic, but fact is that it needs formatting info.
13
- # Maybe initially, heuristic contains both extracted info and formatting info?
14
- # Or maybe we want polymorphism at the formatter level?
15
- display_class_and_message = lambda do |info|
16
- if info.classname == 'ArgumentError'
17
- "#{white}#{info.classname} | "\
18
- "#{bri_red}#{info.explanation} "\
19
- "#{dim_red}(expected #{white}#{info.num_expected},"\
20
- "#{dim_red} sent #{white}#{info.num_received}"\
21
- "#{dim_red})"\
22
- "#{none}"
23
- else
24
- "#{white}#{info.classname} | "\
25
- "#{bri_red}#{info.explanation} "\
26
- "#{none}"
27
- end
28
- end
29
-
30
- display_location = lambda do |attributes|
31
- location = attributes.fetch :location
32
- cwd = Pathname.new attributes.fetch(:cwd)
33
- path = Pathname.new location.path
34
- line_index = location.linenum - 1
35
- highlight = attributes.fetch :highlight, location.label
36
- end_index = bound_num min: 0, num: line_index+attributes.fetch(:context).end
37
- start_index = bound_num min: 0, num: line_index+attributes.fetch(:context).begin
38
- message = attributes.fetch :message, ''
39
- message_offset = line_index - start_index
40
-
41
- # first line gives the path
42
- path_line = ""
43
- path_line << color_path("#{path_to_dir cwd, path}/")
44
- path_line << color_filename(path.basename)
45
- path_line << ":" << color_linenum(location.linenum)
46
-
47
- # then display the code
48
- if path.exist?
49
- code = File.read(path).lines[start_index..end_index].join("")
50
- code = remove_indentation code
51
- code = CodeRay.encode code, :ruby, :terminal
52
- code = prefix_linenos_to code, start_index.next
53
- code = indent code, " "
54
- code = add_message_to code, message_offset, screaming_red(message)
55
- code = highlight_text code, message_offset, highlight
56
- else
57
- code = "Can't find code\n"
58
- end
59
-
60
- # adjust for emphasization
61
- if attributes.fetch(:emphasisis) == :path
62
- path_line = underline path_line
63
- code = indent code, " "
64
- code = desaturate code
65
- code = highlight_text code, message_offset, highlight # b/c desaturate really strips color
66
- end
67
-
68
- # all together
69
- path_line << "\n" << code
70
- end
71
-
72
-
73
- # Display the ArgumentError
74
- display = ""
75
- display << separator
76
- display << display_class_and_message.call(info) << "\n"
77
-
78
- # Display the Heuristic
79
- display << separator
80
-
81
- # FIXME: Some sort of polymorphism or normalization would be way better here, too
82
- # And, at the very least, not switching on classname, but some more abstract piece of info,
83
- # b/c classnames are not completely consistent across the implementations
84
- # (eg: https://github.com/JoshCheek/seeing_is_believing/blob/cc93b4ee3a83145509c235f64d9454dc3e12d8c9/lib/seeing_is_believing/event_stream/producer.rb#L54-55)
85
- if info.classname == 'ArgumentError'
86
- display << display_location.call(location: info.backtrace[0],
87
- highlight: info.backtrace[0].label,
88
- context: 0..5,
89
- message: "EXPECTED #{info.num_expected}",
90
- emphasisis: :code,
91
- cwd: cwd)
92
- display << "\n"
93
- display << display_location.call(location: info.backtrace[1],
94
- highlight: info.backtrace[0].label,
95
- context: -5..5,
96
- message: "SENT #{info.num_received}",
97
- emphasisis: :code,
98
- cwd: cwd)
99
- elsif info.classname == 'NoMethodError'
100
- display << display_location.call(location: info.backtrace[0],
101
- highlight: info.backtrace[0].label,
102
- context: -5..5,
103
- message: "#{info.undefined_method_name} is undefined",
104
- emphasisis: :code,
105
- cwd: cwd)
106
- else
107
- display << display_location.call(location: info.backtrace[0],
108
- highlight: info.backtrace[0].label,
109
- context: -5..5,
110
- emphasisis: :code,
111
- cwd: cwd)
112
- end
113
-
114
- # display the backtrace
115
- display << separator
116
- display << display_location.call(location: info.backtrace[0],
117
- highlight: info.backtrace[0].label,
118
- context: 0..0,
119
- emphasisis: :path,
120
- cwd: cwd)
121
-
122
- display << info.backtrace.each_cons(2).map { |next_loc, crnt_loc|
123
- display_location.call location: crnt_loc,
124
- highlight: next_loc.label,
125
- context: 0..0,
126
- emphasisis: :path,
127
- cwd: cwd
128
- }.join("")
129
-
130
- display
131
- end
132
- end
@@ -1,34 +0,0 @@
1
- require 'error_to_communicate/exception_info'
2
-
3
- module WhatWeveGotHereIsAnErrorToCommunicate
4
- module Parse
5
- module Backtrace
6
- def self.parse?(exception)
7
- # Really, there are better methods, e.g. backtrace_locations,
8
- # but they're unevenly implemented across versions and implementations
9
- exception.respond_to? :backtrace
10
- end
11
-
12
- def self.parse(exception)
13
- locations = exception.backtrace.map &method(:parse_backtrace_line)
14
- locations.each_cons(2) do |crnt, succ|
15
- succ.pred = crnt
16
- crnt.succ = succ
17
- end
18
- locations
19
- end
20
-
21
- # TODO: What if the line doesn't match for some reason?
22
- # Raise an exception?
23
- # Use some reasonable default? (is there one?)
24
- def self.parse_backtrace_line(line)
25
- line =~ /^(.*?):(\d+):in `(.*?)'$/ # Are ^ and $ sufficient? Should be \A and (\Z or \z)?
26
- ExceptionInfo::Location.new(
27
- path: $1,
28
- linenum: $2.to_i,
29
- label: $3,
30
- )
31
- end
32
- end
33
- end
34
- end
@@ -1,21 +0,0 @@
1
- require 'error_to_communicate/exception_info'
2
- require 'error_to_communicate/parse/backtrace'
3
-
4
- module WhatWeveGotHereIsAnErrorToCommunicate
5
- module Parse
6
- module Exception
7
- def self.parse?(exception)
8
- exception.respond_to?(:message) && Backtrace.parse?(exception)
9
- end
10
-
11
- def self.parse(exception)
12
- ExceptionInfo.new(
13
- exception: exception,
14
- classname: exception.class.to_s,
15
- explanation: exception.message,
16
- backtrace: Backtrace.parse(exception),
17
- )
18
- end
19
- end
20
- end
21
- end