seeing_is_believing 0.0.26 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,7 @@
1
1
  require 'seeing_is_believing'
2
2
  require 'seeing_is_believing/binary/arg_parser'
3
- require 'seeing_is_believing/binary/print_results_next_to_lines'
3
+ require 'seeing_is_believing/binary/add_annotations'
4
+ require 'seeing_is_believing/binary/remove_previous_annotations'
4
5
  require 'timeout'
5
6
 
6
7
 
@@ -85,11 +86,11 @@ class SeeingIsBelieving
85
86
  end
86
87
 
87
88
  def print_unexpected_error
88
- stderr.puts unexpected_exception.class, unexpected_exception.message
89
+ stderr.puts unexpected_exception.class, unexpected_exception.message, "", unexpected_exception.backtrace
89
90
  end
90
91
 
91
92
  def printer
92
- @printer ||= PrintResultsNextToLines.new body, flags.merge(stdin: (file_is_on_stdin? ? '' : stdin))
93
+ @printer ||= AddAnnotations.new body, flags.merge(stdin: (file_is_on_stdin? ? '' : stdin))
93
94
  end
94
95
 
95
96
  def results
@@ -154,7 +155,7 @@ class SeeingIsBelieving
154
155
  end
155
156
 
156
157
  def print_cleaned_program
157
- stdout.print PrintResultsNextToLines.remove_previous_output_from body
158
+ stdout.print RemovePreviousAnnotations.call body
158
159
  end
159
160
  end
160
161
  end
@@ -1,6 +1,8 @@
1
+ require 'stringio'
1
2
  require 'seeing_is_believing/queue'
2
3
  require 'seeing_is_believing/has_exception'
3
4
  require 'seeing_is_believing/binary/line_formatter'
5
+ require 'seeing_is_believing/binary/remove_previous_annotations'
4
6
 
5
7
  # I think there is a bug where with xmpfilter_style set,
6
8
  # the exceptions won't be shown. But it's not totally clear
@@ -16,20 +18,13 @@ require 'seeing_is_believing/binary/line_formatter'
16
18
 
17
19
  class SeeingIsBelieving
18
20
  class Binary
19
- class PrintResultsNextToLines
21
+ class AddAnnotations
20
22
  include HasException
21
23
 
24
+ RESULT_PREFIX = '# =>'
25
+ EXCEPTION_PREFIX = '# ~>'
22
26
  STDOUT_PREFIX = '# >>'
23
27
  STDERR_PREFIX = '# !>'
