schlepp 0.0.1.pre.alpha.1 → 0.0.1.pre.alpha.2
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/.travis.yml +6 -5
- data/Gemfile +2 -1
- data/lib/schlepp/sink/filter/chunker.rb +40 -0
- data/lib/schlepp/sink/filter/compressor/stream.rb +44 -0
- data/lib/schlepp/sink/filter/compressor/writer.rb +36 -0
- data/lib/schlepp/sink/filter/compressor.rb +20 -0
- data/lib/schlepp/sink/filter/formatter/csv/writer.rb +25 -0
- data/lib/schlepp/sink/filter/formatter/csv.rb +21 -0
- data/lib/schlepp/sink/filter.rb +20 -0
- data/lib/schlepp/sink/table_object/carosel.rb +65 -0
- data/lib/schlepp/sink/table_object/factory.rb +28 -0
- data/lib/schlepp/sink/table_object/filter/observer.rb +23 -0
- data/lib/schlepp/sink/table_object/filter/writer.rb +26 -0
- data/lib/schlepp/sink/table_object/filters.rb +22 -0
- data/lib/schlepp/sink/table_object/stream.rb +25 -0
- data/lib/schlepp/sink/table_object/writer/factory.rb +22 -0
- data/lib/schlepp/sink.rb +17 -2
- data/lib/schlepp/source/csv.rb +1 -1
- data/lib/schlepp/version.rb +1 -1
- data/test/integration/mock/sink.rb +51 -0
- data/test/integration/mock/source.rb +9 -0
- data/test/integration/schlepp_test.rb +27 -8
- data/test/integration/test_helper.rb +13 -6
- data/test/unit/schlepp/table_object/carosel_test.rb +104 -0
- data/test/unit/schlepp/table_object/factory_test.rb +55 -0
- data/test/unit/schlepp_test.rb +5 -4
- data/test/unit/test_helper.rb +13 -6
- metadata +27 -29
- data/bin/schlepp.rb +0 -29
- data/lib/schlepp/sink/chunker.rb +0 -30
- data/lib/schlepp/sink/data_object.rb +0 -26
- data/lib/schlepp/sink/data_stream.rb +0 -39
- data/lib/schlepp/sink/loader.rb +0 -35
- data/lib/schlepp/sink/sequencer.rb +0 -42
- data/lib/schlepp/sinks/fs/chunker.rb +0 -38
- data/lib/schlepp/sinks/fs/sequencer.rb +0 -21
- data/lib/schlepp/sinks/fs/table_object/collection.rb +0 -23
- data/lib/schlepp/sinks/fs/table_object/writer.rb +0 -33
- data/lib/schlepp/sinks/fs/table_object.rb +0 -31
- data/lib/schlepp/sinks/fs.rb +0 -2
- data/lib/schlepp/table_object/chunker.rb +0 -33
- data/test/unit/schlepp/sink/data_object_test.rb +0 -0
- data/test/unit/schlepp/sink/data_stream_test.rb +0 -0
- data/test/unit/schlepp/sink/loader_test.rb +0 -29
- data/test/unit/schlepp/sink/sequencer_test.rb +0 -0
- data/test/unit/schlepp/table_object/chunker_test.rb +0 -0
- /data/test/unit/schlepp/{sink/chunker_test.rb → table_object/writer/factory_test.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b1ecbdd8608cf937d4aacec3d65899d9a89991c
|
4
|
+
data.tar.gz: e545caacec77ee3eafce431aa2f43b339852f3b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dac1ede8bb4e2ce790e1794d884451223fbc64633ffd2de5404da0d67ea58999e9a2dbe2dfb773f7ef2c9a048b228b93e56ca1fc297dd03ca6beabe022e52c15
|
7
|
+
data.tar.gz: 87cc9f384d060cfe89e67874e549927bb6b892a568f6542693851b1af4f2a59960145cad16e7f4df2d0b27ace85eca4e6b7070eb8f2f98bdfe415c022258a147
|
data/.travis.yml
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
3
|
env:
|
4
|
-
|
5
|
-
|
4
|
+
global:
|
5
|
+
- COVERAGE=true
|
6
|
+
matrix:
|
7
|
+
- TEST_SUITE=unit
|
8
|
+
- TEST_SUITE=integration
|
6
9
|
|
7
10
|
script: "bundle exec rake test:$TEST_SUITE"
|
8
11
|
|
9
12
|
rvm:
|
10
13
|
- "1.9.3"
|
11
|
-
- "2.1.
|
14
|
+
- "2.1.3"
|
12
15
|
- "ruby-head"
|
13
16
|
- "jruby-19mode"
|
14
|
-
- "jruby-head"
|
15
17
|
- "rbx-2"
|
16
|
-
|
data/Gemfile
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'schlepp/sink/table_object/filter/observer'
|
2
|
+
require 'schlepp/sink/table_object/filter/writer'
|
3
|
+
module Schlepp
|
4
|
+
class Sink
|
5
|
+
module Filter
|
6
|
+
class Chunker
|
7
|
+
class Builder
|
8
|
+
def initialize(observer)
|
9
|
+
@observer = observer
|
10
|
+
end
|
11
|
+
|
12
|
+
def new(writer)
|
13
|
+
Schlepp::Sink::TableObject::Filter::Writer.new(writer, @observer)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def initialize(opts = {})
|
17
|
+
@chunk = 0
|
18
|
+
@chunk_size = opts[:chunk_size] || 100000
|
19
|
+
@observer = Schlepp::Sink::TableObject::Filter::Observer.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def extension
|
23
|
+
@chunk.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def should_rotate?
|
27
|
+
@observer.count > @chunk_size
|
28
|
+
end
|
29
|
+
|
30
|
+
def rotate
|
31
|
+
@chunk += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def writer
|
35
|
+
@builder ||= Builder.new(@observer)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
module Schlepp
|
4
|
+
class Sink
|
5
|
+
class TableObject
|
6
|
+
class Compressor
|
7
|
+
class Stream
|
8
|
+
def initialize
|
9
|
+
@buffer = StringIO.new("", "rb+")
|
10
|
+
@compressor = Zlib::GzipWriter.new(@buffer)
|
11
|
+
@dead = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(data)
|
15
|
+
if @dead
|
16
|
+
raise "Stream has been dumped. No more writing permitted."
|
17
|
+
end
|
18
|
+
@compressor << data
|
19
|
+
end
|
20
|
+
|
21
|
+
def length
|
22
|
+
if @dead
|
23
|
+
raise "Stream has been dumped. No more writing permitted."
|
24
|
+
end
|
25
|
+
@compressor.pos
|
26
|
+
end
|
27
|
+
|
28
|
+
def finalize
|
29
|
+
if !@dead
|
30
|
+
@compressor.close
|
31
|
+
@dead = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
finalize
|
37
|
+
@buffer.string
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'schlepp/sink/filter/compressor/stream'
|
2
|
+
|
3
|
+
module Schlepp
|
4
|
+
class Sink
|
5
|
+
class TableObject
|
6
|
+
class Compressor
|
7
|
+
class Writer
|
8
|
+
def initialize(writer, opts = {})
|
9
|
+
@stream = Stream.new
|
10
|
+
@writer = writer
|
11
|
+
@written = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(rows)
|
15
|
+
Array(rows).each do |row|
|
16
|
+
@written += row.length
|
17
|
+
@stream.write(row)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def rotate?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def finalize
|
27
|
+
bits = @stream.to_s
|
28
|
+
|
29
|
+
@writer.write(bits)
|
30
|
+
@writer.finalize
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'schlepp/sink/filter'
|
2
|
+
require 'schlepp/sink/filter/compressor/writer'
|
3
|
+
|
4
|
+
module Schlepp
|
5
|
+
class Sink
|
6
|
+
module Filter
|
7
|
+
class Compressor
|
8
|
+
include Schlepp::Sink::Filter::Static
|
9
|
+
def extension
|
10
|
+
'gz'
|
11
|
+
end
|
12
|
+
|
13
|
+
def writer
|
14
|
+
Schlepp::Sink::TableObject::Compressor::Writer
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Schlepp
|
4
|
+
class Sink
|
5
|
+
class TableObject
|
6
|
+
class Formatter
|
7
|
+
class Csv
|
8
|
+
class Writer
|
9
|
+
def initialize(writer, opts = {})
|
10
|
+
@writer = writer
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(rows)
|
14
|
+
@writer.write(rows.to_csv)
|
15
|
+
end
|
16
|
+
|
17
|
+
def finalize
|
18
|
+
@writer.finalize
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'schlepp/sink/filter'
|
2
|
+
require 'schlepp/sink/table_object/formatter/csv/writer'
|
3
|
+
|
4
|
+
module Schlepp
|
5
|
+
class Sink
|
6
|
+
module Filter
|
7
|
+
class Csv
|
8
|
+
include Schlepp::Sink::Filter::Static
|
9
|
+
def extension
|
10
|
+
'csv'
|
11
|
+
end
|
12
|
+
|
13
|
+
def writer
|
14
|
+
Schlepp::Sink::TableObject::Formatter::Csv::Writer
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'schlepp/sink/table_object/factory'
|
2
|
+
require 'schlepp/sink/table_object/writer/factory'
|
3
|
+
|
4
|
+
module Schlepp
|
5
|
+
class Sink
|
6
|
+
class TableObject
|
7
|
+
class Carosel
|
8
|
+
def initialize(factory, chain)
|
9
|
+
@factory = factory
|
10
|
+
@chain = chain
|
11
|
+
@parts = []
|
12
|
+
@current = rotate
|
13
|
+
@finalized = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(data)
|
17
|
+
raise_if_finalized
|
18
|
+
if rotate?(data)
|
19
|
+
rotate
|
20
|
+
if rotate?(data)
|
21
|
+
raise
|
22
|
+
end
|
23
|
+
end
|
24
|
+
Array(data).each do |r|
|
25
|
+
@current.write(r)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def finalize
|
30
|
+
@current.finalize
|
31
|
+
@finalized = true
|
32
|
+
@parts
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def raise_if_finalized
|
38
|
+
@finalized && raise
|
39
|
+
end
|
40
|
+
|
41
|
+
def rotate?(data)
|
42
|
+
@chain.any?(&:should_rotate?)
|
43
|
+
end
|
44
|
+
|
45
|
+
def rotate
|
46
|
+
@current.finalize if @current
|
47
|
+
|
48
|
+
to = table_object_factory.new
|
49
|
+
|
50
|
+
@parts << to
|
51
|
+
|
52
|
+
builder = Schlepp::Sink::TableObject::Writer::Factory.new(@factory, @chain, to)
|
53
|
+
|
54
|
+
@current = builder.new
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def table_object_factory
|
60
|
+
@table_object_factory ||= Schlepp::Sink::TableObject::Factory.new(@factory, @chain)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'schlepp/sink/table_object/writer/factory'
|
2
|
+
|
3
|
+
module Schlepp
|
4
|
+
class Sink
|
5
|
+
class TableObject
|
6
|
+
class Factory
|
7
|
+
def initialize(factory, components)
|
8
|
+
@factory = factory
|
9
|
+
@components = components
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
@components.each(&:rotate)
|
14
|
+
|
15
|
+
url = @components.inject(@factory.url) do |u, f|
|
16
|
+
u.merge("#{u.path}.#{f.extension}")
|
17
|
+
end
|
18
|
+
|
19
|
+
Hydrogen::TableObject.new(@factory.model, url)
|
20
|
+
end
|
21
|
+
|
22
|
+
def writer
|
23
|
+
Writer::Factory.new(@factory, @components, self.new).new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Schlepp
|
2
|
+
class Sink
|
3
|
+
class TableObject
|
4
|
+
class Filter
|
5
|
+
class Observer
|
6
|
+
def initialize
|
7
|
+
reset
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :count
|
11
|
+
|
12
|
+
def add(data)
|
13
|
+
@count += data.length
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@count = 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Schlepp
|
2
|
+
class Sink
|
3
|
+
class TableObject
|
4
|
+
class Filter
|
5
|
+
class Writer
|
6
|
+
def initialize(writer, observer, opts = {})
|
7
|
+
@writer = writer
|
8
|
+
@observer = observer
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(rows)
|
12
|
+
Array(rows).each do |row|
|
13
|
+
@writer.write(row)
|
14
|
+
@observer.add(row)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def finalize
|
19
|
+
@writer.finalize
|
20
|
+
@observer.reset
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Schlepp
|
2
|
+
class Sink
|
3
|
+
class TableObject
|
4
|
+
class Filters
|
5
|
+
def initialize
|
6
|
+
@filters = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(filter)
|
10
|
+
@filters << filter
|
11
|
+
end
|
12
|
+
|
13
|
+
def decorate(writer)
|
14
|
+
@filters.reverse.inject(writer) do |base, f|
|
15
|
+
f.decorate(base)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Schlepp
|
2
|
+
class Sink
|
3
|
+
class TableObject
|
4
|
+
class Stream
|
5
|
+
def initialize(table_object_writer)
|
6
|
+
@table_object_writer = table_object_writer
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(data)
|
10
|
+
@stream.write(data)
|
11
|
+
end
|
12
|
+
|
13
|
+
def length
|
14
|
+
@stream.length
|
15
|
+
end
|
16
|
+
|
17
|
+
def finalize
|
18
|
+
@stream.finalize
|
19
|
+
|
20
|
+
@table_object_writer.write(@stream.to_s)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Schlepp
|
2
|
+
class Sink
|
3
|
+
class TableObject
|
4
|
+
class Writer
|
5
|
+
class Factory
|
6
|
+
def initialize(factory, components, table_object)
|
7
|
+
@factory = factory
|
8
|
+
@components = components
|
9
|
+
@table_object = table_object
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
writer = @factory.writer(@table_object)
|
14
|
+
@components.reverse.inject(writer) {|base, f|
|
15
|
+
f.writer.new(base)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/schlepp/sink.rb
CHANGED
@@ -1,8 +1,23 @@
|
|
1
|
-
require 'schlepp/sink/
|
1
|
+
require 'schlepp/sink/table_object/carosel'
|
2
2
|
|
3
3
|
module Schlepp
|
4
|
-
|
4
|
+
class Sink
|
5
|
+
def initialize(model, factory, filters)
|
6
|
+
@model = model
|
7
|
+
@carosel = TableObject::Carosel.new(factory, filters)
|
8
|
+
end
|
5
9
|
|
10
|
+
def name
|
11
|
+
@model.name
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(data)
|
15
|
+
@carosel.write(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def finalize
|
19
|
+
@carosel.finalize
|
20
|
+
end
|
6
21
|
end
|
7
22
|
end
|
8
23
|
|
data/lib/schlepp/source/csv.rb
CHANGED
data/lib/schlepp/version.rb
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'schlepp/sink/filter/chunker'
|
2
|
+
|
3
|
+
module Schlepp::IntegrationTest::Mock
|
4
|
+
class Factory
|
5
|
+
def initialize(model, verifier)
|
6
|
+
@model = model
|
7
|
+
@verifier = verifier
|
8
|
+
end
|
9
|
+
|
10
|
+
def url
|
11
|
+
URI("http://www.example.com/")
|
12
|
+
end
|
13
|
+
|
14
|
+
def model
|
15
|
+
@model
|
16
|
+
end
|
17
|
+
|
18
|
+
def writer(to)
|
19
|
+
Writer.new(to, @verifier)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
class Writer
|
23
|
+
def initialize(table_object, verifier)
|
24
|
+
@table_object = table_object
|
25
|
+
@verifier = verifier
|
26
|
+
end
|
27
|
+
|
28
|
+
def write(data)
|
29
|
+
@verifier.write(data)
|
30
|
+
end
|
31
|
+
def finalize
|
32
|
+
@verifier.finalize
|
33
|
+
end
|
34
|
+
end
|
35
|
+
class Sink
|
36
|
+
def initialize(model, verifier)
|
37
|
+
@model = model
|
38
|
+
@factory = Factory.new(@model, verifier)
|
39
|
+
@sink = Schlepp::Sink.new(@model, @factory, [Schlepp::Sink::Filter::Chunker.new])
|
40
|
+
end
|
41
|
+
|
42
|
+
def write(data)
|
43
|
+
@sink.write(data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def finalize
|
47
|
+
@sink.finalize
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -3,23 +3,42 @@ require File.expand_path('../test_helper', __FILE__)
|
|
3
3
|
require 'pp'
|
4
4
|
require 'schlepp'
|
5
5
|
|
6
|
+
$LOAD_PATH.unshift(File.expand_path('../', __FILE__))
|
7
|
+
|
8
|
+
|
6
9
|
class Schlepp::IntegrationTest < Test::Unit::TestCase
|
7
10
|
setup do
|
11
|
+
@model= Hydrogen::Model.new({
|
12
|
+
:table_name => "foo",
|
13
|
+
:key => "key",
|
14
|
+
:columns => ["baz", "blarg"]
|
15
|
+
})
|
16
|
+
|
17
|
+
@verifier = mock
|
18
|
+
|
8
19
|
@mock_writer = mock
|
20
|
+
require 'mock/source'
|
21
|
+
@mock_source = Mock::Source.new
|
9
22
|
|
10
|
-
|
11
|
-
|
23
|
+
require 'mock/sink'
|
24
|
+
@mock_sink = Mock::Sink.new(@model, @verifier)
|
12
25
|
end
|
13
26
|
|
14
|
-
test '
|
15
|
-
|
27
|
+
test '.schlepp' do
|
28
|
+
1.upto(100).each do |x|
|
29
|
+
@verifier.expects(:write).with(x.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
@verifier.expects(:finalize)
|
33
|
+
|
34
|
+
res = Schlepp.schlepp(@mock_source, @mock_sink)
|
16
35
|
|
17
|
-
|
36
|
+
object = res.first
|
18
37
|
|
19
|
-
|
20
|
-
loader.write(["FOO|BAR|BAZ"] * 10)
|
38
|
+
puts res.inspect
|
21
39
|
|
22
|
-
|
40
|
+
assert_equal object.url, URI("http://www.example.com/.1")
|
41
|
+
assert_equal object.name, "foo"
|
23
42
|
end
|
24
43
|
end
|
25
44
|
|
@@ -1,10 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
if ENV["ENABLE_SIMPLE_COV"]
|
1
|
+
if ENV["COVERAGE"]
|
2
|
+
require 'coveralls'
|
3
|
+
require 'codeclimate-test-reporter'
|
5
4
|
require 'simplecov'
|
6
|
-
|
7
|
-
|
5
|
+
SimpleCov.start do
|
6
|
+
add_group "Lib", "lib"
|
7
|
+
add_filter "/test/"
|
8
|
+
command_name "Integration Tests"
|
9
|
+
formatter SimpleCov::Formatter::MultiFormatter[
|
10
|
+
SimpleCov::Formatter::HTMLFormatter,
|
11
|
+
Coveralls::SimpleCov::Formatter,
|
12
|
+
CodeClimate::TestReporter::Formatter
|
13
|
+
]
|
14
|
+
end
|
8
15
|
end
|
9
16
|
|
10
17
|
require 'test/unit'
|