seeing_is_believing 0.0.15 → 0.0.16
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.
- 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
|