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 +4 -4
- data/CHANGELOG.md +16 -2
- data/LICENSE.md +1 -1
- data/README.md +30 -47
- data/lib/erudite.rb +7 -5
- data/lib/erudite/example/outcome.rb +21 -0
- data/lib/erudite/example/parser.rb +56 -0
- data/lib/erudite/executable.rb +13 -3
- data/lib/erudite/extractor.rb +65 -0
- data/lib/erudite/version.rb +5 -0
- data/spec/erudite/{outcome_spec.rb → example/outcome_spec.rb} +1 -1
- data/spec/erudite/{parser_spec.rb → example/parser_spec.rb} +1 -8
- data/spec/erudite/example_spec.rb +4 -4
- data/spec/erudite/executable_spec.rb +35 -5
- data/spec/erudite/extractor_spec.rb +352 -0
- data/spec/erudite/version_spec.rb +9 -0
- data/spec/spec_helper.rb +7 -0
- metadata +33 -13
- data/lib/erudite/outcome.rb +0 -19
- data/lib/erudite/parser.rb +0 -54
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31047c1a7d1cd92aa0092499808bde1d5793b03a
|
4
|
+
data.tar.gz: 9f3256675420eb9c01542654f589bff00a014a95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8310d570566c1050ebbc4772f0e907af356a0e64b1c337c6398c429762d5dded513dcef4e36553ca6fb85438f265f0e6e84d83129aadb697eabab540cb3b67dc
|
7
|
+
data.tar.gz: 469d662e4a0673af98195ef52ca91ffd38831e857bf945df34e18e8bb71b6cb31b7dfb686dc1c8b14a6848428daf96dd432d151d7d6d483fca9146f99dd57b58
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
-
|
13
|
+
- Created an executable.
|
6
14
|
|
7
15
|
## v0.1.0 (2014-08-24)
|
8
16
|
|
9
|
-
-
|
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
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.
|
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.
|
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
|
-
```
|
33
|
-
# example.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
>>
|
38
|
-
=>
|
39
|
-
|
40
|
-
>>
|
41
|
-
=>
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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.
|
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://
|
65
|
+
[2]: https://img.shields.io/gem/v/erudite.svg?style=flat
|
82
66
|
[3]: http://rubygems.org/gems/erudite
|
83
|
-
[4]: https://
|
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://
|
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://
|
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
|
data/lib/erudite.rb
CHANGED
@@ -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
|
data/lib/erudite/executable.rb
CHANGED
@@ -4,7 +4,17 @@ module Erudite
|
|
4
4
|
# Parses, runs, and outputs examples.
|
5
5
|
class Executable
|
6
6
|
def self.run(io)
|
7
|
-
|
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
|
-
<<-
|
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
|
-
|
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
|
@@ -2,14 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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)
|
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 |
|
28
|
-
expect(
|
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(
|
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
|
data/spec/spec_helper.rb
CHANGED
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.
|
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:
|
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.
|
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.
|
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.
|
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.
|
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/
|
85
|
-
- lib/erudite/
|
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/
|
89
|
-
- spec/erudite/
|
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.
|
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/
|
120
|
-
- spec/erudite/
|
139
|
+
- spec/erudite/extractor_spec.rb
|
140
|
+
- spec/erudite/version_spec.rb
|
121
141
|
- spec/erudite_spec.rb
|
122
142
|
- spec/spec_helper.rb
|
data/lib/erudite/outcome.rb
DELETED
@@ -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
|
data/lib/erudite/parser.rb
DELETED
@@ -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
|