transproc 0.4.2 → 1.0.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/CHANGELOG.md +16 -0
- data/README.md +132 -2
- data/lib/transproc.rb +0 -71
- data/lib/transproc/array.rb +36 -74
- data/lib/transproc/class.rb +0 -4
- data/lib/transproc/coercions.rb +0 -4
- data/lib/transproc/composer.rb +0 -26
- data/lib/transproc/conditional.rb +0 -4
- data/lib/transproc/hash.rb +43 -159
- data/lib/transproc/proc.rb +0 -4
- data/lib/transproc/recursion.rb +0 -4
- data/lib/transproc/registry.rb +35 -1
- data/lib/transproc/store.rb +22 -1
- data/lib/transproc/transformer/class_interface.rb +63 -19
- data/lib/transproc/version.rb +1 -1
- data/spec/spec_helper.rb +0 -4
- data/spec/unit/array_transformations_spec.rb +18 -90
- data/spec/unit/function_not_found_error_spec.rb +1 -9
- data/spec/unit/function_spec.rb +17 -12
- data/spec/unit/hash_transformations_spec.rb +78 -240
- data/spec/unit/recursion_spec.rb +8 -24
- data/spec/unit/registry_spec.rb +80 -0
- data/spec/unit/store_spec.rb +35 -0
- data/spec/unit/transformer_spec.rb +205 -9
- data/spec/unit/transproc_spec.rb +2 -45
- metadata +2 -3
- data/lib/transproc/rspec.rb +0 -71
@@ -68,9 +68,5 @@ module Transproc
|
|
68
68
|
def self.is(value, type, fn)
|
69
69
|
guard(value, -> v { v.is_a?(type) }, fn)
|
70
70
|
end
|
71
|
-
|
72
|
-
# @deprecated Register methods globally
|
73
|
-
(methods - Registry.methods - Registry.instance_methods)
|
74
|
-
.each { |name| Transproc.register name, t(name) }
|
75
71
|
end
|
76
72
|
end
|
data/lib/transproc/hash.rb
CHANGED
@@ -28,18 +28,10 @@ module Transproc
|
|
28
28
|
# @return [Hash]
|
29
29
|
#
|
30
30
|
# @api public
|
31
|
-
def self.map_keys(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# Same as `:map_keys` but mutates the hash
|
36
|
-
#
|
37
|
-
# @see HashTransformations.map_keys
|
38
|
-
#
|
39
|
-
# @api public
|
40
|
-
def self.map_keys!(hash, fn)
|
41
|
-
hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
|
42
|
-
hash
|
31
|
+
def self.map_keys(source_hash, fn)
|
32
|
+
Hash[source_hash].tap do |hash|
|
33
|
+
hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
|
34
|
+
end
|
43
35
|
end
|
44
36
|
|
45
37
|
# Symbolize all keys in a hash
|
@@ -54,16 +46,7 @@ module Transproc
|
|
54
46
|
#
|
55
47
|
# @api public
|
56
48
|
def self.symbolize_keys(hash)
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
# Same as `:symbolize_keys` but mutates the hash
|
61
|
-
#
|
62
|
-
# @see HashTransformations.symbolize_keys!
|
63
|
-
#
|
64
|
-
# @api public
|
65
|
-
def self.symbolize_keys!(hash)
|
66
|
-
map_keys!(hash, Coercions[:to_symbol].fn)
|
49
|
+
map_keys(hash, Coercions[:to_symbol].fn)
|
67
50
|
end
|
68
51
|
|
69
52
|
# Symbolize keys in a hash recursively
|
@@ -108,16 +91,7 @@ module Transproc
|
|
108
91
|
#
|
109
92
|
# @api public
|
110
93
|
def self.stringify_keys(hash)
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
# Same as `:stringify_keys` but mutates the hash
|
115
|
-
#
|
116
|
-
# @see HashTransformations.stringify_keys
|
117
|
-
#
|
118
|
-
# @api public
|
119
|
-
def self.stringify_keys!(hash)
|
120
|
-
map_keys!(hash, Coercions[:to_string].fn)
|
94
|
+
map_keys(hash, Coercions[:to_string].fn)
|
121
95
|
end
|
122
96
|
|
123
97
|
# Map all values in a hash using transformation function
|
@@ -131,22 +105,10 @@ module Transproc
|
|
131
105
|
# @return [Hash]
|
132
106
|
#
|
133
107
|
# @api public
|
134
|
-
def self.map_values(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
# Same as `:map_values` but mutates the hash
|
139
|
-
#
|
140
|
-
# @see HashTransformations.map_values
|
141
|
-
#
|
142
|
-
# @param [Hash]
|
143
|
-
#
|
144
|
-
# @return [Hash]
|
145
|
-
#
|
146
|
-
# @api public
|
147
|
-
def self.map_values!(hash, fn)
|
148
|
-
hash.each { |key, value| hash[key] = fn[value] }
|
149
|
-
hash
|
108
|
+
def self.map_values(source_hash, fn)
|
109
|
+
Hash[source_hash].tap do |hash|
|
110
|
+
hash.each { |key, value| hash[key] = fn[value] }
|
111
|
+
end
|
150
112
|
end
|
151
113
|
|
152
114
|
# Rename all keys in a hash using provided mapping hash
|
@@ -155,24 +117,16 @@ module Transproc
|
|
155
117
|
# Transproc(:rename_keys, user_name: :name)[user_name: 'Jane']
|
156
118
|
# # => {:name => "Jane"}
|
157
119
|
#
|
158
|
-
# @param [Hash]
|
120
|
+
# @param [Hash] source_hash The input hash
|
159
121
|
# @param [Hash] mapping The key-rename mapping
|
160
122
|
#
|
161
123
|
# @return [Hash]
|
162
124
|
#
|
163
125
|
# @api public
|
164
|
-
def self.rename_keys(
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
# Same as `:rename_keys` but mutates the hash
|
169
|
-
#
|
170
|
-
# @see HashTransformations.rename_keys
|
171
|
-
#
|
172
|
-
# @api public
|
173
|
-
def self.rename_keys!(hash, mapping)
|
174
|
-
mapping.each { |k, v| hash[v] = hash.delete(k) if hash.has_key?(k) }
|
175
|
-
hash
|
126
|
+
def self.rename_keys(source_hash, mapping)
|
127
|
+
Hash[source_hash].tap do |hash|
|
128
|
+
mapping.each { |k, v| hash[v] = hash.delete(k) if hash.key?(k) }
|
129
|
+
end
|
176
130
|
end
|
177
131
|
|
178
132
|
# Copy all keys in a hash using provided mapping hash
|
@@ -181,28 +135,20 @@ module Transproc
|
|
181
135
|
# Transproc(:copy_keys, user_name: :name)[user_name: 'Jane']
|
182
136
|
# # => {:user_name => "Jane", :name => "Jane"}
|
183
137
|
#
|
184
|
-
# @param [Hash]
|
138
|
+
# @param [Hash] source_hash The input hash
|
185
139
|
# @param [Hash] mapping The key-copy mapping
|
186
140
|
#
|
187
141
|
# @return [Hash]
|
188
142
|
#
|
189
143
|
# @api public
|
190
|
-
def self.copy_keys(
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
# @see HashTransformations.copy_keys
|
197
|
-
#
|
198
|
-
# @api public
|
199
|
-
def self.copy_keys!(hash, mapping)
|
200
|
-
mapping.each do |original_key, new_keys|
|
201
|
-
[*new_keys].each do |new_key|
|
202
|
-
hash[new_key] = hash[original_key]
|
144
|
+
def self.copy_keys(source_hash, mapping)
|
145
|
+
Hash[source_hash].tap do |hash|
|
146
|
+
mapping.each do |original_key, new_keys|
|
147
|
+
[*new_keys].each do |new_key|
|
148
|
+
hash[new_key] = hash[original_key]
|
149
|
+
end
|
203
150
|
end
|
204
151
|
end
|
205
|
-
hash
|
206
152
|
end
|
207
153
|
|
208
154
|
# Rejects specified keys from a hash
|
@@ -218,16 +164,7 @@ module Transproc
|
|
218
164
|
#
|
219
165
|
# @api public
|
220
166
|
def self.reject_keys(hash, keys)
|
221
|
-
|
222
|
-
end
|
223
|
-
|
224
|
-
# Same as `:reject_keys` but mutates the hash
|
225
|
-
#
|
226
|
-
# @see HashTransformations.reject_keys
|
227
|
-
#
|
228
|
-
# @api public
|
229
|
-
def self.reject_keys!(hash, keys)
|
230
|
-
hash.reject { |k, _| keys.include?(k) }
|
167
|
+
Hash[hash].reject { |k, _| keys.include?(k) }
|
231
168
|
end
|
232
169
|
|
233
170
|
# Accepts specified keys from a hash
|
@@ -243,16 +180,7 @@ module Transproc
|
|
243
180
|
#
|
244
181
|
# @api public
|
245
182
|
def self.accept_keys(hash, keys)
|
246
|
-
|
247
|
-
end
|
248
|
-
|
249
|
-
# Same as `:accept_keys` but mutates the hash
|
250
|
-
#
|
251
|
-
# @see HashTransformations.accept
|
252
|
-
#
|
253
|
-
# @api public
|
254
|
-
def self.accept_keys!(hash, keys)
|
255
|
-
reject_keys!(hash, hash.keys - keys)
|
183
|
+
reject_keys(hash, hash.keys - keys)
|
256
184
|
end
|
257
185
|
|
258
186
|
# Map a key in a hash with the provided transformation function
|
@@ -270,15 +198,6 @@ module Transproc
|
|
270
198
|
hash.merge(key => fn[hash[key]])
|
271
199
|
end
|
272
200
|
|
273
|
-
# Same as `:map_value` but mutates the hash
|
274
|
-
#
|
275
|
-
# @see HashTransformations.map_value
|
276
|
-
#
|
277
|
-
# @api public
|
278
|
-
def self.map_value!(hash, key, fn)
|
279
|
-
hash.update(key => fn[hash[key]])
|
280
|
-
end
|
281
|
-
|
282
201
|
# Nest values from specified keys under a new key
|
283
202
|
#
|
284
203
|
# @example
|
@@ -290,25 +209,17 @@ module Transproc
|
|
290
209
|
# @return [Hash]
|
291
210
|
#
|
292
211
|
# @api public
|
293
|
-
def self.nest(
|
294
|
-
|
295
|
-
end
|
296
|
-
|
297
|
-
# Same as `:nest` but mutates the hash
|
298
|
-
#
|
299
|
-
# @see HashTransformations.nest
|
300
|
-
#
|
301
|
-
# @api public
|
302
|
-
def self.nest!(hash, root, keys)
|
303
|
-
nest_keys = hash.keys & keys
|
212
|
+
def self.nest(source_hash, root, keys)
|
213
|
+
nest_keys = source_hash.keys & keys
|
304
214
|
|
305
|
-
if nest_keys.
|
215
|
+
if !nest_keys.empty?
|
216
|
+
hash = Hash[source_hash]
|
306
217
|
child = Hash[nest_keys.zip(nest_keys.map { |key| hash.delete(key) })]
|
307
218
|
old_nest = hash[root]
|
308
219
|
new_nest = old_nest.is_a?(Hash) ? old_nest.merge(child) : child
|
309
|
-
hash.
|
220
|
+
hash.merge(root => new_nest)
|
310
221
|
else
|
311
|
-
|
222
|
+
source_hash.merge(root => {})
|
312
223
|
end
|
313
224
|
end
|
314
225
|
|
@@ -318,9 +229,9 @@ module Transproc
|
|
318
229
|
# Transproc(:unwrap, :address, [:street, :zipcode])[address: { street: 'Street', zipcode: '123' }]
|
319
230
|
# # => {street: "Street", zipcode: "123"}
|
320
231
|
#
|
321
|
-
# @param [Hash]
|
232
|
+
# @param [Hash] source_hash
|
322
233
|
# @param [Mixed] root The root key to unwrap values from
|
323
|
-
# @param [Array]
|
234
|
+
# @param [Array] selected The keys that should be unwrapped (optional)
|
324
235
|
# @param [Hash] options hash of options (optional)
|
325
236
|
# @option options [Boolean] :prefix if true, unwrapped keys will be prefixed
|
326
237
|
# with the root key followed by an underscore (_)
|
@@ -328,37 +239,23 @@ module Transproc
|
|
328
239
|
# @return [Hash]
|
329
240
|
#
|
330
241
|
# @api public
|
331
|
-
def self.unwrap(
|
332
|
-
|
333
|
-
unwrap!(copy, root, keys, prefix: prefix)
|
334
|
-
end
|
242
|
+
def self.unwrap(source_hash, root, selected = nil, prefix: false)
|
243
|
+
return source_hash unless source_hash[root]
|
335
244
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
245
|
+
add_prefix = ->(key) do
|
246
|
+
combined = [root, key].join('_')
|
247
|
+
root.is_a?(::Symbol) ? combined.to_sym : combined
|
248
|
+
end
|
249
|
+
|
250
|
+
Hash[source_hash].merge(root => Hash[source_hash[root]]).tap do |hash|
|
251
|
+
nested_hash = hash[root]
|
343
252
|
keys = nested_hash.keys
|
344
253
|
keys &= selected if selected
|
345
|
-
new_keys =
|
346
|
-
keys.map do |key|
|
347
|
-
if root.is_a?(::Symbol)
|
348
|
-
[root, key].join('_').to_sym
|
349
|
-
else
|
350
|
-
[root, key].join('_')
|
351
|
-
end
|
352
|
-
end
|
353
|
-
else
|
354
|
-
keys
|
355
|
-
end
|
254
|
+
new_keys = prefix ? keys.map(&add_prefix) : keys
|
356
255
|
|
357
256
|
hash.update(Hash[new_keys.zip(keys.map { |key| nested_hash.delete(key) })])
|
358
257
|
hash.delete(root) if nested_hash.empty?
|
359
258
|
end
|
360
|
-
|
361
|
-
hash
|
362
259
|
end
|
363
260
|
|
364
261
|
# Folds array of tuples to array of values from a specified key
|
@@ -381,16 +278,7 @@ module Transproc
|
|
381
278
|
#
|
382
279
|
# @api public
|
383
280
|
def self.fold(hash, key, tuple_key)
|
384
|
-
|
385
|
-
end
|
386
|
-
|
387
|
-
# Same as `:fold` but mutates the hash
|
388
|
-
#
|
389
|
-
# @see HashTransformations.fold
|
390
|
-
#
|
391
|
-
# @api public
|
392
|
-
def self.fold!(hash, key, tuple_key)
|
393
|
-
hash.update(key => ArrayTransformations.extract_key(hash[key], tuple_key))
|
281
|
+
hash.merge(key => ArrayTransformations.extract_key(hash[key], tuple_key))
|
394
282
|
end
|
395
283
|
|
396
284
|
# Splits hash to array by all values from a specified key
|
@@ -502,9 +390,5 @@ module Transproc
|
|
502
390
|
end
|
503
391
|
end
|
504
392
|
end
|
505
|
-
|
506
|
-
# @deprecated Register methods globally
|
507
|
-
(methods - Registry.instance_methods - Registry.methods)
|
508
|
-
.each { |name| Transproc.register name, t(name) }
|
509
393
|
end
|
510
394
|
end
|
data/lib/transproc/proc.rb
CHANGED
@@ -38,9 +38,5 @@ module Transproc
|
|
38
38
|
def self.bind(value, binding, fn)
|
39
39
|
binding.instance_exec(value, &fn)
|
40
40
|
end
|
41
|
-
|
42
|
-
# @deprecated Register methods globally
|
43
|
-
(methods - Registry.instance_methods - Registry.methods)
|
44
|
-
.each { |name| Transproc.register name, t(name) }
|
45
41
|
end
|
46
42
|
end
|
data/lib/transproc/recursion.rb
CHANGED
data/lib/transproc/registry.rb
CHANGED
@@ -45,10 +45,37 @@ module Transproc
|
|
45
45
|
# @alias :t
|
46
46
|
#
|
47
47
|
def [](fn, *args)
|
48
|
-
|
48
|
+
fetched = fetch(fn)
|
49
|
+
return fetched if already_wrapped?(fetched)
|
50
|
+
Function.new(fetched, args: args, name: fn)
|
49
51
|
end
|
50
52
|
alias_method :t, :[]
|
51
53
|
|
54
|
+
# Returns wether the registry contains such transformation by its key
|
55
|
+
#
|
56
|
+
# @param [Symbol] key
|
57
|
+
#
|
58
|
+
# @return [Boolean]
|
59
|
+
#
|
60
|
+
def contain?(key)
|
61
|
+
respond_to?(key) || store.contain?(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Register a new function
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# store.register(:to_json, -> v { v.to_json })
|
68
|
+
|
69
|
+
# store.register(:to_json) { |v| v.to_json }
|
70
|
+
#
|
71
|
+
def register(name, fn = nil, &block)
|
72
|
+
if contain?(name)
|
73
|
+
raise FunctionAlreadyRegisteredError, "Function #{name} is already defined"
|
74
|
+
end
|
75
|
+
@store = store.register(name, fn, &block)
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
52
79
|
# Imports either a method (converted to a proc) from another module, or
|
53
80
|
# all methods from that module.
|
54
81
|
#
|
@@ -106,5 +133,12 @@ module Transproc
|
|
106
133
|
rescue
|
107
134
|
raise FunctionNotFoundError.new(fn, self)
|
108
135
|
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# @api private
|
140
|
+
def already_wrapped?(func)
|
141
|
+
func.is_a?(Transproc::Function) || func.is_a?(Transproc::Composite)
|
142
|
+
end
|
109
143
|
end
|
110
144
|
end
|
data/lib/transproc/store.rb
CHANGED
@@ -33,7 +33,28 @@ module Transproc
|
|
33
33
|
# @return [Proc]
|
34
34
|
#
|
35
35
|
def fetch(key)
|
36
|
-
methods.fetch(key
|
36
|
+
methods.fetch(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns wether the collection contains such procedure by its key
|
40
|
+
#
|
41
|
+
# @param [Symbol] key
|
42
|
+
#
|
43
|
+
# @return [Boolean]
|
44
|
+
#
|
45
|
+
def contain?(key)
|
46
|
+
methods.key?(key)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Register a new function
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# store.register(:to_json, -> v { v.to_json })
|
53
|
+
|
54
|
+
# store.register(:to_json) { |v| v.to_json }
|
55
|
+
#
|
56
|
+
def register(name, fn = nil, &block)
|
57
|
+
self.class.new(methods.merge(name => fn || block))
|
37
58
|
end
|
38
59
|
|
39
60
|
# Imports proc(s) to the collection from another module
|
@@ -24,7 +24,7 @@ module Transproc
|
|
24
24
|
|
25
25
|
# @api private
|
26
26
|
def inherited(subclass)
|
27
|
-
subclass.container(container)
|
27
|
+
subclass.container(@container) if defined?(@container)
|
28
28
|
end
|
29
29
|
|
30
30
|
# Get or set the container to resolve transprocs from.
|
@@ -47,24 +47,70 @@ module Transproc
|
|
47
47
|
# @api private
|
48
48
|
def container(container = ::Transproc::Undefined)
|
49
49
|
if container == ::Transproc::Undefined
|
50
|
+
ensure_container_presence!
|
50
51
|
@container
|
51
52
|
else
|
52
53
|
@container = container
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
57
|
+
# Define an anonymous transproc derived from given Transformer
|
58
|
+
# Evaluates block with transformations and returns initialized transproc.
|
59
|
+
# Does not mutate original Transformer
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
#
|
63
|
+
# class MyTransformer < Transproc::Transformer[MyContainer]
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# transproc = MyTransformer.define do
|
67
|
+
# map_values t(:to_string)
|
68
|
+
# end
|
69
|
+
# transproc.call(a: 1, b: 2)
|
70
|
+
# # => {a: '1', b: '2'}
|
71
|
+
#
|
72
|
+
# @yield Block allowing to define transformations. The same as class level DSL
|
73
|
+
#
|
74
|
+
# @return [Function] Composed transproc
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def define(&block)
|
78
|
+
return transproc unless block_given?
|
79
|
+
|
80
|
+
Class.new(self).tap { |klass| klass.instance_eval(&block) }.transproc
|
81
|
+
end
|
82
|
+
alias build define
|
83
|
+
|
84
|
+
# Get a transformation from the container,
|
85
|
+
# without adding it to the transformation pipeline
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
#
|
89
|
+
# class Stringify < Transproc::Transformer
|
90
|
+
# map_values t(:to_string)
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Stringify.new.call(a: 1, b: 2)
|
94
|
+
# # => {a: '1', b: '2'}
|
95
|
+
#
|
96
|
+
# @param [Proc, Symbol] fn
|
97
|
+
# A proc, a name of the module's own function, or a name of imported
|
98
|
+
# procedure from another module
|
99
|
+
# @param [Object, Array] args
|
100
|
+
# Args to be carried by the transproc
|
101
|
+
#
|
102
|
+
# @return [Transproc::Function]
|
103
|
+
#
|
104
|
+
# @api public
|
105
|
+
def t(fn, *args)
|
106
|
+
container[fn, *args]
|
107
|
+
end
|
108
|
+
|
56
109
|
# @api private
|
57
110
|
def method_missing(method, *args, &block)
|
58
|
-
if container.
|
59
|
-
if block_given?
|
60
|
-
|
61
|
-
method,
|
62
|
-
*args,
|
63
|
-
create(container, &block).transproc
|
64
|
-
]
|
65
|
-
else
|
66
|
-
transformations << container[method, *args]
|
67
|
-
end
|
111
|
+
if container.contain?(method)
|
112
|
+
args.push(define(&block)) if block_given?
|
113
|
+
transformations << t(method, *args)
|
68
114
|
else
|
69
115
|
super
|
70
116
|
end
|
@@ -72,7 +118,7 @@ module Transproc
|
|
72
118
|
|
73
119
|
# @api private
|
74
120
|
def respond_to_missing?(method, _include_private = false)
|
75
|
-
container.
|
121
|
+
container.contain?(method) || super
|
76
122
|
end
|
77
123
|
|
78
124
|
# @api private
|
@@ -81,6 +127,7 @@ module Transproc
|
|
81
127
|
end
|
82
128
|
|
83
129
|
private
|
130
|
+
|
84
131
|
# An array containing the transformation pipeline
|
85
132
|
#
|
86
133
|
# @api private
|
@@ -88,14 +135,11 @@ module Transproc
|
|
88
135
|
@transformations ||= []
|
89
136
|
end
|
90
137
|
|
91
|
-
# Create and return a new instance of Transproc::Transformer
|
92
|
-
# evaluating the block argument as the class body
|
93
|
-
#
|
94
138
|
# @api private
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
139
|
+
def ensure_container_presence!
|
140
|
+
return if defined?(@container)
|
141
|
+
raise ArgumentError, 'Transformer function registry is empty. '\
|
142
|
+
'Provide your registry via Transproc::Transformer[YourRegistry]'
|
99
143
|
end
|
100
144
|
end
|
101
145
|
end
|