erudite 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2dcd8553e0a52b455a6437a4fc7f339e744bc4cf
4
- data.tar.gz: 0bf967f16cc6422116ba59cbbb263b1753c90c8e
3
+ metadata.gz: 31047c1a7d1cd92aa0092499808bde1d5793b03a
4
+ data.tar.gz: 9f3256675420eb9c01542654f589bff00a014a95
5
5
  SHA512:
6
- metadata.gz: 38b880bc0c05e713911ce85df61a43082db3446cd8a63093b0c85a338eb46aa4190590712b76d7136f924de874a610293e88a21812897b3dbb4d5d566e98f2cf
7
- data.tar.gz: 79c25e231334d1d254f7f88fcb8803ed9273ca150d745babe1876cb3d0e3e8009fb65cf187840bcc759844b41b7d2968c2bd790d2f3e01bab09fc8a97277642c
6
+ metadata.gz: 8310d570566c1050ebbc4772f0e907af356a0e64b1c337c6398c429762d5dded513dcef4e36553ca6fb85438f265f0e6e84d83129aadb697eabab540cb3b67dc
7
+ data.tar.gz: 469d662e4a0673af98195ef52ca91ffd38831e857bf945df34e18e8bb71b6cb31b7dfb686dc1c8b14a6848428daf96dd432d151d7d6d483fca9146f99dd57b58
@@ -1,9 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ This project uses [Semantic Versioning][1].
4
+
5
+ ## v0.3.0 (2015-01-26)
6
+
7
+ - Modified the executable to read Ruby files instead of IRB files.
8
+ - Added a class for extracting comments from Ruby source code (#5).
9
+ - Added the ability to refer to variables defined in earlier examples (#3).
10
+
3
11
  ## v0.2.0 (2014-09-18)
4
12
 
5
- - Create an executable.
13
+ - Created an executable.
6
14
 
7
15
  ## v0.1.0 (2014-08-24)
8
16
 
9
- - Initial release.
17
+ - Initially released.
18
+
19
+ ## v0.0.0 (2014-08-23)
20
+
21
+ - Initially created.
22
+
23
+ [1]: http://semver.org/spec/v2.0.0.html
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Taylor Fausak <taylor@fausak.me>
1
+ Copyright (c) 2015 Taylor Fausak <taylor@fausak.me>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of
4
4
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -16,59 +16,43 @@ Executable documentation.
16
16
  Add it to your Gemfile:
17
17
 
18
18
  ``` rb
19
- gem 'erudite', '~> 0.2.0'
19
+ gem 'erudite', '~> 0.3.0'
20
20
  ```
21
21
 
22
22
  Or install it manually:
23
23
 
24
24
  ``` sh
25
- $ gem install erudite --version 0.2.0
25
+ $ gem install erudite --version '~> 0.3.0'
26
26
  ```
27
27
 
28
- This project uses [Semantic Versioning][12].
29
-
30
28
  ## Usage
31
29
 
32
- ``` irb
33
- # example.irb
34
- >> 1 + 2
35
- => 3
36
-
37
- >> Object.new
38
- => #<Object:0x...>
39
-
40
- >> gets
41
- => nil
42
-
43
- >> puts 'hello world'
44
- hello world
45
- => nil
46
-
47
- >> warn 'oh noes'
48
- oh noes
49
- => nil
50
-
51
- >> fail 'catastrophe'
52
- RuntimeError: catastrophe
53
-
54
- >> puts 'chunky
55
- .. bacon'
56
- chunky
57
- bacon
58
- => nil
59
-
60
- >> def double(n)
61
- .. 2 * n
62
- .. end
63
- >> double(3)
64
- => 6
30
+ ``` rb
31
+ # example.rb
32
+
33
+ # >> x = 1
34
+ # => 1
35
+ # >> x + 1
36
+ # => 2
37
+
38
+ # >> x = 2
39
+ # => 2
40
+ #
41
+ # x + 1
42
+ # => 3
43
+
44
+ # >> x
45
+ # NameError: ...
46
+
47
+ # >> f(3)
48
+ # => 9
49
+ def f(x)
50
+ x**2
51
+ end
65
52
  ```
66
53
 
67
54
  ``` sh
68
- $ erudite example.irb
69
- - PASS
70
- - PASS
71
- - PASS
55
+ $ erudite example.rb
72
56
  - PASS
73
57
  - PASS
74
58
  - PASS
@@ -78,14 +62,13 @@ $ erudite example.irb
78
62
  ```
79
63
 
80
64
  [1]: https://github.com/tfausak/erudite
81
- [2]: https://badge.fury.io/rb/erudite.svg
65
+ [2]: https://img.shields.io/gem/v/erudite.svg?style=flat
82
66
  [3]: http://rubygems.org/gems/erudite
83
- [4]: https://travis-ci.org/tfausak/erudite.svg
67
+ [4]: https://img.shields.io/travis/tfausak/erudite/master.svg?style=flat
84
68
  [5]: https://travis-ci.org/tfausak/erudite
85
- [6]: https://img.shields.io/coveralls/tfausak/erudite.svg
69
+ [6]: https://img.shields.io/coveralls/tfausak/erudite/master.svg?style=flat
86
70
  [7]: https://coveralls.io/r/tfausak/erudite
87
- [8]: https://codeclimate.com/github/tfausak/erudite/badges/gpa.svg
71
+ [8]: https://img.shields.io/codeclimate/github/tfausak/erudite.svg?style=flat
88
72
  [9]: https://codeclimate.com/github/tfausak/erudite
89
- [10]: https://gemnasium.com/tfausak/erudite.svg
73
+ [10]: https://img.shields.io/gemnasium/tfausak/erudite.svg?style=flat
90
74
  [11]: https://gemnasium.com/tfausak/erudite
91
- [12]: http://semver.org/spec/v2.0.0.html
@@ -1,10 +1,12 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'erudite/example'
4
- require 'erudite/executable'
5
- require 'erudite/outcome'
6
- require 'erudite/parser'
7
-
8
3
  # Executable documentation.
9
4
  module Erudite
10
5
  end
6
+
7
+ require 'erudite/example'
8
+ require 'erudite/example/outcome'
9
+ require 'erudite/example/parser'
10
+ require 'erudite/executable'
11
+ require 'erudite/extractor'
12
+ require 'erudite/version'
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+
3
+ module Erudite
4
+ class Example
5
+ # Information about an expected or actual outcome.
6
+ class Outcome
7
+ attr_reader :result
8
+ attr_reader :output
9
+
10
+ def initialize(result, output)
11
+ @result = result
12
+ @output = output
13
+ end
14
+
15
+ def ==(other)
16
+ result == other.result &&
17
+ output == other.output
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ # coding: utf-8
2
+
3
+ module Erudite
4
+ class Example
5
+ # Parses IRB output into examples.
6
+ class Parser
7
+ def self.parse(string)
8
+ group(string).map { |lines| exemplify(lines) }
9
+ end
10
+
11
+ def self.group(string)
12
+ buffer = []
13
+
14
+ groups = string.each_line.each_with_object([]) do |line, array|
15
+ if line.start_with?('>> ') && !buffer.empty?
16
+ array.push(buffer)
17
+ buffer = []
18
+ end
19
+
20
+ buffer.push(line)
21
+ end
22
+
23
+ buffer.empty? ? groups : groups.push(buffer)
24
+ end
25
+
26
+ def self.exemplify(lines)
27
+ source = extract_source(lines)
28
+ result = extract_result(lines)
29
+ output = extract_output(lines)
30
+
31
+ Example.new(source, result, output)
32
+ end
33
+
34
+ def self.extract_source(lines)
35
+ source = lines
36
+ .select { |line| line.start_with?('>> ', '.. ') }
37
+ .map { |line| line[3..-1].chomp }
38
+ source.join("\n") unless source.empty?
39
+ end
40
+
41
+ def self.extract_result(lines)
42
+ result = lines
43
+ .select { |line| line.start_with?('=> ') }
44
+ .map { |line| line[3..-1].chomp }
45
+ result.join("\n") unless result.empty?
46
+ end
47
+
48
+ def self.extract_output(lines)
49
+ output = lines
50
+ .reject { |line| line.start_with?('>> ', '.. ', '=> ') }
51
+ .map(&:chomp)
52
+ output.join("\n") unless output.empty?
53
+ end
54
+ end
55
+ end
56
+ end
@@ -4,7 +4,17 @@ module Erudite
4
4
  # Parses, runs, and outputs examples.
5
5
  class Executable
6
6
  def self.run(io)
7
- Parser.parse(io).each { |example| puts format_example(example) }
7
+ source = io.read
8
+
9
+ Extractor.extract(source).each do |group|
10
+ binding = TOPLEVEL_BINDING.dup
11
+ binding.eval(source)
12
+
13
+ group.each do |example|
14
+ example.binding = binding
15
+ puts format_example(example)
16
+ end
17
+ end
8
18
  end
9
19
 
10
20
  def self.format_example(example)
@@ -20,7 +30,7 @@ module Erudite
20
30
  end
21
31
 
22
32
  def self.format_failing_example(example)
23
- <<-TXT
33
+ <<-"TEXT"
24
34
  - FAIL
25
35
  Source: #{example.source}
26
36
  Expected:
@@ -29,7 +39,7 @@ module Erudite
29
39
  Actual:
30
40
  Output: #{example.actual.output.inspect}
31
41
  Result: #{example.actual.result.inspect}
32
- TXT
42
+ TEXT
33
43
  end
34
44
  end
35
45
  end
@@ -0,0 +1,65 @@
1
+ # coding: utf-8
2
+
3
+ require 'parser/current'
4
+
5
+ module Erudite
6
+ # Extracts examples from comments.
7
+ class Extractor
8
+ def self.extract(source)
9
+ group_comments(parse_comments(source))
10
+ .map { |group| group.flat_map { |comment| extract_text(comment) } }
11
+ .map { |text| find_examples(group_text(text)).join("\n") }
12
+ .reject(&:empty?)
13
+ .map { |example| Example::Parser.parse(example) }
14
+ end
15
+
16
+ def self.find_examples(groups)
17
+ groups
18
+ .reject(&:empty?)
19
+ .each_with_object([]) do |lines, examples|
20
+ lines.first.match(/^(\s*)>> /) do |match|
21
+ if lines.all? { |line| line.start_with?(match[1]) }
22
+ examples.push(lines.map { |line| line[match[1].size..-1] })
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.group_text(lines)
29
+ lines.each_with_object([[]]) do |line, groups|
30
+ if line[/^\s*$/]
31
+ groups.push([])
32
+ else
33
+ groups.last.push(line.chomp)
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.extract_text(comment)
39
+ if comment.inline?
40
+ [comment.text[1..-1]]
41
+ else
42
+ comment.text[7..-6].lines.map(&:chomp)
43
+ end
44
+ end
45
+
46
+ def self.group_comments(comments)
47
+ previous = comments.first
48
+
49
+ comments.each_with_object([]) do |comment, groups|
50
+ groups.push([]) unless groupable?(previous, comment)
51
+ groups.last.push(comment)
52
+ previous = comment
53
+ end
54
+ end
55
+
56
+ def self.groupable?(a, b)
57
+ a.loc.line == b.loc.line - 1 &&
58
+ a.loc.column == b.loc.column
59
+ end
60
+
61
+ def self.parse_comments(source)
62
+ Parser::CurrentRuby.parse_with_comments(source).last
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+
3
+ module Erudite # rubocop:disable Style/Documentation
4
+ VERSION = Gem::Version.new('0.3.0')
5
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe Erudite::Outcome do
5
+ describe Erudite::Example::Outcome do
6
6
  it 'requires a result' do
7
7
  expect { described_class.new }.to raise_error(ArgumentError)
8
8
  end
@@ -2,14 +2,7 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- # Monkey patch String to add ability to strip leading whitespace.
6
- class String
7
- def dedent
8
- gsub(/^#{self[/\A\s*/]}/, '')
9
- end
10
- end
11
-
12
- describe Erudite::Parser do
5
+ describe Erudite::Example::Parser do
13
6
  describe '.parse' do
14
7
  it 'parses an example without output or a result' do
15
8
  examples = described_class.parse(<<-'RUBY'.dedent)
@@ -20,7 +20,7 @@ describe Erudite::Example do
20
20
 
21
21
  it 'uses the default expected outcome' do
22
22
  example = described_class.new(nil)
23
- expect(example.expected).to be_an(Erudite::Outcome)
23
+ expect(example.expected).to be_an(Erudite::Example::Outcome)
24
24
  expect(example.expected.result).to be(nil)
25
25
  expect(example.expected.output).to be(nil)
26
26
  end
@@ -312,19 +312,19 @@ describe Erudite::Example do
312
312
 
313
313
  it 'returns true if both the result and output match' do
314
314
  example = described_class
315
- .new('p "something"', '"something"', '"something"')
315
+ .new('p "something"', '"something"', '"something"')
316
316
  expect(example).to be_pass
317
317
  end
318
318
 
319
319
  it "returns false if the result doesn't match" do
320
320
  example = described_class
321
- .new('p "something"', '"something else"', '"something"')
321
+ .new('p "something"', '"something else"', '"something"')
322
322
  expect(example).to_not be_pass
323
323
  end
324
324
 
325
325
  it "returns false if the output doesn't match" do
326
326
  example = described_class
327
- .new('p "something"', '"something"', '"something else"')
327
+ .new('p "something"', '"something"', '"something else"')
328
328
  expect(example).to_not be_pass
329
329
  end
330
330
  end
@@ -20,18 +20,48 @@ describe Erudite::Executable do
20
20
  end
21
21
 
22
22
  context 'with input' do
23
- let(:input) { ">> p(true)\ntrue\n=> true\n" }
23
+ let(:input) do
24
+ <<-'RUBY'
25
+ # >> p(true)
26
+ # true
27
+ # => true
28
+ RUBY
29
+ end
24
30
 
25
- it 'returns the example' do
31
+ it 'returns the example groups' do
26
32
  expect(result).to_not be_empty
27
- result.each do |example|
28
- expect(example).to be_an(Erudite::Example)
33
+ result.each do |group|
34
+ expect(group).to_not be_empty
35
+ group.each do |example|
36
+ expect(example).to be_an(Erudite::Example)
37
+ end
29
38
  end
30
39
  end
31
40
 
32
41
  it 'prints the results' do
33
42
  result
34
- expect($stdout.string).to eql("- PASS\n")
43
+ expect($stdout.string).to eql(<<-'TEXT'.dedent)
44
+ - PASS
45
+ TEXT
46
+ end
47
+ end
48
+
49
+ context 'with multiple examples' do
50
+ let(:input) do
51
+ <<-'RUBY'
52
+ # >> x = 1
53
+ # => 1
54
+ # >> x
55
+ # => 1
56
+ RUBY
57
+ end
58
+
59
+ it 'uses the same binding for all examples' do
60
+ result
61
+ expect($stdout.string).to eql(<<-'TEXT'.dedent)
62
+ - PASS
63
+ - PASS
64
+ TEXT
35
65
  end
36
66
  end
37
67
  end
@@ -0,0 +1,352 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Erudite::Extractor do
6
+ describe '.extract' do
7
+ it 'returns an empty array if there are no examples' do
8
+ expect(described_class.extract(<<-'RUBY'))
9
+ RUBY
10
+ .to eq([])
11
+ end
12
+
13
+ it 'returns an example from inline comments' do
14
+ expect(described_class.extract(<<-'RUBY'))
15
+ # >> p(true)
16
+ # true
17
+ # => true
18
+ RUBY
19
+ .to eq([[Erudite::Example.new('p(true)', 'true', 'true')]])
20
+ end
21
+
22
+ it 'returns an example from block comments' do
23
+ expect(described_class.extract(<<-'RUBY'))
24
+ =begin
25
+ >> p(false)
26
+ false
27
+ => false
28
+ =end
29
+ RUBY
30
+ .to eq([[Erudite::Example.new('p(false)', 'false', 'false')]])
31
+ end
32
+
33
+ it 'extracts an example surrounded by whitespace' do
34
+ expect(described_class.extract(<<-'RUBY'))
35
+ # One plus two is...
36
+ #
37
+ # >> 1 + 2
38
+ # => 3
39
+ #
40
+ # three.
41
+ RUBY
42
+ .to eq([[Erudite::Example.new('1 + 2', '3')]])
43
+ end
44
+
45
+ it 'ignores examples not surrounded by whitespace' do
46
+ expect(described_class.extract(<<-'RUBY'))
47
+ # Four plus five is...
48
+ # >> 4 + 5
49
+ # => 9
50
+ # nine.
51
+ RUBY
52
+ .to eq([])
53
+ end
54
+
55
+ it 'groups multiple examples together' do
56
+ expect(described_class.extract(<<-'RUBY'))
57
+ # >> x = 1
58
+ # => 1
59
+ # >> x + 1
60
+ # => 2
61
+ RUBY
62
+ .to eq([[
63
+ Erudite::Example.new('x = 1', '1'),
64
+ Erudite::Example.new('x + 1', '2')
65
+ ]])
66
+ end
67
+
68
+ it 'groups multiple examples separated by whitespace together' do
69
+ expect(described_class.extract(<<-'RUBY'))
70
+ # >> x = 2
71
+ # => 2
72
+ #
73
+ # >> x * 3
74
+ # => 6
75
+ RUBY
76
+ .to eq([[
77
+ Erudite::Example.new('x = 2', '2'),
78
+ Erudite::Example.new('x * 3', '6')
79
+ ]])
80
+ end
81
+
82
+ it 'puts examples from different comments in different groups' do
83
+ expect(described_class.extract(<<-'RUBY'))
84
+ # >> x = 3
85
+ # => 3
86
+
87
+ # >> x
88
+ # NameError: ...
89
+ RUBY
90
+ .to eq([
91
+ [Erudite::Example.new('x = 3', '3')],
92
+ [Erudite::Example.new('x', nil, 'NameError: ...')]
93
+ ])
94
+ end
95
+
96
+ it 'ignores ragged examples' do
97
+ expect(described_class.extract(<<-'RUBY'))
98
+ # >> nil
99
+ # => nil
100
+ RUBY
101
+ .to eq([])
102
+ end
103
+ end
104
+
105
+ describe '.find_examples' do
106
+ subject(:result) { described_class.find_examples(groups) }
107
+ let(:groups) { [] }
108
+
109
+ context 'with an example' do
110
+ let(:groups) { [['>> true']] }
111
+
112
+ it 'returns the example' do
113
+ expect(result).to eql([['>> true']])
114
+ end
115
+ end
116
+
117
+ context 'with an example and some text' do
118
+ let(:groups) { [['a'], ['>> false'], ['b']] }
119
+
120
+ it 'returns only the example' do
121
+ expect(result).to eql([['>> false']])
122
+ end
123
+ end
124
+
125
+ context 'with an example and some blanks' do
126
+ let(:groups) { [[], ['>> nil'], []] }
127
+
128
+ it 'returns only the example' do
129
+ expect(result).to eql([['>> nil']])
130
+ end
131
+ end
132
+
133
+ context 'with an indented example' do
134
+ let(:groups) { [[' >> true']] }
135
+
136
+ it 'returns the example' do
137
+ expect(result).to eql([['>> true']])
138
+ end
139
+ end
140
+
141
+ context 'with a ragged example' do
142
+ let(:groups) { [[' >> 1', '=> 2']] }
143
+
144
+ it 'does not return the example' do
145
+ expect(result).to eql([])
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '.group_text' do
151
+ subject(:result) { described_class.group_text(lines) }
152
+ let(:lines) { '' }
153
+
154
+ context 'with two adjacent lines' do
155
+ let(:lines) { %w(a b) }
156
+
157
+ it 'returns the lines in their own group' do
158
+ expect(result).to eql([%w(a b)])
159
+ end
160
+ end
161
+
162
+ context 'with two separate lines' do
163
+ let(:lines) { ['a', '', 'b'] }
164
+
165
+ it 'returns the lines in separate groups' do
166
+ expect(result).to eql([['a'], ['b']])
167
+ end
168
+ end
169
+
170
+ context 'with two lines separated by whitespace' do
171
+ let(:lines) { ['a', ' ', 'b'] }
172
+
173
+ it 'returns the lines in separate groups' do
174
+ expect(result).to eql([['a'], ['b']])
175
+ end
176
+ end
177
+ end
178
+
179
+ describe '.extract_text' do
180
+ subject(:result) { described_class.extract_text(comment) }
181
+ let(:comment) { Parser::CurrentRuby.parse_with_comments(source).last.first }
182
+ let(:source) { '' }
183
+
184
+ context 'with an inline comment' do
185
+ let(:source) { '# I know kung-fu.' }
186
+
187
+ it 'extracts the text' do
188
+ expect(result).to eql([' I know kung-fu.'])
189
+ end
190
+ end
191
+
192
+ context 'with a block comment' do
193
+ let(:source) { "=begin\nSo you lie to yourself to be happy.\n=end" }
194
+
195
+ it 'extracts the text' do
196
+ expect(result).to eql(['So you lie to yourself to be happy.'])
197
+ end
198
+ end
199
+
200
+ context 'with a multi-line block comment' do
201
+ let(:source) { "=begin\nThis is the last of Earth!\nI am content!\n=end" }
202
+
203
+ it 'extracts the text' do
204
+ expect(result).to eql(['This is the last of Earth!', 'I am content!'])
205
+ end
206
+ end
207
+ end
208
+
209
+ describe '.group_comments' do
210
+ subject(:result) { described_class.group_comments(comments) }
211
+ let(:comments) { [] }
212
+
213
+ it 'returns an array' do
214
+ expect(result).to be_an(Array)
215
+ end
216
+
217
+ context 'with an inline comment' do
218
+ let(:comments) do
219
+ Parser::CurrentRuby.parse_with_comments(<<-'RUBY').last
220
+ # It was a pleasure to burn.
221
+ RUBY
222
+ end
223
+
224
+ it 'returns the comment in its own group' do
225
+ expect(result).to eql([comments])
226
+ end
227
+ end
228
+
229
+ context 'with multiple inline comments' do
230
+ let(:comments) do
231
+ Parser::CurrentRuby.parse_with_comments(<<-'RUBY').last
232
+ # It was the best of times,
233
+ #
234
+ # it was the worst of times, ...
235
+ RUBY
236
+ end
237
+
238
+ it 'returns the comments in a group' do
239
+ expect(result).to eql([comments])
240
+ end
241
+ end
242
+
243
+ context 'with two separate inline comments' do
244
+ let(:comments) do
245
+ Parser::CurrentRuby.parse_with_comments(<<-'RUBY').last
246
+ # I don't know half of you half as well as I should like;
247
+
248
+ # and I like less than half of you half as well as you deserve.
249
+ RUBY
250
+ end
251
+
252
+ it 'returns the comments in separate groups' do
253
+ expect(result).to eql([[comments.first], [comments.last]])
254
+ end
255
+ end
256
+
257
+ context 'with ragged inline comments' do
258
+ let(:comments) do
259
+ Parser::CurrentRuby.parse_with_comments(<<-'RUBY').last
260
+ # You can't win, Darth.
261
+ # If you strike me down,
262
+ # I shall become more powerful than you can possibly imagine.
263
+ RUBY
264
+ end
265
+
266
+ it 'returns the comments in separate groups' do
267
+ expect(result).to eql([[comments[0]], [comments[1]], [comments[2]]])
268
+ end
269
+ end
270
+
271
+ context 'with a block comment' do
272
+ let(:comments) do
273
+ Parser::CurrentRuby.parse_with_comments(<<-'RUBY').last
274
+ =begin
275
+ Remember - the enemy's gate is down.
276
+ =end
277
+ RUBY
278
+ end
279
+
280
+ it 'returns the comment in its own group' do
281
+ expect(result).to eql([comments])
282
+ end
283
+ end
284
+
285
+ context 'with two block comments' do
286
+ let(:comments) do
287
+ Parser::CurrentRuby.parse_with_comments(<<-'RUBY').last
288
+ =begin
289
+ Time is an illusion.
290
+ =end
291
+ =begin
292
+ Lunchtime doubly so.
293
+ =end
294
+ RUBY
295
+ end
296
+
297
+ it 'returns the comments in separate groups' do
298
+ expect(result).to eql([[comments.first], [comments.last]])
299
+ end
300
+ end
301
+ end
302
+
303
+ describe '.groupable?' do
304
+ subject(:result) { described_class.groupable?(a, b) }
305
+ let(:a) { comments.first }
306
+ let(:b) { comments.last }
307
+ let(:comments) { Parser::CurrentRuby.parse_with_comments(source).last }
308
+ let(:source) { '' }
309
+
310
+ context 'with two adjacent comments' do
311
+ let(:source) { "# 1\n# 2" }
312
+
313
+ it 'returns true' do
314
+ expect(result).to be(true)
315
+ end
316
+ end
317
+
318
+ context 'with two separate comments' do
319
+ let(:source) { "# 1\n\n# 2" }
320
+
321
+ it 'returns false' do
322
+ expect(result).to be(false)
323
+ end
324
+ end
325
+
326
+ context 'with two ragged comments' do
327
+ let(:source) { "# 1\n # 2" }
328
+
329
+ it 'returns false' do
330
+ expect(result).to be(false)
331
+ end
332
+ end
333
+ end
334
+
335
+ describe '.parse_comments' do
336
+ subject(:result) { described_class.parse_comments(source) }
337
+ let(:source) { '' }
338
+
339
+ it 'returns an array' do
340
+ expect(result).to be_an(Array)
341
+ end
342
+
343
+ context 'with a comment' do
344
+ let(:source) { '# Call me Ishmael.' }
345
+
346
+ it 'returns the comment' do
347
+ expect(result).to_not be_empty
348
+ result.each { |e| expect(e).to be_a(Parser::Source::Comment) }
349
+ end
350
+ end
351
+ end
352
+ end
@@ -0,0 +1,9 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Erudite::VERSION do
6
+ it 'is a gem version' do
7
+ expect(described_class).to be_a(Gem::Version)
8
+ end
9
+ end
@@ -4,3 +4,10 @@ require 'coveralls'
4
4
  Coveralls.wear!
5
5
 
6
6
  require 'erudite'
7
+
8
+ # Monkey patch String to add ability to strip leading whitespace.
9
+ class String
10
+ def dedent
11
+ gsub(/^#{self[/\A\s*/]}/, '')
12
+ end
13
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erudite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taylor Fausak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-18 00:00:00.000000000 Z
11
+ date: 2015-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: coveralls
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -30,14 +44,14 @@ dependencies:
30
44
  requirements:
31
45
  - - ~>
32
46
  - !ruby/object:Gem::Version
33
- version: '10.3'
47
+ version: '10.4'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - ~>
39
53
  - !ruby/object:Gem::Version
40
- version: '10.3'
54
+ version: '10.4'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +72,14 @@ dependencies:
58
72
  requirements:
59
73
  - - ~>
60
74
  - !ruby/object:Gem::Version
61
- version: '0.26'
75
+ version: '0.28'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - ~>
67
81
  - !ruby/object:Gem::Version
68
- version: '0.26'
82
+ version: '0.28'
69
83
  description: Executable documentation.
70
84
  email: taylor@fausak.me
71
85
  executables:
@@ -80,13 +94,17 @@ files:
80
94
  - bin/erudite
81
95
  - lib/erudite.rb
82
96
  - lib/erudite/example.rb
97
+ - lib/erudite/example/outcome.rb
98
+ - lib/erudite/example/parser.rb
83
99
  - lib/erudite/executable.rb
84
- - lib/erudite/outcome.rb
85
- - lib/erudite/parser.rb
100
+ - lib/erudite/extractor.rb
101
+ - lib/erudite/version.rb
102
+ - spec/erudite/example/outcome_spec.rb
103
+ - spec/erudite/example/parser_spec.rb
86
104
  - spec/erudite/example_spec.rb
87
105
  - spec/erudite/executable_spec.rb
88
- - spec/erudite/outcome_spec.rb
89
- - spec/erudite/parser_spec.rb
106
+ - spec/erudite/extractor_spec.rb
107
+ - spec/erudite/version_spec.rb
90
108
  - spec/erudite_spec.rb
91
109
  - spec/spec_helper.rb
92
110
  homepage: https://github.com/tfausak/erudite
@@ -109,14 +127,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
127
  version: '0'
110
128
  requirements: []
111
129
  rubyforge_project:
112
- rubygems_version: 2.4.1
130
+ rubygems_version: 2.4.5
113
131
  signing_key:
114
132
  specification_version: 4
115
133
  summary: Executable documentation.
116
134
  test_files:
135
+ - spec/erudite/example/outcome_spec.rb
136
+ - spec/erudite/example/parser_spec.rb
117
137
  - spec/erudite/example_spec.rb
118
138
  - spec/erudite/executable_spec.rb
119
- - spec/erudite/outcome_spec.rb
120
- - spec/erudite/parser_spec.rb
139
+ - spec/erudite/extractor_spec.rb
140
+ - spec/erudite/version_spec.rb
121
141
  - spec/erudite_spec.rb
122
142
  - spec/spec_helper.rb
@@ -1,19 +0,0 @@
1
- # coding: utf-8
2
-
3
- module Erudite
4
- # Information about an expected or actual outcome.
5
- class Outcome
6
- attr_reader :result
7
- attr_reader :output
8
-
9
- def initialize(result, output)
10
- @result = result
11
- @output = output
12
- end
13
-
14
- def ==(other)
15
- result == other.result &&
16
- output == other.output
17
- end
18
- end
19
- end
@@ -1,54 +0,0 @@
1
- # coding: utf-8
2
-
3
- module Erudite
4
- # Parses IRB output into examples.
5
- class Parser
6
- def self.parse(string)
7
- group(string).map { |lines| exemplify(lines) }
8
- end
9
-
10
- def self.group(string)
11
- buffer = []
12
-
13
- groups = string.each_line.each_with_object([]) do |line, array|
14
- if line.start_with?('>> ') && !buffer.empty?
15
- array.push(buffer)
16
- buffer = []
17
- end
18
-
19
- buffer.push(line)
20
- end
21
-
22
- buffer.empty? ? groups : groups.push(buffer)
23
- end
24
-
25
- def self.exemplify(lines)
26
- source = extract_source(lines)
27
- result = extract_result(lines)
28
- output = extract_output(lines)
29
-
30
- Example.new(source, result, output)
31
- end
32
-
33
- def self.extract_source(lines)
34
- source = lines
35
- .select { |line| line.start_with?('>> ', '.. ') }
36
- .map { |line| line[3..-1].chomp }
37
- source.join("\n") unless source.empty?
38
- end
39
-
40
- def self.extract_result(lines)
41
- result = lines
42
- .select { |line| line.start_with?('=> ') }
43
- .map { |line| line[3..-1].chomp }
44
- result.join("\n") unless result.empty?
45
- end
46
-
47
- def self.extract_output(lines)
48
- output = lines
49
- .reject { |line| line.start_with?('>> ', '.. ', '=> ') }
50
- .map(&:chomp)
51
- output.join("\n") unless output.empty?
52
- end
53
- end
54
- end