24
- EXCEPTION_PREFIX = '# ~>'
25
- RESULT_PREFIX = '# =>'
26
-
27
- def self.remove_previous_output_from(string)
28
- string.gsub(/\s+(#{EXCEPTION_PREFIX}|#{RESULT_PREFIX}).*?$/, '')
29
- .gsub(/(^\n)?(^#{STDOUT_PREFIX}[^\n]*\r?\n?)+/m, '')
30
- .gsub(/(^\n)?(^#{STDERR_PREFIX}[^\n]*\r?\n?)+/m, '')
31
- end
32
-
33
28
 
34
29
  def self.method_from_options(*args)
35
30
  define_method(args.first) { options.fetch *args }
@@ -41,19 +36,21 @@ class SeeingIsBelieving
41
36
  method_from_options :line_length, Float::INFINITY
42
37
  method_from_options :result_length, Float::INFINITY
43
38
  method_from_options :xmpfilter_style
39
+ method_from_options :debugger
44
40
 
45
41
  attr_accessor :file_result
46
42
  def initialize(body, options={})
47
- cleaned_body = self.class.remove_previous_output_from body
43
+ cleaned_body = RemovePreviousAnnotations.call body
48
44
  self.options = options
49
45
  self.body = (xmpfilter_style ? body : cleaned_body)
50
46
  self.file_result = SeeingIsBelieving.call body(),
51
- filename: (options[:as] || options[:filename]),
52
- require: options[:require],
53
- load_path: options[:load_path],
54
- encoding: options[:encoding],
55
- stdin: options[:stdin],
56
- timeout: options[:timeout]
47
+ filename: (options[:as] || options[:filename]),
48
+ require: options[:require],
49
+ load_path: options[:load_path],
50
+ encoding: options[:encoding],
51
+ stdin: options[:stdin],
52
+ timeout: options[:timeout],
53
+ debugger: debugger
57
54
  self.alignment_strategy = options[:alignment_strategy].new cleaned_body, start_line, end_line
58
55
  end
59
56
 
@@ -68,8 +65,13 @@ class SeeingIsBelieving
68
65
  .each { |line, line_number| add_line line, line_number }
69
66
  add_stdout
70
67
  add_stderr
68
+ add_exception
71
69
  add_remaining_lines
72
- new_body
70
+ if debugger.enabled?
71
+ debugger.context("RESULT") { new_body }.to_s
72
+ else
73
+ new_body
74
+ end
73
75
  end
74
76
  end
75
77
 
@@ -125,6 +127,20 @@ class SeeingIsBelieving
125
127
  end
126
128
  end
127
129
 
130
+ def add_exception
131
+ return unless file_result.has_exception?
132
+ exception = file_result.exception
133
+ new_body << "\n"
134
+ new_body << LineFormatter.new('', "#{EXCEPTION_PREFIX} ", exception.class_name, options).call << "\n"
135
+ exception.message.each_line do |line|
136
+ new_body << LineFormatter.new('', "#{EXCEPTION_PREFIX} ", line.chomp, options).call << "\n"
137
+ end
138
+ new_body << "#{EXCEPTION_PREFIX}\n"
139
+ exception.backtrace.each do |line|
140
+ new_body << LineFormatter.new('', "#{EXCEPTION_PREFIX} ", line.chomp, options).call << "\n"
141
+ end
142
+ end
143
+
128
144
  def add_remaining_lines
129
145
  line_queue.each { |line, line_number| new_body << line }
130
146
  end
@@ -132,7 +148,7 @@ class SeeingIsBelieving
132
148
  def format_line(line, line_number, line_results)
133
149
  options = options().merge pad_to: alignment_strategy.line_length_for(line_number)
134
150
  formatted_line = if line_results.has_exception?
135
- result = sprintf "%s: %s", line_results.exception.class_name, line_results.exception.message
151
+ result = sprintf "%s: %s", line_results.exception.class_name, line_results.exception.message.gsub("\n", '\n')
136
152
  LineFormatter.new(line, "#{EXCEPTION_PREFIX} ", result, options).call
137
153
  elsif line_results.any?
138
154
  LineFormatter.new(line, "#{RESULT_PREFIX} ", line_results.join(', '), options).call
@@ -1,4 +1,6 @@
1
+ require 'stringio'
1
2
  require 'seeing_is_believing/version'
3
+ require 'seeing_is_believing/debugger'
2
4
  require 'seeing_is_believing/binary/align_file'
3
5
  require 'seeing_is_believing/binary/align_line'
4
6
  require 'seeing_is_believing/binary/align_chunk'
@@ -22,10 +24,11 @@ class SeeingIsBelieving
22
24
  until args.empty?
23
25
  case (arg = args.shift)
24
26
  when '-h', '--help' then options[:help] = self.class.help_screen
25
- when '-v', '--version' then options[:version] = true
26
27
  when '-c', '--clean' then options[:clean] = true
28
+ when '-v', '--version' then options[:version] = true
27
29
  when '-x', '--xmpfilter-style' then options[:xmpfilter_style] = true
28
30
  when '-i', '--inherit-exit-status' then options[:inherit_exit_status] = true
31
+ when '-g', '--debug' then options[:debugger] = Debugger.new(enabled: true, colour: true)
29
32
  when '-l', '--start-line' then extract_positive_int_for :start_line, arg
30
33
  when '-L', '--end-line' then extract_positive_int_for :end_line, arg
31
34
  when '-d', '--line-length' then extract_positive_int_for :line_length, arg
@@ -67,6 +70,7 @@ class SeeingIsBelieving
67
70
 
68
71
  def options
69
72
  @options ||= {
73
+ debugger: Debugger.new(enabled: false, colour: true),
70
74
  version: false,
71
75
  clean: false,
72
76
  xmpfilter_style: false,
@@ -149,6 +153,7 @@ Usage: seeing_is_believing [options] [filename]
149
153
  -K, --encoding encoding # sets file encoding, equivalent to Ruby's -Kx (see `man ruby` for valid values)
150
154
  -a, --as filename # run the program as if it was the specified filename
151
155
  -c, --clean # remove annotations from previous runs of seeing_is_believing
156
+ -g, --debug # print debugging information (useful if program is fucking up, or if you want to brag)
152
157
  -x, --xmpfilter-style # annotate marked lines instead of every line
153
158
  -i, --inherit-exit-status # exit with the exit status of the program being eval
154
159
  -v, --version # print the version (#{VERSION})
@@ -0,0 +1,75 @@
1
+ require 'seeing_is_believing/remove_inline_comments'
2
+
3
+ class SeeingIsBelieving
4
+ class Binary
5
+ class RemovePreviousAnnotations
6
+ def self.call(code)
7
+ new(code).call
8
+ end
9
+
10
+ def initialize(code)
11
+ self.code = code
12
+ self.comments = { result: [],
13
+ exception: [],
14
+ stdout: [],
15
+ stderr: [],
16
+ }
17
+ end
18
+
19
+ def call
20
+ RemoveInlineComments.call code, additional_rewrites: remove_whitespace_preceeding_comments do |comment|
21
+ if comment.text[/\A#\s*=>/] then comments[:result] << comment; true
22
+ elsif comment.text[/\A#\s*~>/] then comments[:exception] << comment; true
23
+ elsif comment.text[/\A#\s*>>/] then comments[:stdout] << comment; true
24
+ elsif comment.text[/\A#\s*!>/] then comments[:stderr] << comment; true
25
+ else false
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_accessor :code, :comments
33
+
34
+ def remove_whitespace_preceeding_comments
35
+ lambda do |buffer, rewriter|
36
+ comments[:result].each { |comment| remove_whitespace_before comment.location.begin_pos, buffer, rewriter, false }
37
+ comments[:exception].each { |comment| remove_whitespace_before comment.location.begin_pos, buffer, rewriter, true }
38
+ comments[:stdout].each { |comment| remove_whitespace_before comment.location.begin_pos, buffer, rewriter, true }
39
+ comments[:stderr].each { |comment| remove_whitespace_before comment.location.begin_pos, buffer, rewriter, true }
40
+ end
41
+ end
42
+
43
+ # any whitespace before the index (on the same line) will be removed
44
+ # if the preceeding whitespace is at the beginning of the line, the newline will be removed
45
+ # if there is a newline before all of that, and remove_preceeding_newline is true, it will be removed as well
46
+ def remove_whitespace_before(index, buffer, rewriter, remove_preceeding_newline)
47
+ end_pos = index
48
+ begin_pos = end_pos - 1
49
+ begin_pos -= 1 while code[begin_pos] =~ /\s/ && code[begin_pos] != "\n"
50
+ begin_pos -= 1 if code[begin_pos] == "\n"
51
+ begin_pos -= 1 if code[begin_pos] == "\n" && remove_preceeding_newline
52
+ return if begin_pos.next == end_pos
53
+ rewriter.remove Parser::Source::Range.new(buffer, begin_pos.next, end_pos)
54
+ end
55
+
56
+ # returns comments in groups that are on consecutive lines
57
+ def adjacent_comments(comments, buffer)
58
+ comments = comments.sort_by { |comment| comment.location.begin_pos }
59
+ current_chunk = 0
60
+ last_line_seen = -100
61
+ chunks_to_comment = comments.chunk do |comment|
62
+ line, col = buffer.decompose_position comment.location.begin_pos
63
+ if last_line_seen.next == line
64
+ last_line_seen = line
65
+ current_chunk
66
+ else
67
+ last_line_seen = line
68
+ current_chunk += 1
69
+ end
70
+ end
71
+ chunks_to_comment.map &:last
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,36 @@
1
+ class SeeingIsBelieving
2
+ class Debugger
3
+
4
+ CONTEXT_COLOUR = "\e[37;44m"
5
+ RESET_COLOUR = "\e[0m"
6
+
7
+ def initialize(options={})
8
+ @contexts = Hash.new { |h, k| h[k] = [] }
9
+ @enabled = options.fetch :enabled, true
10
+ @coloured = options.fetch :colour, false
11
+ end
12
+
13
+ def enabled?
14
+ @enabled
15
+ end
16
+
17
+ def coloured?
18
+ @coloured
19
+ end
20
+
21
+ def context(name, &block)
22
+ @contexts[name] << block.call if enabled?
23
+ self
24
+ end
25
+
26
+ def to_s
27
+ @contexts.map { |name, values|
28
+ string = ""
29
+ string << CONTEXT_COLOUR if coloured?
30
+ string << "#{name}:"
31
+ string << RESET_COLOUR if coloured?
32
+ string << "\n#{values.join "\n"}\n"
33
+ }.join("\n")
34
+ end
35
+ end
36
+ end
@@ -1,8 +1,8 @@
1
1
  require 'open3'
2
+ require 'seeing_is_believing/debugger'
2
3
  require 'seeing_is_believing/syntax_analyzer'
3
4
 
4
- # A lot of colouring going on in this file, maybe should extract a debugging object to contain it
5
-
5
+ # can we get better debugging support so that we don't need to drop ANSI escape sequences in the middle of strings?
6
6
  class SeeingIsBelieving
7
7
  class ExpressionList
8
8
  PendingExpression = Struct.new :expression, :children do
@@ -16,8 +16,7 @@ class SeeingIsBelieving
16
16
  end
17
17
 
18
18
  def initialize(options)
19
- self.debug_stream = options.fetch :debug_stream, $stdout
20
- self.should_debug = options.fetch :debug, false
19
+ self.debugger = options.fetch :debugger, Debugger.new(enabled: false)
21
20
  self.get_next_line = options.fetch :get_next_line
22
21
  self.peek_next_line = options.fetch :peek_next_line
23
22
  self.on_complete = options.fetch :on_complete
@@ -27,7 +26,10 @@ class SeeingIsBelieving
27
26
  offset, expressions, expression = 0, [], nil
28
27
  begin
29
28
  pending_expression = generate(expressions)
30
- debug { "GENERATED: #{pending_expression.expression.inspect}, ADDING IT TO #{inspected_expressions expressions}" }
29
+
30
+ debugger.context debugger_context do
31
+ "GENERATED: #{pending_expression.expression.inspect}, ADDING IT TO #{inspected_expressions expressions}"
32
+ end
31
33
 
32
34
  expression = reduce expressions, offset unless next_line_modifies_current?
33
35
 
@@ -38,7 +40,11 @@ class SeeingIsBelieving
38
40
 
39
41
  private
40
42
 
41
- attr_accessor :debug_stream, :should_debug, :get_next_line, :peek_next_line, :on_complete, :expressions
43
+ attr_accessor :debugger, :get_next_line, :peek_next_line, :on_complete, :expressions
44
+
45
+ def debugger_context
46
+ "EXPRESSION EVALUATION"
47
+ end
42
48
 
43
49
  def generate(expressions)
44
50
  expression = get_next_line.call
@@ -59,15 +65,7 @@ class SeeingIsBelieving
59
65
  end
60
66
 
61
67
  def inspected_expressions(expressions)
62
- "[#{expressions.map { |pe| pe.inspect debug? }.join(', ')}]"
63
- end
64
-
65
- def debug?
66
- @should_debug
67
- end
68
-
69
- def debug
70
- @debug_stream.puts yield if debug?
68
+ "[#{expressions.map { |pe| pe.inspect debugger.enabled? }.join(', ')}]"
71
69
  end
72
70
 
73
71
  def reduce(expressions, offset)
@@ -83,14 +81,14 @@ class SeeingIsBelieving
83
81
  offset)
84
82
  expressions.replace expressions[0, i]
85
83
  expressions[i-1].children << result unless expressions.empty?
86
- debug { "REDUCED: #{result.inspect}, LIST: #{inspected_expressions expressions}" }
84
+ debugger.context(debugger_context) { "REDUCED: #{result.inspect}, LIST: #{inspected_expressions expressions}" }
87
85
  return result
88
86
  end
89
87
  end
90
88
 
91
89
  def valid_ruby?(expression)
92
90
  valid = SyntaxAnalyzer.valid_ruby? expression
93
- debug { "#{valid ? "\e[32mIS VALID:" : "\e[31mIS NOT VALID:"}: #{expression.inspect}\e[0m" }
91
+ debugger.context(debugger_context) { "#{valid ? "\e[32mIS VALID:" : "\e[31mIS NOT VALID:"}: #{expression.inspect}\e[0m" }
94
92
  valid
95
93
  end
96
94
 
@@ -2,21 +2,45 @@ require 'parser/current'
2
2
 
3
3
  class SeeingIsBelieving
4
4
  module RemoveInlineComments
5
- extend self
5
+ module NonLeading
6
+ def self.call(code)
7
+ ranges = []
8
+
9
+ nonleading_comments = lambda do |buffer, rewriter|
10
+ ranges.sort_by(&:begin_pos)
11
+ .drop_while.with_index(1) { |range, index|
12
+ line, col = buffer.decompose_position range.begin_pos
13
+ index == line && col.zero?
14
+ }
15
+ .each { |range| rewriter.remove range }
16
+ end
6
17
 
7
- def self.call(code)
8
- remove_inline_comments code
18
+ RemoveInlineComments.call code, additional_rewrites: nonleading_comments do |comment|
19
+ ranges << comment.location
20
+ false
21
+ end
22
+ end
9
23
  end
10
24
 
11
- def remove_inline_comments(code)
12
- buffer = Parser::Source::Buffer.new "strip_comments"
13
- buffer.source = code
14
- parser = Parser::CurrentRuby.new
15
- rewriter = Parser::Source::Rewriter.new(buffer)
16
- ast, comments = parser.parse_with_comments(buffer)
25
+ extend self
26
+
27
+ # selector is a block that will receive the comment object
28
+ # if it returns true, the comment will be removed
29
+ def self.call(code, options={}, &selector)
30
+ selector ||= Proc.new { true }
31
+ additional_rewrites = options.fetch :additional_rewrites, Proc.new {}
32
+ buffer = Parser::Source::Buffer.new "strip_comments"
33
+ buffer.source = code
34
+ parser = Parser::CurrentRuby.new
35
+ rewriter = Parser::Source::Rewriter.new(buffer)
36
+ ast, comments = parser.parse_with_comments(buffer)
17
37
  comments.select { |comment| comment.type == :inline }
38
+ .select { |comment| selector.call comment }
18
39
  .each { |comment| rewriter.remove comment.location }
40
+ additional_rewrites.call buffer, rewriter
19
41
  rewriter.process
42
+ rescue Parser::SyntaxError => e
43
+ raise SyntaxError, e.message
20
44
  end
21
45
  end
22
46
  end
@@ -139,7 +139,8 @@ class SeeingIsBelieving
139
139
  end
140
140
 
141
141
  def self.ends_in_comment?(code)
142
- code =~ /^=end\Z/ || parsed(code.lines.to_a.last.to_s).has_comment?
142
+ # must do the newline hack or it totally fucks up on comments like "# Transfer-Encoding: chunked"
143
+ code =~ /^=end\Z/ || parsed("\n#{code.lines.to_a.last.to_s}").has_comment?
143
144
  end
144
145
 
145
146
  def self.unclosed_comment?(code)
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '0.0.26'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency "parser", "~> 2.0.0.beta9"
22
+ s.add_dependency "parser", "~> 2.0.0.pre1"
23
23
 
24
24
  s.add_development_dependency "haiti", "~> 0.0.3"
25
25
  s.add_development_dependency "rake", "~> 10.0.3"
@@ -333,5 +333,16 @@ describe SeeingIsBelieving::Binary::ArgParser do
333
333
  parse(['-x'])[:xmpfilter_style].should be_true
334
334
  end
335
335
  end
336
+
337
+ describe ':debugger' do
338
+ it 'defaults to a debugger that is disabled' do
339
+ parse([])[:debugger].should_not be_enabled
340
+ end
341
+
342
+ it 'can be enabled with --debug or -g' do
343
+ parse(['--debug'])[:debugger].should be_enabled
344
+ parse(['-g'])[:debugger].should be_enabled
345
+ end
346
+ end
336
347
  end
337
348