eaternet 0.2.2 → 0.3.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/.gitignore +0 -1
- data/.inch.yml +5 -0
- data/Guardfile +36 -0
- data/README.md +23 -23
- data/Rakefile +1 -1
- data/eaternet.gemspec +13 -11
- data/lib/eaternet.rb +8 -4
- data/lib/eaternet/agencies/nyc.rb +44 -20
- data/lib/eaternet/agencies/snhd.rb +24 -21
- data/lib/eaternet/lives_1_0.rb +240 -0
- data/lib/eaternet/loggable.rb +16 -0
- data/lib/eaternet/prototype.rb +112 -0
- data/lib/eaternet/version.rb +1 -1
- data/test/lives_1_0/business_test.rb +7 -10
- data/test/lives_1_0/feed_info_test.rb +3 -6
- data/test/lives_1_0/inspection_test.rb +10 -13
- data/test/lives_1_0/legend_test.rb +23 -26
- data/test/loggable_test.rb +15 -0
- data/test/nyc_adapter_test.rb +8 -11
- data/test/snhd_adapter_test.rb +39 -7
- data/test/test_helper.rb +3 -0
- metadata +38 -4
- data/lib/eaternet/framework/lives_1_0.rb +0 -232
- data/lib/eaternet/framework/prototype.rb +0 -109
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'eaternet/loggable'
|
4
|
+
|
5
|
+
class LoggableTest < Minitest::Test
|
6
|
+
# Test with include because this is how the module
|
7
|
+
# is intended to be used.
|
8
|
+
include Eaternet::Loggable
|
9
|
+
|
10
|
+
def test_creates_and_sends_log_messages_to_stderr
|
11
|
+
assert_output(nil, /Border Collie/) do
|
12
|
+
logger.info('Border Collie')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/test/nyc_adapter_test.rb
CHANGED
@@ -1,22 +1,20 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
require 'eaternet'
|
4
|
-
require 'eaternet/
|
4
|
+
require 'eaternet/lives_1_0'
|
5
5
|
|
6
6
|
CASSETTE = 'nyc'
|
7
7
|
MORRIS_PARK_CSV = 'test/fixtures/morris-park-bake-shop.csv'
|
8
8
|
|
9
9
|
class NycAdapterTest < Minitest::Test
|
10
|
-
|
11
|
-
|
12
|
-
@@nyc = Nyc.new(csv_path: MORRIS_PARK_CSV)
|
10
|
+
@@nyc = Eaternet::Nyc.new(csv_path: MORRIS_PARK_CSV)
|
13
11
|
|
14
12
|
#
|
15
13
|
# Businesses
|
16
14
|
#
|
17
15
|
|
18
16
|
def test_businesses_returns_an_enumerator_of_business
|
19
|
-
assert enumerable_of? Business, @@nyc.businesses
|
17
|
+
assert enumerable_of? Eaternet::Lives_1_0::Business, @@nyc.businesses
|
20
18
|
end
|
21
19
|
|
22
20
|
def test_businesses_returns_lives_attributes
|
@@ -37,7 +35,7 @@ class NycAdapterTest < Minitest::Test
|
|
37
35
|
#
|
38
36
|
|
39
37
|
def test_inspections_returns_an_enumerable_of_inspection
|
40
|
-
assert enumerable_of? Inspection, @@nyc.inspections
|
38
|
+
assert enumerable_of? Eaternet::Lives_1_0::Inspection, @@nyc.inspections
|
41
39
|
end
|
42
40
|
|
43
41
|
# Use the wiki analysis document as a test.
|
@@ -49,7 +47,7 @@ class NycAdapterTest < Minitest::Test
|
|
49
47
|
09/11/2013
|
50
48
|
08/14/2013
|
51
49
|
01/24/2013
|
52
|
-
12/31/2012).map{ |d| Date.strptime(d, '%m/%d/%Y')}
|
50
|
+
12/31/2012).map { |d| Date.strptime(d, '%m/%d/%Y') }
|
53
51
|
actual_dates = @@nyc.inspections.map(&:date).to_a
|
54
52
|
assert_equal expected_dates, actual_dates
|
55
53
|
end
|
@@ -67,7 +65,7 @@ class NycAdapterTest < Minitest::Test
|
|
67
65
|
#
|
68
66
|
|
69
67
|
def test_violations_returns_an_enumerable_of_violation
|
70
|
-
assert enumerable_of? Violation, @@nyc.violations
|
68
|
+
assert enumerable_of? Eaternet::Lives_1_0::Violation, @@nyc.violations
|
71
69
|
end
|
72
70
|
|
73
71
|
def test_violations_returns_lives_attributes
|
@@ -88,11 +86,10 @@ class NycAdapterTest < Minitest::Test
|
|
88
86
|
#
|
89
87
|
|
90
88
|
def test_implements_feed_info
|
91
|
-
assert_instance_of FeedInfo, @@nyc.feed_info
|
89
|
+
assert_instance_of Eaternet::Lives_1_0::FeedInfo, @@nyc.feed_info
|
92
90
|
end
|
93
91
|
|
94
92
|
def test_has_legends
|
95
|
-
assert_instance_of LegendGroup, @@nyc.legends
|
93
|
+
assert_instance_of Eaternet::Lives_1_0::LegendGroup, @@nyc.legends
|
96
94
|
end
|
97
|
-
|
98
95
|
end
|
data/test/snhd_adapter_test.rb
CHANGED
@@ -6,7 +6,19 @@ require 'eaternet'
|
|
6
6
|
SNHD_CASSETTE = 'snhd'
|
7
7
|
|
8
8
|
class SnhdAdapterTest < Minitest::Test
|
9
|
-
@@snhd = Snhd.new
|
9
|
+
@@snhd = Eaternet::Snhd.new
|
10
|
+
|
11
|
+
#
|
12
|
+
# Namespace and access
|
13
|
+
#
|
14
|
+
|
15
|
+
def test_global_namespace_not_polluted
|
16
|
+
assert_raises(NameError) { Snhd }
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# #businesses
|
21
|
+
#
|
10
22
|
|
11
23
|
def test_businesses_returns_an_enumerator
|
12
24
|
VCR.use_cassette(SNHD_CASSETTE) do
|
@@ -16,7 +28,7 @@ class SnhdAdapterTest < Minitest::Test
|
|
16
28
|
|
17
29
|
def test_businesses_returns_business_data_objects
|
18
30
|
VCR.use_cassette(SNHD_CASSETTE) do
|
19
|
-
assert_kind_of Eaternet::
|
31
|
+
assert_kind_of Eaternet::Prototype::BusinessData, @@snhd.businesses.first
|
20
32
|
end
|
21
33
|
end
|
22
34
|
|
@@ -31,6 +43,10 @@ class SnhdAdapterTest < Minitest::Test
|
|
31
43
|
end
|
32
44
|
end
|
33
45
|
|
46
|
+
#
|
47
|
+
# #inspections
|
48
|
+
#
|
49
|
+
|
34
50
|
def test_inspections_returns_an_enumerable
|
35
51
|
VCR.use_cassette(SNHD_CASSETTE) do
|
36
52
|
assert_kind_of Enumerable, @@snhd.inspections
|
@@ -39,7 +55,10 @@ class SnhdAdapterTest < Minitest::Test
|
|
39
55
|
|
40
56
|
def test_inspections_returns_inspection_data_objects
|
41
57
|
VCR.use_cassette(SNHD_CASSETTE) do
|
42
|
-
assert_kind_of
|
58
|
+
assert_kind_of(
|
59
|
+
Eaternet::Prototype::InspectionData,
|
60
|
+
@@snhd.inspections.first
|
61
|
+
)
|
43
62
|
end
|
44
63
|
end
|
45
64
|
|
@@ -53,6 +72,10 @@ class SnhdAdapterTest < Minitest::Test
|
|
53
72
|
end
|
54
73
|
end
|
55
74
|
|
75
|
+
#
|
76
|
+
# #violations
|
77
|
+
#
|
78
|
+
|
56
79
|
def test_violations_returns_an_enumerable
|
57
80
|
VCR.use_cassette(SNHD_CASSETTE) do
|
58
81
|
assert_kind_of Enumerable, @@snhd.violations
|
@@ -61,7 +84,7 @@ class SnhdAdapterTest < Minitest::Test
|
|
61
84
|
|
62
85
|
def test_violations_returns_violation_data_objects
|
63
86
|
VCR.use_cassette(SNHD_CASSETTE) do
|
64
|
-
assert_kind_of Eaternet::
|
87
|
+
assert_kind_of Eaternet::Prototype::ViolationData, @@snhd.violations.first
|
65
88
|
end
|
66
89
|
end
|
67
90
|
|
@@ -74,6 +97,10 @@ class SnhdAdapterTest < Minitest::Test
|
|
74
97
|
end
|
75
98
|
end
|
76
99
|
|
100
|
+
#
|
101
|
+
# #violation_kinds
|
102
|
+
#
|
103
|
+
|
77
104
|
def test_violation_kinds_returns_an_enumerable
|
78
105
|
VCR.use_cassette(SNHD_CASSETTE) do
|
79
106
|
assert_kind_of Enumerable, @@snhd.violation_kinds
|
@@ -82,7 +109,10 @@ class SnhdAdapterTest < Minitest::Test
|
|
82
109
|
|
83
110
|
def test_violation_kinds_returns_violation_kind_data_objects
|
84
111
|
VCR.use_cassette(SNHD_CASSETTE) do
|
85
|
-
assert_kind_of
|
112
|
+
assert_kind_of(
|
113
|
+
Eaternet::Prototype::ViolationKindData,
|
114
|
+
@@snhd.violation_kinds.first
|
115
|
+
)
|
86
116
|
end
|
87
117
|
end
|
88
118
|
|
@@ -92,8 +122,10 @@ class SnhdAdapterTest < Minitest::Test
|
|
92
122
|
assert_equal '1', v.orig_key
|
93
123
|
assert_equal '1', v.code
|
94
124
|
assert_equal '6', v.demerits
|
95
|
-
assert_equal
|
96
|
-
|
125
|
+
assert_equal(
|
126
|
+
'Food not obtained from approved sources and/or improperly identified.',
|
127
|
+
v.description
|
128
|
+
)
|
97
129
|
end
|
98
130
|
end
|
99
131
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eaternet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robb Shecter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: guard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: guard-minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: minitest
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,8 +173,10 @@ extensions: []
|
|
145
173
|
extra_rdoc_files: []
|
146
174
|
files:
|
147
175
|
- ".gitignore"
|
176
|
+
- ".inch.yml"
|
148
177
|
- ".travis.yml"
|
149
178
|
- Gemfile
|
179
|
+
- Guardfile
|
150
180
|
- LICENSE.txt
|
151
181
|
- README.md
|
152
182
|
- Rakefile
|
@@ -155,8 +185,9 @@ files:
|
|
155
185
|
- lib/eaternet/agencies/nyc.rb
|
156
186
|
- lib/eaternet/agencies/snhd.rb
|
157
187
|
- lib/eaternet/agencies/snhd_config.rb
|
158
|
-
- lib/eaternet/
|
159
|
-
- lib/eaternet/
|
188
|
+
- lib/eaternet/lives_1_0.rb
|
189
|
+
- lib/eaternet/loggable.rb
|
190
|
+
- lib/eaternet/prototype.rb
|
160
191
|
- lib/eaternet/util.rb
|
161
192
|
- lib/eaternet/version.rb
|
162
193
|
- lib/ext/lazy.rb
|
@@ -166,6 +197,7 @@ files:
|
|
166
197
|
- test/lives_1_0/feed_info_test.rb
|
167
198
|
- test/lives_1_0/inspection_test.rb
|
168
199
|
- test/lives_1_0/legend_test.rb
|
200
|
+
- test/loggable_test.rb
|
169
201
|
- test/nyc_adapter_test.rb
|
170
202
|
- test/snhd_adapter_test.rb
|
171
203
|
- test/test_helper.rb
|
@@ -200,6 +232,8 @@ test_files:
|
|
200
232
|
- test/lives_1_0/feed_info_test.rb
|
201
233
|
- test/lives_1_0/inspection_test.rb
|
202
234
|
- test/lives_1_0/legend_test.rb
|
235
|
+
- test/loggable_test.rb
|
203
236
|
- test/nyc_adapter_test.rb
|
204
237
|
- test/snhd_adapter_test.rb
|
205
238
|
- test/test_helper.rb
|
239
|
+
has_rdoc:
|
@@ -1,232 +0,0 @@
|
|
1
|
-
module Eaternet
|
2
|
-
module Framework
|
3
|
-
|
4
|
-
# Framework for creating LIVES 1.0 apps.
|
5
|
-
#
|
6
|
-
# The goal is to make it as easy as possible to create
|
7
|
-
# compliant implementations. And so, the Business, Inspection
|
8
|
-
# and Violation data objects use validations to ensure that they
|
9
|
-
# correctly produce the entities listed in the spec.
|
10
|
-
#
|
11
|
-
# @see http://www.yelp.com/healthscores
|
12
|
-
# @see http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/
|
13
|
-
# @see http://www.rubyinside.com/rails-3-0s-activemodel-how-to-give-ruby-classes-some-activerecord-magic-2937.html
|
14
|
-
module Lives_1_0
|
15
|
-
require 'active_model'
|
16
|
-
|
17
|
-
|
18
|
-
class Adapter
|
19
|
-
# Required.
|
20
|
-
# @return [Enumerable<Business>]
|
21
|
-
def businesses
|
22
|
-
fail 'Override this to return an Enumerable of Business'
|
23
|
-
end
|
24
|
-
|
25
|
-
# Required.
|
26
|
-
# @return [Enumerable<Inspection>]
|
27
|
-
def inspections
|
28
|
-
fail 'Override this to return an Enumerable of Inspection'
|
29
|
-
end
|
30
|
-
|
31
|
-
# Optional.
|
32
|
-
# @return [Enumerable<Violation>]
|
33
|
-
def violations
|
34
|
-
fail 'Optionally override this to return an Enumerable of Violation'
|
35
|
-
end
|
36
|
-
|
37
|
-
# Optional.
|
38
|
-
# @return [FeedInfo]
|
39
|
-
def feed_info
|
40
|
-
fail 'Optionally override this to return a FeedInfo'
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
class DataTransferObject
|
46
|
-
include ActiveModel::Validations
|
47
|
-
|
48
|
-
def initialize(&block)
|
49
|
-
block.call(self)
|
50
|
-
check_validations!
|
51
|
-
end
|
52
|
-
|
53
|
-
def check_validations!
|
54
|
-
fail ArgumentError, errors.messages.inspect if invalid?
|
55
|
-
end
|
56
|
-
|
57
|
-
class TypeValidator < ActiveModel::EachValidator
|
58
|
-
def validate_each(record, attribute, value)
|
59
|
-
record.errors.add attribute, (options[:message] || "is not of class #{options[:with]}") unless
|
60
|
-
value.class == options[:with]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
|
66
|
-
# @see http://www.yelp.com/healthscores#businesses
|
67
|
-
class Business < DataTransferObject
|
68
|
-
attr_accessor :business_id, :name, :address, :city, :state,
|
69
|
-
:postal_code, :latitude, :longitude, :phone_number
|
70
|
-
|
71
|
-
validates :business_id, :name, :address,
|
72
|
-
type: String,
|
73
|
-
presence: true
|
74
|
-
validates :city, :state, :postal_code, :phone_number,
|
75
|
-
type: String,
|
76
|
-
allow_nil: true
|
77
|
-
validates :latitude,
|
78
|
-
numericality:
|
79
|
-
{
|
80
|
-
greater_than_or_equal_to: -90,
|
81
|
-
less_than_or_equal_to: 90
|
82
|
-
},
|
83
|
-
allow_nil: true
|
84
|
-
validates :longitude,
|
85
|
-
numericality:
|
86
|
-
{
|
87
|
-
greater_than_or_equal_to: -180,
|
88
|
-
less_than_or_equal_to: 180
|
89
|
-
},
|
90
|
-
allow_nil: true
|
91
|
-
|
92
|
-
def ==(other)
|
93
|
-
@business_id == other.business_id
|
94
|
-
end
|
95
|
-
|
96
|
-
def eql?(other)
|
97
|
-
self == other
|
98
|
-
end
|
99
|
-
|
100
|
-
def hash
|
101
|
-
@business_id.hash
|
102
|
-
end
|
103
|
-
|
104
|
-
# @return [String]
|
105
|
-
def to_s
|
106
|
-
"Business #{@business_id}"
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
|
-
# @see http://www.yelp.com/healthscores#inspections
|
112
|
-
class Inspection < DataTransferObject
|
113
|
-
attr_accessor :business_id, :score, :date, :description, :type
|
114
|
-
|
115
|
-
ZERO_TO_ONE_HUNDRED_AND_BLANK = (0..100).to_a + ['']
|
116
|
-
|
117
|
-
validates :business_id,
|
118
|
-
type: String,
|
119
|
-
presence: true
|
120
|
-
validates :score,
|
121
|
-
inclusion: { in: ZERO_TO_ONE_HUNDRED_AND_BLANK },
|
122
|
-
allow_nil: true
|
123
|
-
validates :date,
|
124
|
-
type: Date,
|
125
|
-
presence: true
|
126
|
-
validates :type,
|
127
|
-
inclusion: { in: %w(initial routine followup complaint) },
|
128
|
-
allow_nil: true
|
129
|
-
|
130
|
-
def score
|
131
|
-
@score.nil? ? '' : @score
|
132
|
-
end
|
133
|
-
|
134
|
-
def to_s
|
135
|
-
"Inspection #{@business_id}/#{@date}/#{@score}"
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
|
140
|
-
# @see http://www.yelp.com/healthscores#violations
|
141
|
-
class Violation < DataTransferObject
|
142
|
-
attr_accessor :business_id, :date, :code, :description
|
143
|
-
|
144
|
-
validates :business_id,
|
145
|
-
type: String,
|
146
|
-
presence: true
|
147
|
-
validates :date,
|
148
|
-
type: Date,
|
149
|
-
presence: true
|
150
|
-
validates :code, :description,
|
151
|
-
type: String,
|
152
|
-
allow_nil: true
|
153
|
-
end
|
154
|
-
|
155
|
-
|
156
|
-
# @see http://www.yelp.com/healthscores#feed_info
|
157
|
-
class FeedInfo < DataTransferObject
|
158
|
-
attr_accessor :feed_date, :feed_version, :municipality_name,
|
159
|
-
:municipality_url, :contact_email
|
160
|
-
|
161
|
-
HAS_AN_AT_SOMEWHERE_IN_THE_MIDDLE = /\A[^@]+@[^@]+\z/
|
162
|
-
|
163
|
-
validates :feed_date,
|
164
|
-
type: Date,
|
165
|
-
presence: true
|
166
|
-
validates :feed_version,
|
167
|
-
type: String,
|
168
|
-
presence: true
|
169
|
-
validates :municipality_name,
|
170
|
-
type: String,
|
171
|
-
presence: true
|
172
|
-
validates :municipality_url,
|
173
|
-
type: String,
|
174
|
-
format: { with: %r(\Ahttps?:/) },
|
175
|
-
allow_nil: true
|
176
|
-
validates :contact_email,
|
177
|
-
type: String,
|
178
|
-
format: { with: HAS_AN_AT_SOMEWHERE_IN_THE_MIDDLE },
|
179
|
-
allow_nil: true
|
180
|
-
end
|
181
|
-
|
182
|
-
|
183
|
-
# @see http://www.yelp.com/healthscores#legend
|
184
|
-
class Legend < DataTransferObject
|
185
|
-
attr_accessor :minimum_score, :maximum_score, :description
|
186
|
-
|
187
|
-
validates :minimum_score, :maximum_score,
|
188
|
-
inclusion: { in: (0..100) }
|
189
|
-
validates :description,
|
190
|
-
presence: true,
|
191
|
-
type: String
|
192
|
-
end
|
193
|
-
|
194
|
-
|
195
|
-
# A container for all the Legends in the data set. Performs
|
196
|
-
# validation on the whole set of Legends ensure they cover
|
197
|
-
# the entire range of scores and do not overlap.
|
198
|
-
class LegendGroup < DataTransferObject
|
199
|
-
attr_accessor :legends
|
200
|
-
|
201
|
-
# Check that all the items in this LegendGroup:
|
202
|
-
#
|
203
|
-
# 1. Are of the class, Legend
|
204
|
-
# 2. Cover the range of scores from 0-100 without overlap
|
205
|
-
class ComprehensiveValidator < ActiveModel::EachValidator
|
206
|
-
def validate_each(record, attribute, legends)
|
207
|
-
scores = (0..100).to_a
|
208
|
-
legends.each do |legend|
|
209
|
-
unless legend.class == Legend
|
210
|
-
record.errors.add attribute, 'must be a Legend'
|
211
|
-
return
|
212
|
-
end
|
213
|
-
range = (legend.minimum_score..legend.maximum_score)
|
214
|
-
range.each do |score|
|
215
|
-
if scores.delete(score).nil?
|
216
|
-
unless score == legend.minimum_score || score == legend.maximum_score
|
217
|
-
record.errors.add attribute, 'may not overlap'
|
218
|
-
return
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
record.errors.add attribute, 'do not cover entire span from 0–100' unless scores.empty?
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
validates :legends, comprehensive: true
|
228
|
-
end
|
229
|
-
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|