rb-brain 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/Rakefile +5 -0
- data/lib/brain/neuralnetwork.rb +86 -0
- data/rb-brain.gemspec +1 -1
- data/spec/lib/bitwise_spec.rb +31 -0
- data/spec/lib/hash_spec.rb +70 -0
- data/spec/lib/json_spec.rb +21 -0
- data/spec/lib/lookup_spec.rb +32 -0
- data/spec/lib/options_spec.rb +83 -0
- data/spec/lib/trainopts_spec.rb +26 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/supports/helpers.rb +14 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f43371c0a74e1690e8814ae7230dd12d6d0f1be0
|
4
|
+
data.tar.gz: ea374d2706bd4d54466201eb805c842d7478ba2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c7e8ee3b5d5b07b99b5c6e972201487188f33f173a3b48025d317975602abe931163e05f1ee5ffef2327e8dd24019fb09c1609edf8dd049ca84fe69891b1a32
|
7
|
+
data.tar.gz: 86600d5d1c10f1e5fc796a19aaee8ee3019ea7040f0649b8a2930d0018d3b11c4b132920e92e0ed7be301222e7a32becfc1287df4ec3c2e0666a846118d3d0a3
|
data/.rspec
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rb-brain (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.0)
|
10
|
+
diff-lcs (1.2.5)
|
11
|
+
hashr (0.0.22)
|
12
|
+
method_source (0.8.2)
|
13
|
+
pry (0.10.1)
|
14
|
+
coderay (~> 1.1.0)
|
15
|
+
method_source (~> 0.8.1)
|
16
|
+
slop (~> 3.4)
|
17
|
+
rake (10.4.2)
|
18
|
+
rspec (3.1.0)
|
19
|
+
rspec-core (~> 3.1.0)
|
20
|
+
rspec-expectations (~> 3.1.0)
|
21
|
+
rspec-mocks (~> 3.1.0)
|
22
|
+
rspec-core (3.1.7)
|
23
|
+
rspec-support (~> 3.1.0)
|
24
|
+
rspec-expectations (3.1.2)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.1.0)
|
27
|
+
rspec-mocks (3.1.3)
|
28
|
+
rspec-support (~> 3.1.0)
|
29
|
+
rspec-support (3.1.2)
|
30
|
+
slop (3.6.0)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
hashr
|
37
|
+
pry
|
38
|
+
rake
|
39
|
+
rb-brain!
|
40
|
+
rspec
|
data/Rakefile
ADDED
data/lib/brain/neuralnetwork.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
require 'brain/lookup'
|
2
|
+
require 'json'
|
3
|
+
require 'pry'
|
4
|
+
require 'hashr'
|
2
5
|
|
3
6
|
module Brain
|
4
7
|
class NeuralNetwork
|
@@ -187,6 +190,89 @@ module Brain
|
|
187
190
|
data
|
188
191
|
end
|
189
192
|
|
193
|
+
def to_json
|
194
|
+
# make json look like:
|
195
|
+
# {
|
196
|
+
# layers: [
|
197
|
+
# { x: {},
|
198
|
+
# y: {}},
|
199
|
+
# {'0': {bias: -0.98771313, weights: {x: 0.8374838, y: 1.245858},
|
200
|
+
# '1': {bias: 3.48192004, weights: {x: 1.7825821, y: -2.67899}}},
|
201
|
+
# { f: {bias: 0.27205739, weights: {'0': 1.3161821, '1': 2.00436}}}
|
202
|
+
# ]
|
203
|
+
# }
|
204
|
+
layers = []
|
205
|
+
(0..@output_layer).each do |layer|
|
206
|
+
layers[layer] = {}
|
207
|
+
|
208
|
+
if layer == 0 and @input_lookup
|
209
|
+
nodes = @input_lookup.keys
|
210
|
+
elsif layer == @output_layer and @output_lookup
|
211
|
+
nodes = @output_lookup.keys
|
212
|
+
else
|
213
|
+
nodes = (0...@sizes[layer]).to_a
|
214
|
+
end
|
215
|
+
|
216
|
+
(0...nodes.length).each do |j|
|
217
|
+
node = nodes[j]
|
218
|
+
layers[layer][node] = {}
|
219
|
+
|
220
|
+
if layer > 0
|
221
|
+
layers[layer][node][:bias] = @biases[layer][j]
|
222
|
+
layers[layer][node][:weights] = {}
|
223
|
+
layers[layer - 1].each do |k,v|
|
224
|
+
index = k
|
225
|
+
if layer == 1 and @input_lookup
|
226
|
+
index = @input_lookup[k]
|
227
|
+
end
|
228
|
+
layers[layer][node][:weights][k] = @weights[layer][j][index]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
{
|
235
|
+
layers: layers,
|
236
|
+
output_lookup: !!@output_lookup,
|
237
|
+
input_lookup: !!@input_lookup
|
238
|
+
}.to_json
|
239
|
+
end
|
240
|
+
|
241
|
+
def from_json(json)
|
242
|
+
json = JSON.parse(json).deep_symbolize_keys
|
243
|
+
size = json[:layers].length
|
244
|
+
|
245
|
+
|
246
|
+
@output_layer = size - 1
|
247
|
+
@sizes = Array.new size
|
248
|
+
@weights = Array.new size
|
249
|
+
@biases = Array.new size
|
250
|
+
@outputs = Array.new size
|
251
|
+
|
252
|
+
(0..@output_layer).each do |i|
|
253
|
+
layer = json[:layers][i]
|
254
|
+
if i == 0 and (!layer[0] or json[:input_lookup])
|
255
|
+
@input_lookup = Lookup.lookup_from_hash layer
|
256
|
+
elsif i == @output_layer and (!layer[0] or json[:output_lookup])
|
257
|
+
@output_lookup = Lookup.lookup_from_hash layer
|
258
|
+
end
|
259
|
+
|
260
|
+
nodes = layer.keys
|
261
|
+
@sizes[i] = nodes.length
|
262
|
+
@weights[i] = []
|
263
|
+
@biases[i] = []
|
264
|
+
@outputs[i] = []
|
265
|
+
|
266
|
+
(0...nodes.length).each do |j|
|
267
|
+
|
268
|
+
node = nodes[j]
|
269
|
+
@biases[i][j] = layer[node][:bias]
|
270
|
+
@weights[i][j] = layer[node][:weights]
|
271
|
+
@weights[i][j] = @weights[i][j].values unless @weights[i][j].nil?
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
190
276
|
private
|
191
277
|
def random_weight
|
192
278
|
Random.rand * 0.4 - 0.2
|
data/rb-brain.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "rb-brain"
|
7
|
-
gem.version = "0.0
|
7
|
+
gem.version = "0.1.0"
|
8
8
|
gem.authors = ["Eric Zhang"]
|
9
9
|
gem.email = ["i@qinix.com"]
|
10
10
|
gem.description = %q{rb-brain is an easy-to-use neural network written in ruby}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
describe 'bitwase functions' do
|
2
|
+
it 'NOT function' do
|
3
|
+
data_not = [{input: [0], output: [1]},
|
4
|
+
{input: [1], output: [0]}]
|
5
|
+
test_bitwise data_not, 'not'
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'XOR function' do
|
9
|
+
data_xor = [{input: [0, 0], output: [0]},
|
10
|
+
{input: [0, 1], output: [1]},
|
11
|
+
{input: [1, 0], output: [1]},
|
12
|
+
{input: [1, 1], output: [0]}]
|
13
|
+
test_bitwise data_xor, 'xor'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'OR function' do
|
17
|
+
data_or = [{input: [0, 0], output: [0]},
|
18
|
+
{input: [0, 1], output: [1]},
|
19
|
+
{input: [1, 0], output: [1]},
|
20
|
+
{input: [1, 1], output: [1]}]
|
21
|
+
test_bitwise data_or, 'or'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'AND function' do
|
25
|
+
data_and = [{input: [0, 0], output: [0]},
|
26
|
+
{input: [0, 1], output: [0]},
|
27
|
+
{input: [1, 0], output: [0]},
|
28
|
+
{input: [1, 1], output: [1]}]
|
29
|
+
test_bitwise data_and, 'and'
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
describe 'hash input and output' do
|
2
|
+
before :each do
|
3
|
+
@net = Brain::NeuralNetwork.new
|
4
|
+
end
|
5
|
+
|
6
|
+
it 'runs correctly with array input and output' do
|
7
|
+
@net.train([{input: { x: 0, y: 0 }, output: [0]},
|
8
|
+
{input: { x: 0, y: 1 }, output: [1]},
|
9
|
+
{input: { x: 1, y: 0 }, output: [1]},
|
10
|
+
{input: { x: 1, y: 1 }, output: [0]}])
|
11
|
+
output = @net.run({x: 1, y: 0})
|
12
|
+
|
13
|
+
expect(output[0]).to be > 0.9
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'runs correctly with hash input' do
|
17
|
+
@net.train([{input: { x: 0, y: 0 }, output: [0]},
|
18
|
+
{input: { x: 0, y: 1 }, output: [1]},
|
19
|
+
{input: { x: 1, y: 0 }, output: [1]},
|
20
|
+
{input: { x: 1, y: 1 }, output: [0]}])
|
21
|
+
output = @net.run({x: 1, y: 0})
|
22
|
+
|
23
|
+
expect(output[0]).to be > 0.9
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'runs correctly with hash output' do
|
27
|
+
@net.train([{input: [0, 0], output: { answer: 0 }},
|
28
|
+
{input: [0, 1], output: { answer: 1 }},
|
29
|
+
{input: [1, 0], output: { answer: 1 }},
|
30
|
+
{input: [1, 1], output: { answer: 0 }}])
|
31
|
+
|
32
|
+
output = @net.run([1, 0])
|
33
|
+
|
34
|
+
expect(output[:answer]).to be > 0.9
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'runs correctly with hash input and output' do
|
38
|
+
@net.train([{input: { x: 0, y: 0 }, output: { answer: 0 }},
|
39
|
+
{input: { x: 0, y: 1 }, output: { answer: 1 }},
|
40
|
+
{input: { x: 1, y: 0 }, output: { answer: 1 }},
|
41
|
+
{input: { x: 1, y: 1 }, output: { answer: 0 }}])
|
42
|
+
|
43
|
+
output = @net.run({x: 1, y: 0})
|
44
|
+
|
45
|
+
expect(output[:answer]).to be > 0.9
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'runs correctly with sparse hashes' do
|
49
|
+
@net.train([{input: {}, output: {}},
|
50
|
+
{input: { y: 1 }, output: { answer: 1 }},
|
51
|
+
{input: { x: 1 }, output: { answer: 1 }},
|
52
|
+
{input: { x: 1, y: 1 }, output: {}}])
|
53
|
+
|
54
|
+
|
55
|
+
output = @net.run({x: 1})
|
56
|
+
|
57
|
+
expect(output[:answer]).to be > 0.9
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'runs correctly with unseen input' do
|
61
|
+
@net.train([{input: {}, output: {}},
|
62
|
+
{input: { y: 1 }, output: { answer: 1 }},
|
63
|
+
{input: { x: 1 }, output: { answer: 1 }},
|
64
|
+
{input: { x: 1, y: 1 }, output: {}}])
|
65
|
+
|
66
|
+
output = @net.run({x: 1, z: 1})
|
67
|
+
|
68
|
+
expect(output[:answer]).to be > 0.9
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
describe 'JSON' do
|
2
|
+
it 'to_json()/from_json()' do
|
3
|
+
net = Brain::NeuralNetwork.new
|
4
|
+
|
5
|
+
net.train([{input: {a: Random.rand, b: Random.rand},
|
6
|
+
output: {c: Random.rand, a: Random.rand}},
|
7
|
+
{input: {a: Random.rand, b: Random.rand},
|
8
|
+
output: {c: Random.rand, a: Random.rand}}])
|
9
|
+
|
10
|
+
json = net.to_json()
|
11
|
+
net2 = Brain::NeuralNetwork.new
|
12
|
+
net2.from_json json
|
13
|
+
|
14
|
+
input = {a: Random.rand, b: Random.rand}
|
15
|
+
|
16
|
+
output1 = net.run(input)
|
17
|
+
output2 = net2.run(input)
|
18
|
+
|
19
|
+
expect(output1).to eq output2
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
describe 'Lookup' do
|
2
|
+
it 'lookup_from_hash()' do
|
3
|
+
lup = Brain::Lookup.lookup_from_hash({ a: 6, b: 7, c: 8 })
|
4
|
+
|
5
|
+
expect(lup).to eq({ a: 0, b: 1, c: 2})
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'build_lookup()' do
|
9
|
+
lup = Brain::Lookup.build_lookup([{ x: 0, y: 0 },
|
10
|
+
{ x: 1, z: 0 },
|
11
|
+
{ q: 0 },
|
12
|
+
{ x: 1, y: 1 }])
|
13
|
+
|
14
|
+
expect(lup).to eq({ x: 0, y: 1, z: 2, q: 3 })
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'to_array()' do
|
18
|
+
lup = { a: 0, b: 1, c: 2 }
|
19
|
+
|
20
|
+
array = Brain::Lookup.to_array(lup, { b: 8, notinlookup: 9 })
|
21
|
+
|
22
|
+
expect(array).to eq([0, 8, 0])
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'to_hash()' do
|
26
|
+
lup = { b: 1, a: 0, c: 2 }
|
27
|
+
|
28
|
+
hash = Brain::Lookup.to_hash(lup, [0, 9, 8])
|
29
|
+
|
30
|
+
expect(hash).to eq({a: 0, b: 9, c: 8})
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'hashr'
|
3
|
+
|
4
|
+
describe 'neural network options' do
|
5
|
+
it 'hiddenLayers' do
|
6
|
+
net = Brain::NeuralNetwork.new({ hidden_layers: [8, 7] })
|
7
|
+
|
8
|
+
net.train([{input: [0, 0], output: [0]},
|
9
|
+
{input: [0, 1], output: [1]},
|
10
|
+
{input: [1, 0], output: [1]},
|
11
|
+
{input: [1, 1], output: [0]}])
|
12
|
+
|
13
|
+
json = net.to_json
|
14
|
+
json = JSON.parse json
|
15
|
+
json.deep_symbolize_keys!
|
16
|
+
|
17
|
+
expect(json[:layers].length).to eq(4)
|
18
|
+
expect(json[:layers][1].length).to eq(8)
|
19
|
+
expect(json[:layers][2].length).to eq(7)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'hiddenLayers default expand to input size' do
|
23
|
+
net = Brain::NeuralNetwork.new()
|
24
|
+
|
25
|
+
net.train([{input: [0, 0, 1, 1, 1, 1, 1, 1, 1], output: [0]},
|
26
|
+
{input: [0, 1, 1, 1, 1, 1, 1, 1, 1], output: [1]},
|
27
|
+
{input: [1, 0, 1, 1, 1, 1, 1, 1, 1], output: [1]},
|
28
|
+
{input: [1, 1, 1, 1, 1, 1, 1, 1, 1], output: [0]}])
|
29
|
+
|
30
|
+
json = net.to_json
|
31
|
+
json = JSON.parse json
|
32
|
+
json.deep_symbolize_keys!
|
33
|
+
|
34
|
+
expect(json[:layers].length).to eq(3)
|
35
|
+
expect(json[:layers][1].length).to eq(4)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
it 'learning_rate - higher learning rate should train faster' do
|
40
|
+
data = [{input: [0, 0], output: [0]},
|
41
|
+
{input: [0, 1], output: [1]},
|
42
|
+
{input: [1, 0], output: [1]},
|
43
|
+
{input: [1, 1], output: [1]}]
|
44
|
+
|
45
|
+
net1 = Brain::NeuralNetwork.new()
|
46
|
+
iters1 = net1.train(data, learning_rate: 0.5)[:iterations]
|
47
|
+
|
48
|
+
net2 = Brain::NeuralNetwork.new()
|
49
|
+
iters2 = net2.train(data, learning_rate: 0.8)[:iterations]
|
50
|
+
|
51
|
+
expect(iters1).to be > (iters2 * 1.1)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'learning_rate - backwards compatibility' do
|
55
|
+
data = [{input: [0, 0], output: [0]},
|
56
|
+
{input: [0, 1], output: [1]},
|
57
|
+
{input: [1, 0], output: [1]},
|
58
|
+
{input: [1, 1], output: [1]}]
|
59
|
+
|
60
|
+
net1 = Brain::NeuralNetwork.new(learning_rate: 0.5)
|
61
|
+
iters1 = net1.train(data)[:iterations]
|
62
|
+
|
63
|
+
net2 = Brain::NeuralNetwork.new(learning_rate: 0.8)
|
64
|
+
iters2 = net2.train(data)[:iterations]
|
65
|
+
|
66
|
+
expect(iters1).to be > (iters2 * 1.1)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'momentum - higher momentum should train faster' do
|
70
|
+
data = [{input: [0, 0], output: [0]},
|
71
|
+
{input: [0, 1], output: [1]},
|
72
|
+
{input: [1, 0], output: [1]},
|
73
|
+
{input: [1, 1], output: [1]}]
|
74
|
+
|
75
|
+
net1 = Brain::NeuralNetwork.new(momentum: 0.1)
|
76
|
+
iters1 = net1.train(data)[:iterations]
|
77
|
+
|
78
|
+
net2 = Brain::NeuralNetwork.new(momentum: 0.5)
|
79
|
+
iters2 = net2.train(data)[:iterations]
|
80
|
+
|
81
|
+
expect(iters1).to be > (iters2 * 1.1)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
data = [{input: [0, 0], output: [0]},
|
2
|
+
{input: [0, 1], output: [1]},
|
3
|
+
{input: [1, 0], output: [1]},
|
4
|
+
{input: [1, 1], output: [1]}]
|
5
|
+
|
6
|
+
describe 'train() options' do
|
7
|
+
before :each do
|
8
|
+
@net = Brain::NeuralNetwork.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'train until error threshold reached' do
|
12
|
+
error = @net.train(data,
|
13
|
+
errorThresh: 0.2,
|
14
|
+
iterations: 100000)[:error]
|
15
|
+
|
16
|
+
expect(error).to be < 0.2
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'train until max iterations reached' do
|
20
|
+
stats = @net.train(data,
|
21
|
+
errorThresh: 0.001,
|
22
|
+
iterations: 1)
|
23
|
+
|
24
|
+
expect(stats[:iterations]).to eq(1)
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Helpers
|
2
|
+
def test_bitwise(data, op)
|
3
|
+
wiggle = 0.1
|
4
|
+
|
5
|
+
net = Brain::NeuralNetwork.new
|
6
|
+
net.train(data, error_thresh: 0.003)
|
7
|
+
|
8
|
+
data.each do |item|
|
9
|
+
output = net.run(item[:input])
|
10
|
+
target = item[:output]
|
11
|
+
expect(output[0]).to be_within(wiggle).of(target[0])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rb-brain
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Zhang
|
@@ -17,13 +17,24 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
+
- ".rspec"
|
20
21
|
- Gemfile
|
22
|
+
- Gemfile.lock
|
21
23
|
- LICENSE
|
22
24
|
- README.md
|
25
|
+
- Rakefile
|
23
26
|
- lib/brain.rb
|
24
27
|
- lib/brain/lookup.rb
|
25
28
|
- lib/brain/neuralnetwork.rb
|
26
29
|
- rb-brain.gemspec
|
30
|
+
- spec/lib/bitwise_spec.rb
|
31
|
+
- spec/lib/hash_spec.rb
|
32
|
+
- spec/lib/json_spec.rb
|
33
|
+
- spec/lib/lookup_spec.rb
|
34
|
+
- spec/lib/options_spec.rb
|
35
|
+
- spec/lib/trainopts_spec.rb
|
36
|
+
- spec/spec_helper.rb
|
37
|
+
- spec/supports/helpers.rb
|
27
38
|
homepage: https://github.com/qinix/rb-brain
|
28
39
|
licenses:
|
29
40
|
- MIT
|
@@ -48,4 +59,12 @@ rubygems_version: 2.4.3
|
|
48
59
|
signing_key:
|
49
60
|
specification_version: 4
|
50
61
|
summary: rb-brain is an easy-to-use neural network written in ruby
|
51
|
-
test_files:
|
62
|
+
test_files:
|
63
|
+
- spec/lib/bitwise_spec.rb
|
64
|
+
- spec/lib/hash_spec.rb
|
65
|
+
- spec/lib/json_spec.rb
|
66
|
+
- spec/lib/lookup_spec.rb
|
67
|
+
- spec/lib/options_spec.rb
|
68
|
+
- spec/lib/trainopts_spec.rb
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
- spec/supports/helpers.rb
|