seeing_is_believing 0.0.15 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/Readme.md +1 -0
- data/features/examples.feature +7 -0
- data/features/flags.feature +6 -0
- data/features/step_definitions/steps.rb +7 -7
- data/features/support/env.rb +1 -0
- data/lib/seeing_is_believing.rb +8 -7
- data/lib/seeing_is_believing/binary.rb +9 -0
- data/lib/seeing_is_believing/binary/arg_parser.rb +5 -2
- data/lib/seeing_is_believing/binary/print_results_next_to_lines.rb +4 -3
- data/lib/seeing_is_believing/expression_list.rb +1 -1
- data/lib/seeing_is_believing/syntax_analyzer.rb +24 -2
- data/lib/seeing_is_believing/version.rb +1 -1
- data/spec/arg_parser_spec.rb +11 -0
- data/spec/seeing_is_believing_spec.rb +35 -0
- data/spec/syntax_analyzer_spec.rb +55 -16
- metadata +2 -2
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -132,6 +132,7 @@ Known Issues
|
|
132
132
|
* Heredocs aren't recorded. It might actually be possible if the ExpressionList were to get smarter
|
133
133
|
* Return statements are dealt with poorly, causing some situations where you could capture and display a value to not capture
|
134
134
|
* errors come out really shitty if you're calling them from another program like TextMate, would be better to put a line in that shows where the error is.
|
135
|
+
* Add a time limit to auto-kill it if it gets stuck or something (e.g. stack overflow is painful to wait for)
|
135
136
|
|
136
137
|
License
|
137
138
|
=======
|
data/features/examples.feature
CHANGED
@@ -255,3 +255,10 @@ Feature: Running the binary successfully
|
|
255
255
|
And the exit status is 0
|
256
256
|
And stdout is "1 + 1 # => 2"
|
257
257
|
|
258
|
+
Scenario: Regression: A program containing a single comment
|
259
|
+
Given I have the stdin content "# single comment"
|
260
|
+
When I run "seeing_is_believing"
|
261
|
+
Then stderr is empty
|
262
|
+
And the exit status is 0
|
263
|
+
And stdout is "# single comment"
|
264
|
+
|
data/features/flags.feature
CHANGED
@@ -231,6 +231,12 @@ Feature: Using flags
|
|
231
231
|
And the exit status is 0
|
232
232
|
And stdout is '1+'
|
233
233
|
|
234
|
+
Scenario: --version
|
235
|
+
When I run 'seeing_is_believing --version'
|
236
|
+
Then stderr is empty
|
237
|
+
And the exit status is 0
|
238
|
+
And stdout is '{{SeeingIsBelieving::VERSION}}'
|
239
|
+
|
234
240
|
Scenario: --help
|
235
241
|
When I run "seeing_is_believing --help"
|
236
242
|
Then stderr is empty
|
@@ -1,11 +1,11 @@
|
|
1
|
-
Given('the file "$filename" "$body"') { |filename, body| CommandLineHelpers.write_file filename, body }
|
2
|
-
Given('the file "$filename":') { |filename, body| CommandLineHelpers.write_file filename, body }
|
3
|
-
Given('I have the stdin content "$content"') { |content| @stdin_data = content }
|
4
|
-
Given('I have the stdin content:') { |content| @stdin_data = content }
|
1
|
+
Given('the file "$filename" "$body"') { |filename, body| CommandLineHelpers.write_file filename, eval_curlies(body) }
|
2
|
+
Given('the file "$filename":') { |filename, body| CommandLineHelpers.write_file filename, eval_curlies(body) }
|
3
|
+
Given('I have the stdin content "$content"') { |content| @stdin_data = eval_curlies(content) }
|
4
|
+
Given('I have the stdin content:') { |content| @stdin_data = eval_curlies(content) }
|
5
5
|
When('I run "$command"') { |command| @last_executed = CommandLineHelpers.execute command, @stdin_data }
|
6
6
|
When("I run '$command'") { |command| @last_executed = CommandLineHelpers.execute command, @stdin_data }
|
7
|
-
Then(/^(stderr|stdout) is:$/) { |stream_name, output| @last_executed.send(stream_name).chomp.should == output }
|
8
|
-
Then(/^(stderr|stdout) is ["'](.*?)["']$/) { |stream_name, output| @last_executed.send(stream_name).chomp.should == output }
|
7
|
+
Then(/^(stderr|stdout) is:$/) { |stream_name, output| @last_executed.send(stream_name).chomp.should == eval_curlies(output) }
|
8
|
+
Then(/^(stderr|stdout) is ["'](.*?)["']$/) { |stream_name, output| @last_executed.send(stream_name).chomp.should == eval_curlies(output) }
|
9
9
|
Then(/^(stderr|stdout) is empty$/) { |stream_name| @last_executed.send(stream_name).should == '' }
|
10
|
-
Then(/^(stderr|stdout) includes "([^"]*)"$/) { |stream_name, content| @last_executed.send(stream_name).should include content }
|
10
|
+
Then(/^(stderr|stdout) includes "([^"]*)"$/) { |stream_name, content| @last_executed.send(stream_name).should include eval_curlies(content) }
|
11
11
|
Then('the exit status is $status') { |status| @last_executed.exitstatus.to_s.should == status }
|
data/features/support/env.rb
CHANGED
data/lib/seeing_is_believing.rb
CHANGED
@@ -3,6 +3,7 @@ require 'tmpdir'
|
|
3
3
|
|
4
4
|
require 'seeing_is_believing/queue'
|
5
5
|
require 'seeing_is_believing/result'
|
6
|
+
require 'seeing_is_believing/version'
|
6
7
|
require 'seeing_is_believing/expression_list'
|
7
8
|
require 'seeing_is_believing/evaluate_by_moving_files'
|
8
9
|
|
@@ -35,13 +36,13 @@ class SeeingIsBelieving
|
|
35
36
|
leading_comments = ''
|
36
37
|
|
37
38
|
# extract leading comments (e.g. encoding) so they don't get wrapped in begin/rescue/end
|
38
|
-
while next_line_queue.peek
|
39
|
+
while SyntaxAnalyzer.line_is_comment?(next_line_queue.peek)
|
39
40
|
leading_comments << next_line_queue.dequeue << "\n"
|
40
41
|
@line_number += 1
|
41
42
|
end
|
42
43
|
|
43
44
|
# extract leading =begin/=end so they don't get wrapped in begin/rescue/end
|
44
|
-
while next_line_queue.peek
|
45
|
+
while SyntaxAnalyzer.begins_multiline_comment?(next_line_queue.peek)
|
45
46
|
lines = next_line_queue.dequeue << "\n"
|
46
47
|
@line_number += 1
|
47
48
|
until SyntaxAnalyzer.begin_and_end_comments_are_complete? lines
|
@@ -53,7 +54,7 @@ class SeeingIsBelieving
|
|
53
54
|
|
54
55
|
# extract program body
|
55
56
|
body = ''
|
56
|
-
until next_line_queue.
|
57
|
+
until next_line_queue.empty? || data_segment?
|
57
58
|
expression, expression_size = expression_list.call
|
58
59
|
body << expression
|
59
60
|
track_line_number @line_number
|
@@ -128,7 +129,7 @@ class SeeingIsBelieving
|
|
128
129
|
end
|
129
130
|
|
130
131
|
def data_segment?
|
131
|
-
next_line_queue.peek
|
132
|
+
SyntaxAnalyzer.begins_data_segment?(next_line_queue.peek)
|
132
133
|
end
|
133
134
|
|
134
135
|
def next_line_queue
|
@@ -140,9 +141,9 @@ class SeeingIsBelieving
|
|
140
141
|
end
|
141
142
|
|
142
143
|
def do_not_record?(code)
|
143
|
-
code =~ BLANK_REGEX
|
144
|
-
SyntaxAnalyzer.ends_in_comment?(code)
|
145
|
-
SyntaxAnalyzer.
|
144
|
+
code =~ BLANK_REGEX ||
|
145
|
+
SyntaxAnalyzer.ends_in_comment?(code) ||
|
146
|
+
SyntaxAnalyzer.void_value_expression?(code) ||
|
146
147
|
SyntaxAnalyzer.here_doc?(code)
|
147
148
|
end
|
148
149
|
end
|
@@ -17,6 +17,7 @@ class SeeingIsBelieving
|
|
17
17
|
def call
|
18
18
|
@exitstatus ||= if flags_have_errors? then print_errors ; 1
|
19
19
|
elsif should_print_help? then print_help ; 0
|
20
|
+
elsif should_print_version? then print_version ; 0
|
20
21
|
elsif has_filename? && file_dne? then print_file_dne ; 1
|
21
22
|
elsif should_clean? then print_cleaned_program ; 0
|
22
23
|
elsif invalid_syntax? then print_syntax_error ; 1
|
@@ -70,6 +71,14 @@ class SeeingIsBelieving
|
|
70
71
|
stdout.puts flags[:help]
|
71
72
|
end
|
72
73
|
|
74
|
+
def should_print_version?
|
75
|
+
flags[:version]
|
76
|
+
end
|
77
|
+
|
78
|
+
def print_version
|
79
|
+
stdout.puts SeeingIsBelieving::VERSION
|
80
|
+
end
|
81
|
+
|
73
82
|
def file_is_on_stdin?
|
74
83
|
flags[:filename].nil? && flags[:program].nil?
|
75
84
|
end
|
@@ -16,8 +16,9 @@ class SeeingIsBelieving
|
|
16
16
|
@result ||= begin
|
17
17
|
until args.empty?
|
18
18
|
case (arg = args.shift)
|
19
|
-
when '-h', '--help' then options[:help]
|
20
|
-
when '-
|
19
|
+
when '-h', '--help' then options[:help] = self.class.help_screen
|
20
|
+
when '-v', '--version' then options[:version] = true
|
21
|
+
when '-c', '--clean' then options[:clean] = true
|
21
22
|
when '-l', '--start-line' then extract_positive_int_for :start_line, arg
|
22
23
|
when '-L', '--end-line' then extract_positive_int_for :end_line, arg
|
23
24
|
when '-d', '--line-length' then extract_positive_int_for :line_length, arg
|
@@ -57,6 +58,7 @@ class SeeingIsBelieving
|
|
57
58
|
|
58
59
|
def options
|
59
60
|
@options ||= {
|
61
|
+
version: false,
|
60
62
|
clean: false,
|
61
63
|
program: nil,
|
62
64
|
filename: nil,
|
@@ -104,6 +106,7 @@ Usage: #{$0} [options] [filename]
|
|
104
106
|
-K, --encoding encoding # sets file encoding, equivalent to Ruby's -Kx (see `man ruby` for valid values)
|
105
107
|
-a, --as filename # run the program as if it was the specified filename
|
106
108
|
-c, --clean # remove annotations from previous runs of seeing_is_believing
|
109
|
+
-v, --version # print the version (#{VERSION})
|
107
110
|
-h, --help # this help screen
|
108
111
|
HELP_SCREEN
|
109
112
|
end
|
@@ -78,18 +78,19 @@ class SeeingIsBelieving
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def start_of_data_segment?(line)
|
81
|
-
line.chomp
|
81
|
+
SyntaxAnalyzer.begins_data_segment?(line.chomp)
|
82
82
|
end
|
83
83
|
|
84
|
-
# max line length of the lines to output (exempting
|
84
|
+
# max line length of the lines to output (exempting comments) + 2 spaces for padding
|
85
85
|
def max_source_line_length
|
86
86
|
@max_source_line_length ||= 2 + body.each_line
|
87
87
|
.map(&:chomp)
|
88
88
|
.select.with_index(1) { |line, index| start_line <= index && index <= end_line }
|
89
89
|
.take_while { |line| not start_of_data_segment? line }
|
90
|
-
.select { |line| not (line
|
90
|
+
.select { |line| not SyntaxAnalyzer.begins_multiline_comment?(line) .. SyntaxAnalyzer.ends_multiline_comment?(line ) }
|
91
91
|
.reject { |line| SyntaxAnalyzer.ends_in_comment? line }
|
92
92
|
.map(&:length)
|
93
|
+
.concat([0])
|
93
94
|
.max
|
94
95
|
end
|
95
96
|
|
@@ -49,7 +49,7 @@ class SeeingIsBelieving
|
|
49
49
|
# method invocations can be put on the next line, and begin with a dot.
|
50
50
|
# I think that's the only case we need to worry about.
|
51
51
|
# e.g: `3\n.times { |i| p i }`
|
52
|
-
peek_next_line.call && peek_next_line.call
|
52
|
+
peek_next_line.call && SyntaxAnalyzer.next_line_modifies_current?(peek_next_line.call)
|
53
53
|
end
|
54
54
|
|
55
55
|
def inspected_expressions(expressions)
|
@@ -51,6 +51,14 @@ class SeeingIsBelieving
|
|
51
51
|
parsed(code).valid_ruby? && begin_and_end_comments_are_complete?(code)
|
52
52
|
end
|
53
53
|
|
54
|
+
def self.begins_multiline_comment?(line)
|
55
|
+
line == '=begin'
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.ends_multiline_comment?(line)
|
59
|
+
line == '=end'
|
60
|
+
end
|
61
|
+
|
54
62
|
def self.begin_and_end_comments_are_complete?(code)
|
55
63
|
code.scan(/^=(?:begin|end)$/)
|
56
64
|
.each_slice(2)
|
@@ -65,6 +73,16 @@ class SeeingIsBelieving
|
|
65
73
|
@has_error || unclosed_string? || unclosed_regexp?
|
66
74
|
end
|
67
75
|
|
76
|
+
# MISC
|
77
|
+
|
78
|
+
def self.begins_data_segment?(line)
|
79
|
+
line == '__END__'
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.next_line_modifies_current?(line)
|
83
|
+
line =~ /^\s*\./
|
84
|
+
end
|
85
|
+
|
68
86
|
# STRINGS
|
69
87
|
|
70
88
|
def self.unclosed_string?(code)
|
@@ -115,6 +133,10 @@ class SeeingIsBelieving
|
|
115
133
|
|
116
134
|
# COMMENTS
|
117
135
|
|
136
|
+
def self.line_is_comment?(line)
|
137
|
+
line =~ /^\s*#/
|
138
|
+
end
|
139
|
+
|
118
140
|
def self.ends_in_comment?(code)
|
119
141
|
code =~ /^=end\Z/ || parsed(code.lines.to_a.last.to_s).has_comment?
|
120
142
|
end
|
@@ -136,8 +158,8 @@ class SeeingIsBelieving
|
|
136
158
|
|
137
159
|
# this is conspicuosuly inferior, but I can't figure out how to actually parse it
|
138
160
|
# see: http://www.ruby-forum.com/topic/4409633
|
139
|
-
def self.
|
140
|
-
/(^|\s)return.*?\n?\z/ =~ code
|
161
|
+
def self.void_value_expression?(code)
|
162
|
+
/(^|\s)(?:return|next|redo|retry|break).*?\n?\z/ =~ code
|
141
163
|
end
|
142
164
|
|
143
165
|
# HERE DOCS
|
data/spec/arg_parser_spec.rb
CHANGED
@@ -248,5 +248,16 @@ describe SeeingIsBelieving::Binary::ArgParser do
|
|
248
248
|
parse(%w[--clean])[:clean].should == true
|
249
249
|
end
|
250
250
|
end
|
251
|
+
|
252
|
+
describe ':version' do
|
253
|
+
it 'defaults to false' do
|
254
|
+
parse([])[:version].should == false
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'can be set with -v and --version' do
|
258
|
+
parse(%w[-v])[:version].should == true
|
259
|
+
parse(%w[--version])[:version].should == true
|
260
|
+
end
|
261
|
+
end
|
251
262
|
end
|
252
263
|
|
@@ -172,6 +172,41 @@ describe SeeingIsBelieving do
|
|
172
172
|
# values_for("-> { return 1 }.call" ).should == [['1']]
|
173
173
|
end
|
174
174
|
|
175
|
+
it 'does not try to record the keyword next' do
|
176
|
+
values_for("(1..2).each do |i|\nnext if i == 1\ni\nend").should == [[], [], ['2'], ['1..2']]
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'does not try to record the keyword redo' do
|
180
|
+
values_for(<<-DOC).should == [[], ['0'], [], ['1', '2', '3', '4'], [], ['0...3'], ['nil'], ['0...3']]
|
181
|
+
def meth
|
182
|
+
n = 0
|
183
|
+
for i in 0...3
|
184
|
+
n += 1
|
185
|
+
redo if n == 2
|
186
|
+
end
|
187
|
+
end
|
188
|
+
meth
|
189
|
+
DOC
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'does not try to record the keyword retry' do
|
193
|
+
values_for(<<-DOC).should == [[], [], [], ['nil']]
|
194
|
+
def meth
|
195
|
+
rescue
|
196
|
+
retry
|
197
|
+
end
|
198
|
+
DOC
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'does not try to record the keyword retry' do
|
202
|
+
values_for(<<-DOC).should == [[], ['0'], [], ['nil']]
|
203
|
+
(0..2).each do |n|
|
204
|
+
n
|
205
|
+
break
|
206
|
+
end
|
207
|
+
DOC
|
208
|
+
end
|
209
|
+
|
175
210
|
it 'does not affect its environment' do
|
176
211
|
invoke 'def Object.abc() end'
|
177
212
|
Object.should_not respond_to :abc
|
@@ -62,6 +62,23 @@ describe SeeingIsBelieving::SyntaxAnalyzer do
|
|
62
62
|
is_unclosed_comment[" =begin"].should be_false
|
63
63
|
end
|
64
64
|
|
65
|
+
it 'knows if the line begins a multiline comment' do
|
66
|
+
described_class.begins_multiline_comment?('=begin').should be_true
|
67
|
+
described_class.begins_multiline_comment?('=begins').should be_false
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'knows if the line ends a multiline comment' do
|
71
|
+
described_class.ends_multiline_comment?('=end').should be_true
|
72
|
+
described_class.ends_multiline_comment?('=ends').should be_false
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'knows when the line is a comment' do
|
76
|
+
described_class.line_is_comment?('# abc').should be_true
|
77
|
+
described_class.line_is_comment?(' # abc').should be_true
|
78
|
+
described_class.line_is_comment?('a # abc').should be_false
|
79
|
+
described_class.line_is_comment?('abc').should be_false
|
80
|
+
end
|
81
|
+
|
65
82
|
# probably don't really need this many tests, but I'm unfamiliar with how thorough Ripper is
|
66
83
|
# and already found areas where it doesn't behave correctly
|
67
84
|
it 'knows if the code contains an unclosed string' do
|
@@ -154,22 +171,44 @@ describe SeeingIsBelieving::SyntaxAnalyzer do
|
|
154
171
|
end
|
155
172
|
end
|
156
173
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
174
|
+
shared_examples_for 'void_value_expression?' do |keyword|
|
175
|
+
it "returns true when the expression ends in #{keyword}" do
|
176
|
+
described_class.void_value_expression?("#{keyword} 1").should be_true
|
177
|
+
described_class.void_value_expression?("#{keyword} 1\n").should be_true
|
178
|
+
described_class.void_value_expression?("#{keyword} 1 if true").should be_true
|
179
|
+
described_class.void_value_expression?("#{keyword} 1 if false").should be_true
|
180
|
+
described_class.void_value_expression?("o.#{keyword}").should be_false
|
181
|
+
described_class.void_value_expression?(":#{keyword}").should be_false
|
182
|
+
described_class.void_value_expression?("'#{keyword}'").should be_false
|
183
|
+
described_class.void_value_expression?("def a\n#{keyword} 1\nend").should be_false
|
184
|
+
described_class.void_value_expression?("-> {\n#{keyword} 1\n}").should be_false
|
185
|
+
described_class.void_value_expression?("Proc.new {\n#{keyword} 1\n}").should be_false
|
186
|
+
end
|
187
|
+
|
188
|
+
it "doesn't work because the return and next keyword evaluators are insufficient regexps" do
|
189
|
+
pending "doesn't pass yet (and prob never will >.<)" do
|
190
|
+
described_class.send(evalutor, "'#{keyword}\n#{keyword}\n#{keyword}'").should be_false
|
191
|
+
described_class.send(evalutor, "#{keyword} \\\n1").should be_true
|
192
|
+
end
|
173
193
|
end
|
174
194
|
end
|
195
|
+
|
196
|
+
it_should_behave_like 'void_value_expression?', 'return'
|
197
|
+
it_should_behave_like 'void_value_expression?', 'next'
|
198
|
+
it_should_behave_like 'void_value_expression?', 'redo'
|
199
|
+
it_should_behave_like 'void_value_expression?', 'retry'
|
200
|
+
it_should_behave_like 'void_value_expression?', 'break'
|
201
|
+
|
202
|
+
it 'knows when a line opens the data segment' do
|
203
|
+
described_class.begins_data_segment?('__END__').should be_true
|
204
|
+
described_class.begins_data_segment?('__ENDS__').should be_false
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'knows when the next line modifies the current line' do
|
208
|
+
described_class.next_line_modifies_current?('.meth').should be_true
|
209
|
+
described_class.next_line_modifies_current?(' .meth').should be_true
|
210
|
+
|
211
|
+
described_class.next_line_modifies_current?('meth').should be_false
|
212
|
+
described_class.next_line_modifies_current?(' meth').should be_false
|
213
|
+
end
|
175
214
|
end
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: seeing_is_believing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.16
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Josh Cheek
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-04-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
version_requirements: !ruby/object:Gem::Requirement
|