seeing_is_believing 0.0.2 → 0.0.3
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/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
|