andyw8-seeing_is_believing 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +60 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +2 -0
  6. data/README.md +70 -0
  7. data/Rakefile +88 -0
  8. data/appveyor.yml +32 -0
  9. data/bin/seeing_is_believing +7 -0
  10. data/docs/example.gif +0 -0
  11. data/docs/frog-brown.png +0 -0
  12. data/docs/sib-streaming.gif +0 -0
  13. data/features/deprecated-flags.feature +91 -0
  14. data/features/errors.feature +155 -0
  15. data/features/examples.feature +423 -0
  16. data/features/flags.feature +852 -0
  17. data/features/regression.feature +898 -0
  18. data/features/support/env.rb +102 -0
  19. data/features/xmpfilter-style.feature +471 -0
  20. data/lib/seeing_is_believing/binary/align_chunk.rb +47 -0
  21. data/lib/seeing_is_believing/binary/align_file.rb +24 -0
  22. data/lib/seeing_is_believing/binary/align_line.rb +25 -0
  23. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +56 -0
  24. data/lib/seeing_is_believing/binary/annotate_every_line.rb +52 -0
  25. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +179 -0
  26. data/lib/seeing_is_believing/binary/comment_lines.rb +36 -0
  27. data/lib/seeing_is_believing/binary/commentable_lines.rb +126 -0
  28. data/lib/seeing_is_believing/binary/config.rb +455 -0
  29. data/lib/seeing_is_believing/binary/data_structures.rb +58 -0
  30. data/lib/seeing_is_believing/binary/engine.rb +161 -0
  31. data/lib/seeing_is_believing/binary/format_comment.rb +79 -0
  32. data/lib/seeing_is_believing/binary/interline_align.rb +57 -0
  33. data/lib/seeing_is_believing/binary/remove_annotations.rb +113 -0
  34. data/lib/seeing_is_believing/binary/rewrite_comments.rb +62 -0
  35. data/lib/seeing_is_believing/binary.rb +73 -0
  36. data/lib/seeing_is_believing/code.rb +139 -0
  37. data/lib/seeing_is_believing/compatibility.rb +28 -0
  38. data/lib/seeing_is_believing/debugger.rb +32 -0
  39. data/lib/seeing_is_believing/error.rb +17 -0
  40. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +195 -0
  41. data/lib/seeing_is_believing/event_stream/consumer.rb +221 -0
  42. data/lib/seeing_is_believing/event_stream/events.rb +193 -0
  43. data/lib/seeing_is_believing/event_stream/handlers/debug.rb +61 -0
  44. data/lib/seeing_is_believing/event_stream/handlers/record_exit_events.rb +26 -0
  45. data/lib/seeing_is_believing/event_stream/handlers/stream_json_events.rb +23 -0
  46. data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +41 -0
  47. data/lib/seeing_is_believing/event_stream/producer.rb +178 -0
  48. data/lib/seeing_is_believing/hard_core_ensure.rb +58 -0
  49. data/lib/seeing_is_believing/hash_struct.rb +206 -0
  50. data/lib/seeing_is_believing/result.rb +89 -0
  51. data/lib/seeing_is_believing/safe.rb +112 -0
  52. data/lib/seeing_is_believing/swap_files.rb +90 -0
  53. data/lib/seeing_is_believing/the_matrix.rb +97 -0
  54. data/lib/seeing_is_believing/version.rb +3 -0
  55. data/lib/seeing_is_believing/wrap_expressions.rb +265 -0
  56. data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +19 -0
  57. data/lib/seeing_is_believing.rb +69 -0
  58. data/seeing_is_believing.gemspec +84 -0
  59. data/spec/binary/alignment_specs.rb +27 -0
  60. data/spec/binary/comment_lines_spec.rb +852 -0
  61. data/spec/binary/config_spec.rb +831 -0
  62. data/spec/binary/engine_spec.rb +114 -0
  63. data/spec/binary/format_comment_spec.rb +210 -0
  64. data/spec/binary/marker_spec.rb +71 -0
  65. data/spec/binary/remove_annotations_spec.rb +342 -0
  66. data/spec/binary/rewrite_comments_spec.rb +106 -0
  67. data/spec/code_spec.rb +233 -0
  68. data/spec/debugger_spec.rb +45 -0
  69. data/spec/evaluate_by_moving_files_spec.rb +204 -0
  70. data/spec/event_stream_spec.rb +762 -0
  71. data/spec/hard_core_ensure_spec.rb +120 -0
  72. data/spec/hash_struct_spec.rb +514 -0
  73. data/spec/seeing_is_believing_spec.rb +1094 -0
  74. data/spec/sib_spec_helpers/version.rb +17 -0
  75. data/spec/spec_helper.rb +26 -0
  76. data/spec/spec_helper_spec.rb +16 -0
  77. data/spec/wrap_expressions_spec.rb +1013 -0
  78. metadata +340 -0
@@ -0,0 +1,56 @@
1
+ require 'seeing_is_believing/binary' # defines the markers
2
+ require 'seeing_is_believing/binary/format_comment'
3
+
4
+ class SeeingIsBelieving
5
+ module Binary
6
+ module AnnotateEndOfFile
7
+ extend self
8
+
9
+ def add_stdout_stderr_and_exceptions_to(new_body, results, options)
10
+ output = stdout_ouptut_for(results, options) <<
11
+ stderr_ouptut_for(results, options) <<
12
+ exception_output_for(results, options)
13
+
14
+ code = Code.new(new_body)
15
+ code.rewriter.insert_after code.body_range, output
16
+ new_body.replace code.rewriter.process
17
+ end
18
+
19
+ def stdout_ouptut_for(results, options)
20
+ return '' unless results.has_stdout?
21
+ output = "\n"
22
+ results.stdout.each_line do |line|
23
+ output << FormatComment.call(0, options[:markers][:stdout][:prefix], line.chomp, options) << "\n"
24
+ end
25
+ output
26
+ end
27
+
28
+ def stderr_ouptut_for(results, options)
29
+ return '' unless results.has_stderr?
30
+ output = "\n"
31
+ results.stderr.each_line do |line|
32
+ output << FormatComment.call(0, options[:markers][:stderr][:prefix], line.chomp, options) << "\n"
33
+ end
34
+ output
35
+ end
36
+
37
+ def exception_output_for(results, options)
38
+ return '' unless results.has_exception?
39
+ exception_marker = options[:markers][:exception][:prefix]
40
+ output = ""
41
+ results.exceptions.each do |exception|
42
+ output << "\n"
43
+ output << FormatComment.new(0, exception_marker, exception.class_name, options).call << "\n"
44
+ exception.message.each_line do |line|
45
+ output << FormatComment.new(0, exception_marker, line.chomp, options).call << "\n"
46
+ end
47
+ output << exception_marker.sub(/\s+$/, '') << "\n"
48
+ exception.backtrace.each do |line|
49
+ output << FormatComment.new(0, exception_marker, line.chomp, options).call << "\n"
50
+ end
51
+ end
52
+ output
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ require 'seeing_is_believing/binary/interline_align'
2
+
3
+ class SeeingIsBelieving
4
+ module Binary
5
+ class AnnotateEveryLine
6
+ def self.call(body, results, options)
7
+ new(body, results, options).call
8
+ end
9
+
10
+ def initialize(body, results, options={})
11
+ @options = options
12
+ @body = body
13
+ @results = results
14
+ @interline_align = InterlineAlign.new(results)
15
+ end
16
+
17
+ def call
18
+ @new_body ||= begin
19
+ require 'seeing_is_believing/binary/comment_lines'
20
+ require 'seeing_is_believing/binary/format_comment'
21
+ exception_prefix = @options[:markers][:exception][:prefix]
22
+ value_prefix = @options[:markers][:value][:prefix]
23
+ exceptions = Hash.[] @results.exceptions.map { |e| [e.line_number, e] }
24
+
25
+ alignment_strategy = @options[:alignment_strategy].new(@body)
26
+ new_body = CommentLines.call @body do |line, line_number|
27
+ exception = exceptions[line_number]
28
+ options = @options.merge pad_to: alignment_strategy.line_length_for(line_number)
29
+ if exception
30
+ result = sprintf "%s: %s", exception.class_name, exception.message.gsub("\n", '\n')
31
+ FormatComment.call(line.size, exception_prefix, result, options)
32
+ elsif @results[line_number].any?
33
+ if @options[:interline_align]
34
+ result = @interline_align.call line_number, @results[line_number].map { |result| result.gsub "\n", '\n' }
35
+ else
36
+ result = @results[line_number].map { |result| result.gsub "\n", '\n' }.join(', ')
37
+ end
38
+ FormatComment.call(line.size, value_prefix, result, options)
39
+ else
40
+ ''
41
+ end
42
+ end
43
+
44
+ require 'seeing_is_believing/binary/annotate_end_of_file'
45
+ AnnotateEndOfFile.add_stdout_stderr_and_exceptions_to new_body, @results, @options
46
+
47
+ new_body
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,179 @@
1
+ # encoding: utf-8
2
+ require 'seeing_is_believing/code'
3
+
4
+ # *sigh* need to find a way to join the annotators.
5
+ # They are sinful ugly, kinda hard to work with,
6
+ # and absurdly duplicated.
7
+
8
+ class SeeingIsBelieving
9
+ module Binary
10
+ # Based on the behaviour of xmpfilter (a binary in the rcodetools gem)
11
+ # See https://github.com/JoshCheek/seeing_is_believing/issues/44 for more details
12
+ class AnnotateMarkedLines
13
+ def self.map_markers_to_linenos(program, markers)
14
+ value_regex = markers[:value][:regex]
15
+ recordable_lines = []
16
+ inspect_linenos = []
17
+ pp_map = {}
18
+ WrapExpressions.call program, before_each: -> line_number {
19
+ recordable_lines << line_number
20
+ ''
21
+ }
22
+
23
+ Code.new(program).inline_comments.each do |c|
24
+ next unless c.text[value_regex]
25
+ if c.whitespace_col == 0
26
+ lineno = c.line_number
27
+ loop do
28
+ lineno -= 1
29
+ break if recordable_lines.include?(lineno) || lineno.zero?
30
+ end
31
+ pp_map[c.line_number] = lineno
32
+ else
33
+ inspect_linenos << c.line_number
34
+ end
35
+ end
36
+
37
+ return inspect_linenos, pp_map
38
+ end
39
+
40
+ def self.code_rewriter(markers)
41
+ lambda do |program|
42
+ inspect_linenos, pp_map = map_markers_to_linenos(program, markers)
43
+ pp_linenos = pp_map.values
44
+
45
+ should_inspect = false
46
+ should_pp = false
47
+ WrapExpressions.call \
48
+ program,
49
+ before_all: -> { "BEGIN { $SiB.file_loaded };" },
50
+ before_each: -> line_number {
51
+ inspect = "$SiB.record_result(:inspect, #{line_number}, ("
52
+ pp = "$SiB.record_result(:pp, #{line_number}, ("
53
+
54
+ should_inspect = inspect_linenos.include? line_number
55
+ should_pp = pp_linenos.include? line_number
56
+
57
+ if should_inspect && should_pp then "#{pp}#{inspect}"
58
+ elsif should_inspect then inspect
59
+ elsif should_pp then pp
60
+ else ""
61
+ end
62
+ },
63
+ after_each: -> line_number {
64
+ # 74 b/c pretty print_defaults to 79 (guessing 80 chars with 1 reserved for newline), and
65
+ # 79 - "# => ".length # => 4
66
+ # ALSO: This should be configurable, b/c otherwise you have to go into the guts of `pp`
67
+ # https://gist.github.com/JoshCheek/6472c8f334ae493f4ab1f7865e2470e5
68
+ inspect = ")) { |v| v.inspect }"
69
+ pp = ")) { |v| PP.pp v, '', 74 }"
70
+
71
+ should_inspect = inspect_linenos.include? line_number
72
+ should_pp = pp_linenos.include? line_number
73
+
74
+ if should_inspect && should_pp then "#{inspect}#{pp}"
75
+ elsif should_inspect then inspect
76
+ elsif should_pp then pp
77
+ else ""
78
+ end
79
+ }
80
+ end
81
+ end
82
+
83
+ def self.call(body, results, options)
84
+ new(body, results, options).call
85
+ end
86
+
87
+ def initialize(body, results, options={})
88
+ @options = options
89
+ @body = body
90
+ @results = results
91
+ @interline_align = InterlineAlign.new(results)
92
+ end
93
+
94
+ # seems like maybe this should respect the alignment strategy (not what xmpfilter does, but there are other ways I'd like to deviate anyway)
95
+ # and we should just add a new alignment strategy for default xmpfilter style
96
+ def call
97
+ @new_body ||= begin
98
+ require 'seeing_is_believing/binary/rewrite_comments'
99
+ require 'seeing_is_believing/binary/format_comment'
100
+ include_lines = []
101
+ exception_results = {}
102
+
103
+ @results.exceptions.each do |exception|
104
+ exception_results[exception.line_number] =
105
+ sprintf "%s: %s", exception.class_name, exception.message.gsub("\n", '\n')
106
+ include_lines << exception.line_number
107
+ end
108
+
109
+ _, pp_map = self.class.map_markers_to_linenos(@body, @options[:markers])
110
+ new_body = RewriteComments.call @body, include_lines: include_lines do |comment|
111
+ exception_result = exception_results[comment.line_number]
112
+ annotate_this_line = comment.text[value_regex]
113
+ pp_annotation = annotate_this_line && comment.whitespace_col.zero?
114
+ normal_annotation = annotate_this_line && !pp_annotation
115
+ if exception_result && annotate_this_line
116
+ [comment.whitespace, FormatComment.call(comment.text_col, value_prefix, exception_result, @options)]
117
+ elsif exception_result && comment.text.empty?
118
+ whitespace = comment.whitespace
119
+ whitespace = " " if whitespace.empty?
120
+ [whitespace, FormatComment.call(0, exception_prefix, exception_result, @options)]
121
+ elsif normal_annotation
122
+ if @options[:interline_align]
123
+ annotation = @interline_align.call comment.line_number, @results[comment.line_number].map { |result| result.gsub "\n", '\n' }
124
+ [comment.whitespace, FormatComment.call(comment.text_col, value_prefix, annotation, @options)]
125
+ else
126
+ annotation = @results[comment.line_number].map { |result| result.gsub "\n", '\n' }.join(', ')
127
+ [comment.whitespace, FormatComment.call(comment.text_col, value_prefix, annotation, @options)]
128
+ end
129
+ elsif pp_annotation
130
+ result = @results[pp_map[comment.line_number], :pp]
131
+ annotation = result.map { |result| result.chomp }.join("\n,") # ["1\n2", "1\n2", ...
132
+ swap_leading_whitespace_in_multiline_comment(annotation)
133
+ comment_lines = annotation.each_line.map.with_index do |comment_line, result_offest|
134
+ if result_offest == 0
135
+ FormatComment.call(comment.whitespace_col, value_prefix, comment_line.chomp, @options)
136
+ else
137
+ leading_whitespace = " " * comment.text_col
138
+ leading_whitespace << FormatComment.call(comment.whitespace_col, nextline_prefix, comment_line.chomp, @options)
139
+ end
140
+ end
141
+ comment_lines = [value_prefix.rstrip] if comment_lines.empty?
142
+ [comment.whitespace, comment_lines.join("\n")]
143
+ else
144
+ [comment.whitespace, comment.text]
145
+ end
146
+ end
147
+
148
+ require 'seeing_is_believing/binary/annotate_end_of_file'
149
+ AnnotateEndOfFile.add_stdout_stderr_and_exceptions_to new_body, @results, @options
150
+
151
+ new_body
152
+ end
153
+ end
154
+
155
+ def value_prefix
156
+ @value_prefix ||= @options[:markers][:value][:prefix]
157
+ end
158
+
159
+ def nextline_prefix
160
+ @nextline_prefix ||= ('#' + ' '*value_prefix.length.pred)
161
+ end
162
+
163
+ def exception_prefix
164
+ @exception_prefix ||= @options[:markers][:exception][:prefix]
165
+ end
166
+
167
+ def value_regex
168
+ @value_regex ||= @options[:markers][:value][:regex]
169
+ end
170
+
171
+ def swap_leading_whitespace_in_multiline_comment(comment)
172
+ return if comment.scan("\n").size < 2
173
+ return if comment[0] =~ /\S/
174
+ nonbreaking_space = " "
175
+ comment[0] = nonbreaking_space
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,36 @@
1
+ require 'seeing_is_believing/binary/commentable_lines'
2
+
3
+ class SeeingIsBelieving
4
+ module Binary
5
+ # takes a body and a block
6
+ # passes the block the line
7
+ # the block returns the comment to add at the end of it
8
+ class CommentLines
9
+ def self.call(raw_code, &commenter)
10
+ new(raw_code, &commenter).call
11
+ end
12
+
13
+ def initialize(raw_code, &commenter)
14
+ self.raw_code, self.commenter = raw_code, commenter
15
+ end
16
+
17
+ def call
18
+ @call ||= begin
19
+ commentable_lines = CommentableLines.new raw_code
20
+ commentable_lines.call.each do |line_number, (index_of_newline, _col)|
21
+ first_index = last_index = index_of_newline
22
+ first_index -= 1 while first_index > 0 && raw_code[first_index-1] != "\n"
23
+ comment_text = commenter.call raw_code[first_index...last_index], line_number
24
+ range = Parser::Source::Range.new(commentable_lines.buffer, first_index, last_index)
25
+ commentable_lines.rewriter.insert_after range, comment_text
26
+ end
27
+ commentable_lines.rewriter.process
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_accessor :raw_code, :commenter
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,126 @@
1
+ require 'seeing_is_believing/code'
2
+
3
+ class SeeingIsBelieving
4
+ module Binary
5
+
6
+ class CommentableLines
7
+ def self.call(code)
8
+ new(code).call
9
+ end
10
+
11
+ def initialize(code)
12
+ self.code = code
13
+ self.code_obj = Code.new(code, 'finding_commentable_lines')
14
+ end
15
+
16
+ def call
17
+ @call ||= begin
18
+ line_num_to_location = line_nums_to_last_index_and_col(buffer)
19
+ remove_lines_after_data_segment line_num_to_location
20
+ remove_lines_whose_newline_is_escaped line_num_to_location
21
+ remove_lines_ending_in_comments line_num_to_location, code_obj.raw_comments
22
+ remove_lines_inside_of_strings_and_things line_num_to_location, root
23
+ line_num_to_location
24
+ end
25
+ end
26
+
27
+ def buffer
28
+ code_obj.buffer
29
+ end
30
+
31
+ def rewriter
32
+ code_obj.rewriter
33
+ end
34
+
35
+ private
36
+
37
+ attr_writer :buffer, :rewriter
38
+ attr_accessor :code, :code_obj
39
+
40
+ def root
41
+ code_obj.root
42
+ end
43
+
44
+ def line_nums_to_last_index_and_col(buffer)
45
+ code.each_char
46
+ .with_index
47
+ .select { |char, index| char == "\n" }
48
+ .each_with_object(Hash.new) do |(_, index), hash|
49
+ line, col = buffer.decompose_position index
50
+ hash[line] = [index, col]
51
+ end
52
+ end
53
+
54
+ def remove_lines_whose_newline_is_escaped(line_num_to_location)
55
+ ors_indexes = code_obj.indexes_of_ors_at_eol
56
+ line_num_to_location
57
+ .select { |line_number, (index_of_newline, _col)|
58
+ code[index_of_newline-1] == '\\'
59
+ }
60
+ .reject { |line_number, (index_of_newline, _col)|
61
+ ors_indexes.include? index_of_newline
62
+ }
63
+ .each { |line_number, (_index_of_newline, _col)|
64
+ line_num_to_location.delete line_number
65
+ }
66
+ end
67
+
68
+ def remove_lines_ending_in_comments(line_num_to_location, comments)
69
+ comments.each do |comment|
70
+ if comment.type == :inline
71
+ line_num_to_location.delete comment.location.line
72
+ else
73
+ begin_pos = comment.location.expression.begin_pos
74
+ end_pos = comment.location.expression.end_pos
75
+ range = begin_pos...end_pos
76
+ line_num_to_location.select { |line_number, (index_of_newline, _col)| range.include? index_of_newline }
77
+ .each { |line_number, (_index_of_newline, _col)| line_num_to_location.delete line_number }
78
+ end
79
+ end
80
+ end
81
+
82
+ def remove_lines_inside_of_strings_and_things(line_num_to_location, ast)
83
+ invalid_boundaries = ranges_of_atomic_expressions ast, []
84
+ invalid_boundaries.each do |invalid_boundary|
85
+ line_num_to_location.select { |line_number, (index_of_newline, _col)| invalid_boundary.include? index_of_newline }
86
+ .each { |line_number, (_index_of_newline, _col)| line_num_to_location.delete line_number }
87
+ end
88
+ end
89
+
90
+ def ranges_of_atomic_expressions(ast, found_ranges)
91
+ return found_ranges unless ast.kind_of? ::AST::Node
92
+ if no_comment_zone?(ast) && code_obj.heredoc?(ast)
93
+ begin_pos = ast.location.heredoc_body.begin_pos
94
+ end_pos = ast.location.heredoc_end.end_pos.next
95
+ found_ranges << (begin_pos...end_pos)
96
+ elsif no_comment_zone? ast
97
+ begin_pos = ast.location.expression.begin.begin_pos
98
+ end_pos = ast.location.expression.end.end_pos
99
+ found_ranges << (begin_pos...end_pos)
100
+ else
101
+ ast.children.each { |child| ranges_of_atomic_expressions child, found_ranges }
102
+ end
103
+ found_ranges
104
+ end
105
+
106
+ def no_comment_zone?(ast)
107
+ case ast.type
108
+ when :dstr, :str, :xstr, :regexp
109
+ true
110
+ when :array
111
+ the_begin = ast.location.begin
112
+ the_begin && the_begin.source =~ /\A%/
113
+ else
114
+ false
115
+ end
116
+ end
117
+
118
+ def remove_lines_after_data_segment(line_num_to_location)
119
+ end_index = code_obj.body_range.end_pos
120
+ body_end = code_obj.index_to_linenum end_index
121
+ file_end = line_num_to_location.keys.max
122
+ body_end.upto(file_end) { |line_number| line_num_to_location.delete line_number }
123
+ end
124
+ end
125
+ end
126
+ end