seeing_is_believing 0.0.26 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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