boba 0.0.10 → 0.0.11
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a299592dce0ee68ddd4ad96f0c6d35a665739102bfa02c8e5d3f3cf26ed0531
|
4
|
+
data.tar.gz: 71d4ebe6c98f11586fef81e8d7b8892adce83bb2fbeafa516369947cbcd0dd9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b34ab72d0801c768da549880ad247e978538e29197c51ff09b8d0e3c6d18b23e4433a808948613b98f3621ed0f486cd5fc7f44dad2f0e4587aec1070bf14cb0a
|
7
|
+
data.tar.gz: 1fca80c8c8a75f2763e4682b4b3e5543af3700abe8b2bd3c0fcb097c749d9292b67375c4121fc0ec43c49502678e0c8ee5fd3da59f02ce930a39ec5a1971f302
|
data/lib/boba/version.rb
CHANGED
@@ -49,7 +49,9 @@ module Tapioca
|
|
49
49
|
|
50
50
|
sig { override.void }
|
51
51
|
def decorate
|
52
|
-
|
52
|
+
# this is a bit awkward, but load order determines the return order here, so sort to ensure consistency across
|
53
|
+
# all environments.
|
54
|
+
attachments = ::Paperclip::AttachmentRegistry.names_for(constant).sort
|
53
55
|
return if attachments.empty?
|
54
56
|
|
55
57
|
root.create_path(constant) do |klass|
|
@@ -0,0 +1,385 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
return unless defined?(StateMachines)
|
5
|
+
|
6
|
+
require "tapioca/dsl/helpers/active_record_constants_helper"
|
7
|
+
|
8
|
+
module Tapioca
|
9
|
+
module Dsl
|
10
|
+
module Compilers
|
11
|
+
# `Tapioca::Dsl::Compilers::StateMachines` generates RBI files for classes that setup a
|
12
|
+
# [`state_machine`](https://github.com/state-machines/state_machines). The compiler also
|
13
|
+
# processes the extra methods generated by
|
14
|
+
# [StateMachines Active Record](https://github.com/state-machines/state_machines-activerecord)
|
15
|
+
# and [StateMachines Active Model](https://github.com/state-machines/state_machines-activemodel)
|
16
|
+
# integrations.
|
17
|
+
#
|
18
|
+
# For example, with the following `Vehicle` class:
|
19
|
+
#
|
20
|
+
# ~~~rb
|
21
|
+
# class Vehicle
|
22
|
+
# state_machine :alarm_state, initial: :active, namespace: :'alarm' do
|
23
|
+
# event :enable do
|
24
|
+
# transition all => :active
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# event :disable do
|
28
|
+
# transition all => :off
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# state :active, :value => 1
|
32
|
+
# state :off, :value => 0
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# ~~~
|
36
|
+
#
|
37
|
+
# this compiler will produce the RBI file `vehicle.rbi` with the following content:
|
38
|
+
#
|
39
|
+
# ~~~rbi
|
40
|
+
# # vehicle.rbi
|
41
|
+
# # typed: true
|
42
|
+
# class Vehicle
|
43
|
+
# include StateMachineInstanceHelperModule
|
44
|
+
# extend StateMachineClassHelperModule
|
45
|
+
#
|
46
|
+
# module StateMachineClassHelperModule
|
47
|
+
# sig { params(event: T.any(String, Symbol)).returns(String) }
|
48
|
+
# def human_alarm_state_event_name(event); end
|
49
|
+
#
|
50
|
+
# sig { params(state: T.any(String, Symbol)).returns(String) }
|
51
|
+
# def human_alarm_state_name(state); end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# module StateMachineInstanceHelperModule
|
55
|
+
# sig { returns(T::Boolean) }
|
56
|
+
# def alarm_active?; end
|
57
|
+
#
|
58
|
+
# sig { returns(T::Boolean) }
|
59
|
+
# def alarm_off?; end
|
60
|
+
#
|
61
|
+
# sig { returns(Integer) }
|
62
|
+
# def alarm_state; end
|
63
|
+
#
|
64
|
+
# sig { params(value: Integer).returns(Integer) }
|
65
|
+
# def alarm_state=(value); end
|
66
|
+
#
|
67
|
+
# sig { params(state: T.any(String, Symbol)).returns(T::Boolean) }
|
68
|
+
# def alarm_state?(state); end
|
69
|
+
#
|
70
|
+
# sig { params(args: T.untyped).returns(T::Array[T.any(String, Symbol)]) }
|
71
|
+
# def alarm_state_events(*args); end
|
72
|
+
#
|
73
|
+
# sig { returns(T.any(String, Symbol)) }
|
74
|
+
# def alarm_state_name; end
|
75
|
+
#
|
76
|
+
# sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
|
77
|
+
# def alarm_state_paths(*args); end
|
78
|
+
#
|
79
|
+
# sig { params(args: T.untyped).returns(T::Array[::StateMachines::Transition]) }
|
80
|
+
# def alarm_state_transitions(*args); end
|
81
|
+
#
|
82
|
+
# sig { returns(T::Boolean) }
|
83
|
+
# def can_disable_alarm?; end
|
84
|
+
#
|
85
|
+
# sig { returns(T::Boolean) }
|
86
|
+
# def can_enable_alarm?; end
|
87
|
+
#
|
88
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
89
|
+
# def disable_alarm(*args); end
|
90
|
+
#
|
91
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
92
|
+
# def disable_alarm!(*args); end
|
93
|
+
#
|
94
|
+
# sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
|
95
|
+
# def disable_alarm_transition(*args); end
|
96
|
+
#
|
97
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
98
|
+
# def enable_alarm(*args); end
|
99
|
+
#
|
100
|
+
# sig { params(args: T.untyped).returns(T::Boolean) }
|
101
|
+
# def enable_alarm!(*args); end
|
102
|
+
#
|
103
|
+
# sig { params(args: T.untyped).returns(T.nilable(::StateMachines::Transition)) }
|
104
|
+
# def enable_alarm_transition(*args); end
|
105
|
+
#
|
106
|
+
# sig { params(event: T.any(String, Symbol), args: T.untyped).returns(T::Boolean) }
|
107
|
+
# def fire_alarm_state_event(event, *args); end
|
108
|
+
#
|
109
|
+
# sig { returns(String) }
|
110
|
+
# def human_alarm_state_name; end
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
# ~~~
|
114
|
+
class StateMachines < Compiler
|
115
|
+
extend T::Sig
|
116
|
+
|
117
|
+
ACTIVE_RECORD_RELATION_MODULE_NAMES = [
|
118
|
+
"GeneratedRelationMethods",
|
119
|
+
"GeneratedAssociationRelationMethods",
|
120
|
+
].freeze
|
121
|
+
|
122
|
+
ConstantType = type_member { { fixed: T.all(Module, ::StateMachines::ClassMethods) } }
|
123
|
+
|
124
|
+
sig { override.void }
|
125
|
+
def decorate
|
126
|
+
return if constant.state_machines.empty?
|
127
|
+
|
128
|
+
root.create_path(T.unsafe(constant)) do |klass|
|
129
|
+
instance_module_name = "StateMachineInstanceHelperModule"
|
130
|
+
class_module_name = "StateMachineClassHelperModule"
|
131
|
+
|
132
|
+
instance_module = RBI::Module.new(instance_module_name)
|
133
|
+
klass << instance_module
|
134
|
+
|
135
|
+
class_module = RBI::Module.new(class_module_name)
|
136
|
+
klass << class_module
|
137
|
+
|
138
|
+
constant.state_machines.each_value do |machine|
|
139
|
+
state_type = state_type_for(machine)
|
140
|
+
|
141
|
+
define_state_accessor(instance_module, machine, state_type)
|
142
|
+
define_state_predicate(instance_module, machine)
|
143
|
+
define_event_helpers(instance_module, machine)
|
144
|
+
define_path_helpers(instance_module, machine)
|
145
|
+
define_name_helpers(instance_module, class_module, machine)
|
146
|
+
define_scopes(class_module, machine)
|
147
|
+
|
148
|
+
define_state_methods(instance_module, machine)
|
149
|
+
define_event_methods(instance_module, machine)
|
150
|
+
end
|
151
|
+
|
152
|
+
if uses_active_record_integration?(constant)
|
153
|
+
define_activerecord_methods(instance_module)
|
154
|
+
|
155
|
+
[
|
156
|
+
Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper::RelationMethodsModuleName,
|
157
|
+
Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper::AssociationRelationMethodsModuleName,
|
158
|
+
].each do |module_name|
|
159
|
+
klass.create_module(module_name).create_include(class_module_name)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
klass.create_include(instance_module_name)
|
164
|
+
klass.create_extend(class_module_name)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class << self
|
169
|
+
extend T::Sig
|
170
|
+
|
171
|
+
sig { override.returns(T::Enumerable[Module]) }
|
172
|
+
def gather_constants
|
173
|
+
all_classes.select { |mod| ::StateMachines::InstanceMethods > mod }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
sig { params(constant: Module).returns(T::Boolean) }
|
180
|
+
def uses_active_record_integration?(constant)
|
181
|
+
::StateMachines::Integrations.match(constant)&.integration_name == :active_record
|
182
|
+
end
|
183
|
+
|
184
|
+
sig { params(machine: ::StateMachines::Machine).returns(String) }
|
185
|
+
def state_type_for(machine)
|
186
|
+
value_types = machine.states.map { |state| state.value.class.name }.uniq
|
187
|
+
|
188
|
+
if value_types.size == 1
|
189
|
+
value_types.first
|
190
|
+
else
|
191
|
+
"T.any(#{value_types.join(", ")})"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
sig { params(instance_module: RBI::Module).void }
|
196
|
+
def define_activerecord_methods(instance_module)
|
197
|
+
instance_module.create_method(
|
198
|
+
"changed_for_autosave?",
|
199
|
+
return_type: "T::Boolean",
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
|
204
|
+
def define_state_methods(instance_module, machine)
|
205
|
+
machine.states.each do |state|
|
206
|
+
instance_module.create_method(
|
207
|
+
"#{state.qualified_name}?",
|
208
|
+
return_type: "T::Boolean",
|
209
|
+
)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
|
214
|
+
def define_event_methods(instance_module, machine)
|
215
|
+
machine.events.each do |event|
|
216
|
+
instance_module.create_method(
|
217
|
+
"can_#{event.qualified_name}?",
|
218
|
+
return_type: "T::Boolean",
|
219
|
+
)
|
220
|
+
instance_module.create_method(
|
221
|
+
"#{event.qualified_name}_transition",
|
222
|
+
parameters: [create_rest_param("args", type: "T.untyped")],
|
223
|
+
return_type: "T.nilable(::StateMachines::Transition)",
|
224
|
+
)
|
225
|
+
instance_module.create_method(
|
226
|
+
event.qualified_name.to_s,
|
227
|
+
parameters: [create_rest_param("args", type: "T.untyped")],
|
228
|
+
return_type: "T::Boolean",
|
229
|
+
)
|
230
|
+
instance_module.create_method(
|
231
|
+
"#{event.qualified_name}!",
|
232
|
+
parameters: [create_rest_param("args", type: "T.untyped")],
|
233
|
+
return_type: "T::Boolean",
|
234
|
+
)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
sig do
|
239
|
+
params(
|
240
|
+
instance_module: RBI::Module,
|
241
|
+
machine: ::StateMachines::Machine,
|
242
|
+
state_type: String,
|
243
|
+
).void
|
244
|
+
end
|
245
|
+
def define_state_accessor(instance_module, machine, state_type)
|
246
|
+
owner_class = machine.owner_class
|
247
|
+
attribute = machine.attribute.to_sym
|
248
|
+
attribute_name = machine.attribute.to_s
|
249
|
+
|
250
|
+
# AR classes don't define their attributes until the DB is hit and they're fully loaded, so force it here if
|
251
|
+
# needed
|
252
|
+
if !owner_class.instance_methods.include?(attribute) && owner_class.respond_to?(:define_attribute_methods)
|
253
|
+
owner_class.define_attribute_methods
|
254
|
+
end
|
255
|
+
|
256
|
+
if owner_class.instance_methods.include?(attribute)
|
257
|
+
instance_module.create_method(
|
258
|
+
attribute_name,
|
259
|
+
return_type: state_type,
|
260
|
+
) if ::StateMachines::HelperModule === owner_class.instance_method(attribute).owner
|
261
|
+
instance_module.create_method(
|
262
|
+
"#{attribute_name}=",
|
263
|
+
parameters: [create_param("value", type: state_type)],
|
264
|
+
return_type: state_type,
|
265
|
+
) if ::StateMachines::HelperModule === owner_class.instance_method("#{attribute}=").owner
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
|
270
|
+
def define_state_predicate(instance_module, machine)
|
271
|
+
instance_module.create_method(
|
272
|
+
"#{machine.name}?",
|
273
|
+
parameters: [create_param("state", type: "T.any(String, Symbol)")],
|
274
|
+
return_type: "T::Boolean",
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
|
279
|
+
def define_event_helpers(instance_module, machine)
|
280
|
+
events_attribute = machine.attribute(:events).to_s
|
281
|
+
transitions_attribute = machine.attribute(:transitions).to_s
|
282
|
+
event_attribute = machine.attribute(:event).to_s
|
283
|
+
event_transition_attribute = machine.attribute(:event_transition).to_s
|
284
|
+
|
285
|
+
instance_module.create_method(
|
286
|
+
events_attribute,
|
287
|
+
parameters: [create_rest_param("args", type: "T.untyped")],
|
288
|
+
return_type: "T::Array[T.any(String, Symbol)]",
|
289
|
+
)
|
290
|
+
instance_module.create_method(
|
291
|
+
transitions_attribute,
|
292
|
+
parameters: [create_rest_param("args", type: "T.untyped")],
|
293
|
+
return_type: "T::Array[::StateMachines::Transition]",
|
294
|
+
)
|
295
|
+
instance_module.create_method(
|
296
|
+
"fire_#{event_attribute}",
|
297
|
+
parameters: [
|
298
|
+
create_param("event", type: "T.any(String, Symbol)"),
|
299
|
+
create_rest_param("args", type: "T.untyped"),
|
300
|
+
],
|
301
|
+
return_type: "T::Boolean",
|
302
|
+
)
|
303
|
+
if machine.action
|
304
|
+
instance_module.create_method(
|
305
|
+
event_attribute,
|
306
|
+
return_type: "T.nilable(Symbol)",
|
307
|
+
)
|
308
|
+
instance_module.create_method(
|
309
|
+
"#{event_attribute}=",
|
310
|
+
parameters: [create_param("value", type: "T.any(String, Symbol)")],
|
311
|
+
return_type: "T.any(String, Symbol)",
|
312
|
+
)
|
313
|
+
instance_module.create_method(
|
314
|
+
event_transition_attribute,
|
315
|
+
return_type: "T.nilable(::StateMachines::Transition)",
|
316
|
+
)
|
317
|
+
instance_module.create_method(
|
318
|
+
"#{event_transition_attribute}=",
|
319
|
+
parameters: [create_param("value", type: "::StateMachines::Transition")],
|
320
|
+
return_type: "::StateMachines::Transition",
|
321
|
+
)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
sig { params(instance_module: RBI::Module, machine: ::StateMachines::Machine).void }
|
326
|
+
def define_path_helpers(instance_module, machine)
|
327
|
+
paths_attribute = machine.attribute(:paths).to_s
|
328
|
+
|
329
|
+
instance_module.create_method(
|
330
|
+
paths_attribute,
|
331
|
+
parameters: [create_rest_param("args", type: "T.untyped")],
|
332
|
+
return_type: "T::Array[::StateMachines::Transition]",
|
333
|
+
)
|
334
|
+
end
|
335
|
+
|
336
|
+
sig do
|
337
|
+
params(
|
338
|
+
instance_module: RBI::Module,
|
339
|
+
class_module: RBI::Module,
|
340
|
+
machine: ::StateMachines::Machine,
|
341
|
+
).void
|
342
|
+
end
|
343
|
+
def define_name_helpers(instance_module, class_module, machine)
|
344
|
+
name_attribute = machine.attribute(:name).to_s
|
345
|
+
event_name_attribute = machine.attribute(:event_name).to_s
|
346
|
+
|
347
|
+
class_module.create_method(
|
348
|
+
"human_#{name_attribute}",
|
349
|
+
parameters: [create_param("state", type: "T.any(String, Symbol)")],
|
350
|
+
return_type: "String",
|
351
|
+
)
|
352
|
+
class_module.create_method(
|
353
|
+
"human_#{event_name_attribute}",
|
354
|
+
parameters: [create_param("event", type: "T.any(String, Symbol)")],
|
355
|
+
return_type: "String",
|
356
|
+
)
|
357
|
+
instance_module.create_method(
|
358
|
+
name_attribute,
|
359
|
+
return_type: "T.any(String, Symbol)",
|
360
|
+
)
|
361
|
+
instance_module.create_method(
|
362
|
+
"human_#{name_attribute}",
|
363
|
+
return_type: "String",
|
364
|
+
)
|
365
|
+
end
|
366
|
+
|
367
|
+
sig { params(class_module: RBI::Module, machine: ::StateMachines::Machine).void }
|
368
|
+
def define_scopes(class_module, machine)
|
369
|
+
helper_modules = machine.instance_variable_get(:@helper_modules)
|
370
|
+
class_methods = helper_modules[:class].instance_methods(false)
|
371
|
+
|
372
|
+
class_methods
|
373
|
+
.select { |method| method.to_s.start_with?("with_", "without_") }
|
374
|
+
.each do |method|
|
375
|
+
class_module.create_method(
|
376
|
+
method.to_s,
|
377
|
+
parameters: [create_rest_param("states", type: "T.any(String, Symbol)")],
|
378
|
+
return_type: "T.untyped",
|
379
|
+
)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Angellist
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sorbet-static-and-runtime
|
@@ -57,15 +57,15 @@ files:
|
|
57
57
|
- lib/tapioca/dsl/compilers/attr_json.rb
|
58
58
|
- lib/tapioca/dsl/compilers/money_rails.rb
|
59
59
|
- lib/tapioca/dsl/compilers/paperclip.rb
|
60
|
-
- lib/tapioca/dsl/compilers/
|
60
|
+
- lib/tapioca/dsl/compilers/state_machines.rb
|
61
61
|
homepage: https://github.com/angellist/boba
|
62
62
|
licenses:
|
63
63
|
- MIT
|
64
64
|
metadata:
|
65
65
|
bug_tracker_uri: https://github.com/angellist/boba/issues
|
66
|
-
changelog_uri: https://github.com/angellist/boba/blob/0.0.
|
66
|
+
changelog_uri: https://github.com/angellist/boba/blob/0.0.11/History.md
|
67
67
|
homepage_uri: https://github.com/angellist/boba
|
68
|
-
source_code_uri: https://github.com/angellist/boba/tree/0.0.
|
68
|
+
source_code_uri: https://github.com/angellist/boba/tree/0.0.11
|
69
69
|
rubygems_mfa_required: 'true'
|
70
70
|
post_install_message:
|
71
71
|
rdoc_options: []
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# typed: ignore
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "tapioca/dsl/compilers/state_machines"
|
5
|
-
|
6
|
-
return unless defined?(Tapioca::Dsl::Compilers::StateMachines)
|
7
|
-
|
8
|
-
module Tapioca
|
9
|
-
module Dsl
|
10
|
-
module Compilers
|
11
|
-
# `Tapioca::Dsl::Compilers::StateMachinesExtended` extends the default state machines compiler provided by Tapioca
|
12
|
-
# to allow for calling `with_state` and `without_state` on all Active Record relations. This is a temporary fix
|
13
|
-
# until a more durable solution can be found for this type of issue.
|
14
|
-
# See https://github.com/Shopify/tapioca/pull/1994#issuecomment-2302624697.
|
15
|
-
class StateMachinesExtended < ::Tapioca::Dsl::Compilers::StateMachines
|
16
|
-
ACTIVE_RECORD_RELATION_MODULE_NAMES = [
|
17
|
-
"GeneratedRelationMethods",
|
18
|
-
"GeneratedAssociationRelationMethods",
|
19
|
-
].freeze
|
20
|
-
|
21
|
-
def decorate
|
22
|
-
# This should really get checked at the gather_constants level but here we are...
|
23
|
-
return if T::AbstractUtils.abstract_module?(constant)
|
24
|
-
return if constant.state_machines.empty?
|
25
|
-
|
26
|
-
# This is a hack to make sure the instance methods are defined on the constant. Somehow the constant is being
|
27
|
-
# loaded but the actual `state_machine` call is not being executed, so the instance methods don't exist yet.
|
28
|
-
# Instantiating an empty class fixes it.
|
29
|
-
constant.try(:new)
|
30
|
-
|
31
|
-
super()
|
32
|
-
|
33
|
-
root.create_path(T.unsafe(constant)) do |klass|
|
34
|
-
class_module_name = "StateMachineClassHelperModule"
|
35
|
-
|
36
|
-
ACTIVE_RECORD_RELATION_MODULE_NAMES.each do |module_name|
|
37
|
-
klass.create_module(module_name).create_include(class_module_name)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|