seeing_is_believing 3.0.0.beta.1 → 3.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c4baad8edd542826d976fb5439fe4523724e25c2
4
- data.tar.gz: 806d6b1cb7b43dc168c7653ff9995b3dd1b37220
3
+ metadata.gz: 981bfb703701767923dee2010cd2073ec901e20f
4
+ data.tar.gz: 0eaa0290fac0e7b0b312196371507f717b2ad1d7
5
5
  SHA512:
6
- metadata.gz: 74cde1ec9bd35565d7e49110210fadc6e8b9eb01538e78411138babf50abf5bc299a6cb592e3e7cc0d873c97c5549d8ddb5aa0a6a3e2eb07dc76130f433f077c
7
- data.tar.gz: 57811be453dd99be97df3c4ed6a17b7a0329178776c2bce14a4247f13d51acac04f1d9ccfc9930a7ae55983b2f285e0ad6c351c703041cab3dafa071e09f8941
6
+ metadata.gz: 019b0ac6587cb94c98bc9e916daa49f8b21e6eba166a3a6c5e930de573ecce274004f11de8cd47e366af46c4da881376b05c4ef4d3a8a43a5e0c614517a53d75
7
+ data.tar.gz: d515e9959c12741755f0ff298a5f571d8860290a15f3548e3386ae7ef8b4b9c0364ccac1c7a7816a771fc9a100746f813e259dce2cac943d01c6f8d70510dc1d
@@ -3,6 +3,12 @@ Feature: Xmpfilter style
3
3
  Support the same (or highly similar) interface as xmpfilter,
4
4
  so that people who use that lib can easily transition to SiB.
5
5
 
6
+ TODO:
7
+ * multiple values on pp lines
8
+ * show that exceptions respect line-length restriction flags
9
+ * Scenario: pp output on line with exception
10
+ * when input has previously identified exception
11
+
6
12
 
7
13
  Scenario: --xmpfilter-style Generic updating of marked lines
8
14
  Given the file "magic_comments.rb":
@@ -18,6 +24,7 @@ Feature: Xmpfilter style
18
24
  # =>
19
25
  "omg2"
20
26
  # => "not omg2"
27
+ 3+3#=>
21
28
  """
22
29
  When I run "seeing_is_believing --xmpfilter-style magic_comments.rb"
23
30
  Then stderr is empty
@@ -35,6 +42,7 @@ Feature: Xmpfilter style
35
42
  # => "omg"
36
43
  "omg2"
37
44
  # => "omg2"
45
+ 3+3# => 6
38
46
  """
39
47
 
40
48
 
@@ -101,42 +109,44 @@ Feature: Xmpfilter style
101
109
  """
102
110
 
103
111
 
104
- @josh1
105
112
  Scenario: Errors on annotated lines
106
113
  Given the file "xmpfilter_error_on_annotated_line.rb":
107
114
  """
108
- raise "omg" # =>
115
+ raise "ZOMG\n!!!!" # =>
109
116
  """
110
117
  When I run "seeing_is_believing --xmpfilter-style xmpfilter_error_on_annotated_line.rb"
111
118
  Then stderr is empty
112
119
  And the exit status is 1
113
120
  Then stdout is:
114
121
  """
115
- raise "omg" # => # ~> RuntimeError: ZOMG\n!!!!
122
+ raise "ZOMG\n!!!!" # => RuntimeError: ZOMG\n!!!!
116
123
 
117
124
  # ~> RuntimeError
118
- # ~> omg
125
+ # ~> ZOMG
126
+ # ~> !!!!
119
127
  # ~>
120
128
  # ~> xmpfilter_error_on_annotated_line.rb:1:in `<main>'
121
129
  """
122
130
 
123
131
 
124
- @josh2
125
132
  Scenario: Errors on unannotated lines
126
- Given the file "xmpfilter_error_on_annotated_line.rb":
133
+ Given the file "xmpfilter_error_on_unannotated_line.rb":
127
134
  """
128
- raise "omg"
135
+ raise "ZOMG\n!!!!"
129
136
  """
130
- When I run "seeing_is_believing --xmpfilter-style xmpfilter_error_on_annotated_line.rb"
137
+ When I run "seeing_is_believing --xmpfilter-style xmpfilter_error_on_unannotated_line.rb"
131
138
  Then stderr is empty
132
139
  And the exit status is 1
133
140
  Then stdout is:
134
141
  """
135
- raise "omg" # =>
136
- """
142
+ raise "ZOMG\n!!!!" # ~> RuntimeError: ZOMG\n!!!!
137
143
 
138
-
139
- Scenario: pp output on line with exception
144
+ # ~> RuntimeError
145
+ # ~> ZOMG
146
+ # ~> !!!!
147
+ # ~>
148
+ # ~> xmpfilter_error_on_unannotated_line.rb:1:in `<main>'
149
+ """
140
150
 
141
151
 
142
152
  Scenario: Cleaning previous output
@@ -34,7 +34,7 @@ class SeeingIsBelieving
34
34
  end
35
35
 
36
36
  if options.print_cleaned?
37
- stdout.print RemoveAnnotations.call(options.prepared_body, true, options.markers)
37
+ stdout.print RemoveAnnotations.call(options.prepared_body, true, options.marker_regexes)
38
38
  return SUCCESS_STATUS
39
39
  end
40
40
 
@@ -1,12 +1,12 @@
1
1
  class SeeingIsBelieving
2
2
  module Binary
3
3
  class AnnotateEveryLine
4
- def self.prepare_body(uncleaned_body, markers)
4
+ def self.prepare_body(uncleaned_body, marker_regexes)
5
5
  require 'seeing_is_believing/binary/remove_annotations'
6
- RemoveAnnotations.call uncleaned_body, true, markers
6
+ RemoveAnnotations.call uncleaned_body, true, marker_regexes
7
7
  end
8
8
 
9
- def self.expression_wrapper(markers)
9
+ def self.expression_wrapper(markers, marker_regexes)
10
10
  require 'seeing_is_believing/inspect_expressions'
11
11
  InspectExpressions
12
12
  end
