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 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 ["Inventing on Principle"][inventing_on_principle].
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
- a = '12' # => "12"
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
- ```ruby
45
- 1 + 1
46
- raise "ZOMG!"
47
- 1 + 1
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
 
@@ -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
@@ -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.glob("#{dir}/*").map(&File.method(:basename)).include?("features")
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
- write_line line.chomp, results[index]
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
- def has_exception?
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
- def write_line(line, results)
35
- if results.any?
36
- output << sprintf("%-#{line_length}s# => %s\n", line, results.join(', '))
37
- elsif results.has_exception?
38
- self.exception = results.exception
39
- output << sprintf("%-#{line_length}s# ~> %s: %s\n", line, results.exception.class, results.exception.message)
40
- else
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 line_length
46
- @line_length ||= body.each_line.map(&:chomp).map(&:length).max + 2
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("RESULT: #{result.inspect}, LIST: [#{@list.map { |e| e.inspect debug? }.join(', ')}]")
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
- Open3.capture3('ruby -c', stdin_data: expression).last.success?
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
- class Line
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].results, *Array(self[line_number].exception)]]
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 { |hash, line_number| hash[line_number] = Line.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
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -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 = to_stream string_or_stream
10
- @result = Result.new
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 expression, line_number
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})))\n"
46
+ "($seeing_is_believing_current_result.record_result(#{line_number}, (#{line})))"
47
47
  end
48
48
 
49
49
  def record_exceptions_in(code)
50
- require 'pp'
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.2
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-02 00:00:00.000000000 Z
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: &70263953788620 !ruby/object:Gem::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: *70263953788620
24
+ version_requirements: *70251047915180
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: cucumber
27
- requirement: &70263953787880 !ruby/object:Gem::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: *70263953787880
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