erudite 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/CHANGELOG.md +8 -0
- data/LICENSE.md +18 -18
- data/README.md +54 -16
- data/bin/erudite +6 -0
- data/lib/erudite.rb +10 -9
- data/lib/erudite/example.rb +82 -79
- data/lib/erudite/executable.rb +35 -0
- data/lib/erudite/outcome.rb +19 -19
- data/lib/erudite/parser.rb +54 -54
- data/spec/erudite/example_spec.rb +366 -352
- data/spec/erudite/executable_spec.rb +76 -0
- data/spec/erudite/outcome_spec.rb +53 -53
- data/spec/erudite/parser_spec.rb +104 -104
- data/spec/erudite_spec.rb +0 -1
- data/spec/spec_helper.rb +3 -0
- metadata +30 -11
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NjA3YjdkZWY3OTVjMDM2OTI2NTUzZWU2YzRhNTViMjI5NmE4NjkxOQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2dcd8553e0a52b455a6437a4fc7f339e744bc4cf
|
4
|
+
data.tar.gz: 0bf967f16cc6422116ba59cbbb263b1753c90c8e
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
OWM5M2Y2N2ZhMWM0YzIxNzI4NzdjOTI3MTQ0YTFlNjdlNzU2ZjhhMjNmZDE4
|
11
|
-
ZDhhMWU1NTc5NzUwY2Q0ZWIxYzVmNDEzZjQzOGE1OWMxNzY4OTU=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MThjNzM2YTllMTY2NzBkNGY4ZjYwZTRmODljMGE1YTkzOGNmYmMzYzQzMDg4
|
14
|
-
N2Y1YzIwY2I4Y2JiMmJmNGVmMTBiMWQwMTY4YjIzZDcxMzk5NmJmZWQ4ZDFh
|
15
|
-
YjhlMGMyOTBmMjVkOTYyYzZkZTI3YTY1NWU1MDI4YzRlMGY2NzY=
|
6
|
+
metadata.gz: 38b880bc0c05e713911ce85df61a43082db3446cd8a63093b0c85a338eb46aa4190590712b76d7136f924de874a610293e88a21812897b3dbb4d5d566e98f2cf
|
7
|
+
data.tar.gz: 79c25e231334d1d254f7f88fcb8803ed9273ca150d745babe1876cb3d0e3e8009fb65cf187840bcc759844b41b7d2968c2bd790d2f3e01bab09fc8a97277642c
|
data/CHANGELOG.md
CHANGED
data/LICENSE.md
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
Copyright (c) 2014 Taylor Fausak <taylor@fausak.me>
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
-
this software and associated documentation files (the "Software"), to deal in
|
5
|
-
the Software without restriction, including without limitation the rights to
|
6
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
-
subject to the following conditions:
|
9
|
-
|
10
|
-
The above copyright notice and this permission notice shall be included in all
|
11
|
-
copies or substantial portions of the Software.
|
12
|
-
|
13
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
|
+
Copyright (c) 2014 Taylor Fausak <taylor@fausak.me>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,9 +1,36 @@
|
|
1
1
|
# [Erudite][1]
|
2
2
|
|
3
|
+
[![Gem version][2]][3]
|
4
|
+
[![Build status][4]][5]
|
5
|
+
[![Code coverage][6]][7]
|
6
|
+
[![Code quality][8]][9]
|
7
|
+
[![Dependency status][10]][11]
|
8
|
+
|
3
9
|
Executable documentation.
|
4
10
|
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Usage](#usage)
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add it to your Gemfile:
|
17
|
+
|
5
18
|
``` rb
|
6
|
-
|
19
|
+
gem 'erudite', '~> 0.2.0'
|
20
|
+
```
|
21
|
+
|
22
|
+
Or install it manually:
|
23
|
+
|
24
|
+
``` sh
|
25
|
+
$ gem install erudite --version 0.2.0
|
26
|
+
```
|
27
|
+
|
28
|
+
This project uses [Semantic Versioning][12].
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
``` irb
|
33
|
+
# example.irb
|
7
34
|
>> 1 + 2
|
8
35
|
=> 3
|
9
36
|
|
@@ -21,8 +48,8 @@ hello world
|
|
21
48
|
oh noes
|
22
49
|
=> nil
|
23
50
|
|
24
|
-
>> fail 'catastrophe
|
25
|
-
RuntimeError: catastrophe
|
51
|
+
>> fail 'catastrophe'
|
52
|
+
RuntimeError: catastrophe
|
26
53
|
|
27
54
|
>> puts 'chunky
|
28
55
|
.. bacon'
|
@@ -35,19 +62,30 @@ bacon
|
|
35
62
|
.. end
|
36
63
|
>> double(3)
|
37
64
|
=> 6
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
65
|
+
```
|
66
|
+
|
67
|
+
``` sh
|
68
|
+
$ erudite example.irb
|
69
|
+
- PASS
|
70
|
+
- PASS
|
71
|
+
- PASS
|
72
|
+
- PASS
|
73
|
+
- PASS
|
74
|
+
- PASS
|
75
|
+
- PASS
|
76
|
+
- PASS
|
77
|
+
- PASS
|
51
78
|
```
|
52
79
|
|
53
80
|
[1]: https://github.com/tfausak/erudite
|
81
|
+
[2]: https://badge.fury.io/rb/erudite.svg
|
82
|
+
[3]: http://rubygems.org/gems/erudite
|
83
|
+
[4]: https://travis-ci.org/tfausak/erudite.svg
|
84
|
+
[5]: https://travis-ci.org/tfausak/erudite
|
85
|
+
[6]: https://img.shields.io/coveralls/tfausak/erudite.svg
|
86
|
+
[7]: https://coveralls.io/r/tfausak/erudite
|
87
|
+
[8]: https://codeclimate.com/github/tfausak/erudite/badges/gpa.svg
|
88
|
+
[9]: https://codeclimate.com/github/tfausak/erudite
|
89
|
+
[10]: https://gemnasium.com/tfausak/erudite.svg
|
90
|
+
[11]: https://gemnasium.com/tfausak/erudite
|
91
|
+
[12]: http://semver.org/spec/v2.0.0.html
|
data/bin/erudite
ADDED
data/lib/erudite.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
|
-
require 'erudite/example'
|
4
|
-
require 'erudite/
|
5
|
-
require 'erudite/
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'erudite/example'
|
4
|
+
require 'erudite/executable'
|
5
|
+
require 'erudite/outcome'
|
6
|
+
require 'erudite/parser'
|
7
|
+
|
8
|
+
# Executable documentation.
|
9
|
+
module Erudite
|
10
|
+
end
|
data/lib/erudite/example.rb
CHANGED
@@ -1,79 +1,82 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_reader :
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module Erudite
|
7
|
+
# Code to be run and compared against its expected outcome.
|
8
|
+
class Example
|
9
|
+
attr_reader :source
|
10
|
+
attr_reader :expected
|
11
|
+
attr_writer :binding
|
12
|
+
|
13
|
+
def initialize(source, result = nil, output = nil)
|
14
|
+
@source = source
|
15
|
+
@expected = Outcome.new(result, output)
|
16
|
+
end
|
17
|
+
|
18
|
+
def binding
|
19
|
+
@binding ||= TOPLEVEL_BINDING.dup
|
20
|
+
end
|
21
|
+
|
22
|
+
def run!
|
23
|
+
binding.eval(source, __FILE__, __LINE__)
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
[run!, nil]
|
28
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
29
|
+
[nil, exception]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.without_stdio
|
33
|
+
stdin, stdout, stderr, argv = $stdin, $stdout, $stderr, $ARGV.dup
|
34
|
+
io = $stdin = $stdout = $stderr = StringIO.new
|
35
|
+
$ARGV.clear
|
36
|
+
[yield, io]
|
37
|
+
ensure
|
38
|
+
$stdin, $stdout, $stderr = stdin, stdout, stderr
|
39
|
+
$ARGV.replace(argv)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.format_exception(exception)
|
43
|
+
"#{exception.class.name}: #{exception.message}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def actual
|
47
|
+
return @actual if defined?(@actual)
|
48
|
+
|
49
|
+
result, io = self.class.without_stdio do
|
50
|
+
result, exception = run
|
51
|
+
warn(self.class.format_exception(exception)) if exception
|
52
|
+
result.inspect
|
53
|
+
end
|
54
|
+
|
55
|
+
@actual = Outcome.new(result, io.string)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.pattern(string)
|
59
|
+
Regexp.new(Regexp.escape(string).gsub('\.\.\.', '.*?'))
|
60
|
+
end
|
61
|
+
|
62
|
+
def valid_result?
|
63
|
+
return true unless expected.result
|
64
|
+
self.class.pattern(expected.result).match(actual.result)
|
65
|
+
end
|
66
|
+
|
67
|
+
def valid_output?
|
68
|
+
return true unless expected.output
|
69
|
+
self.class.pattern(expected.output).match(actual.output)
|
70
|
+
end
|
71
|
+
|
72
|
+
def pass?
|
73
|
+
actual
|
74
|
+
valid_result? && valid_output?
|
75
|
+
end
|
76
|
+
|
77
|
+
def ==(other)
|
78
|
+
source == other.source &&
|
79
|
+
expected == other.expected
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Erudite
|
4
|
+
# Parses, runs, and outputs examples.
|
5
|
+
class Executable
|
6
|
+
def self.run(io)
|
7
|
+
Parser.parse(io).each { |example| puts format_example(example) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.format_example(example)
|
11
|
+
if example.pass?
|
12
|
+
format_passing_example(example)
|
13
|
+
else
|
14
|
+
format_failing_example(example)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.format_passing_example(_example)
|
19
|
+
'- PASS'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.format_failing_example(example)
|
23
|
+
<<-TXT
|
24
|
+
- FAIL
|
25
|
+
Source: #{example.source}
|
26
|
+
Expected:
|
27
|
+
Output: #{example.expected.output.inspect}
|
28
|
+
Result: #{example.expected.result.inspect}
|
29
|
+
Actual:
|
30
|
+
Output: #{example.actual.output.inspect}
|
31
|
+
Result: #{example.actual.result.inspect}
|
32
|
+
TXT
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/erudite/outcome.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
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
|
+
# 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
CHANGED
@@ -1,54 +1,54 @@
|
|
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
|
51
|
-
output.join("\n") unless output.empty?
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
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
|