seeing_is_believing 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme.md +27 -33
- data/features/binary.feature +19 -6
- data/features/support/env.rb +2 -4
- data/lib/seeing_is_believing/example_use.rb +19 -15
- data/lib/seeing_is_believing/expression_list.rb +5 -2
- data/lib/seeing_is_believing/result.rb +7 -21
- data/lib/seeing_is_believing/syntax_analyzer.rb +49 -0
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing.rb +12 -8
- data/spec/expression_list_spec.rb +9 -0
- data/spec/seeing_is_believing_spec.rb +7 -0
- data/spec/syntax_analyzer_spec.rb +19 -0
- metadata +9 -6
data/Readme.md
CHANGED
@@ -2,65 +2,59 @@ Seeing Is Believing
|
|
2
2
|
===================
|
3
3
|
|
4
4
|
Evaluates a file, recording the results of each line of code.
|
5
|
-
You can then use this to display output values like Bret Victor does with JavaScript in his talk [
|
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
8
|
Reeaally rough at the moment, but it works for simple examples.
|
9
9
|
|
10
10
|
Also comes with a binary to show how it might be used.
|
11
11
|
|
12
|
-
Install
|
13
|
-
=======
|
14
|
-
|
15
|
-
gem install seeing_is_believing
|
16
|
-
|
17
12
|
Use
|
18
13
|
===
|
19
14
|
|
20
|
-
$ cat proving_grounds/basic_functionality.rb
|
21
|
-
|
22
|
-
```ruby
|
23
|
-
a = '12'
|
24
|
-
a + a
|
25
|
-
|
26
|
-
5.times do |i|
|
27
|
-
i * 2
|
28
|
-
end
|
29
|
-
```
|
30
|
-
|
31
|
-
$ seeing_is_believing proving_grounds/basic_functionality.rb
|
32
|
-
|
33
15
|
```ruby
|
34
|
-
|
35
|
-
a + a # => "1212"
|
36
|
-
|
16
|
+
# $ seeing_is_believing proving_grounds/basic_functionality.rb
|
37
17
|
5.times do |i|
|
38
18
|
i * 2 # => 0, 2, 4, 6, 8
|
39
19
|
end # => 5
|
40
|
-
```
|
41
|
-
|
42
|
-
$ cat proving_grounds/raises_exception.rb
|
43
20
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
21
|
+
def meth(n)
|
22
|
+
n # => "12", "34"
|
23
|
+
end # => nil
|
24
|
+
meth "12" # => "12"
|
25
|
+
meth "34" # => "34"
|
48
26
|
```
|
49
27
|
|
50
|
-
$ bin/seeing_is_believing proving_grounds/raises_exception.rb 2>/dev/null
|
51
|
-
|
52
28
|
```ruby
|
29
|
+
# $ bin/seeing_is_believing proving_grounds/raises_exception.rb 2>/dev/null
|
53
30
|
1 + 1 # => 2
|
54
31
|
raise "ZOMG!" # ~> RuntimeError: ZOMG!
|
55
32
|
1 + 1
|
56
33
|
```
|
57
34
|
|
58
|
-
$ bin/seeing_is_believing proving_grounds/raises_exception.rb 1>/dev/null
|
59
|
-
|
60
35
|
```bash
|
36
|
+
# $ bin/seeing_is_believing proving_grounds/raises_exception.rb 1>/dev/null
|
61
37
|
ZOMG!
|
62
38
|
```
|
63
39
|
|
40
|
+
Install
|
41
|
+
=======
|
42
|
+
|
43
|
+
$ gem install seeing_is_believing
|
44
|
+
|
45
|
+
Or if you haven't fixed your gem home, and you aren't using any version managers:
|
46
|
+
|
47
|
+
$ sudo gem install seeing_is_believing
|
48
|
+
|
49
|
+
Known Issues
|
50
|
+
============
|
51
|
+
|
52
|
+
* comments will kill it, probably going to have to actually parse the code to fix this
|
53
|
+
* multi-line strings will probably kill it, probably going to have to actually parse the code to fix this
|
54
|
+
* I have no idea what happens if you talk to stdout/stderr directly. This should become a non-issue if we evaluate it in its own process like xmpfilter.
|
55
|
+
* If it dies, it will take your program with it. Same as above. (e.g. running the binary against itself will cause it to recursively invoke itself forever WARNING: DON'T REALLY DO THAT, ITS CRAYAZAY)
|
56
|
+
* 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.
|
57
|
+
|
64
58
|
License
|
65
59
|
=======
|
66
60
|
|
data/features/binary.feature
CHANGED
@@ -8,24 +8,34 @@ Feature: Running the binary
|
|
8
8
|
Scenario: Some basic functionality
|
9
9
|
Given the file "basic_functionality.rb":
|
10
10
|
"""
|
11
|
-
a = '12'
|
12
|
-
a + a
|
13
|
-
|
14
11
|
5.times do |i|
|
15
12
|
i * 2
|
16
13
|
end
|
14
|
+
|
15
|
+
def meth(n)
|
16
|
+
n
|
17
|
+
end
|
18
|
+
|
19
|
+
# some invocations
|
20
|
+
meth "12"
|
21
|
+
meth "34"
|
17
22
|
"""
|
18
23
|
When I run "seeing_is_believing basic_functionality.rb"
|
19
24
|
Then stderr is empty
|
20
25
|
And the exit status is 0
|
21
26
|
And stdout is:
|
22
27
|
"""
|
23
|
-
a = '12' # => "12"
|
24
|
-
a + a # => "1212"
|
25
|
-
|
26
28
|
5.times do |i|
|
27
29
|
i * 2 # => 0, 2, 4, 6, 8
|
28
30
|
end # => 5
|
31
|
+
|
32
|
+
def meth(n)
|
33
|
+
n # => "12", "34"
|
34
|
+
end # => nil
|
35
|
+
|
36
|
+
# some invocations
|
37
|
+
meth "12" # => "12"
|
38
|
+
meth "34" # => "34"
|
29
39
|
"""
|
30
40
|
|
31
41
|
Scenario: Raising exceptions
|
@@ -48,3 +58,6 @@ Feature: Running the binary
|
|
48
58
|
Scenario: Printing within the file
|
49
59
|
Scenario: Requiring other files
|
50
60
|
Scenario: Syntactically invalid file
|
61
|
+
Scenario: Passing a nonexistent file
|
62
|
+
Scenario: Evaluating a file that requires other files, from a different directory
|
63
|
+
Scenario: Passing the file on stdin
|
data/features/support/env.rb
CHANGED
@@ -34,10 +34,8 @@ module CommandLineHelpers
|
|
34
34
|
|
35
35
|
def root_dir
|
36
36
|
@root_dir ||= begin
|
37
|
-
dir = Dir.pwd
|
38
|
-
until Dir
|
39
|
-
dir = File.expand_path File.dirname dir
|
40
|
-
end
|
37
|
+
dir = File.expand_path Dir.pwd
|
38
|
+
dir = File.dirname dir until Dir["#{dir}/*"].map { |fn| File.basename fn }.include?('lib')
|
41
39
|
dir
|
42
40
|
end
|
43
41
|
end
|
@@ -10,7 +10,9 @@ class SeeingIsBelieving
|
|
10
10
|
|
11
11
|
def call
|
12
12
|
body.each_line.with_index 1 do |line, index|
|
13
|
-
|
13
|
+
line_results = results[index]
|
14
|
+
self.exception = line_results.exception if line_results.has_exception?
|
15
|
+
output << format_line(line.chomp, line_results)
|
14
16
|
end
|
15
17
|
output
|
16
18
|
end
|
@@ -19,9 +21,7 @@ class SeeingIsBelieving
|
|
19
21
|
@result ||= ''
|
20
22
|
end
|
21
23
|
|
22
|
-
|
23
|
-
!!@exception
|
24
|
-
end
|
24
|
+
alias has_exception? exception
|
25
25
|
|
26
26
|
private
|
27
27
|
|
@@ -31,19 +31,23 @@ class SeeingIsBelieving
|
|
31
31
|
@results ||= SeeingIsBelieving.new(body).call
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
output << line << "\n"
|
42
|
-
end
|
34
|
+
# max line length of the body + 2 spaces for padding
|
35
|
+
def line_length
|
36
|
+
@line_length ||= 2 + body.each_line
|
37
|
+
.map(&:chomp)
|
38
|
+
.reject { |line| SyntaxAnalyzer.ends_in_comment? line }
|
39
|
+
.map(&:length)
|
40
|
+
.max
|
43
41
|
end
|
44
42
|
|
45
|
-
def
|
46
|
-
|
43
|
+
def format_line(line, line_results)
|
44
|
+
if line_results.has_exception?
|
45
|
+
sprintf "%-#{line_length}s# ~> %s: %s\n", line, line_results.exception.class, line_results.exception.message
|
46
|
+
elsif line_results.any?
|
47
|
+
sprintf "%-#{line_length}s# => %s\n", line, line_results.join(', ')
|
48
|
+
else
|
49
|
+
line + "\n"
|
50
|
+
end
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'open3'
|
2
|
+
require 'seeing_is_believing/syntax_analyzer'
|
2
3
|
|
3
4
|
class SeeingIsBelieving
|
4
5
|
class ExpressionList
|
5
6
|
PendingExpression = Struct.new :expression, :children do
|
7
|
+
# coloured debug because there's so much syntax that I get lost trying to parse the output
|
6
8
|
def inspect(debug=false)
|
7
9
|
colour1 = colour2 = lambda { |s| s }
|
8
10
|
colour1 = lambda { |s| "\e[30;46m#{s}\e[0m" } if debug
|
@@ -54,6 +56,7 @@ class SeeingIsBelieving
|
|
54
56
|
|
55
57
|
def reduce_expressions
|
56
58
|
@list.size.times do |i|
|
59
|
+
# uhm, should this expression we are checking for validity consider the children?
|
57
60
|
expression = @list[i..-1].map(&:expression).join("\n") # must use newline otherwise can get expressions like `a\\+b` that should be `a\\\n+b`, former is invalid
|
58
61
|
next unless valid_ruby? expression
|
59
62
|
result = on_complete.call(@list[i].expression,
|
@@ -62,13 +65,13 @@ class SeeingIsBelieving
|
|
62
65
|
@line_number)
|
63
66
|
@list = @list[0, i]
|
64
67
|
@list[i-1].children << result unless @list.empty?
|
65
|
-
debug? && debug("
|
68
|
+
debug? && debug("REDUCED: #{result.inspect}, LIST: #{inspected_list}")
|
66
69
|
return result
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
70
73
|
def valid_ruby?(expression)
|
71
|
-
|
74
|
+
SyntaxAnalyzer.valid_ruby? expression
|
72
75
|
end
|
73
76
|
end
|
74
77
|
end
|
@@ -1,27 +1,11 @@
|
|
1
1
|
class SeeingIsBelieving
|
2
|
+
|
2
3
|
class Result
|
3
|
-
|
4
|
-
attr_reader :results
|
4
|
+
module HasException
|
5
5
|
attr_accessor :exception
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@results = []
|
9
|
-
end
|
10
|
-
|
11
|
-
def <<(result)
|
12
|
-
results << result
|
13
|
-
end
|
14
|
-
|
15
|
-
def any?
|
16
|
-
results.any?
|
17
|
-
end
|
18
|
-
|
19
|
-
def join(*args)
|
20
|
-
results.join(*args)
|
21
|
-
end
|
22
|
-
|
23
6
|
alias has_exception? exception
|
24
7
|
end
|
8
|
+
|
25
9
|
attr_reader :min_line_number, :max_line_number
|
26
10
|
|
27
11
|
def initialize
|
@@ -46,7 +30,7 @@ class SeeingIsBelieving
|
|
46
30
|
# probably not really useful, just exists to satisfy the tests
|
47
31
|
def to_a
|
48
32
|
(min_line_number..max_line_number).map do |line_number|
|
49
|
-
[line_number, [*self[line_number]
|
33
|
+
[line_number, [*self[line_number], *Array(self[line_number].exception)]]
|
50
34
|
end
|
51
35
|
end
|
52
36
|
|
@@ -58,7 +42,9 @@ class SeeingIsBelieving
|
|
58
42
|
private
|
59
43
|
|
60
44
|
def results
|
61
|
-
@results ||= Hash.new
|
45
|
+
@results ||= Hash.new do |hash, line_number|
|
46
|
+
hash[line_number] = Array.new.extend HasException
|
47
|
+
end
|
62
48
|
end
|
63
49
|
end
|
64
50
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'ripper'
|
2
|
+
|
3
|
+
class SyntaxAnalyzer < Ripper::SexpBuilder
|
4
|
+
|
5
|
+
# I don't actually know if all of the error methods should invoke has_error!
|
6
|
+
# or just parse errors. I don't actually know how to produce the other errors O.o
|
7
|
+
#
|
8
|
+
# Here is what it is defining as of ruby-1.9.3-p125:
|
9
|
+
# on_alias_error
|
10
|
+
# on_assign_error
|
11
|
+
# on_class_name_error
|
12
|
+
# on_param_error
|
13
|
+
# on_parse_error
|
14
|
+
instance_methods.grep(/error/i).each do |error_meth|
|
15
|
+
class_eval "
|
16
|
+
def #{error_meth}(*)
|
17
|
+
@has_error = true
|
18
|
+
super
|
19
|
+
end
|
20
|
+
"
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_comment(*)
|
24
|
+
@has_comment = true
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parsed(code)
|
29
|
+
instance = new code
|
30
|
+
instance.parse
|
31
|
+
instance
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.valid_ruby?(code)
|
35
|
+
!parsed(code).has_error?
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.ends_in_comment?(code)
|
39
|
+
parsed(code.lines.to_a.last.to_s).has_comment?
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_error?
|
43
|
+
@has_error
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_comment?
|
47
|
+
@has_comment
|
48
|
+
end
|
49
|
+
end
|
data/lib/seeing_is_believing.rb
CHANGED
@@ -6,8 +6,8 @@ require 'seeing_is_believing/expression_list'
|
|
6
6
|
# might not work on windows b/c of assumptions about line ends
|
7
7
|
class SeeingIsBelieving
|
8
8
|
def initialize(string_or_stream)
|
9
|
-
@stream
|
10
|
-
@result
|
9
|
+
@stream = to_stream string_or_stream
|
10
|
+
@result = Result.new
|
11
11
|
end
|
12
12
|
|
13
13
|
def call
|
@@ -29,10 +29,10 @@ class SeeingIsBelieving
|
|
29
29
|
on_complete: lambda { |line, children, completions, line_number|
|
30
30
|
@result.contains_line_number line_number
|
31
31
|
expression = [line, *children, *completions].join("\n")
|
32
|
-
if expression == ''
|
33
|
-
expression
|
32
|
+
if expression == '' || ends_in_comment?(expression)
|
33
|
+
expression + "\n"
|
34
34
|
else
|
35
|
-
record_yahself
|
35
|
+
record_yahself(expression, line_number) + "\n"
|
36
36
|
end
|
37
37
|
}
|
38
38
|
end
|
@@ -43,16 +43,20 @@ class SeeingIsBelieving
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def record_yahself(line, line_number)
|
46
|
-
"($seeing_is_believing_current_result.record_result(#{line_number}, (#{line})))
|
46
|
+
"($seeing_is_believing_current_result.record_result(#{line_number}, (#{line})))"
|
47
47
|
end
|
48
48
|
|
49
49
|
def record_exceptions_in(code)
|
50
|
-
|
50
|
+
# must use newline after code, or comments will comment out rescue section
|
51
51
|
"begin;"\
|
52
|
-
"#{code}
|
52
|
+
"#{code}\n"\
|
53
53
|
"rescue Exception;"\
|
54
54
|
"line_number = $!.backtrace.first[/:\\d+:/][1..-2].to_i;"\
|
55
55
|
"$seeing_is_believing_current_result.record_exception line_number, $!;"\
|
56
56
|
"end"
|
57
57
|
end
|
58
|
+
|
59
|
+
def ends_in_comment?(expression)
|
60
|
+
SyntaxAnalyzer.ends_in_comment? expression
|
61
|
+
end
|
58
62
|
end
|
@@ -104,4 +104,13 @@ describe SeeingIsBelieving::ExpressionList do
|
|
104
104
|
"n1 + n2\n"\
|
105
105
|
"end end"\
|
106
106
|
end
|
107
|
+
|
108
|
+
example "example; smoke test debug option" do
|
109
|
+
stream = StringIO.new
|
110
|
+
result = call %w[a+ b], debug: true, debug_stream: stream do |line, children, completions, line_number|
|
111
|
+
[line, *children, *completions].join("\n")
|
112
|
+
end
|
113
|
+
stream.string.should include "GENERATED"
|
114
|
+
stream.string.should include "REDUCED"
|
115
|
+
end
|
107
116
|
end
|
@@ -90,6 +90,12 @@ describe SeeingIsBelieving do
|
|
90
90
|
2").should == [[], ['3']]
|
91
91
|
end
|
92
92
|
|
93
|
+
it 'does not record expressions that end in a comment' do
|
94
|
+
values_for("1
|
95
|
+
2 # on internal expression
|
96
|
+
3 # at end of program").should == [['1'], [], []]
|
97
|
+
end
|
98
|
+
|
93
99
|
it 'has no output for empty lines' do
|
94
100
|
values_for('').should == [[]]
|
95
101
|
values_for("1\n\n2").should == [['1'],[],['2']]
|
@@ -108,6 +114,7 @@ describe SeeingIsBelieving do
|
|
108
114
|
result.size.should == 3
|
109
115
|
end
|
110
116
|
|
117
|
+
# it ignores lines that end in comments
|
111
118
|
# something about printing to stdout
|
112
119
|
# something about printing to stderr
|
113
120
|
# something about when the whole input is invalid
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'seeing_is_believing'
|
2
|
+
|
3
|
+
describe SeeingIsBelieving::SyntaxAnalyzer do
|
4
|
+
it 'knows if syntax is valid' do
|
5
|
+
is_valid = lambda { |code| described_class.valid_ruby? code }
|
6
|
+
is_valid['+'].should be_false
|
7
|
+
is_valid['1+2'].should be_true
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'knows if the last line is a comment' do
|
11
|
+
is_comment = lambda { |code| described_class.ends_in_comment? code }
|
12
|
+
is_comment['# whatev'].should be_true
|
13
|
+
is_comment['a # whatev'].should be_true
|
14
|
+
is_comment["a \n b # whatev"].should be_true
|
15
|
+
is_comment['a'].should be_false
|
16
|
+
is_comment["a # whatev \n b"].should be_false
|
17
|
+
is_comment[""].should be_false
|
18
|
+
end
|
19
|
+
end
|
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.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70251047915180 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 2.12.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70251047915180
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: cucumber
|
27
|
-
requirement: &
|
27
|
+
requirement: &70251047914440 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 1.2.1
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70251047914440
|
36
36
|
description: Records the results of every line of code in your file (intended to be
|
37
37
|
like xmpfilter), inspired by Bret Victor's JavaScript example in his talk "Inventing
|
38
38
|
on Principle"
|
@@ -55,10 +55,12 @@ files:
|
|
55
55
|
- lib/seeing_is_believing/example_use.rb
|
56
56
|
- lib/seeing_is_believing/expression_list.rb
|
57
57
|
- lib/seeing_is_believing/result.rb
|
58
|
+
- lib/seeing_is_believing/syntax_analyzer.rb
|
58
59
|
- lib/seeing_is_believing/version.rb
|
59
60
|
- seeing_is_believing.gemspec
|
60
61
|
- spec/expression_list_spec.rb
|
61
62
|
- spec/seeing_is_believing_spec.rb
|
63
|
+
- spec/syntax_analyzer_spec.rb
|
62
64
|
homepage: https://github.com/JoshCheek/seeing_is_believing
|
63
65
|
licenses: []
|
64
66
|
post_install_message:
|
@@ -89,3 +91,4 @@ test_files:
|
|
89
91
|
- features/support/env.rb
|
90
92
|
- spec/expression_list_spec.rb
|
91
93
|
- spec/seeing_is_believing_spec.rb
|
94
|
+
- spec/syntax_analyzer_spec.rb
|