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 +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
|