seeing_is_believing 0.0.5 → 0.0.7
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 +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:
|