attributor 5.0.2 → 5.1.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/.rubocop.yml +30 -0
- data/.travis.yml +6 -4
- data/CHANGELOG.md +6 -1
- data/Gemfile +1 -1
- data/Guardfile +14 -8
- data/Rakefile +4 -5
- data/attributor.gemspec +34 -29
- data/lib/attributor.rb +23 -29
- data/lib/attributor/attribute.rb +108 -127
- data/lib/attributor/attribute_resolver.rb +12 -26
- data/lib/attributor/dsl_compiler.rb +17 -21
- data/lib/attributor/dumpable.rb +1 -2
- data/lib/attributor/example_mixin.rb +5 -8
- data/lib/attributor/exceptions.rb +5 -6
- data/lib/attributor/extensions/randexp.rb +3 -5
- data/lib/attributor/extras/field_selector.rb +4 -4
- data/lib/attributor/extras/field_selector/transformer.rb +6 -7
- data/lib/attributor/families/numeric.rb +0 -2
- data/lib/attributor/families/temporal.rb +1 -4
- data/lib/attributor/hash_dsl_compiler.rb +22 -25
- data/lib/attributor/type.rb +24 -32
- data/lib/attributor/types/bigdecimal.rb +7 -14
- data/lib/attributor/types/boolean.rb +5 -8
- data/lib/attributor/types/class.rb +9 -10
- data/lib/attributor/types/collection.rb +34 -44
- data/lib/attributor/types/container.rb +9 -15
- data/lib/attributor/types/csv.rb +7 -10
- data/lib/attributor/types/date.rb +20 -25
- data/lib/attributor/types/date_time.rb +7 -14
- data/lib/attributor/types/float.rb +4 -6
- data/lib/attributor/types/hash.rb +171 -196
- data/lib/attributor/types/ids.rb +2 -6
- data/lib/attributor/types/integer.rb +12 -17
- data/lib/attributor/types/model.rb +39 -48
- data/lib/attributor/types/object.rb +2 -4
- data/lib/attributor/types/polymorphic.rb +118 -0
- data/lib/attributor/types/regexp.rb +4 -5
- data/lib/attributor/types/string.rb +6 -7
- data/lib/attributor/types/struct.rb +8 -15
- data/lib/attributor/types/symbol.rb +3 -6
- data/lib/attributor/types/tempfile.rb +5 -6
- data/lib/attributor/types/time.rb +11 -11
- data/lib/attributor/types/uri.rb +9 -10
- data/lib/attributor/version.rb +1 -1
- data/spec/attribute_resolver_spec.rb +57 -78
- data/spec/attribute_spec.rb +174 -216
- data/spec/attributor_spec.rb +11 -15
- data/spec/dsl_compiler_spec.rb +19 -33
- data/spec/dumpable_spec.rb +6 -7
- data/spec/extras/field_selector/field_selector_spec.rb +1 -1
- data/spec/families_spec.rb +1 -3
- data/spec/hash_dsl_compiler_spec.rb +65 -74
- data/spec/spec_helper.rb +9 -3
- data/spec/support/hashes.rb +2 -3
- data/spec/support/models.rb +30 -36
- data/spec/support/polymorphics.rb +10 -0
- data/spec/type_spec.rb +38 -61
- data/spec/types/bigdecimal_spec.rb +11 -15
- data/spec/types/boolean_spec.rb +12 -39
- data/spec/types/class_spec.rb +10 -11
- data/spec/types/collection_spec.rb +72 -81
- data/spec/types/container_spec.rb +22 -26
- data/spec/types/csv_spec.rb +15 -16
- data/spec/types/date_spec.rb +16 -33
- data/spec/types/date_time_spec.rb +16 -33
- data/spec/types/file_upload_spec.rb +1 -2
- data/spec/types/float_spec.rb +7 -14
- data/spec/types/hash_spec.rb +285 -289
- data/spec/types/ids_spec.rb +5 -7
- data/spec/types/integer_spec.rb +37 -46
- data/spec/types/model_spec.rb +111 -128
- data/spec/types/polymorphic_spec.rb +134 -0
- data/spec/types/regexp_spec.rb +4 -7
- data/spec/types/string_spec.rb +17 -21
- data/spec/types/struct_spec.rb +40 -47
- data/spec/types/tempfile_spec.rb +1 -2
- data/spec/types/temporal_spec.rb +9 -0
- data/spec/types/time_spec.rb +16 -32
- data/spec/types/type_spec.rb +15 -0
- data/spec/types/uri_spec.rb +6 -7
- metadata +77 -25
@@ -1,49 +1,45 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '../spec_helper.rb')
|
2
2
|
|
3
|
-
|
4
3
|
describe Attributor::Container do
|
5
|
-
|
6
4
|
context '.decode_json' do
|
7
|
-
|
8
5
|
let(:mock_type) do
|
9
6
|
Class.new do
|
10
7
|
include Attributor::Container
|
11
8
|
def self.native_type
|
12
|
-
::Hash
|
9
|
+
::Hash
|
13
10
|
end
|
14
11
|
end
|
15
|
-
end
|
12
|
+
end
|
16
13
|
context 'for valid JSON strings' do
|
17
|
-
it
|
18
|
-
a_hash = {
|
19
|
-
json_hash =
|
20
|
-
mock_type.decode_json(json_hash).
|
14
|
+
it 'parses JSON string into the native type' do
|
15
|
+
a_hash = { 'a' => 1, 'b' => 2 }
|
16
|
+
json_hash = JSON.dump(a_hash)
|
17
|
+
expect(mock_type.decode_json(json_hash)).to eq a_hash
|
21
18
|
end
|
22
19
|
it 'complains when trying to decode a non-String value' do
|
23
|
-
expect
|
20
|
+
expect do
|
24
21
|
mock_type.decode_json(Object.new)
|
25
|
-
|
22
|
+
end.to raise_error(Attributor::DeserializationError, /Error deserializing a Object using JSON/)
|
26
23
|
end
|
27
|
-
|
24
|
+
|
28
25
|
it 'complains when the deserialized value is not of the native_type' do
|
29
|
-
expect
|
30
|
-
mock_type.decode_json(
|
31
|
-
|
26
|
+
expect do
|
27
|
+
mock_type.decode_json('[1,2,3]')
|
28
|
+
end.to raise_error(Attributor::CoercionError, /Error coercing from Array/)
|
32
29
|
end
|
33
|
-
|
30
|
+
|
34
31
|
it 'complains if there is a error deserializing the string' do
|
35
|
-
expect
|
36
|
-
mock_type.decode_json(
|
37
|
-
|
32
|
+
expect do
|
33
|
+
mock_type.decode_json('{invalid_json}')
|
34
|
+
end.to raise_error(Attributor::DeserializationError, /Error deserializing a String using JSON/)
|
38
35
|
end
|
39
|
-
|
36
|
+
|
40
37
|
it 'uses the passed context in the output error' do
|
41
|
-
expect
|
42
|
-
mock_type.decode_json(
|
43
|
-
|
38
|
+
expect do
|
39
|
+
mock_type.decode_json('{invalid_json}', %w(my_context attribute_name))
|
40
|
+
end.to raise_error(Attributor::DeserializationError,
|
41
|
+
/Error deserializing a String using JSON.* while loading my_context.attribute_name/)
|
44
42
|
end
|
45
|
-
|
46
43
|
end
|
47
44
|
end
|
48
|
-
|
49
|
-
end
|
45
|
+
end
|
data/spec/types/csv_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
2
2
|
|
3
3
|
describe Attributor::CSV do
|
4
|
-
|
5
4
|
subject!(:csv) { Attributor::CSV.of(Integer) }
|
6
5
|
|
7
6
|
context '.load' do
|
@@ -9,7 +8,7 @@ describe Attributor::CSV do
|
|
9
8
|
let!(:value) { array.join(',') }
|
10
9
|
|
11
10
|
it 'parses the value and returns an array with the right types' do
|
12
|
-
csv.load(value).
|
11
|
+
expect(csv.load(value)).to match_array array
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
@@ -18,46 +17,46 @@ describe Attributor::CSV do
|
|
18
17
|
let!(:loaded_example) { csv.load(example) }
|
19
18
|
|
20
19
|
it 'generates a String example' do
|
21
|
-
example.
|
20
|
+
expect(example).to be_a(String)
|
22
21
|
end
|
23
22
|
|
24
23
|
it 'generates a comma-separated list of Integer values' do
|
25
|
-
loaded_example.
|
26
|
-
loaded_example.size.
|
27
|
-
loaded_example.each { |e| e.
|
24
|
+
expect(loaded_example).to be_a(csv)
|
25
|
+
expect(loaded_example.size).to be > 1
|
26
|
+
loaded_example.each { |e| expect(e).to be_a(Integer) }
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
30
|
context '.dump' do
|
32
31
|
let!(:int_vals) { [1, 2, 3] }
|
33
|
-
let!(:str_vals)
|
32
|
+
let!(:str_vals) { (0..2).collect { /\w+/.gen } }
|
34
33
|
|
35
34
|
it 'dumps a String value' do
|
36
|
-
csv.dump(int_vals).
|
35
|
+
expect(csv.dump(int_vals)).to be_a(String)
|
37
36
|
end
|
38
37
|
|
39
38
|
it 'dumps a comma-separated list of Integers' do
|
40
|
-
csv.dump(int_vals).
|
39
|
+
expect(csv.dump(int_vals)).to eq(int_vals.join(','))
|
41
40
|
end
|
42
41
|
|
43
42
|
it 'dumps non-Integer values also' do
|
44
|
-
csv.dump(str_vals).
|
43
|
+
expect(csv.dump(str_vals)).to eq(str_vals.join(','))
|
45
44
|
end
|
46
45
|
|
47
46
|
it 'dumps nil values as nil' do
|
48
|
-
csv.dump(nil).
|
47
|
+
expect(csv.dump(nil)).to eq(nil)
|
49
48
|
end
|
50
49
|
end
|
51
50
|
|
52
51
|
context '.describe' do
|
53
|
-
let(:example){ csv.example }
|
54
|
-
subject(:described){ csv.describe(example: example)}
|
52
|
+
let(:example) { csv.example }
|
53
|
+
subject(:described) { csv.describe(example: example) }
|
55
54
|
it 'adds a string example if an example is passed' do
|
56
|
-
described.
|
57
|
-
described[:example].
|
55
|
+
expect(described).to have_key(:example)
|
56
|
+
expect(described[:example]).to eq(csv.dump(example))
|
58
57
|
end
|
59
58
|
it 'ensures no member_attribute key exists from underlying Collection' do
|
60
|
-
described.
|
59
|
+
expect(described).not_to have_key(:member_attribute)
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
data/spec/types/date_spec.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
2
2
|
|
3
3
|
describe Attributor::Date do
|
4
|
-
|
5
4
|
subject(:type) { Attributor::Date }
|
6
5
|
|
7
6
|
it 'it is not Dumpable' do
|
8
|
-
type.new.is_a?(Attributor::Dumpable).
|
7
|
+
expect(type.new.is_a?(Attributor::Dumpable)).not_to be(true)
|
9
8
|
end
|
10
9
|
|
11
10
|
context '.native_type' do
|
@@ -17,45 +16,40 @@ describe Attributor::Date do
|
|
17
16
|
end
|
18
17
|
|
19
18
|
context '.dump' do
|
20
|
-
let(:example) { type.example}
|
19
|
+
let(:example) { type.example }
|
21
20
|
subject(:value) { type.dump(example) }
|
22
21
|
it 'is formatted correctly' do
|
23
|
-
value.
|
22
|
+
expect(value).to match(/\d{4}-\d{2}-\d{2}T00:00:00\+00:00/)
|
24
23
|
end
|
25
24
|
context 'nil values' do
|
26
25
|
it 'should be nil' do
|
27
|
-
type.dump(nil).
|
26
|
+
expect(type.dump(nil)).to be_nil
|
28
27
|
end
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
32
|
-
|
33
31
|
context '.load' do
|
34
|
-
|
35
32
|
it 'returns nil for nil' do
|
36
|
-
type.load(nil).
|
33
|
+
expect(type.load(nil)).to be(nil)
|
37
34
|
end
|
38
35
|
|
39
36
|
context 'for incoming objects' do
|
40
|
-
|
41
|
-
it "returns correct Date for Time objects" do
|
37
|
+
it 'returns correct Date for Time objects' do
|
42
38
|
object = Time.now
|
43
39
|
loaded = type.load(object)
|
44
|
-
loaded.
|
45
|
-
loaded.to_date.
|
40
|
+
expect(loaded).to be_a(::Date)
|
41
|
+
expect(loaded.to_date).to eq object.to_date
|
46
42
|
end
|
47
43
|
|
48
|
-
it
|
44
|
+
it 'returns correct Date for DateTime objects' do
|
49
45
|
object = DateTime.now
|
50
46
|
loaded = type.load(object)
|
51
|
-
loaded.
|
52
|
-
loaded.
|
47
|
+
expect(loaded).to be_a(::Date)
|
48
|
+
expect(loaded).to be(object)
|
53
49
|
end
|
54
|
-
|
55
50
|
end
|
56
51
|
|
57
52
|
context 'for incoming strings' do
|
58
|
-
|
59
53
|
[
|
60
54
|
'2001-02-03T04:05:06+07:00',
|
61
55
|
'Sat, 03 Feb 2001 04:05:06 GMT',
|
@@ -67,11 +61,9 @@ describe Attributor::Date do
|
|
67
61
|
'2007-10-19T04:11:33Z',
|
68
62
|
'2001-02-03T04:05:06+07:00.123456', # custom format with microseconds
|
69
63
|
].each do |value|
|
70
|
-
|
71
64
|
it "returns correct Date for #{value.inspect}" do
|
72
|
-
type.load(value).
|
65
|
+
expect(type.load(value)).to eq Date.parse(value)
|
73
66
|
end
|
74
|
-
|
75
67
|
end
|
76
68
|
|
77
69
|
[
|
@@ -80,13 +72,11 @@ describe Attributor::Date do
|
|
80
72
|
'2007-10-33T04:11:33Z',
|
81
73
|
'2001-02-33T04:05:06+07:00.123456', # custom format with microseconds
|
82
74
|
].each do |value|
|
83
|
-
|
84
75
|
it "raises Attributor::AttributorException for #{value.inspect}" do
|
85
|
-
expect
|
76
|
+
expect do
|
86
77
|
type.load(value)
|
87
|
-
|
78
|
+
end.to raise_error(Attributor::DeserializationError, /Error deserializing a String using Date/)
|
88
79
|
end
|
89
|
-
|
90
80
|
end
|
91
81
|
|
92
82
|
[
|
@@ -94,19 +84,12 @@ describe Attributor::Date do
|
|
94
84
|
'foobar',
|
95
85
|
'Sat, 30 Feb 2001 04:05:06 FOOBAR', # No such date format exists
|
96
86
|
].each do |value|
|
97
|
-
|
98
87
|
it "raises Attributor::AttributorException for #{value.inspect}" do
|
99
|
-
expect
|
88
|
+
expect do
|
100
89
|
type.load(value)
|
101
|
-
|
90
|
+
end.to raise_error(Attributor::DeserializationError, /Error deserializing a String using Date/)
|
102
91
|
end
|
103
|
-
|
104
92
|
end
|
105
|
-
|
106
93
|
end
|
107
|
-
|
108
94
|
end
|
109
|
-
|
110
95
|
end
|
111
|
-
|
112
|
-
|
@@ -1,11 +1,10 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
2
2
|
|
3
3
|
describe Attributor::DateTime do
|
4
|
-
|
5
4
|
subject(:type) { Attributor::DateTime }
|
6
5
|
|
7
6
|
it 'it is not Dumpable' do
|
8
|
-
type.new.is_a?(Attributor::Dumpable).
|
7
|
+
expect(type.new.is_a?(Attributor::Dumpable)).not_to be(true)
|
9
8
|
end
|
10
9
|
|
11
10
|
context '.native_type' do
|
@@ -17,45 +16,40 @@ describe Attributor::DateTime do
|
|
17
16
|
end
|
18
17
|
|
19
18
|
context '.dump' do
|
20
|
-
let(:example) { type.example}
|
19
|
+
let(:example) { type.example }
|
21
20
|
subject(:value) { type.dump(example) }
|
22
21
|
it 'is formatted correctly' do
|
23
|
-
value.
|
22
|
+
expect(value).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\+-]\d{2}:\d{2}/)
|
24
23
|
end
|
25
24
|
context 'nil values' do
|
26
25
|
it 'should be nil' do
|
27
|
-
type.dump(nil).
|
26
|
+
expect(type.dump(nil)).to be_nil
|
28
27
|
end
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
32
|
-
|
33
31
|
context '.load' do
|
34
|
-
|
35
32
|
it 'returns nil for nil' do
|
36
|
-
type.load(nil).
|
33
|
+
expect(type.load(nil)).to be(nil)
|
37
34
|
end
|
38
35
|
|
39
36
|
context 'for incoming objects' do
|
40
|
-
|
41
|
-
it "returns correct DateTime for Time objects" do
|
37
|
+
it 'returns correct DateTime for Time objects' do
|
42
38
|
object = Time.now
|
43
39
|
loaded = type.load(object)
|
44
|
-
loaded.
|
45
|
-
loaded.to_time.
|
40
|
+
expect(loaded).to be_a(::DateTime)
|
41
|
+
expect(loaded.to_time).to eq object
|
46
42
|
end
|
47
43
|
|
48
|
-
it
|
44
|
+
it 'returns correct DateTime for DateTime objects' do
|
49
45
|
object = DateTime.now
|
50
46
|
loaded = type.load(object)
|
51
|
-
loaded.
|
52
|
-
loaded.
|
47
|
+
expect(loaded).to be_a(::DateTime)
|
48
|
+
expect(loaded).to be(object)
|
53
49
|
end
|
54
|
-
|
55
50
|
end
|
56
51
|
|
57
52
|
context 'for incoming strings' do
|
58
|
-
|
59
53
|
[
|
60
54
|
'2001-02-03T04:05:06+07:00',
|
61
55
|
'Sat, 03 Feb 2001 04:05:06 GMT',
|
@@ -67,11 +61,9 @@ describe Attributor::DateTime do
|
|
67
61
|
'2007-10-19T04:11:33Z',
|
68
62
|
'2001-02-03T04:05:06+07:00.123456', # custom format with microseconds
|
69
63
|
].each do |value|
|
70
|
-
|
71
64
|
it "returns correct DateTime for #{value.inspect}" do
|
72
|
-
type.load(value).
|
65
|
+
expect(type.load(value)).to eq DateTime.parse(value)
|
73
66
|
end
|
74
|
-
|
75
67
|
end
|
76
68
|
|
77
69
|
[
|
@@ -80,13 +72,11 @@ describe Attributor::DateTime do
|
|
80
72
|
'2007-10-33T04:11:33Z',
|
81
73
|
'2001-02-33T04:05:06+07:00.123456', # custom format with microseconds
|
82
74
|
].each do |value|
|
83
|
-
|
84
75
|
it "raises Attributor::AttributorException for #{value.inspect}" do
|
85
|
-
expect
|
76
|
+
expect do
|
86
77
|
type.load(value)
|
87
|
-
|
78
|
+
end.to raise_error(Attributor::DeserializationError, /Error deserializing a String using DateTime/)
|
88
79
|
end
|
89
|
-
|
90
80
|
end
|
91
81
|
|
92
82
|
[
|
@@ -94,19 +84,12 @@ describe Attributor::DateTime do
|
|
94
84
|
'foobar',
|
95
85
|
'Sat, 30 Feb 2001 04:05:06 FOOBAR', # No such date format exists
|
96
86
|
].each do |value|
|
97
|
-
|
98
87
|
it "raises Attributor::AttributorException for #{value.inspect}" do
|
99
|
-
expect
|
88
|
+
expect do
|
100
89
|
type.load(value)
|
101
|
-
|
90
|
+
end.to raise_error(Attributor::DeserializationError, /Error deserializing a String using DateTime/)
|
102
91
|
end
|
103
|
-
|
104
92
|
end
|
105
|
-
|
106
93
|
end
|
107
|
-
|
108
94
|
end
|
109
|
-
|
110
95
|
end
|
111
|
-
|
112
|
-
|
data/spec/types/float_spec.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
2
2
|
|
3
3
|
describe Attributor::Float do
|
4
|
-
|
5
4
|
subject(:type) { Attributor::Float }
|
6
5
|
|
7
6
|
it 'it is not Dumpable' do
|
8
|
-
type.new.is_a?(Attributor::Dumpable).
|
7
|
+
expect(type.new.is_a?(Attributor::Dumpable)).not_to be(true)
|
9
8
|
end
|
10
9
|
|
11
10
|
context '.native_type' do
|
@@ -19,7 +18,7 @@ describe Attributor::Float do
|
|
19
18
|
let(:min) { 1 }
|
20
19
|
let(:max) { 2 }
|
21
20
|
|
22
|
-
subject(:examples) { (0..100).collect { type.example(options:{min:min, max:max})}}
|
21
|
+
subject(:examples) { (0..100).collect { type.example(options: { min: min, max: max }) } }
|
23
22
|
|
24
23
|
its(:min) { should be > min }
|
25
24
|
its(:max) { should be < max }
|
@@ -30,34 +29,31 @@ describe Attributor::Float do
|
|
30
29
|
let(:value) { nil }
|
31
30
|
|
32
31
|
it 'returns nil for nil' do
|
33
|
-
type.load(nil).
|
32
|
+
expect(type.load(nil)).to be(nil)
|
34
33
|
end
|
35
34
|
|
36
35
|
context 'for incoming Float values' do
|
37
|
-
|
38
36
|
it 'returns the incoming value' do
|
39
37
|
[0.0, -1.0, 1.0, 1e-10].each do |value|
|
40
|
-
type.load(value).
|
38
|
+
expect(type.load(value)).to be(value)
|
41
39
|
end
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
45
43
|
context 'for incoming Integer values' do
|
46
|
-
|
47
44
|
context 'with an integer value' do
|
48
45
|
let(:value) { 1 }
|
49
46
|
it 'decodes it if the Integer represents a Float' do
|
50
|
-
type.load(value).
|
47
|
+
expect(type.load(value)).to eq 1.0
|
51
48
|
end
|
52
49
|
end
|
53
50
|
end
|
54
51
|
|
55
52
|
context 'for incoming String values' do
|
56
|
-
|
57
53
|
context 'that are valid Floats' do
|
58
54
|
['0.0', '-1.0', '1.0', '1e-10'].each do |value|
|
59
55
|
it 'decodes it if the String represents a Float' do
|
60
|
-
type.load(value).
|
56
|
+
expect(type.load(value)).to eq Float(value)
|
61
57
|
end
|
62
58
|
end
|
63
59
|
end
|
@@ -65,12 +61,11 @@ describe Attributor::Float do
|
|
65
61
|
context 'that are valid Integers' do
|
66
62
|
let(:value) { '1' }
|
67
63
|
it 'decodes it if the String represents an Integer' do
|
68
|
-
type.load(value).
|
64
|
+
expect(type.load(value)).to eq 1.0
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
72
68
|
context 'that are not valid Floats' do
|
73
|
-
|
74
69
|
context 'with simple alphanumeric text' do
|
75
70
|
let(:value) { 'not a Float' }
|
76
71
|
|
@@ -78,9 +73,7 @@ describe Attributor::Float do
|
|
78
73
|
expect { type.load(value) }.to raise_error(/invalid value/)
|
79
74
|
end
|
80
75
|
end
|
81
|
-
|
82
76
|
end
|
83
|
-
|
84
77
|
end
|
85
78
|
end
|
86
79
|
end
|