conject 0.0.1
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.
- data/.gitignore +4 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/NOTES.txt +61 -0
- data/README.md +8 -0
- data/Rakefile +12 -0
- data/TODO +9 -0
- data/conject.gemspec +21 -0
- data/lib/conject.rb +40 -0
- data/lib/conject/borrowed_active_support_inflector.rb +525 -0
- data/lib/conject/class_ext_construct_with.rb +123 -0
- data/lib/conject/class_finder.rb +11 -0
- data/lib/conject/composition_error.rb +33 -0
- data/lib/conject/dependency_resolver.rb +16 -0
- data/lib/conject/extended_metaid.rb +33 -0
- data/lib/conject/object_context.rb +61 -0
- data/lib/conject/object_definition.rb +10 -0
- data/lib/conject/object_factory.rb +28 -0
- data/lib/conject/utilities.rb +8 -0
- data/lib/conject/version.rb +3 -0
- data/rake_tasks/rspec.rake +25 -0
- data/spec/acceptance/dev/README +7 -0
- data/spec/acceptance/regression/README +12 -0
- data/spec/acceptance/regression/basic_composition_spec.rb +29 -0
- data/spec/acceptance/regression/basic_object_creation_spec.rb +42 -0
- data/spec/acceptance/regression/nested_contexts_spec.rb +86 -0
- data/spec/conject/borrowed_active_support_inflector_spec.rb +28 -0
- data/spec/conject/class_ext_construct_with_spec.rb +226 -0
- data/spec/conject/class_finder_spec.rb +36 -0
- data/spec/conject/composition_error_spec.rb +124 -0
- data/spec/conject/dependency_resolver_spec.rb +32 -0
- data/spec/conject/extended_metaid_spec.rb +90 -0
- data/spec/conject/object_context_spec.rb +186 -0
- data/spec/conject/object_definition_spec.rb +31 -0
- data/spec/conject/object_factory_spec.rb +89 -0
- data/spec/conject/utilities_spec.rb +30 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/SPEC_HELPERS_GO_HERE +0 -0
- data/spec/support/load_path_helpers.rb +27 -0
- data/spec/test_data/basic_composition/fence.rb +5 -0
- data/spec/test_data/basic_composition/front_desk.rb +2 -0
- data/spec/test_data/basic_composition/grass.rb +2 -0
- data/spec/test_data/basic_composition/guest.rb +5 -0
- data/spec/test_data/basic_composition/lobby.rb +5 -0
- data/spec/test_data/basic_composition/nails.rb +2 -0
- data/spec/test_data/basic_composition/tv.rb +2 -0
- data/spec/test_data/basic_composition/wood.rb +2 -0
- data/spec/test_data/simple_stuff/some_random_class.rb +2 -0
- data/spike/arity_funny_business_in_different_ruby_versions.rb +34 -0
- data/spike/depends_on_spike.rb +146 -0
- data/spike/donkey_fail.rb +48 -0
- data/spike/donkey_journey.rb +50 -0
- data/spike/go.rb +11 -0
- data/spike/metaid.rb +28 -0
- data/spike/object_definition.rb +125 -0
- data/spike/sample.rb +125 -0
- data/src/user_model.rb +4 -0
- data/src/user_presenter.rb +10 -0
- data/src/user_view.rb +3 -0
- metadata +165 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
|
3
|
+
|
4
|
+
describe "nested contexts" do
|
5
|
+
subject { Conject.default_object_context }
|
6
|
+
|
7
|
+
let(:root_context) { Conject.default_object_context }
|
8
|
+
|
9
|
+
before do
|
10
|
+
append_test_load_path "basic_composition"
|
11
|
+
require 'lobby'
|
12
|
+
require 'front_desk'
|
13
|
+
require 'guest'
|
14
|
+
require 'tv'
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
restore_load_path
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can create an object within a subcontext" do
|
22
|
+
root_context.in_subcontext do |hotel|
|
23
|
+
front_desk = hotel[:front_desk]
|
24
|
+
front_desk.should be
|
25
|
+
front_desk.class.should == FrontDesk
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "reuse objects within themselves, but do not share with peer contexts" do
|
30
|
+
front_desk = nil
|
31
|
+
another_front_desk = nil
|
32
|
+
|
33
|
+
root_context.in_subcontext do |hotel|
|
34
|
+
front_desk = hotel[:front_desk]
|
35
|
+
hotel[:front_desk].object_id.should == front_desk.object_id
|
36
|
+
hotel.directly_has?(:front_desk).should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
root_context.in_subcontext do |hotel|
|
40
|
+
another_front_desk = hotel[:front_desk]
|
41
|
+
hotel[:front_desk].object_id.should == another_front_desk.object_id
|
42
|
+
end
|
43
|
+
|
44
|
+
another_front_desk.object_id.should_not == front_desk.object_id
|
45
|
+
end
|
46
|
+
|
47
|
+
it "provide dependencies by generating objects within itself" do
|
48
|
+
root_context.in_subcontext do |hotel|
|
49
|
+
lobby = hotel[:lobby]
|
50
|
+
lobby.front_desk.should be
|
51
|
+
lobby.front_desk.should == hotel[:front_desk]
|
52
|
+
|
53
|
+
hotel.directly_has?(:front_desk).should be_true
|
54
|
+
hotel.directly_has?(:lobby).should be_true
|
55
|
+
|
56
|
+
root_context.has?(:front_desk).should_not be_true
|
57
|
+
root_context.has?(:lobby).should_not be_true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "provide dependencies via its parent context, THEN generating objects within itself" do
|
62
|
+
root_context.in_subcontext do |hotel|
|
63
|
+
lobby = hotel[:lobby] # this establishes the lobby and front desk in the hotel context
|
64
|
+
|
65
|
+
hotel.in_subcontext do |room|
|
66
|
+
guest = room[:guest]
|
67
|
+
# Guest should have reference to TV from the room context, and front desk from the hotel context
|
68
|
+
guest.tv.should be
|
69
|
+
guest.front_desk.should be
|
70
|
+
|
71
|
+
# Double check which contexts own which objects
|
72
|
+
room.directly_has?(:guest).should be_true
|
73
|
+
room.directly_has?(:tv).should be_true
|
74
|
+
|
75
|
+
hotel.directly_has?(:guest).should_not be_true
|
76
|
+
hotel.directly_has?(:tv).should_not be_true
|
77
|
+
|
78
|
+
guest.front_desk.should == hotel[:front_desk]
|
79
|
+
room.directly_has?(:front_desk).should_not be_true
|
80
|
+
hotel.directly_has?(:front_desk).should be_true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
#
|
4
|
+
# Some super-basic specs that don't really exercise Inflector,
|
5
|
+
# but demonstrate that we have this capability built-in
|
6
|
+
# when using ObjectContext.
|
7
|
+
#
|
8
|
+
describe "Borrowed ActiveSupport Inflector" do
|
9
|
+
describe "#camelize" do
|
10
|
+
it "converts underscored strings to camel case" do
|
11
|
+
"four_five_six".camelize.should == "FourFiveSix"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "leaves camel case words along" do
|
15
|
+
"HoppingSizzler".camelize.should == "HoppingSizzler"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#underscore" do
|
20
|
+
it "converts camel-case words to underscored" do
|
21
|
+
"HoppingSizzler".underscore.should == "hopping_sizzler"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "leaves underscored strings alone" do
|
25
|
+
"four_five_six".underscore.should == "four_five_six"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe "Class" do
|
4
|
+
|
5
|
+
describe ".construct_with" do
|
6
|
+
subject do
|
7
|
+
Class.new do
|
8
|
+
construct_with :object1, :object2
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@saw = "initialize has access to #{object1} and #{object2}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def explain
|
15
|
+
"Made of #{object1} and #{object2}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@saw
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "lets you construct instances with sets of named objects" do
|
25
|
+
instance = subject.new :object1 => "first object", :object2 => "second object"
|
26
|
+
instance.explain.should == "Made of first object and second object"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "sets objects before initialize is invoked" do
|
30
|
+
instance = subject.new :object1 => "one", :object2 => "two"
|
31
|
+
instance.to_s.should == "initialize has access to one and two"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "defines the object accessors as private" do
|
35
|
+
instance = subject.new :object1 => "one", :object2 => "two"
|
36
|
+
lambda do instance.object1 end.should raise_error(NoMethodError)
|
37
|
+
lambda do instance.object2 end.should raise_error(NoMethodError)
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "when user defines initialize with one argument" do
|
41
|
+
subject do
|
42
|
+
Class.new do
|
43
|
+
construct_with :ant, :aardvark
|
44
|
+
|
45
|
+
attr_reader :map_string, :also
|
46
|
+
|
47
|
+
def initialize(map)
|
48
|
+
@map_string = map.inspect
|
49
|
+
@also = "Preset #{ant} and #{aardvark}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
let :map do { :ant => "red", :aardvark => "blue" } end
|
55
|
+
|
56
|
+
it "passes along the object map" do
|
57
|
+
subject.new(map).map_string.should == map.inspect
|
58
|
+
end
|
59
|
+
|
60
|
+
it "still pre-sets the object accessors" do
|
61
|
+
subject.new(map).also.should == "Preset red and blue"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "when user defines initialize with var args" do
|
66
|
+
subject do
|
67
|
+
Class.new do
|
68
|
+
construct_with :ant, :aardvark
|
69
|
+
|
70
|
+
attr_reader :map_string, :also
|
71
|
+
|
72
|
+
def initialize(*stuff)
|
73
|
+
map = stuff.shift
|
74
|
+
@map_string = map.inspect
|
75
|
+
@also = "Preset #{ant} and #{aardvark}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
let :map do { :ant => "red", :aardvark => "blue" } end
|
81
|
+
|
82
|
+
it "passes along the object map" do
|
83
|
+
subject.new(map).map_string.should == map.inspect
|
84
|
+
end
|
85
|
+
|
86
|
+
it "still pre-sets the object accessors" do
|
87
|
+
subject.new(map).also.should == "Preset red and blue"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "when user defines initialize with too many args" do
|
92
|
+
subject do
|
93
|
+
Class.new do
|
94
|
+
construct_with :beevis, :butthead
|
95
|
+
def initialize(one,two)
|
96
|
+
# won't ever get run because the .new override will freak due to our arity
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
let :map do { :beevis => "nh nh!", :butthead => "uhhh huh huh" } end
|
102
|
+
|
103
|
+
it "raises an error due to invalid #initialize signature" do
|
104
|
+
lambda do subject.new(map) end.should raise_error(RuntimeError, /initialize method defined with 2 parameters/)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "when user doesn't define an initialize" do
|
109
|
+
subject do
|
110
|
+
Class.new do
|
111
|
+
construct_with :something
|
112
|
+
def to_s
|
113
|
+
"Something is #{something}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it "works normally" do
|
119
|
+
subject.new(:something => "normal").to_s.should == "Something is normal"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "when no object map is supplied to constructor" do
|
124
|
+
it "raises a typical argument error" do
|
125
|
+
lambda do subject.new end.should raise_error(ArgumentError)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "when object map does not contain any required objects" do
|
130
|
+
it "raises a composition error explaining missing objects" do
|
131
|
+
lambda do subject.new({}) end.should raise_error(
|
132
|
+
Conject::CompositionError,
|
133
|
+
/missing required.*object1.*object2/i
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "when object map contains only SOME of the required objects" do
|
139
|
+
it "raises a composition error explaining which missing objects" do
|
140
|
+
lambda do subject.new(:object2 => "ok") end.should raise_error(
|
141
|
+
Conject::CompositionError,
|
142
|
+
/missing required.*object1/i
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "when object map contains objects that are not accepted" do
|
148
|
+
it "raises a composition error explaining rejected objects" do
|
149
|
+
lambda do
|
150
|
+
subject.new(
|
151
|
+
:object1 => "ok",
|
152
|
+
:object2 => "ok",
|
153
|
+
:not_even_supposed => "to be here",
|
154
|
+
:stop => "whining"
|
155
|
+
)
|
156
|
+
end.should raise_error(
|
157
|
+
Conject::CompositionError,
|
158
|
+
/unexpected object.*not_even_supposed.*stop/i
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "when object map has a mix of missing and unexpected objects" do
|
164
|
+
it "raises a composition error explaining missing and rejected objects" do
|
165
|
+
lambda do
|
166
|
+
subject.new(
|
167
|
+
:object1 => "ok",
|
168
|
+
:not_even_supposed => "to be here"
|
169
|
+
)
|
170
|
+
end.should raise_error(
|
171
|
+
Conject::CompositionError,
|
172
|
+
/missing required.*object2.*unexpected object.*not_even_supposed/i
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
describe ".object_definition" do
|
180
|
+
subject do
|
181
|
+
Class.new do
|
182
|
+
construct_with :cats, :dogs
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "exists on classes that use construct_with" do
|
187
|
+
subject.should respond_to(:object_definition)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "references the owning class" do
|
191
|
+
subject.object_definition.owner.should == subject
|
192
|
+
end
|
193
|
+
|
194
|
+
it "contains the list of required object names for construction of new instances" do
|
195
|
+
subject.object_definition.component_names.to_set.should == [:cats,:dogs].to_set
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "for classes that don't use construct_with" do
|
199
|
+
|
200
|
+
before do
|
201
|
+
append_test_load_path "simple_stuff"
|
202
|
+
require 'some_random_class'
|
203
|
+
end
|
204
|
+
|
205
|
+
after do
|
206
|
+
restore_load_path
|
207
|
+
end
|
208
|
+
|
209
|
+
it "doesn't exist" do
|
210
|
+
require 'some_random_class'
|
211
|
+
SomeRandomClass.should_not respond_to(:object_definition)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe ".has_object_definition?" do
|
217
|
+
it "returns false for all regular classes" do
|
218
|
+
Class.new.has_object_definition?.should be_false
|
219
|
+
end
|
220
|
+
|
221
|
+
it "returns true for classes that use construct_with" do
|
222
|
+
Class.new do construct_with :a end.has_object_definition?.should be_true
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
describe Conject::ClassFinder do
|
4
|
+
before do
|
5
|
+
append_test_load_path "simple_stuff"
|
6
|
+
require 'some_random_class'
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
restore_load_path
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns the class implied by the given name" do
|
14
|
+
c = subject.find_class('some_random_class')
|
15
|
+
c.should_not be_nil
|
16
|
+
c.should == SomeRandomClass
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can accept symbols for object names" do
|
20
|
+
c = subject.find_class(:some_random_class)
|
21
|
+
c.should_not be_nil
|
22
|
+
c.should == SomeRandomClass
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises an error if the name doesn't imply a regular class in the current runtime" do
|
26
|
+
lambda do
|
27
|
+
subject.find_class('something_undefined')
|
28
|
+
end.should raise_error(/could not find class.*something_undefined/i)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "raises an error for nil input" do
|
32
|
+
lambda do
|
33
|
+
subject.find_class('something_undefined')
|
34
|
+
end.should raise_error(/could not find class.*something_undefined/i)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
2
|
+
|
3
|
+
|
4
|
+
describe Conject::CompositionError do
|
5
|
+
subject do
|
6
|
+
described_class.new construction_args
|
7
|
+
end
|
8
|
+
|
9
|
+
before do
|
10
|
+
append_test_load_path "simple_stuff"
|
11
|
+
require 'some_random_class'
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
restore_load_path
|
16
|
+
end
|
17
|
+
|
18
|
+
let :construction_args do
|
19
|
+
{
|
20
|
+
:object_definition => object_definition,
|
21
|
+
:provided => []
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
let :object_definition do
|
26
|
+
Conject::ObjectDefinition.new(
|
27
|
+
:owner => SomeRandomClass,
|
28
|
+
:component_names => []
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "when missing one required component" do
|
33
|
+
before { object_definition.component_names << :an_object }
|
34
|
+
|
35
|
+
it "indicates the missing object" do
|
36
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:an_object]."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "when missing multiple required components" do
|
41
|
+
before do
|
42
|
+
object_definition.component_names << :an_object
|
43
|
+
object_definition.component_names << :another_object
|
44
|
+
end
|
45
|
+
|
46
|
+
it "indicates all missing objects" do
|
47
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:an_object, :another_object]."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "when an unexpected component is provided" do
|
52
|
+
before { construction_args[:provided] = [ :surprise ] }
|
53
|
+
|
54
|
+
it "calls out the unexpected object" do
|
55
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Unexpected object(s) provided [:surprise]."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "when multiple unexpected components are provided" do
|
60
|
+
before { construction_args[:provided] = [ :surprise, :hello ] }
|
61
|
+
it "calls out all unexpected objects" do
|
62
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Unexpected object(s) provided [:surprise, :hello]."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "when unexpected components are provided AND required components are missing" do
|
67
|
+
before do
|
68
|
+
object_definition.component_names << :part_one
|
69
|
+
object_definition.component_names << :part_two
|
70
|
+
object_definition.component_names << :part_three
|
71
|
+
construction_args[:provided] = [ :part_two, :surprise_one, :surprise_two ]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "generates a message for both missing AND unexpected components" do
|
75
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:part_one, :part_three]. Unexpected object(s) provided [:surprise_one, :surprise_two]."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "without object_definition" do
|
80
|
+
it "has a vague message" do
|
81
|
+
construction_args.delete :object_definition
|
82
|
+
construction_args[:provided] = [ :one, :two ]
|
83
|
+
subject.message.should == "Failed to construct... something. Provided objects were: [:one, :two]"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "with nil construction args" do
|
88
|
+
let :construction_args do nil end
|
89
|
+
|
90
|
+
it "has a vague message" do
|
91
|
+
subject.message.should == "Failed to construct... something."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "without any args supplied at all" do
|
96
|
+
let :construction_args do nil end
|
97
|
+
it "has a vague message" do
|
98
|
+
described_class.new.message.should == "Failed to construct... something."
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "without an object_definition.owner" do
|
103
|
+
let :object_definition do
|
104
|
+
Conject::ObjectDefinition.new(
|
105
|
+
:component_names => [:a, :b]
|
106
|
+
# no owner
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "uses a placeholder name, but otherwise generates good info" do
|
111
|
+
subject.message.should == "Wrong components when building new object. Missing required object(s) [:a, :b]."
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "without provided object names" do
|
116
|
+
it "indicates which components are required but not what's missing" do
|
117
|
+
object_definition.component_names << :wanting
|
118
|
+
construction_args.delete :provided
|
119
|
+
# p object_definition
|
120
|
+
subject.message.should == "Wrong components when building new SomeRandomClass. Missing required object(s) [:wanting]."
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|