@@ -45,7 +45,7 @@ class SeeingIsBelieving
45
45
  AnnotateEndOfFile.add_stdout_stderr_and_exceptions_to new_body, @results, @options
46
46
 
47
47
  # What's w/ this debugger? maybe this should move higher?
48
- @options[:debugger].context "OUTPUT"
48
+ @options.fetch(:debugger).context "OUTPUT"
49
49
  new_body
50
50
  end
51
51
  end
@@ -53,11 +53,11 @@ class SeeingIsBelieving
53
53
  private
54
54
 
55
55
  def value_marker
56
- @value_marker ||= @options[:markers][:value]
56
+ @value_marker ||= @options.fetch(:markers).fetch(:value)
57
57
  end
58
58
 
59
59
  def exception_marker
60
- @exception_marker ||= @options[:markers][:exception]
60
+ @xnextline_marker ||= @options.fetch(:markers).fetch(:exception)
61
61
  end
62
62
  end
63
63
  end
@@ -3,34 +3,17 @@ require 'seeing_is_believing/code'
3
3
  class SeeingIsBelieving
4
4
  module Binary
5
5
  class AnnotateXmpfilterStyle
6
- def self.prepare_body(uncleaned_body, markers)
7
- # TODO: There's definitely a lot of overlap in responsibilities with invoking of parser
8
- # and this is a conspicuous hack, since this functionality should really be provided by RemoveAnnotations
9
- code = Code.new(uncleaned_body)
10
- code.inline_comments
11
- .select { |c| c.whitespace_col == 0 } # TODO: Would be nice to support indentation here
12
- .slice_before { |c| c.text.start_with? markers[:value] }
13
- .flat_map { |cs|
14
- consecutives = cs.each_cons(2).take_while { |c1, c2| c1.line_number.next == c2.line_number }
15
- cs[1, consecutives.size]
16
- }
17
- .select { |c| c.text.start_with? markers[:nextline] }
18
- .each { |c|
19
- range_with_preceding_newline = code.range_for(c.comment_range.begin_pos.pred, c.comment_range.end_pos)
20
- code.rewriter.remove range_with_preceding_newline
21
- }
22
- partially_cleaned_body = code.rewriter.process
23
-
6
+ def self.prepare_body(uncleaned_body, marker_regexes)
24
7
  require 'seeing_is_believing/binary/remove_annotations'
25
- RemoveAnnotations.call partially_cleaned_body, false, markers
8
+ RemoveAnnotations.call uncleaned_body, false, marker_regexes
26
9
  end
27
10
 
