erudite 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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