schlepp 0.0.1.pre.alpha.1 → 0.0.1.pre.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|