28
- def self.expression_wrapper(markers)
11
+ def self.expression_wrapper(markers, marker_regexes)
29
12
  -> program, number_of_captures {
30
13
  inspect_linenos = []
31
14
  pp_linenos = []
32
15
  Code.new(program).inline_comments.each do |c|
33
- next unless c.text.start_with? markers[:value].sub(/\s+$/, '')
16
+ next unless c.text[marker_regexes[:value]]
34
17
  c.whitespace_col == 0 ? pp_linenos << c.line_number - 1
35
18
  : inspect_linenos << c.line_number
36
19
  end
@@ -73,14 +56,33 @@ class SeeingIsBelieving
73
56
  def call
74
57
  @new_body ||= begin
75
58
  # TODO: doesn't currently realign output markers, do we want to do that?
76
- require 'seeing_is_believing/binary' # defines the markers
77
59
  require 'seeing_is_believing/binary/rewrite_comments'
78
60
  require 'seeing_is_believing/binary/comment_formatter'
79
- exception_lineno = @results.has_exception? ? @results.exception.line_number : -1
80
- new_body = RewriteComments.call @body do |comment|
81
- if !comment.text[value_regex]
82
- [comment.whitespace, comment.text]
83
- elsif comment.whitespace_col == 0
61
+ always_rewrite = []
62
+
63
+ if @results.has_exception?
64
+ exception_result = sprintf "%s: %s", @results.exception.class_name, @results.exception.message.gsub("\n", '\n')
65
+ exception_lineno = @results.exception.line_number
66
+ always_rewrite << exception_lineno
67
+ end
68
+
69
+ new_body = RewriteComments.call @body, always_rewrite: always_rewrite do |comment|
70
+ exception_on_line = exception_lineno == comment.line_number
71
+ annotate_this_line = comment.text[value_regex]
72
+ pp_annotation = annotate_this_line && comment.whitespace_col.zero?
73
+ normal_annotation = annotate_this_line && !pp_annotation
74
+ if exception_on_line && annotate_this_line
75
+ [comment.whitespace, CommentFormatter.call(comment.text_col, value_marker, exception_result, @options)]
76
+ elsif exception_on_line
77
+ whitespace = comment.whitespace
78
+ whitespace = " " if whitespace.empty?
79
+ [whitespace, CommentFormatter.call(comment.line_number, exception_marker, exception_result, @options)]
80
+ elsif normal_annotation
81
+ result = @results[comment.line_number].map { |result| result.gsub "\n", '\n' }.join(', ')
82
+ [comment.whitespace, CommentFormatter.call(comment.text_col, value_marker, result, @options)]
83
+ elsif pp_annotation
84
+ # result = sprintf "%s: %s", @results.exception.class_name, @results.exception.message.gsub("\n", '\n')
85
+ # CommentFormatter.call(line.size, exception_marker, result, options)
84
86
  # TODO: check that having multiple mult-line output values here looks good (e.g. avdi's example in a loop)
85
87
  result = @results[comment.line_number-1, :pp].map { |result| result.chomp }.join(', ')
86
88
  comment_lines = result.each_line.map.with_index do |comment_line, result_offest|
@@ -92,41 +94,33 @@ class SeeingIsBelieving
92
94
  end
93
95
  [comment.whitespace, comment_lines.join("\n")]
94
96
  else
95
- result = @results[comment.line_number].map { |result| result.gsub "\n", '\n' }.join(', ')
96
- [comment.whitespace, CommentFormatter.call(comment.text_col, value_marker, result, @options)]
97
+ [comment.whitespace, comment.text]
97
98
  end
98
99
  end
99
100
 
100
- # if exception_lineno == line_number
101
- # if comment.text[value_regex] # has exception and comment
102
- # # '# => # ~> exception...'
103
- # [comment.whitespace, comment.text]
104
- # else # exception, no comment
105
- # # NORMAL EXCEPTION
106
- # # result = @results[line_number].map { |result| result.gsub "\n", '\n' }.join(', ')
107
- # # CommentFormatter.call(line.size, value_marker, result, options)
108
- # # [comment.whitespace, comment.text]
109
- # end
110
-
111
101
  require 'seeing_is_believing/binary/annotate_end_of_file'
112
102
  AnnotateEndOfFile.add_stdout_stderr_and_exceptions_to new_body, @results, @options
113
103
 
114
104
  # What's w/ this debugger? maybe this should move higher?
115
- @options[:debugger].context "OUTPUT"
105
+ @options.fetch(:debugger).context "OUTPUT"
116
106
  new_body
117
107
  end
118
108
  end
119
109
 
120
110
  def value_marker
121
- @value_marker ||= @options[:markers][:value]
111
+ @value_marker ||= @options.fetch(:markers).fetch(:value)
122
112
  end
123
113
 
124
114
  def nextline_marker
125
- @xnextline_marker ||= @options[:markers][:nextline]
115
+ @xnextline_marker ||= ('#' + ' '*value_marker.size.pred)
116
+ end
117
+
118
+ def exception_marker
119
+ @exception_marker ||= @options.fetch(:markers).fetch(:exception)
126
120
  end
127
121
 
128
122
  def value_regex
129
- @value_regex ||= /\A#{value_marker.sub(/\s+$/, '')}/
123
+ @value_regex ||= @options.fetch(:marker_regexes).fetch(:value)
130
124
  end
131
125
  end
132
126
  end
@@ -17,6 +17,14 @@ require 'seeing_is_believing/binary/annotate_xmpfilter_style'
17
17
  class SeeingIsBelieving
18
18
  module Binary
19
19
  class InterpretFlags
20
+ def self.to_regex(string)
21
+ flag_to_bit = {'i' => 0b001, 'x' => 0b010, 'm' => 0b100}
22
+ string =~ %r{\A/(.*)/([mxi]*)\Z}
23
+ Regexp.new ($1||string),
24
+ ($2||"").each_char.inject(0) { |bits, flag| bits|flag_to_bit[flag] }
25
+ end
26
+
27
+
20
28
  def self.attr_predicate(name)
21
29
  define_method("#{name}?") { predicates.fetch name }
22
30
  end
@@ -35,6 +43,7 @@ class SeeingIsBelieving
35
43
  attr_attribute :help_screen
36
44
  attr_attribute :debugger
37
45
  attr_attribute :markers
46
+ attr_attribute :marker_regexes
38
47
  attr_attribute :timeout
39
48
  attr_attribute :shebang
40
49
  attr_attribute :filename
@@ -47,11 +56,12 @@ class SeeingIsBelieving
47
56
  def initialize(flags, stdin, stdout)
48
57
  # Some simple attributes
49
58
  self.attributes = {}
50
- attributes[:errors] = flags.fetch(:errors)
51
- attributes[:markers] = flags.fetch(:markers) # TODO: Should probably object-ify these
52
- attributes[:timeout] = flags.fetch(:timeout) # b/c binary prints this out in the error message TODO: rename seconds_until_timeout
53
- attributes[:shebang] = flags.fetch(:shebang) # b/c binary uses this to validate syntax atm
54
- attributes[:filename] = flags.fetch(:filename)
59
+ attributes[:errors] = flags.fetch(:errors)
60
+ attributes[:markers] = flags.fetch(:markers) # TODO: Should probably object-ify these
61
+ attributes[:marker_regexes] = flags.fetch(:marker_regexes).each_with_object({}) { |(k, v), rs| rs[k] = self.class.to_regex v }
62
+ attributes[:timeout] = flags.fetch(:timeout) # b/c binary prints this out in the error message TODO: rename seconds_until_timeout
63
+ attributes[:shebang] = flags.fetch(:shebang) # b/c binary uses this to validate syntax atm
64
+ attributes[:filename] = flags.fetch(:filename)
55
65
 
56
66
  # All predicates
57
67
  self.predicates = {}
@@ -74,7 +84,7 @@ class SeeingIsBelieving
74
84
  String.new
75
85
 
76
86
  # Attributes that depend on predicates
77
- attributes[:prepared_body] = body && annotator.prepare_body(body, markers)
87
+ attributes[:prepared_body] = body && annotator.prepare_body(body, marker_regexes)
78
88
 
79
89
  # The lib's options (passed to SeeingIsBelieving.new)
80
90
  attributes[:lib_options] = {
@@ -88,7 +98,7 @@ class SeeingIsBelieving
88
98
  timeout: timeout,
89
99
  debugger: debugger,
90
100
  number_of_captures: flags.fetch(:number_of_captures), # TODO: Rename to max_number_of_captures
91
- record_expressions: annotator.expression_wrapper(markers), # TODO: rename to wrap_expressions
101
+ record_expressions: annotator.expression_wrapper(markers, marker_regexes), # TODO: rename to wrap_expressions
92
102
  }
93
103
 
94
104
  # The annotator's options (passed to annotator.call)
@@ -96,6 +106,7 @@ class SeeingIsBelieving
96
106
  alignment_strategy: extract_alignment_strategy(flags.fetch(:alignment_strategy), errors),
97
107
  debugger: debugger,
98
108
  markers: markers,
109
+ marker_regexes: marker_regexes,
99
110
  max_line_length: flags.fetch(:max_line_length),
100
111
  max_result_length: flags.fetch(:max_result_length),
101
112
  }
@@ -10,7 +10,15 @@ class SeeingIsBelieving
10
10
  exception: '# ~> ',
11
11
  stdout: '# >> ',
12
12
  stderr: '# !> ',
13
- nextline: '# ',
13
+ }
14
+ end
15
+
16
+ # TODO: rename to default_marker_regexes ...or turn into fkn objects
17
+ def self.marker_regexes
18
+ { value: '#\s*=>\s*',
19
+ exception: '#\s*~>\s*',
20
+ stdout: '#\s*>>\s*',
21
+ stderr: '#\s*!>\s*',
14
22
  }
