seeing_is_believing 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +3 -1
- data/Readme.md +31 -5
- data/bin/seeing_is_believing +0 -2
- data/features/binary_examples.feature +42 -2
- data/features/step_definitions/steps.rb +5 -1
- data/features/support/env.rb +3 -2
- data/lib/seeing_is_believing.rb +10 -2
- data/lib/seeing_is_believing/binary.rb +48 -21
- data/lib/seeing_is_believing/error.rb +8 -1
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +20 -14
- data/lib/seeing_is_believing/expression_list.rb +1 -1
- data/lib/seeing_is_believing/print_results_next_to_lines.rb +9 -7
- data/lib/seeing_is_believing/syntax_analyzer.rb +44 -2
- data/lib/seeing_is_believing/version.rb +1 -1
- data/seeing_is_believing.gemspec +1 -0
- data/spec/evaluate_by_moving_files_spec.rb +9 -2
- data/spec/expression_list_spec.rb +21 -0
- data/spec/seeing_is_believing_spec.rb +27 -5
- data/spec/syntax_analyzer_spec.rb +40 -0
- data/textmate-integration.png +0 -0
- metadata +20 -8
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
seeing_is_believing (0.0.
|
4
|
+
seeing_is_believing (0.0.6)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
@@ -17,6 +17,7 @@ GEM
|
|
17
17
|
json (>= 1.4.6)
|
18
18
|
ichannel (5.1.1)
|
19
19
|
json (1.7.6)
|
20
|
+
rake (10.0.3)
|
20
21
|
rspec (2.12.0)
|
21
22
|
rspec-core (~> 2.12.0)
|
22
23
|
rspec-expectations (~> 2.12.0)
|
@@ -32,5 +33,6 @@ PLATFORMS
|
|
32
33
|
DEPENDENCIES
|
33
34
|
cucumber (~> 1.2.1)
|
34
35
|
ichannel (~> 5.1.1)
|
36
|
+
rake (~> 10.0.3)
|
35
37
|
rspec (~> 2.12.0)
|
36
38
|
seeing_is_believing!
|
data/Readme.md
CHANGED
@@ -5,10 +5,11 @@ Evaluates a file, recording the results of each line of code.
|
|
5
5
|
You can then use this to display output values like Bret Victor does with JavaScript in his talk [Inventing on Principle][inventing_on_principle].
|
6
6
|
Except, obviously, his is like a million better.
|
7
7
|
|
8
|
-
Reeaally rough at the moment, but it works for simple examples.
|
9
|
-
|
10
8
|
Also comes with a binary to show how it might be used.
|
11
9
|
|
10
|
+
For whatever reason, I can't embed videos, but here's a ~1 minute [video][video] showing it off.
|
11
|
+
|
12
|
+
|
12
13
|
Use The Binary
|
13
14
|
==============
|
14
15
|
|
@@ -63,19 +64,42 @@ result[2] # => ['"A"', '"B"', '"C"']
|
|
63
64
|
Install
|
64
65
|
=======
|
65
66
|
|
67
|
+
For now, since Rubygems is not allowing pushes:
|
68
|
+
|
69
|
+
$ git clone https://github.com/JoshCheek/seeing_is_believing/
|
70
|
+
$ cd seeing_is_believing
|
71
|
+
$ gem build seeing_is_believing.gemspec
|
72
|
+
$ gem install seeing_is_believing-0.0.7.gem
|
73
|
+
$ cd ..
|
74
|
+
$ rm -rf "./seeing_is_believing"
|
75
|
+
|
76
|
+
When Rubygems gets back up:
|
77
|
+
|
66
78
|
$ gem install seeing_is_believing
|
67
79
|
|
68
80
|
Or if you haven't fixed your gem home, and you aren't using any version managers:
|
69
81
|
|
70
82
|
$ sudo gem install seeing_is_believing
|
71
83
|
|
84
|
+
Hook it into TextMate
|
85
|
+
=====================
|
86
|
+
|
87
|
+
Go to the bundle editor, create this new command in the Ruby bundle:
|
88
|
+
|
89
|
+
"${TM_RUBY}" -r seeing_is_believing/binary -e '
|
90
|
+
SeeingIsBelieving::Binary.new(ARGV, $stdin, $stdout, $stderr).call
|
91
|
+
' $TM_FILEPATH 2>/dev/null
|
92
|
+
|
93
|
+
It should look like this:
|
94
|
+
|
95
|
+
![textmate-integration][textmate-integration]
|
96
|
+
|
72
97
|
Known Issues
|
73
98
|
============
|
74
99
|
|
75
|
-
* No idea what happens if you give it a syntactically invalid file. It probably just raises an exception, but might possibly freeze up or something.
|
76
|
-
* heredocs breaks things maybe also `BEGIN/END` and `=begin/=end`
|
77
100
|
* There are expressions which continue on the next line even though the previous line is a valid expression, e.g. "3\n.times { |i| i }" which will blow up. This is a fundamental flaw in the algorithm and will either require a smarter algorithm, or some sort of more sophisticated parsing in order to handle correctly
|
78
|
-
*
|
101
|
+
* `BEGIN/END` breaks things and I probably won't take the time to fix it, becuase it's nontrivial, but there is currently a cuke for it
|
102
|
+
* Heredocs aren't recorded. It might actually be possible if the ExpressionList were to get smarter
|
79
103
|
|
80
104
|
License
|
81
105
|
=======
|
@@ -97,3 +121,5 @@ License
|
|
97
121
|
|
98
122
|
|
99
123
|
[inventing_on_principle]: http://vimeo.com/36579366
|
124
|
+
[textmate-integration]: https://raw.github.com/JoshCheek/seeing_is_believing/master/textmate-integration.png
|
125
|
+
[video]: http://vimeo.com/58766950
|
data/bin/seeing_is_believing
CHANGED
@@ -21,10 +21,20 @@ Feature: Running the binary successfully
|
|
21
21
|
meth "12"
|
22
22
|
meth "34"
|
23
23
|
|
24
|
+
=begin
|
25
|
+
I don't ever actually write
|
26
|
+
comments like this
|
27
|
+
=end
|
28
|
+
|
24
29
|
# multilinezzz
|
25
30
|
"a
|
26
31
|
b
|
27
32
|
c"
|
33
|
+
|
34
|
+
# don't record heredocs b/c they're just too fucking different
|
35
|
+
<<HERE
|
36
|
+
is a doc
|
37
|
+
HERE
|
28
38
|
"""
|
29
39
|
When I run "seeing_is_believing basic_functionality.rb"
|
30
40
|
Then stderr is empty
|
@@ -43,10 +53,20 @@ Feature: Running the binary successfully
|
|
43
53
|
meth "12" # => "12"
|
44
54
|
meth "34" # => "34"
|
45
55
|
|
56
|
+
=begin
|
57
|
+
I don't ever actually write
|
58
|
+
comments like this
|
59
|
+
=end
|
60
|
+
|
46
61
|
# multilinezzz
|
47
62
|
"a
|
48
63
|
b
|
49
64
|
c" # => "a\n b\n c"
|
65
|
+
|
66
|
+
# don't record heredocs b/c they're just too fucking different
|
67
|
+
<<HERE
|
68
|
+
is a doc
|
69
|
+
HERE
|
50
70
|
"""
|
51
71
|
|
52
72
|
Scenario: Passing previous output back into input
|
@@ -170,6 +190,26 @@ Feature: Running the binary successfully
|
|
170
190
|
# >> 2
|
171
191
|
"""
|
172
192
|
|
173
|
-
Scenario:
|
174
|
-
|
193
|
+
Scenario: Reading from stdin
|
194
|
+
Given I have the stdin content "hi!"
|
195
|
+
And the file "reads_from_stdin.rb":
|
196
|
+
"""
|
197
|
+
puts "You said: #{gets}"
|
198
|
+
"""
|
199
|
+
When I run "seeing_is_believing reads_from_stdin.rb"
|
200
|
+
Then stderr is empty
|
201
|
+
And the exit status is 0
|
202
|
+
And stdout is:
|
203
|
+
"""
|
204
|
+
puts "You said: #{gets}" # => nil
|
205
|
+
|
206
|
+
# >> You said: hi!
|
207
|
+
"""
|
208
|
+
|
175
209
|
Scenario: Passing the file on stdin
|
210
|
+
Given I have the stdin content "1 + 1"
|
211
|
+
When I run "seeing_is_believing"
|
212
|
+
Then stderr is empty
|
213
|
+
And the exit status is 0
|
214
|
+
And stdout is "1 + 1 # => 2"
|
215
|
+
|
@@ -2,8 +2,12 @@ Given 'the file "$filename":' do |filename, body|
|
|
2
2
|
CommandLineHelpers.write_file filename, body
|
3
3
|
end
|
4
4
|
|
5
|
+
Given 'I have the stdin content "$content"' do |content|
|
6
|
+
@stdin_data = content
|
7
|
+
end
|
8
|
+
|
5
9
|
When 'I run "$command"' do |command|
|
6
|
-
@last_executed = CommandLineHelpers.execute command
|
10
|
+
@last_executed = CommandLineHelpers.execute command, @stdin_data
|
7
11
|
end
|
8
12
|
|
9
13
|
Then /^(stderr|stdout) is:$/ do |stream_name, output|
|
data/features/support/env.rb
CHANGED
@@ -18,10 +18,11 @@ module CommandLineHelpers
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def execute(command)
|
21
|
+
def execute(command, stdin_data=nil)
|
22
|
+
stdin_data ||= ''
|
22
23
|
in_proving_grounds do
|
23
24
|
bin_in_path = {'PATH' => "#{bin_dir}:#{ENV['PATH']}"}
|
24
|
-
Invocation.new *Open3.capture3(bin_in_path, command)
|
25
|
+
Invocation.new *Open3.capture3(bin_in_path, command, stdin_data: stdin_data)
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
data/lib/seeing_is_believing.rb
CHANGED
@@ -14,6 +14,7 @@ class SeeingIsBelieving
|
|
14
14
|
@string = string_or_stream
|
15
15
|
@stream = to_stream string_or_stream
|
16
16
|
@filename = options[:filename]
|
17
|
+
@stdin = to_stream options.fetch(:stdin, '')
|
17
18
|
end
|
18
19
|
|
19
20
|
def call
|
@@ -35,7 +36,7 @@ class SeeingIsBelieving
|
|
35
36
|
on_complete: lambda { |line, children, completions, line_number|
|
36
37
|
track_line_number line_number
|
37
38
|
expression = [line, *children, *completions].map(&:chomp).join("\n")
|
38
|
-
if
|
39
|
+
if do_not_record? expression
|
39
40
|
expression + "\n"
|
40
41
|
else
|
41
42
|
record_yahself(expression, line_number) + "\n"
|
@@ -65,7 +66,7 @@ class SeeingIsBelieving
|
|
65
66
|
def result_for(program, min_line_number, max_line_number)
|
66
67
|
Dir.mktmpdir "seeing_is_believing_temp_dir" do |dir|
|
67
68
|
filename = @filename || File.join(dir, 'program.rb')
|
68
|
-
EvaluateByMovingFiles.new(program, filename).call.tap do |result|
|
69
|
+
EvaluateByMovingFiles.new(program, filename, input_stream: @stdin).call.tap do |result|
|
69
70
|
result.track_line_number min_line_number
|
70
71
|
result.track_line_number max_line_number
|
71
72
|
end
|
@@ -100,4 +101,11 @@ class SeeingIsBelieving
|
|
100
101
|
def the_rest_of_the_stream
|
101
102
|
get_next_line << "\n" << stream.read
|
102
103
|
end
|
104
|
+
|
105
|
+
def do_not_record?(code)
|
106
|
+
code =~ BLANK_REGEX ||
|
107
|
+
SyntaxAnalyzer.ends_in_comment?(code) ||
|
108
|
+
SyntaxAnalyzer.will_return?(code) ||
|
109
|
+
SyntaxAnalyzer.here_doc?(code)
|
110
|
+
end
|
103
111
|
end
|
@@ -15,27 +15,10 @@ class SeeingIsBelieving
|
|
15
15
|
return if @already_called
|
16
16
|
@already_called = true
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
out, err, syntax_status = Open3.capture3('ruby', '-c', filename)
|
25
|
-
unless syntax_status.success?
|
26
|
-
@exitstatus = 1
|
27
|
-
stderr.puts err
|
28
|
-
return
|
29
|
-
end
|
30
|
-
|
31
|
-
believer = SeeingIsBelieving::PrintResultsNextToLines.new File.read(filename), filename
|
32
|
-
stdout.puts believer.call
|
33
|
-
if believer.has_exception?
|
34
|
-
stderr.puts believer.exception.message
|
35
|
-
@exitstatus = 1
|
36
|
-
else
|
37
|
-
@exitstatus = 0
|
38
|
-
end
|
18
|
+
file_exists_or_is_on_stdin &&
|
19
|
+
syntax_is_valid &&
|
20
|
+
print_program &&
|
21
|
+
display_exceptions
|
39
22
|
end
|
40
23
|
|
41
24
|
def exitstatus
|
@@ -45,8 +28,52 @@ class SeeingIsBelieving
|
|
45
28
|
|
46
29
|
private
|
47
30
|
|
31
|
+
def on_stdin?
|
32
|
+
argv.empty?
|
33
|
+
end
|
34
|
+
|
48
35
|
def filename
|
49
36
|
argv.first
|
50
37
|
end
|
38
|
+
|
39
|
+
def believer
|
40
|
+
@believer ||= begin
|
41
|
+
if on_stdin?
|
42
|
+
PrintResultsNextToLines.new stdin.read, ''
|
43
|
+
else
|
44
|
+
PrintResultsNextToLines.new File.read(filename), stdin, filename
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def file_exists_or_is_on_stdin
|
50
|
+
return true if on_stdin? || File.exist?(filename)
|
51
|
+
@exitstatus = 1
|
52
|
+
stderr.puts "#{filename} does not exist!"
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def syntax_is_valid
|
57
|
+
return true if on_stdin? # <-- should probably check stdin too
|
58
|
+
out, err, syntax_status = Open3.capture3('ruby', '-c', filename)
|
59
|
+
return true if syntax_status.success?
|
60
|
+
@exitstatus = 1
|
61
|
+
stderr.puts err
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def print_program
|
66
|
+
stdout.puts believer.call
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
def display_exceptions
|
71
|
+
if believer.has_exception?
|
72
|
+
stderr.puts believer.exception.message
|
73
|
+
@exitstatus = 1
|
74
|
+
else
|
75
|
+
@exitstatus = 0
|
76
|
+
end
|
77
|
+
end
|
51
78
|
end
|
52
79
|
end
|
@@ -3,5 +3,12 @@ class SeeingIsBelieving
|
|
3
3
|
# can catch any error generated by this lib
|
4
4
|
SeeingIsBelievingError = Class.new StandardError
|
5
5
|
|
6
|
-
TempFileAlreadyExists
|
6
|
+
class TempFileAlreadyExists < SeeingIsBelievingError
|
7
|
+
def initialize(from_filename, temp_filename)
|
8
|
+
super "Trying to back up #{from_filename.inspect} (FILE) to"\
|
9
|
+
" #{temp_filename.inspect} (TEMPFILE) but TEMPFILE already exists."\
|
10
|
+
" You should check the contents of these files. If FILE is correct,"\
|
11
|
+
" then delete TEMPFILE. Otherwise rename TEMPFILE to FILE."
|
12
|
+
end
|
13
|
+
end
|
7
14
|
end
|
@@ -12,6 +12,7 @@
|
|
12
12
|
# when it sees this.
|
13
13
|
|
14
14
|
require 'open3'
|
15
|
+
require 'stringio'
|
15
16
|
require 'fileutils'
|
16
17
|
require 'seeing_is_believing/error'
|
17
18
|
require 'seeing_is_believing/result'
|
@@ -19,12 +20,13 @@ require 'seeing_is_believing/hard_core_ensure'
|
|
19
20
|
|
20
21
|
class SeeingIsBelieving
|
21
22
|
class EvaluateByMovingFiles
|
22
|
-
attr_accessor :program, :filename, :error_stream
|
23
|
+
attr_accessor :program, :filename, :error_stream, :input_stream
|
23
24
|
|
24
25
|
def initialize(program, filename, options={})
|
25
26
|
self.program = program
|
26
27
|
self.filename = File.expand_path(filename)
|
27
|
-
self.error_stream = options.fetch :error_stream, $stderr
|
28
|
+
self.error_stream = options.fetch :error_stream, $stderr # hmm, not really liking the global here
|
29
|
+
self.input_stream = options.fetch :input_stream, StringIO.new('')
|
28
30
|
end
|
29
31
|
|
30
32
|
def call
|
@@ -61,11 +63,7 @@ class SeeingIsBelieving
|
|
61
63
|
attr_accessor :stdout, :stderr, :exitstatus
|
62
64
|
|
63
65
|
def dont_overwrite_existing_tempfile!
|
64
|
-
|
65
|
-
raise TempFileAlreadyExists,
|
66
|
-
"Trying to back up #{filename.inspect} (FILE) to #{temp_filename.inspect} (TEMPFILE) but TEMPFILE already exists."\
|
67
|
-
" You should check the contents of these files. If FILE is correct, then delete TEMPFILE."\
|
68
|
-
" Otherwise rename TEMPFILE to FILE."
|
66
|
+
raise TempFileAlreadyExists.new(filename, temp_filename) if File.exist? temp_filename
|
69
67
|
end
|
70
68
|
|
71
69
|
def move_file_to_tempfile
|
@@ -84,13 +82,21 @@ class SeeingIsBelieving
|
|
84
82
|
end
|
85
83
|
|
86
84
|
def evaluate_file
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
85
|
+
Open3.popen3 'ruby', '-W0', # no warnings (b/c I hijack STDOUT/STDERR)
|
86
|
+
'-I', File.expand_path('../..', __FILE__), # fix load path
|
87
|
+
'-r', 'seeing_is_believing/the_matrix', # hijack the environment so it can be recorded
|
88
|
+
'-C', file_directory, # run in the file's directory
|
89
|
+
filename do |i, o, e, t|
|
90
|
+
out_reader = Thread.new { o.read }
|
91
|
+
err_reader = Thread.new { e.read }
|
92
|
+
Thread.new do
|
93
|
+
input_stream.each_char { |char| i.write char }
|
94
|
+
i.close
|
95
|
+
end
|
96
|
+
self.stdout = out_reader.value
|
97
|
+
self.stderr = err_reader.value
|
98
|
+
self.exitstatus = t.value
|
99
|
+
end
|
94
100
|
end
|
95
101
|
|
96
102
|
def fail
|
@@ -84,7 +84,7 @@ class SeeingIsBelieving
|
|
84
84
|
def children_will_never_be_valid?(expression)
|
85
85
|
analyzer = SyntaxAnalyzer.new(expression)
|
86
86
|
analyzer.parse
|
87
|
-
analyzer.unclosed_string? || analyzer.unclosed_regexp?
|
87
|
+
analyzer.unclosed_string? || analyzer.unclosed_regexp? || SyntaxAnalyzer.unclosed_comment?(expression)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
@@ -10,9 +10,10 @@ class SeeingIsBelieving
|
|
10
10
|
EXCEPTION_PREFIX = '# ~>'
|
11
11
|
RESULT_PREFIX = '# =>'
|
12
12
|
|
13
|
-
def initialize(body, filename=nil)
|
13
|
+
def initialize(body, stdin, filename=nil)
|
14
14
|
self.body = remove_previous_output_from body
|
15
15
|
self.filename = filename
|
16
|
+
self.stdin = stdin
|
16
17
|
end
|
17
18
|
|
18
19
|
def new_body
|
@@ -26,15 +27,15 @@ class SeeingIsBelieving
|
|
26
27
|
add_stdout
|
27
28
|
add_stderr
|
28
29
|
add_data_segment
|
29
|
-
new_body
|
30
|
+
return new_body
|
30
31
|
end
|
31
32
|
|
32
33
|
private
|
33
34
|
|
34
|
-
attr_accessor :body, :filename, :file_result
|
35
|
+
attr_accessor :body, :filename, :file_result, :stdin
|
35
36
|
|
36
37
|
def evaluate_program
|
37
|
-
self.file_result = SeeingIsBelieving.new(body, filename: filename).call
|
38
|
+
self.file_result = SeeingIsBelieving.new(body, filename: filename, stdin: stdin).call
|
38
39
|
end
|
39
40
|
|
40
41
|
def inherit_exception
|
@@ -58,11 +59,12 @@ class SeeingIsBelieving
|
|
58
59
|
line.chomp == '__END__'
|
59
60
|
end
|
60
61
|
|
61
|
-
# max line length of the body + 2 spaces for padding
|
62
|
+
# max line length of the body (exempting coments) + 2 spaces for padding
|
62
63
|
def line_length
|
63
64
|
@line_length ||= 2 + body.each_line
|
64
65
|
.map(&:chomp)
|
65
66
|
.take_while { |line| not start_of_data_segment? line }
|
67
|
+
.select { |line| not (line == "=begin") .. (line == "=end") }
|
66
68
|
.reject { |line| SyntaxAnalyzer.ends_in_comment? line }
|
67
69
|
.map(&:length)
|
68
70
|
.max
|
@@ -70,8 +72,8 @@ class SeeingIsBelieving
|
|
70
72
|
|
71
73
|
def remove_previous_output_from(string)
|
72
74
|
string.gsub(/\s+(#{EXCEPTION_PREFIX}|#{RESULT_PREFIX}).*?$/, '')
|
73
|
-
.gsub(
|
74
|
-
.gsub(
|
75
|
+
.gsub(/\n?(^#{STDOUT_PREFIX}[^\n]*\r?\n?)+/m, '')
|
76
|
+
.gsub(/\n?(^#{STDERR_PREFIX}[^\n]*\r?\n?)+/m, '')
|
75
77
|
end
|
76
78
|
|
77
79
|
def add_stdout
|
@@ -46,7 +46,13 @@ class SeeingIsBelieving
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.valid_ruby?(code)
|
49
|
-
parsed(code).valid_ruby?
|
49
|
+
parsed(code).valid_ruby? && begin_and_end_comments_are_complete?(code)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.begin_and_end_comments_are_complete?(code)
|
53
|
+
code.scan(/^=(?:begin|end)$/)
|
54
|
+
.each_slice(2)
|
55
|
+
.all? { |b, e| b == '=begin' && e == '=end' }
|
50
56
|
end
|
51
57
|
|
52
58
|
def valid_ruby?
|
@@ -108,7 +114,11 @@ class SeeingIsBelieving
|
|
108
114
|
# COMMENTS
|
109
115
|
|
110
116
|
def self.ends_in_comment?(code)
|
111
|
-
parsed(code.lines.to_a.last.to_s).has_comment?
|
117
|
+
code =~ /^=end\Z/ || parsed(code.lines.to_a.last.to_s).has_comment?
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.unclosed_comment?(code)
|
121
|
+
!begin_and_end_comments_are_complete?(code)
|
112
122
|
end
|
113
123
|
|
114
124
|
def has_comment?
|
@@ -127,5 +137,37 @@ class SeeingIsBelieving
|
|
127
137
|
def self.will_return?(code)
|
128
138
|
/(^|\s)return.*?\Z$/ =~ code
|
129
139
|
end
|
140
|
+
|
141
|
+
# HERE DOCS
|
142
|
+
|
143
|
+
def self.here_doc?(code)
|
144
|
+
instance = parsed code
|
145
|
+
instance.has_heredoc? && code.scan("\n").size.next <= instance.here_doc_last_line_number
|
146
|
+
end
|
147
|
+
|
148
|
+
def heredocs
|
149
|
+
@heredocs ||= []
|
150
|
+
end
|
151
|
+
|
152
|
+
def on_heredoc_beg(beginning)
|
153
|
+
heredocs << [beginning]
|
154
|
+
super
|
155
|
+
end
|
156
|
+
|
157
|
+
def on_heredoc_end(ending)
|
158
|
+
result = super
|
159
|
+
line_number = result.last.first
|
160
|
+
doc = heredocs.find { |(beginning)| beginning.include? ending.strip }
|
161
|
+
doc << ending << line_number
|
162
|
+
result
|
163
|
+
end
|
164
|
+
|
165
|
+
def has_heredoc?
|
166
|
+
heredocs.any?
|
167
|
+
end
|
168
|
+
|
169
|
+
def here_doc_last_line_number
|
170
|
+
heredocs.last.last
|
171
|
+
end
|
130
172
|
end
|
131
173
|
end
|
data/seeing_is_believing.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
+
s.add_development_dependency "rake", "~> 10.0.3"
|
21
22
|
s.add_development_dependency "rspec", "~> 2.12.0"
|
22
23
|
s.add_development_dependency "cucumber", "~> 1.2.1"
|
23
24
|
s.add_development_dependency "ichannel", "~> 5.1.1"
|
@@ -4,8 +4,8 @@ describe SeeingIsBelieving::EvaluateByMovingFiles do
|
|
4
4
|
let(:filedir) { File.expand_path '../../proving_grounds', __FILE__ }
|
5
5
|
let(:filename) { File.join filedir, 'some_filename' }
|
6
6
|
|
7
|
-
def invoke(program)
|
8
|
-
evaluator = described_class.new(program, filename)
|
7
|
+
def invoke(program, options={})
|
8
|
+
evaluator = described_class.new(program, filename, options)
|
9
9
|
FileUtils.rm_f evaluator.temp_filename
|
10
10
|
evaluator.call
|
11
11
|
end
|
@@ -65,4 +65,11 @@ describe SeeingIsBelieving::EvaluateByMovingFiles do
|
|
65
65
|
expect { evaluator.call }.to raise_error
|
66
66
|
stderr.string.should include "It blew up"
|
67
67
|
end
|
68
|
+
|
69
|
+
it "doesn't block waiting for io on stdin" do
|
70
|
+
reader, writer = IO.pipe
|
71
|
+
thread = Thread.new { invoke '1', input_stream: reader }
|
72
|
+
sleep 0.05
|
73
|
+
thread.should_not be_alive
|
74
|
+
end
|
68
75
|
end
|
@@ -125,6 +125,27 @@ describe SeeingIsBelieving::ExpressionList do
|
|
125
125
|
block_invocations.should == 1
|
126
126
|
end
|
127
127
|
|
128
|
+
example "example: =begin/=end comments" do
|
129
|
+
block_invocations = 0
|
130
|
+
call ['=begin', '1', '=end'] do |*expressions, line_number|
|
131
|
+
expressions.join('').should == "=begin1=end"
|
132
|
+
line_number.should == 3
|
133
|
+
block_invocations += 1
|
134
|
+
end
|
135
|
+
block_invocations.should == 1
|
136
|
+
end
|
137
|
+
|
138
|
+
example "example: heredoc" do
|
139
|
+
pending 'Not sure how to do this, for now just catch it at a higher level' do
|
140
|
+
result = call ['strings = [<<A, <<-B]', '1', 'A', '2', ' B'] do |*expressions, line_number|
|
141
|
+
line_number.should == 1
|
142
|
+
expressions.should == ['strings = [<<A, <<B]']
|
143
|
+
'zomg!'
|
144
|
+
end
|
145
|
+
result.should == "zomg!\n1\nA\n2\n B"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
128
149
|
example "example: smoke test debug option" do
|
129
150
|
stream = StringIO.new
|
130
151
|
call(%w[a+ b], debug: true, debug_stream: stream) { |*expressions, _| expressions.join("\n") }
|
@@ -2,8 +2,8 @@ require 'seeing_is_believing'
|
|
2
2
|
require 'stringio'
|
3
3
|
|
4
4
|
describe SeeingIsBelieving do
|
5
|
-
def invoke(input)
|
6
|
-
described_class.new(input).call
|
5
|
+
def invoke(input, options={})
|
6
|
+
described_class.new(input, options).call
|
7
7
|
end
|
8
8
|
|
9
9
|
def values_for(input)
|
@@ -104,6 +104,15 @@ describe SeeingIsBelieving do
|
|
104
104
|
3 # at end of program").should == [['1'], [], []]
|
105
105
|
end
|
106
106
|
|
107
|
+
it "does not record expressions that are here docs (only really b/c it's not smart enough)" do
|
108
|
+
values_for("<<A\n1\nA").should be_all &:empty?
|
109
|
+
values_for(" <<A\n1\nA").should be_all &:empty?
|
110
|
+
values_for("<<-A\n1\n A").should be_all &:empty?
|
111
|
+
values_for(" <<-A\n1\n A").should be_all &:empty?
|
112
|
+
values_for("s=<<-A\n1\n A").should be_all &:empty?
|
113
|
+
values_for("def meth\n<<-A\n1\nA\nend").should == [[], [], [], [], ['nil']]
|
114
|
+
end
|
115
|
+
|
107
116
|
it 'has no output for empty lines' do
|
108
117
|
values_for('').should == [[]]
|
109
118
|
values_for(' ').should == [[]]
|
@@ -185,15 +194,16 @@ describe SeeingIsBelieving do
|
|
185
194
|
it 'can be told to run as a given file (in a given dir/with a given filename)' do
|
186
195
|
filename = File.join proving_grounds_dir, 'mah_file.rb'
|
187
196
|
FileUtils.rm_f filename
|
188
|
-
result =
|
197
|
+
result = invoke 'print File.expand_path __FILE__', filename: filename
|
189
198
|
result.stdout.should == filename
|
190
199
|
end
|
191
200
|
|
201
|
+
# this can be refactored to use invoke
|
192
202
|
specify 'cwd is the directory of the file' do
|
193
203
|
filename = File.join proving_grounds_dir, 'mah_file.rb'
|
194
204
|
FileUtils.rm_f filename
|
195
|
-
result =
|
196
|
-
result =
|
205
|
+
result = invoke 'print File.expand_path __FILE__', filename: filename
|
206
|
+
result = invoke 'print File.expand_path(Dir.pwd)', filename: filename
|
197
207
|
result.stdout.should == proving_grounds_dir
|
198
208
|
end
|
199
209
|
|
@@ -204,4 +214,16 @@ describe SeeingIsBelieving do
|
|
204
214
|
it 'raises a SyntaxError when the whole program is invalid' do
|
205
215
|
expect { invoke '"' }.to raise_error SyntaxError
|
206
216
|
end
|
217
|
+
|
218
|
+
it 'can be given a stdin stream' do
|
219
|
+
invoke('$stdin.read', stdin: StringIO.new("input"))[1].should == ['"input"']
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'can be given a stdin string' do
|
223
|
+
invoke('$stdin.read', stdin: "input")[1].should == ['"input"']
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'defaults the stdin stream to an empty string' do
|
227
|
+
invoke('$stdin.read')[1].should == ['""']
|
228
|
+
end
|
207
229
|
end
|
@@ -5,21 +5,61 @@ describe SeeingIsBelieving::SyntaxAnalyzer do
|
|
5
5
|
is_valid = lambda { |code| described_class.valid_ruby? code }
|
6
6
|
is_valid['1+2'].should be_true
|
7
7
|
is_valid['+'].should be_false
|
8
|
+
is_valid["=begin\n1\n=end"].should be_true
|
8
9
|
|
9
10
|
# due to what are possibly bugs in Ripper
|
10
11
|
# these don't raise any errors, so have to check them explicitly
|
11
12
|
is_valid["'"].should be_false
|
12
13
|
is_valid["/"].should be_false
|
14
|
+
is_valid["=begin"].should be_false
|
15
|
+
is_valid[" =begin"].should be_false
|
16
|
+
is_valid[" = begin"].should be_false
|
17
|
+
is_valid["=begin\n1"].should be_false
|
18
|
+
is_valid["=begin\n1\n=end\n=begin"].should be_false
|
19
|
+
is_valid["=begin\n1\n=end\n=end"].should be_false
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'knows if the expression is a heredoc' do
|
23
|
+
is_here_doc = lambda { |code| described_class.here_doc? code }
|
24
|
+
is_here_doc["<<A\nA"].should be_true
|
25
|
+
is_here_doc["a=<<A\nabc\nA"].should be_true
|
26
|
+
is_here_doc["meth(<<A)\nabc\nA"].should be_true
|
27
|
+
is_here_doc["meth(<<A)\nabc\nA"].should be_true
|
28
|
+
is_here_doc["meth(<<-A)\n abc\n A"].should be_true
|
29
|
+
is_here_doc["meth(<<-\"a b\")\n abc\n a b"].should be_true
|
30
|
+
is_here_doc["meth(<<-\"a b\", <<something)\n 1\n a b\n2\nsomething"].should be_true
|
31
|
+
|
32
|
+
is_here_doc["a=<<A\nabc\nA\na"].should be_false
|
33
|
+
is_here_doc["def meth\nwhateva(<<A)\nabc\nA\nend"].should be_false
|
34
|
+
is_here_doc["a << b\nb"].should be_false
|
35
|
+
is_here_doc["a<<b\nb"].should be_false
|
13
36
|
end
|
14
37
|
|
15
38
|
it 'knows if the last line is a comment' do
|
16
39
|
is_comment = lambda { |code| described_class.ends_in_comment? code }
|
40
|
+
|
41
|
+
# true
|
17
42
|
is_comment['# whatev'].should be_true
|
18
43
|
is_comment['a # whatev'].should be_true
|
19
44
|
is_comment["a \n b # whatev"].should be_true
|
45
|
+
is_comment["=begin\n1\n=end"].should be_true
|
46
|
+
|
47
|
+
# false
|
20
48
|
is_comment['a'].should be_false
|
21
49
|
is_comment["a # whatev \n b"].should be_false
|
22
50
|
is_comment[""].should be_false
|
51
|
+
is_comment["=begin\n=end\n\n =end"].should be_false
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'knows if it contains an unclosed comment' do
|
55
|
+
unclosed_comment = lambda { |code| described_class.unclosed_comment? code }
|
56
|
+
unclosed_comment["=begin"].should be_true
|
57
|
+
unclosed_comment["=begin\n"].should be_true
|
58
|
+
unclosed_comment["=begin\n1"].should be_true
|
59
|
+
unclosed_comment["1\n=begin\n1\n"].should be_true
|
60
|
+
unclosed_comment["1\n=begin\n1\n =end"].should be_true
|
61
|
+
unclosed_comment["1\n=begin\n1\n=end"].should be_false
|
62
|
+
unclosed_comment[" =begin"].should be_false
|
23
63
|
end
|
24
64
|
|
25
65
|
# probably don't really need this many tests, but I'm unfamiliar with how thorough Ripper is
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seeing_is_believing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70192747454180 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 10.0.3
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70192747454180
|
14
25
|
- !ruby/object:Gem::Dependency
|
15
26
|
name: rspec
|
16
|
-
requirement: &
|
27
|
+
requirement: &70192747453400 !ruby/object:Gem::Requirement
|
17
28
|
none: false
|
18
29
|
requirements:
|
19
30
|
- - ~>
|
@@ -21,10 +32,10 @@ dependencies:
|
|
21
32
|
version: 2.12.0
|
22
33
|
type: :development
|
23
34
|
prerelease: false
|
24
|
-
version_requirements: *
|
35
|
+
version_requirements: *70192747453400
|
25
36
|
- !ruby/object:Gem::Dependency
|
26
37
|
name: cucumber
|
27
|
-
requirement: &
|
38
|
+
requirement: &70192747452720 !ruby/object:Gem::Requirement
|
28
39
|
none: false
|
29
40
|
requirements:
|
30
41
|
- - ~>
|
@@ -32,10 +43,10 @@ dependencies:
|
|
32
43
|
version: 1.2.1
|
33
44
|
type: :development
|
34
45
|
prerelease: false
|
35
|
-
version_requirements: *
|
46
|
+
version_requirements: *70192747452720
|
36
47
|
- !ruby/object:Gem::Dependency
|
37
48
|
name: ichannel
|
38
|
-
requirement: &
|
49
|
+
requirement: &70192747452260 !ruby/object:Gem::Requirement
|
39
50
|
none: false
|
40
51
|
requirements:
|
41
52
|
- - ~>
|
@@ -43,7 +54,7 @@ dependencies:
|
|
43
54
|
version: 5.1.1
|
44
55
|
type: :development
|
45
56
|
prerelease: false
|
46
|
-
version_requirements: *
|
57
|
+
version_requirements: *70192747452260
|
47
58
|
description: Records the results of every line of code in your file (intended to be
|
48
59
|
like xmpfilter), inspired by Bret Victor's JavaScript example in his talk "Inventing
|
49
60
|
on Principle"
|
@@ -83,6 +94,7 @@ files:
|
|
83
94
|
- spec/hard_core_ensure_spec.rb
|
84
95
|
- spec/seeing_is_believing_spec.rb
|
85
96
|
- spec/syntax_analyzer_spec.rb
|
97
|
+
- textmate-integration.png
|
86
98
|
homepage: https://github.com/JoshCheek/seeing_is_believing
|
87
99
|
licenses: []
|
88
100
|
post_install_message:
|