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,79 @@
1
+ # Polyfill String#scrub on Ruby 2.0.0
2
+ require 'seeing_is_believing/compatibility'
3
+ using SeeingIsBelieving::Compatibility
4
+
5
+ class SeeingIsBelieving
6
+ module Binary
7
+ # not sure I like this name, it formats comments that show results
8
+ # e.g. "# => [1, 2, 3]"
9
+ #
10
+ # line_length is the length of the line this comment is being appended to
11
+ #
12
+ # For examples of what the options are, and how they all fit together, see
13
+ # spec/binary/format_comment.rb
14
+ class FormatComment
15
+ def self.call(*args)
16
+ new(*args).call
17
+ end
18
+
19
+ def initialize(line_length, separator, result, options)
20
+ self.line_length = line_length
21
+ self.separator = separator
22
+ self.result = result
23
+ self.options = options
24
+ end
25
+
26
+ def call
27
+ @formatted ||= begin
28
+ formatted = escape_non_printable result, chars_not_to_escape
29
+ formatted = truncate "#{separator}#{formatted}", max_result_length
30
+ formatted = "#{' '*padding_length}#{formatted}"
31
+ formatted = truncate formatted, max_line_length
32
+ formatted = '' unless formatted.sub(/^ */, '').start_with? separator
33
+ formatted
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ attr_accessor :line_length, :separator, :result, :options
40
+
41
+ def max_line_length
42
+ length = options.fetch(:max_line_length, Float::INFINITY) - line_length
43
+ length = 0 if length < 0
44
+ length
45
+ end
46
+
47
+ def max_result_length
48
+ options.fetch :max_result_length, Float::INFINITY
49
+ end
50
+
51
+ def padding_length
52
+ padding_length = options.fetch(:pad_to, 0) - line_length
53
+ padding_length = 0 if padding_length < 0
54
+ padding_length
55
+ end
56
+
57
+ def truncate(string, length)
58
+ return string if string.size <= length
59
+ ellipsify string.slice(0, length)
60
+ end
61
+
62
+ def ellipsify(string)
63
+ string.sub(/.{0,3}$/) { |last_chars| '.' * last_chars.size }
64
+ end
65
+
66
+ def chars_not_to_escape
67
+ options.fetch :dont_escape, []
68
+ end
69
+
70
+ def escape_non_printable(str, omissions)
71
+ str.scrub { |c| c.inspect[1...-1] }
72
+ .gsub(/[\u0000-\u0020]/) { |char|
73
+ next char if omissions.include? char
74
+ char.inspect[1...-1]
75
+ }
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,57 @@
1
+ class SeeingIsBelieving
2
+ module Binary
3
+ class InterlineAlign
4
+ def initialize(results)
5
+ @results = results
6
+ @format_strings = {}
7
+ end
8
+
9
+ def call(lineno, results)
10
+ format_string_for_line(lineno) % results
11
+ end
12
+
13
+ private
14
+
15
+ attr_accessor :results
16
+
17
+ def format_string_for_line(lineno)
18
+ group = groups_with_same_number_of_results(@results)[lineno]
19
+ format_string_for(results, group, lineno)
20
+ end
21
+
22
+ def groups_with_same_number_of_results(results)
23
+ @grouped_by_no_results ||= begin
24
+ length = 0
25
+ groups = 1.upto(results.num_lines)
26
+ .slice_before { |num|
27
+ new_length = results[num].length
28
+ slice = length != new_length
29
+ length = new_length
30
+ slice
31
+ }.to_a
32
+
33
+ groups.each_with_object Hash.new do |group, lineno_to_group|
34
+ group.each { |lineno| lineno_to_group[lineno] = group }
35
+ end
36
+ end
37
+ end
38
+
39
+ def format_string_for(results, group, lineno)
40
+ @format_strings[lineno] ||= begin
41
+ index = group.index lineno
42
+ group
43
+ .map { |lineno| results[lineno] }
44
+ .transpose
45
+ .map { |col|
46
+ lengths = col.map(&:length)
47
+ max = lengths.max
48
+ crnt = lengths[index]
49
+ "%-#{crnt}s,#{" "*(max-crnt)} "
50
+ }
51
+ .join
52
+ .sub(/, *$/, "")
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,113 @@
1
+ require 'seeing_is_believing/binary' # Defines the regexes to locate the markers
2
+ require 'seeing_is_believing/code' # We have to parse the file to find the comments
3
+
4
+ class SeeingIsBelieving
5
+ module Binary
6
+ class RemoveAnnotations
7
+ def self.call(raw_code, remove_value_prefixes, markers)
8
+ new(raw_code, remove_value_prefixes, markers).call
9
+ end
10
+
11
+ def initialize(raw_code, remove_value_prefixes, markers)
12
+ self.remove_value_prefixes = remove_value_prefixes
13
+ self.raw_code = raw_code
14
+ self.markers = markers
15
+ self.code = Code.new(raw_code, 'strip_comments')
16
+ end
17
+
18
+ def call
19
+ annotation_chunks_in(code).each do |comment, rest|
20
+ rest.each { |comment|
21
+ code.rewriter.remove comment.comment_range
22
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, false
23
+ }
24
+
25
+ case comment.text
26
+ when value_regex
27
+ if remove_value_prefixes
28
+ code.rewriter.remove comment.comment_range
29
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, false
30
+ else
31
+ prefix = comment.text[value_regex].rstrip
32
+ code.rewriter.replace comment.comment_range, prefix
33
+ end
34
+ when exception_regex
35
+ code.rewriter.remove comment.comment_range
36
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, true
37
+ when stdout_regex
38
+ code.rewriter.remove comment.comment_range
39
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, true
40
+ when stderr_regex
41
+ code.rewriter.remove comment.comment_range
42
+ remove_whitespace_before comment.comment_range.begin_pos, code.buffer, code.rewriter, true
43
+ else
44
+ raise "This should be impossible! Something must be broken in the comment section above"
45
+ end
46
+ end
47
+
48
+ code.rewriter.process
49
+ end
50
+
51
+ private
52
+
53
+ attr_accessor :raw_code, :remove_value_prefixes, :markers, :code
54
+
55
+ # any whitespace before the index (on the same line) will be removed
56
+ # if the preceding whitespace is at the beginning of the line, the newline will be removed
57
+ # if there is a newline before all of that, and remove_preceding_newline is true, it will be removed as well
58
+ def remove_whitespace_before(index, buffer, rewriter, remove_preceding_newline)
59
+ end_pos = index
60
+ begin_pos = end_pos - 1
61
+ begin_pos -= 1 while 0 <= begin_pos && raw_code[begin_pos] =~ /\s/ && raw_code[begin_pos] != "\n"
62
+ begin_pos -= 1 if 0 <= begin_pos && raw_code[begin_pos] == "\n"
63
+ begin_pos -= 1 if remove_preceding_newline && 0 <= begin_pos && raw_code[begin_pos] == "\n"
64
+ return if begin_pos.next == end_pos
65
+ rewriter.remove code.range_for(begin_pos.next, end_pos)
66
+ end
67
+
68
+ def annotation_chunks_in(code)
69
+ code
70
+ .inline_comments
71
+ .map { |comment| # associate each annotation to its comment
72
+ annotation = comment.text[value_regex] ||
73
+ comment.text[exception_regex] ||
74
+ comment.text[stdout_regex] ||
75
+ comment.text[stderr_regex]
76
+ [annotation, comment]
77
+ }
78
+ .slice_before { |annotation, comment| annotation } # annotations begin chunks
79
+ .select { |(annotation, _start), *| annotation } # discard chunks not beginning with an annotation (probably can only happens on first comment)
80
+ .map { |(annotation, start), *rest| # end the chunk if the comment doesn't meet nextline criteria
81
+ nextline_comments = []
82
+ prev = start
83
+ rest.each { |_, potential_nextline|
84
+ sequential = (prev.line_number.next == potential_nextline.line_number)
85
+ vertically_aligned = start.text_col == potential_nextline.text_col
86
+ only_preceded_by_whitespace = potential_nextline.whitespace_col.zero?
87
+ indention_matches_annotation = annotation.length <= potential_nextline.text[/#\s*/].length
88
+ break unless sequential && vertically_aligned && only_preceded_by_whitespace && indention_matches_annotation
89
+ nextline_comments << potential_nextline
90
+ prev = potential_nextline
91
+ }
92
+ [start, nextline_comments]
93
+ }
94
+ end
95
+
96
+ def value_regex
97
+ @value_regex ||= markers.fetch(:value).fetch(:regex)
98
+ end
99
+
100
+ def exception_regex
101
+ @exception_regex ||= markers.fetch(:exception).fetch(:regex)
102
+ end
103
+
104
+ def stdout_regex
105
+ @stdout_regex ||= markers.fetch(:stdout).fetch(:regex)
106
+ end
107
+
108
+ def stderr_regex
109
+ @stderr_regex ||= markers.fetch(:stderr).fetch(:regex)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,62 @@
1
+ require 'seeing_is_believing/code'
2
+ require 'seeing_is_believing/binary/commentable_lines'
3
+
4
+ class SeeingIsBelieving
5
+ module Binary
6
+ # can this be joined into CommentLines?
7
+ # that one yields every commentable line, this one just lines which have comments
8
+ # what they yield is a little different, too, but their algorithms and domain are very similar
9
+ module RewriteComments
10
+ Options = HashStruct.anon do
11
+ attribute(:include_lines) { [] }
12
+ end
13
+
14
+ def self.call(raw_code, options={}, &mapping)
15
+ code = Code.new(raw_code)
16
+ comments = code.inline_comments
17
+ extra_lines = Options.new(options).include_lines
18
+
19
+ # update existing comments
20
+ comments.each do |comment|
21
+ new_whitespace, new_comment = mapping.call comment
22
+ code.rewriter.replace comment.whitespace_range, new_whitespace
23
+ code.rewriter.replace comment.comment_range, new_comment
24
+ end
25
+
26
+ # remove extra lines that are handled / uncommentable
27
+ comments.each { |c| extra_lines.delete c.line_number }
28
+ commentable_linenums = CommentableLines.call(code.raw).map { |linenum, *| linenum }
29
+ extra_lines.select! { |linenum| commentable_linenums.include? linenum }
30
+
31
+ # add additional comments
32
+ extra_lines.each do |line_number|
33
+ line_begin_col = code.linenum_to_index(line_number)
34
+ nextline_begin_col = code.linenum_to_index(line_number.next)
35
+ nextline_begin_col -= 1 if code.raw[nextline_begin_col-1] == "\n"
36
+ whitespace_col = nextline_begin_col-1
37
+ whitespace_col -= 1 while line_begin_col < whitespace_col &&
38
+ code.raw[whitespace_col] =~ /\s/
39
+ whitespace_col += 1
40
+ whitespace_range = code.range_for(whitespace_col, nextline_begin_col)
41
+ comment_range = code.range_for(nextline_begin_col, nextline_begin_col)
42
+
43
+ comment = Code::InlineComment.new \
44
+ line_number: line_number,
45
+ whitespace_col: whitespace_col-line_begin_col,
46
+ whitespace: code.raw[whitespace_col...nextline_begin_col]||"",
47
+ text_col: nextline_begin_col-line_begin_col,
48
+ text: "",
49
+ full_range: whitespace_range,
50
+ whitespace_range: whitespace_range,
51
+ comment_range: comment_range
52
+
53
+ whitespace, body = mapping.call comment
54
+ code.rewriter.replace whitespace_range, "#{whitespace}#{body}"
55
+ end
56
+
57
+ # perform the rewrite
58
+ code.rewriter.process
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,73 @@
1
+ require 'seeing_is_believing'
2
+ require 'seeing_is_believing/binary/config'
3
+ require 'seeing_is_believing/binary/engine'
4
+
5
+ class SeeingIsBelieving
6
+ module Binary
7
+ SUCCESS_STATUS = 0
8
+ DISPLAYABLE_ERROR_STATUS = 1 # e.g. user code raises an exception (we can display this in the output)
9
+ NONDISPLAYABLE_ERROR_STATUS = 2 # e.g. SiB was invoked incorrectly
10
+
11
+ def self.call(argv, stdin, stdout, stderr)
12
+ config = Config.new.parse_args(argv).finalize(stdin, stdout, stderr, File)
13
+ engine = Engine.new config
14
+
15
+ if config.print_help?
16
+ stdout.puts config.help_screen
17
+ return SUCCESS_STATUS
18
+ end
19
+
20
+ if config.print_version?
21
+ stdout.puts SeeingIsBelieving::VERSION
22
+ return SUCCESS_STATUS
23
+ end
24
+
25
+ if config.errors.any?
26
+ stderr.puts(*config.errors, *config.deprecations)
27
+ return NONDISPLAYABLE_ERROR_STATUS
28
+ end
29
+
30
+ if config.print_cleaned?
31
+ stdout.print engine.cleaned_body
32
+ return SUCCESS_STATUS
33
+ end
34
+
35
+ if config.toggle_mark?
36
+ stdout.print engine.toggled_mark
37
+ return SUCCESS_STATUS
38
+ end
39
+
40
+ if engine.syntax_error?
41
+ stderr.puts engine.syntax_error
42
+ return NONDISPLAYABLE_ERROR_STATUS
43
+ end
44
+
45
+ engine.evaluate!
46
+
47
+ if engine.timed_out?
48
+ stderr.puts "Timeout Error after #{engine.timeout_seconds} seconds!"
49
+ return NONDISPLAYABLE_ERROR_STATUS
50
+ end
51
+
52
+ if config.result_as_json?
53
+ require 'json'
54
+ stdout.puts JSON.dump(engine.result.as_json)
55
+ return SUCCESS_STATUS
56
+ elsif config.print_event_stream?
57
+ # no op, the event stream handler has been printing it all along
58
+ elsif config.debug?
59
+ config.debugger.context("OUTPUT") { engine.annotated_body }
60
+ else
61
+ stdout.print engine.annotated_body
62
+ end
63
+
64
+ if config.inherit_exitstatus?
65
+ engine.exitstatus
66
+ elsif engine.exitstatus.zero?
67
+ SUCCESS_STATUS
68
+ else
69
+ DISPLAYABLE_ERROR_STATUS
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,139 @@
1
+ # With new versioning, there's lots of small versions
2
+ # we don't need Parser to complain that we're on 2.1.1 and its parsing 2.1.5
3
+ # https://github.com/whitequark/parser/blob/e2249d7051b1adb6979139928e14a81bc62f566e/lib/parser/current.rb#L3
4
+ class << (Parser ||= Module.new)
5
+ def warn(*) end
6
+ require 'parser/current'
7
+ remove_method :warn
8
+ end
9
+
10
+ require 'set'
11
+ require 'seeing_is_believing/hash_struct'
12
+
13
+ class SeeingIsBelieving
14
+ class Code
15
+ InlineComment = HashStruct.for :line_number, :whitespace_col, :whitespace, :text_col, :text, :full_range, :whitespace_range, :comment_range
16
+ Syntax = HashStruct.for error_message: nil, line_number: nil do
17
+ def valid?() !error_message end
18
+ def invalid?() !valid? end
19
+ end
20
+
21
+ attr_reader :raw, :buffer, :parser, :rewriter, :inline_comments, :root, :raw_comments, :syntax, :body_range
22
+
23
+ def initialize(raw_code, name=nil)
24
+ raw_code[-1] == "\n" || raise(SyntaxError, "Code must end in a newline for the sake of consistency (sanity)")
25
+ @raw = raw_code
26
+ @buffer = Parser::Source::Buffer.new(name||"SeeingIsBelieving")
27
+ @buffer.source = raw
28
+ @rewriter = Parser::Source::TreeRewriter.new buffer
29
+ builder = Parser::Builders::Default.new.tap { |b| b.emit_file_line_as_literals = false }
30
+ @parser = Parser::CurrentRuby.new builder
31
+ @syntax = Syntax.new
32
+ parser.diagnostics.consumer = lambda do |diagnostic|
33
+ if :fatal == diagnostic.level || :error == diagnostic.level
34
+ @syntax = Syntax.new error_message: diagnostic.message, line_number: index_to_linenum(diagnostic.location.begin_pos)
35
+ end
36
+ end
37
+ @root, @raw_comments, @tokens = parser.tokenize(@buffer, true)
38
+ @body_range = body_range_from_tokens(@tokens)
39
+ @inline_comments = raw_comments.select(&:inline?).map { |c| wrap_comment c }
40
+ @root ||= null_node
41
+ end
42
+
43
+ def range_for(start_index, end_index)
44
+ Parser::Source::Range.new buffer, start_index, end_index
45
+ end
46
+
47
+ def index_to_linenum(char_index)
48
+ line_indexes.index { |line_index| char_index < line_index } || line_indexes.size
49
+ end
50
+
51
+ def linenum_to_index(line_num)
52
+ return raw.size if line_indexes.size < line_num
53
+ line_indexes[line_num - 1]
54
+ end
55
+
56
+ def heredoc?(ast)
57
+ # some strings are fucking weird.
58
+ # e.g. the "1" in `%w[1]` returns nil for ast.location.begin
59
+ # and `__FILE__` is a string whose location is a Parser::Source::Map instead of a Parser::Source::Map::Collection,
60
+ # so it has no #begin
61
+ ast.kind_of?(Parser::AST::Node) &&
62
+ (ast.type == :dstr || ast.type == :str) &&
63
+ (location = ast.location) &&
64
+ (location.kind_of? Parser::Source::Map::Heredoc)
65
+ end
66
+
67
+ def void_value?(ast)
68
+ case ast && ast.type
69
+ when :begin, :kwbegin, :resbody
70
+ void_value?(ast.children.last)
71
+ when :rescue, :ensure
72
+ ast.children.any? { |child| void_value? child }
73
+ when :if
74
+ void_value?(ast.children[1]) || void_value?(ast.children[2])
75
+ when :return, :next, :redo, :retry, :break
76
+ true
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ def line_indexes
83
+ @line_indexes ||= [ 0,
84
+ *raw.each_char
85
+ .with_index(1)
86
+ .select { |char, index| char == "\n" }
87
+ .map { |char, index| index },
88
+ ].freeze
89
+ end
90
+
91
+ def indexes_of_ors_at_eol
92
+ Set.new(
93
+ @tokens.select { |type, *| type == :tGVAR }
94
+ .select { |_, (var, _range)| var == '$\\'.freeze }
95
+ .map { |_, (_var, range)| range.end_pos }
96
+ )
97
+ end
98
+
99
+ private
100
+
101
+ def comments_and_tokens(builder, buffer)
102
+ parser = Parser::CurrentRuby.new builder
103
+ _, all_comments, tokens = parser.tokenize(@buffer, true)
104
+ [all_comments, tokens]
105
+ end
106
+
107
+ def body_range_from_tokens(tokens)
108
+ return range_for(0, 0) if raw.start_with? "__END__\n"
109
+ (name, (_, range)) = tokens.max_by { |name, (_data, range)| range.end_pos } ||
110
+ [nil, [nil, range_for(0, 1)]]
111
+ end_pos = range.end_pos
112
+ end_pos += 1 if name == :tCOMMENT || name == :tSTRING_END || name == :tSEMI
113
+ range_for 0, end_pos
114
+ end
115
+
116
+ def wrap_comment(comment)
117
+ last_char = comment.location.expression.begin_pos
118
+ first_char = last_char
119
+ first_char -= 1 while first_char > 0 && raw[first_char-1] =~ /[ \t]/
120
+ preceding_whitespace = buffer.source[first_char...last_char]
121
+ preceding_whitespace_range = range_for first_char, last_char
122
+
123
+ InlineComment.new line_number: comment.location.line,
124
+ whitespace_col: preceding_whitespace_range.column,
125
+ whitespace: preceding_whitespace,
126
+ text_col: comment.location.column,
127
+ text: comment.text,
128
+ full_range: range_for(first_char, comment.location.expression.end_pos),
129
+ whitespace_range: preceding_whitespace_range,
130
+ comment_range: comment.location.expression
131
+ end
132
+
133
+ def null_node
134
+ location = Parser::Source::Map::Collection.new nil, nil, range_for(0, 0)
135
+ Parser::AST::Node.new :null_node, [], location: location
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,28 @@
1
+ class SeeingIsBelieving
2
+ module Compatibility
3
+ end
4
+ end
5
+
6
+ # Ruby 2.0.0 is soooooo painful >.<
7
+ # want to stop supporting this so bad!!
8
+ is_v2_0 = !String.instance_methods.include?(:scrub)
9
+
10
+ is_v2_0 && begin
11
+ old_verbose, $VERBOSE = $VERBOSE, nil
12
+ module SeeingIsBelieving::Compatibility
13
+ refine String do
14
+ def scrub(char=nil, &block)
15
+ char && block = lambda { |c| char }
16
+ each_char.inject("") do |new_str, char|
17
+ if char.valid_encoding?
18
+ new_str << char
19
+ else
20
+ new_str << block.call(char)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ ensure
27
+ $VERBOSE = old_verbose
28
+ end
@@ -0,0 +1,32 @@
1
+ class SeeingIsBelieving
2
+ class Debugger
3
+ CONTEXT_COLOUR = "\e[37;44m" # background blue
4
+ RESET_COLOUR = "\e[0m"
5
+
6
+ def initialize(options={})
7
+ @coloured = options[:colour]
8
+ @stream = options[:stream]
9
+ end
10
+
11
+ Null = new stream: nil
12
+
13
+ def coloured?
14
+ @coloured
15
+ end
16
+
17
+ attr_reader :stream
18
+ alias enabled? stream
19
+
20
+ def context(name, &block)
21
+ if enabled?
22
+ stream << CONTEXT_COLOUR if coloured?
23
+ stream << "#{name}:"
24
+ stream << RESET_COLOUR if coloured?
25
+ stream << "\n"
26
+ stream << block.call.to_s << "\n" if block
27
+ end
28
+ self
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,17 @@
1
+ class SeeingIsBelieving
2
+ # All our errors will inherit from this so that a user can catch any error generated by this lib
3
+ SeeingIsBelievingError = Class.new StandardError
4
+
5
+ class TempFileAlreadyExists < SeeingIsBelievingError
6
+ def initialize(from_filename, backup_filename)
7
+ super "Trying to back up #{from_filename.inspect} (FILE) to"\
8
+ " #{backup_filename.inspect} (TEMPFILE) but TEMPFILE already exists."\
9
+ " You should check the contents of these files. If FILE is correct,"\
10
+ " then delete TEMPFILE. Otherwise rename TEMPFILE to FILE."
11
+ end
12
+ end
13
+
14
+ # EventStream
15
+ NoMoreEvents = Class.new SeeingIsBelievingError
16
+ UnknownEvent = Class.new SeeingIsBelievingError
17
+ end