15
23
  end
16
24
 
@@ -88,6 +96,7 @@ class SeeingIsBelieving
88
96
  shebang: 'ruby',
89
97
  result_as_json: false,
90
98
  markers: self.class.default_markers,
99
+ marker_regexes: self.class.marker_regexes,
91
100
  short_help_screen: self.class.help_screen(false),
92
101
  long_help_screen: self.class.help_screen(true),
93
102
  safe: false,
@@ -133,7 +142,6 @@ class SeeingIsBelieving
133
142
  exception_marker = markers.fetch(:exception)
134
143
  stdout_marker = markers.fetch(:stdout)
135
144
  stderr_marker = markers.fetch(:stderr)
136
- nextline_marker = markers.fetch(:nextline)
137
145
 
138
146
  <<FLAGS + if include_examples then <<EXAMPLES else '' end
139
147
  Usage: seeing_is_believing [options] [filename]
@@ -3,60 +3,53 @@ require 'seeing_is_believing/parser_helpers' # We have to parse the file to find
3
3
 
4
4
  class SeeingIsBelieving
5
5
  module Binary
6
+ # TODO: might be here that we hit the issue where
7
+ # you sometimes have to run it 2x to get it to correctly reset whitespace
8
+ # should wipe out the full_range rather than just the comment_range
6
9
  class RemoveAnnotations
7
- def self.call(code, should_clean_values, markers)
8
- new(code, should_clean_values, markers).call
10
+ def self.call(raw_code, should_clean_values, markers)
11
+ new(raw_code, should_clean_values, markers).call
9
12
  end
10
13
 
11
- def initialize(code, should_clean_values, markers)
14
+ def initialize(raw_code, should_clean_values, markers)
12
15
  self.should_clean_values = should_clean_values
13
- self.code = code
14
- self.markers = markers
16
+ self.raw_code = raw_code
17
+ self.markers = markers # TECHNICALLY THESE ARE REGEXES RIGHT NOW
18
+ self.code = Code.new(raw_code, 'strip_comments')
15
19
  end
16
20
 
17
21
  def call
18
- code_obj = Code.new(code, 'strip_comments')
19
- removed_comments = { result: [], exception: [], stdout: [], stderr: [] }
22
+ annotation_chunks_in(code).each do |comment, rest|
23
+ rest.each { |comment|
24
+ code.rewriter.remove comment.comment_range
25
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, false
26
+ }
20
27
 
21
- # TODO: This is why you sometimes have to run it 2x to get it to correctly reset whitespace
22
- # should wipe out the full_range rather than just the comment_range
23
- code_obj.inline_comments.each do |comment|
24
28
  case comment.text
25
29
  when value_regex
26
- if should_clean_values
27
- # puts "REMOVING VALUE: #{comment.text}"
28
- removed_comments[:result] << comment
29
- code_obj.rewriter.remove comment.comment_range
30
- end
30
+ next unless should_clean_values
31
+ code.rewriter.remove comment.comment_range
32
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, false
31
33
  when exception_regex
32
- # puts "REMOVING EXCEPTION: #{comment.text}"
33
- removed_comments[:exception] << comment
34
- code_obj.rewriter.remove comment.comment_range
34
+ code.rewriter.remove comment.comment_range
35
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, true
35
36
  when stdout_regex
36
- # puts "REMOVING STDOUT: #{comment.text}"
37
- removed_comments[:stdout] << comment
38
- code_obj.rewriter.remove comment.comment_range
37
+ code.rewriter.remove comment.comment_range
38
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, true
39
39
  when stderr_regex
40
- # puts "REMOVING STDERR: #{comment.text}"
41
- removed_comments[:stderr] << comment
42
- code_obj.rewriter.remove comment.comment_range
40
+ code.rewriter.remove comment.comment_range
41
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, true
42
+ else
43
+ raise "This should be impossible! Something must be broken in the comment section above"
43
44
  end
44
45
  end
45
46
 
46
- remove_whitespace_preceding_comments(code_obj.buffer, code_obj.rewriter, removed_comments)
47
- code_obj.rewriter.process
47
+ code.rewriter.process
48
48
  end
49
49
 
50
50
  private
51
51
 
52
- attr_accessor :code, :should_clean_values, :buffer, :markers
53
-
54
- def remove_whitespace_preceding_comments(buffer, rewriter, removed_comments)
55
- removed_comments[:result].each { |comment| remove_whitespace_before comment.comment_range.begin_pos, buffer, rewriter, false }
56
- removed_comments[:exception].each { |comment| remove_whitespace_before comment.comment_range.begin_pos, buffer, rewriter, true }
57
- removed_comments[:stdout].each { |comment| remove_whitespace_before comment.comment_range.begin_pos, buffer, rewriter, true }
58
- removed_comments[:stderr].each { |comment| remove_whitespace_before comment.comment_range.begin_pos, buffer, rewriter, true }
59
- end
52
+ attr_accessor :raw_code, :should_clean_values, :markers, :code
60
53
 
61
54
  # any whitespace before the index (on the same line) will be removed
62
55
  # if the preceding whitespace is at the beginning of the line, the newline will be removed
@@ -64,31 +57,57 @@ class SeeingIsBelieving
64
57
  def remove_whitespace_before(index, buffer, rewriter, remove_preceding_newline)
65
58
  end_pos = index
66
59
  begin_pos = end_pos - 1
67
- begin_pos -= 1 while code[begin_pos] =~ /\s/ && code[begin_pos] != "\n"
68
- begin_pos -= 1 if code[begin_pos] == "\n"
69
- begin_pos -= 1 if code[begin_pos] == "\n" && remove_preceding_newline
60
+ begin_pos -= 1 while raw_code[begin_pos] =~ /\s/ && raw_code[begin_pos] != "\n"
61
+ begin_pos -= 1 if raw_code[begin_pos] == "\n"
62
+ begin_pos -= 1 if raw_code[begin_pos] == "\n" && remove_preceding_newline
70
63
  return if begin_pos.next == end_pos
71
64
  rewriter.remove Parser::Source::Range.new(buffer, begin_pos.next, end_pos)
72
65
  end
73
66
 
67
+ def annotation_chunks_in(code)
68
+ code
69
+ .inline_comments
70
+ .map { |comment| [ (comment.text[value_regex] || # associates each comment to its annotation
71
+ comment.text[exception_regex] ||
72
+ comment.text[stdout_regex] ||
73
+ comment.text[stderr_regex]
74
+ ),
75
+ comment]}
76
+ .slice_before { |annotation, comment| annotation } # annotatios begin chunks
77
+ .select { |(annotation, start), *| annotation } # discard chunks not beginning with an annotation (probably can only happens on first comment)
78
+ .map { |(annotation, start), *rest| # end the chunk if the comment doesn't meet nextline criteria
79
+ nextline_comments = []
80
+ prev = start
81
+ rest.each { |_, potential_nextline|
82
+ break unless prev.line_number.next == potential_nextline.line_number &&
83
+ start.text_col == potential_nextline.text_col &&
84
+ potential_nextline.whitespace_col.zero? &&
85
+ annotation.length <= potential_nextline.text[/#\s*/].length
86
+ prev = potential_nextline
87
+ nextline_comments << potential_nextline
88
+ }
89
+ [start, nextline_comments]
90
+ }
91
+ end
92
+
74
93
  def value_regex
75
- @value_regex ||= marker_to_regex markers[:value]
94
+ markers.fetch(:value)
76
95
  end
77
96
 
78
97
  def exception_regex
79
- @exception_regex ||= marker_to_regex markers[:exception]
98
+ markers.fetch(:exception)
80
99
  end
81
100
 
82
101
  def stdout_regex
83
- @stdout_regex ||= marker_to_regex markers[:stdout]
102
+ markers.fetch(:stdout)
84
103
  end
85
104
 
86
105
  def stderr_regex
87
- @stderr_regex ||= marker_to_regex markers[:stderr]
106
+ markers.fetch(:stderr)
88
107
  end
89
108
 
90
- def marker_to_regex(marker)
91
- /\A#{Regexp.escape marker.sub(/\s+$/, '')}/
109
+ def nextline_regex
110
+ markers.fetch(:nextline)
92
111
  end
93
112
  end
94
113
  end
@@ -3,15 +3,61 @@ require 'seeing_is_believing/code'
3
3
  class SeeingIsBelieving
4
4
  module Binary
5
5
  module RewriteComments
6
- def self.call(code, &mapping)
7
- code = Code.new(code)
8
- code.inline_comments.each do |comment|
6
+ def self.call(code, options={}, &mapping)
7
+ code = Code.new(code)
8
+ comments = code.inline_comments
9
+ buffer = code.buffer
10
+
11
+ comments.each do |comment|
9
12
  new_whitespace, new_comment = mapping.call comment
10
13
  code.rewriter.replace comment.whitespace_range, new_whitespace
11
14
  code.rewriter.replace comment.comment_range, new_comment
12
15
  end
16
+
17
+ line_begins = line_begins_for(buffer.source)
18
+ options.fetch(:always_rewrite, []).each { |line_number|
19
+ next if comments.any? { |c| c.line_number == line_number }
20
+
21
+ # TODO: can this move down into Code?
22
+ _, next_line_index = (line_begins.find { |ln, index| ln == line_number } || [nil, buffer.source.size.next])
23
+ col = 0
24
+ col += 1 until col == next_line_index || buffer.source[next_line_index-2-col] == "\n"
25
+
26
+ index = next_line_index - 1
27
+ range = Parser::Source::Range.new buffer, index, index
28
+
29
+ comment = Code::InlineComment.new line_number, # line_number,
30
+ col, # preceding_whitespace_range.column,
31
+ "", # preceding_whitespace,
32
+ col, # comment.location.column,
33
+ "", # comment.text,
34
+ range, # range_for(first_char, comment.location.expression.end_pos),
35
+ range, # preceding_whitespace_range,
36
+ range # comment.location.expression
37
+
38
+ whitespace, body = mapping.call comment
39
+ code.rewriter.insert_before range, "#{whitespace}#{body}"
40
+ }
41
+
13
42
  code.rewriter.process
14
43
  end
44
+
45
+ # TODO: Move down into the Code obj?
46
+ # returns: [[lineno, index], ...]
47
+ def self.line_begins_for(raw_code)
48
+ # Copied from here https://github.com/whitequark/parser/blob/34c40479293bb9b5ba217039cf349111466d1f9a/lib/parser/source/buffer.rb#L213-227
49
+ # I figured it's better to copy it than to violate encapsulation since this is private
50
+ line_begins, index = [ [ 0, 0 ] ], 1
51
+
52
+ raw_code.each_char do |char|
53
+ if char == "\n"
54
+ line_begins.unshift [ line_begins.length, index ]
55
+ end
56
+
57
+ index += 1
58
+ end
59
+ line_begins
60
+ end
15
61
  end
16
62
  end
17
63
  end
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '3.0.0.beta.1'
2
+ VERSION = '3.0.0.beta.2'
3
3
  end
@@ -27,7 +27,7 @@ class SeeingIsBelieving
27
27
 
28
28
  def call
29
29
  @called ||= begin
30
- find_wrappings
30
+ wrap_recursive
31
31
 
32
32
  rewriter.insert_before root_range, before_all.call
33
33
 
@@ -81,19 +81,19 @@ class SeeingIsBelieving
81
81
 
82
82
  def add_children(ast, omit_first = false)
83
83
  (omit_first ? ast.children.drop(1) : ast.children)
84
- .each { |child| find_wrappings child }
84
+ .each { |child| wrap_recursive child }
85
85
  end
86
86
 
87
- def find_wrappings(ast=root)
87
+ # todo: is this actually add_wrappings
88
+ # and add_wrappings is actually add_wrapping?
89
+ def wrap_recursive(ast=root)
88
90
  return wrappings unless ast.kind_of? ::AST::Node
89
-
90
91
  case ast.type
91
92
  when :args, :redo, :retry, :alias, :undef, :splat, :match_current_line
92
93
  # no op
93
94
  when :defs
94
95
  add_to_wrappings ast
95
- child = ast.children.last
96
- add_to_wrappings child if child
96
+ add_children ast, true
97
97
  when :rescue, :ensure, :return, :break, :next
98
98
  add_children ast
99
99
  when :if
@@ -108,10 +108,10 @@ class SeeingIsBelieving
108
108
  add_children ast
109
109
  end
110
110
  when :when, :pair, :class, :module, :sclass
111
- find_wrappings ast.children.last
111
+ wrap_recursive ast.children.last
112
112
  when :resbody
113
113
  exception_type, variable_name, body = ast.children
114
- find_wrappings body
114
+ wrap_recursive body
115
115
  when :array
116
116
  add_to_wrappings ast
117
117
  the_begin = ast.location.begin
@@ -136,7 +136,7 @@ class SeeingIsBelieving
136
136
  # I can't think of anything other than a :send that could be the first child
137
137
  # but I'll check for it anyway.
138
138
  the_send = ast.children[0]
139
- find_wrappings the_send.children.first if the_send.type == :send
139
+ wrap_recursive the_send.children.first if the_send.type == :send
140
140
  add_children ast, true
141
141
  when :masgn
142
142
  # we must look at RHS because [1,<<A] and 1,<<A are both allowed
@@ -24,6 +24,45 @@ class SeeingIsBelieving
24
24
  InterpretFlags.new(flags.merge(overrides), stdin, stdout)
25
25
  end
26
26
 
27
+ describe '.to_regex' do
28
+ def call(input, regex)
29
+ expect(InterpretFlags.to_regex input).to eq regex
30
+ end
31
+
32
+ it 'converts strings into regexes' do
33
+ call '', %r()
34
+ call 'a', %r(a)
35
+ end
36
+
37
+ it 'ignores surrounding slashes' do
38
+ call '//', %r()
39
+ call '/a/', %r(a)
40
+ end
41
+
42
+ it 'respects flags after the trailing slash in surrounding slashes' do
43
+ call '/a/', %r(a)
44
+ call '/a//', %r(a/)
45
+ call '//a/', %r(/a)
46
+ call '/a/i', %r(a)i
47
+ call '/a/im', %r(a)im
48
+ call '/a/xim', %r(a)xim
49
+ call '/a/mix', %r(a)mix
50
+ call '/a/mixi', %r(a)mixi
51
+ end
52
+
53
+ it 'isn\'t fooled by strings that kinda look regexy' do
54
+ call '/a', %r(/a)
55
+ call 'a/', %r(a/)
56
+ call '/', %r(/)
57
+ call '/i', %r(/i)
58
+ end
59
+
60
+ it 'does not escape the content' do
61
+ call 'a\\s+', %r(a\s+)
62
+ call '/a\\s+/', %r(a\s+)
63
+ end
64
+ end
65
+
27
66
  describe 'annotator' do
28
67
  it 'annotates every line by default' do
29
68
  expect(call.annotator).to eq AnnotateEveryLine
@@ -380,8 +380,8 @@ RSpec.describe SeeingIsBelieving::Binary::ParseArgs do
380
380
  end
381
381
 
382
382
  describe ':markers' do
383
- it 'defaults to a hash with :value, :exception, :stdout, :stderr, and :nextline' do
384
- expect(parse([])[:markers].keys).to eq [:value, :exception, :stdout, :stderr, :nextline]
383
+ it 'defaults to a hash with :value, :exception, :stdout, and :stderr' do
384
+ expect(parse([])[:markers].keys).to eq [:value, :exception, :stdout, :stderr]
385
385
  end
386
386
 
387
387
  def assert_default(marker_name, value)
@@ -392,14 +392,25 @@ RSpec.describe SeeingIsBelieving::Binary::ParseArgs do
392
392
  it('defaults :exception to "# ~> "') { assert_default :exception , "# ~> " }
393
393
  it('defaults :stdout to "# >> "') { assert_default :stdout , "# >> " }
394
394
  it('defaults :stderr to "# !> "') { assert_default :stderr , "# !> " }
395
- it('defaults :nextline to "# "') { assert_default :nextline , "# " }
396
395
 
397
396
  # TODO: When things get a little more stable, don't feel like adding all the cukes to play with this right now
398
397
  it 'overrides :value with --value-marker'
399
398
  it 'overrides :exception with --exception-marker'
400
399
  it 'overrides :stdout with --stdout-marker'
401
400
  it 'overrides :stderr with --stderr-marker'
402
- it 'overrides :nextline with --xmpfilter-nextline-marker'
401
+ end
402
+
403
+ describe ':marker_regexes' do
404
+ it 'is a hash with the same keys as the markers' do
405
+ marker_keys = parse([])[:markers].keys
406
+ marker_regexes_keys = parse([])[:marker_regexes].keys
407
+ expect(marker_regexes_keys).to eq marker_keys
408
+ end
409
+
410
+ it 'overrides :value with --value-regex'
411
+ it 'overrides :exception with --exception-regex'
412
+ it 'overrides :stdout with --stdout-regex'
413
+ it 'overrides :stderr with --stderr-regex'
403
414
  end
404
415
  end
405
416
 
@@ -1,51 +1,155 @@
1
1
  require 'spec_helper'
2
2
  require 'seeing_is_believing/binary/remove_annotations'
3
+ require 'seeing_is_believing/binary/parse_args' # for marker info
3
4
 
4
5
  RSpec.describe SeeingIsBelieving::Binary::RemoveAnnotations do
5
6
  def call(code, should_clean_values=true)
6
7
  indentation = code[/\A */]
7
8
  code = code.gsub /^#{indentation}/, ''
8
- described_class.call(code,
9
- should_clean_values,
10
- value: '# => ',
11
- exception: '# ~> ',
12
- stdout: '# >> ',
13
- stderr: '# !> ',
14
- nextline: '# ',
15
- ).chomp
9
+ described_class.call(code, should_clean_values, regexes).chomp
10
+ end
11
+
12
+ def regexes
13
+ SeeingIsBelieving::Binary::ParseArgs
14
+ .marker_regexes
15
+ .each_with_object({}) { |(name, str), rs| rs[name] = Regexp.new str }
16
+ end
17
+
18
+ context 'when there are lines that are just normal comments' do
19
+ example { expect(call "1 # hello").to eq "1 # hello" }
20
+ example { expect(call "1 # hello\n"\
21
+ "# world").to eq "1 # hello\n"\
22
+ "# world" }
23
+ example { expect(call "1 # not special\n"\
24
+ "2 # => 3").to eq "1 # not special\n2" }
16
25
  end
17
26
 
18
27
  context 'when told to clean out value annotations' do
19
- example { expect(call "1# => 1", true).to eq "1" }
20
- example { expect(call "1 # => 1", true).to eq "1" }
21
- example { expect(call "1 # => 1", true).to eq "1" }
22
- example { expect(call "1 # => 1", true).to eq "1" }
23
- example { expect(call "1 # => 1", true).to eq "1" }
24
- example { expect(call "1 # => 1", true).to eq "1" }
28
+ example { expect(call "1# => 1", true).to eq "1" }
29
+ example { expect(call "1 # => 1", true).to eq "1" }
30
+ example { expect(call "1 # => 1", true).to eq "1" }
31
+ example { expect(call "1 # => 1", true).to eq "1" }
32
+ example { expect(call "1 # => 1", true).to eq "1" }
33
+ example { expect(call "1 # => 1", true).to eq "1" }
25
34
  example { expect(call "\n1 # => 1", true).to eq "\n1" }
26
35
  end
27
36
 
28
37
  context 'when told not to clean out value annotations' do
29
- example { expect(call "1# => 1", false).to eq "1# => 1" }
30
- example { expect(call "1 # => 1", false).to eq "1 # => 1" }
31
- example { expect(call "1 # => 1", false).to eq "1 # => 1" }
32
- example { expect(call "1 # => 1", false).to eq "1 # => 1" }
33
- example { expect(call "1 # => 1", false).to eq "1 # => 1" }
34
- example { expect(call "1 # => 1", false).to eq "1 # => 1" }
38
+ example { expect(call "1# => 1", false).to eq "1# => 1" }
39
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
40
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
41
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
42
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
43
+ example { expect(call "1 # => 1", false).to eq "1 # => 1" }
35
44
  example { expect(call "\n1 # => 1", false).to eq "\n1 # => 1" }
36
45
  end
37
46
 
38
47
  context 'cleaning inline exception annotations' do
39
- example { expect(call "1# ~> 1" ).to eq "1" }
40
- example { expect(call "1 # ~> 1" ).to eq "1" }
41
- example { expect(call "1 # ~> 1" ).to eq "1" }
42
- example { expect(call "1 # ~> 1" ).to eq "1" }
43
- example { expect(call "1 # ~> 1" ).to eq "1" }
44
- example { expect(call "1 # ~> 1" ).to eq "1" }
48
+ example { expect(call "1# ~> 1" ).to eq "1" }
49
+ example { expect(call "1 # ~> 1" ).to eq "1" }
50
+ example { expect(call "1 # ~> 1" ).to eq "1" }
51
+ example { expect(call "1 # ~> 1" ).to eq "1" }
52
+ example { expect(call "1 # ~> 1").to eq "1" }
53
+ example { expect(call "1 # ~> 1").to eq "1" }
45
54
  example { expect(call "\n1 # ~> 1").to eq "\n1" }
46
55
 
47
- example { expect(call "# >> 1").to eq "" }
48
- example { expect(call "# !> 1").to eq "" }
56
+ example { expect(call "# >> 1").to eq "" }
57
+ example { expect(call "# !> 1").to eq "" }
58
+ end
59
+
60
+ context 'cleaning multiline results' do
61
+ it 'cleans values whose hash and value locations exactly match the annotation on the line prior' do
62
+ expect(call "1# => 2\n"\
63
+ " # 3").to eq "1"
64
+ end
65
+
66
+ it 'does not clean values where the comment appears at a different position' do
67
+ expect(call "1# => 2\n"\
68
+ "# 3").to eq "1\n"\
69
+ "# 3"
70
+
71
+ expect(call "1# => 2\n"\
72
+ " # 3").to eq "1\n"\
73
+ " # 3"
74
+
75
+ expect(call "1# => 2\n"\
76
+ "# 3").to eq "1\n"\
77
+ "# 3"
78
+ expect(call "1# => 2\n"\
79
+ " # 3").to eq "1\n"\
80
+ " # 3"
81
+
82
+ end
83
+
84
+ it 'does not clean values where the nextline value appears before the initial annotation value' do
85
+ # does clean
86
+ expect(call "1# => 2\n"\
87
+ " # 3").to eq "1"
88
+ expect(call "1# => 2\n"\
89
+ " # 3").to eq "1"
90
+
91
+ # does not clean
92
+ expect(call "1# => 2\n"\
93
+ " # 3 4").to eq "1\n"\
94
+ " # 3 4"
95
+ expect(call "1# => 2\n"\
96
+ " # 3").to eq "1\n"\
97
+ " # 3"
98
+ expect(call "1# => 2\n"\
99
+ " # 3").to eq "1\n"\
100
+ " # 3"
101
+ end
102
+
103
+ it 'does not clean values where there is content before the comment' do
104
+ expect(call "1# => 2\n"\
105
+ "3# 4").to eq "1\n"\
106
+ "3# 4"
107
+ end
108
+
109
+ it 'cleans successive rows of these' do
110
+ expect(call "1# => 2\n"\
111
+ " # 3\n"\
112
+ " # 4" ).to eq "1"
113
+ expect(call "1# => 2\n"\
114
+ " # 3\n"\
115
+ " # 4\n"\
116
+ "5# => 6\n"\
117
+ " # 7\n"\
118
+ " # 8" ).to eq "1\n5"
119
+ end
120
+
121
+ it 'does not clean values where there is non-annotation inbetween' do
122
+ expect(call "1# => 2\n"\
123
+ "# 3\n"\
124
+ " # 4").to eq "1\n"\
125
+ "# 3\n"\
126
+ " # 4"
127
+
128
+ expect(call "1# => 2\n"\
129
+ "3 \n"\
130
+ " # 4").to eq "1\n"\
131
+ "3 \n"\
132
+ " # 4"
133
+ expect(call "1# => 2\n"\
134
+ "# 3\n"\
135
+ " # 4").to eq "1\n"\
136
+ "# 3\n"\
137
+ " # 4"
138
+ end
139
+
140
+ it 'cleans multiline portion, regardless of whether cleaning values (this is soooooo xmpfilter specific)' do
141
+ expect(call "1# => 2\n"\
142
+ " # 3").to eq "1"
143
+
144
+ expect(call "1# => 2\n"\
145
+ " # 3",
146
+ false).to eq "1# => 2"
147
+ end
148
+
149
+ it 'works on inline exceptions' do
150
+ expect(call "1# ~> 2\n"\
151
+ " # 3").to eq "1"
152
+ end
49
153
  end
50
154
 
51
155
  context 'cleaning stdout annotations' do
@@ -2,8 +2,8 @@ require 'spec_helper'
2
2
  require 'seeing_is_believing/binary/rewrite_comments'
3
3
 
4
4
  RSpec.describe SeeingIsBelieving::Binary::RewriteComments do
5
- def call(code, &block)
6
- described_class.call code, &block
5
+ def call(code, options={}, &block)
6
+ described_class.call code, options, &block
7
7
  end
8
8
 
9
9
  it 'ignores multiline comments' do
@@ -52,4 +52,40 @@ RSpec.describe SeeingIsBelieving::Binary::RewriteComments do
52
52
  "%Q{\n"\
53
53
  " 1}NEW_WHITESPACE6--COMMENT-6--"
54
54
  end
55
+
56
+ it 'can be given additional lines to make sure are provided, whether they have comments on them or not' do
57
+ rewritten = call("'a'\n"\
58
+ "'b'\n"\
59
+ "'c' # c\n"\
60
+ "'d'",
61
+ always_rewrite: [2, 3]) do |c|
62
+ value = sprintf "%d|%d|%p|%d|%p|%d..%d|%d..%d|%d..%d",
63
+ c.line_number,
64
+ c.whitespace_col,
65
+ c.whitespace,
66
+ c.text_col,
67
+ c.text,
68
+ c.full_range.begin_pos,
69
+ c.full_range.end_pos,
70
+ c.whitespace_range.begin_pos,
71
+ c.whitespace_range.end_pos,
72
+ c.comment_range.begin_pos,
73
+ c.comment_range.end_pos
74
+ ['pre', value]
75
+ end
76
+ expect(rewritten).to eq \
77
+ "'a'\n"\
78
+ "'b'pre2|3|\"\"|3|\"\"|7..7|7..7|7..7\n"\
79
+ "'c'pre3|3|\" \"|4|\"# c\"|11..15|11..12|12..15\n"\
80
+ "'d'"
81
+
82
+ rewritten = call("", always_rewrite: [1]) { |c| ['a', 'b'] }
83
+ expect(rewritten).to eq "ab"
84
+
85
+ rewritten = call("a", always_rewrite: [1]) { |c| ['b', 'c'] }
86
+ expect(rewritten).to eq "abc"
87
+
88
+ rewritten = call("a\n", always_rewrite: [1]) { |c| ['b', 'c'] }
89
+ expect(rewritten).to eq "abc\n"
90
+ end
55
91
  end
@@ -17,8 +17,8 @@ RSpec.describe SeeingIsBelieving::WrapExpressions do
17
17
  end
18
18
 
19
19
  describe 'wrapping the body' do
20
- let(:options) { { before_all: -> { "[".freeze },
21
- after_all: -> { "]".freeze },
20
+ let(:options) { { before_all: -> { "[".freeze },
21
+ after_all: -> { "]".freeze },
22
22
  before_each: -> * { '<'.freeze },
23
23
  after_each: -> * { '>'.freeze } } }
24
24
 
@@ -798,14 +798,16 @@ RSpec.describe SeeingIsBelieving::WrapExpressions do
798
798
  expect(wrap("def a(b,c=1,*d,&e)\nend")).to eq "<def a(b,c=1,*d,&e)\nend>"
799
799
  end
800
800
 
801
- it 'wraps the body' do
801
+ it 'wraps the the body' do
802
802
  expect(wrap("def a\n1\nend")).to eq "<def a\n<1>\nend>"
803
803
  expect(wrap("def a()\n1\nend")).to eq "<def a()\n<1>\nend>"
804
+ expect(wrap("def a\n1\n2\nend")).to eq "<def a\n<1>\n<2>\nend>"
804
805
  end
805
806
 
806
- it 'wrap singleton method definitions' do
807
+ it 'wraps singleton method definitions' do
807
808
  expect(wrap("def a.b\n1\nend")).to eq "<def a.b\n<1>\nend>"
808
- # expect(wrap("def a.b()\n1\nend")).to eq "<def a.b()\n<1>\nend>"
809
+ expect(wrap("def a.b()\n1\nend")).to eq "<def a.b()\n<1>\nend>"
810
+ expect(wrap("def a.b\n1\n2\nend")).to eq "<def a.b\n<1>\n<2>\nend>" # <-- seems redundant, but this was a regression
809
811
  end
810
812
 
811
813
  it 'wraps calls to yield' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seeing_is_believing
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.beta.1
4
+ version: 3.0.0.beta.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Cheek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-23 00:00:00.000000000 Z
11
+ date: 2014-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eval_in
@@ -267,7 +267,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
267
267
  version: 1.3.1
268
268
  requirements: []
269
269
  rubyforge_project: seeing_is_believing
270
- rubygems_version: 2.4.1
270
+ rubygems_version: 2.0.14
271
271
  signing_key:
272
272
  specification_version: 4
273
273
  summary: Records results of every line of code in your file
@@ -293,4 +293,3 @@ test_files:
293
293
  - spec/seeing_is_believing_spec.rb
294
294
  - spec/spec_helper.rb
295
295
  - spec/wrap_expressions_spec.rb
296
- has_rdoc: