sinclair 1.8.0 → 1.10.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +20 -6
  3. data/.rubocop.yml +5 -2
  4. data/Dockerfile +1 -1
  5. data/README.md +25 -1
  6. data/config/check_specs.yml +1 -0
  7. data/config/yardstick.yml +9 -1
  8. data/lib/sinclair/comparable/class_methods.rb +33 -0
  9. data/lib/sinclair/comparable.rb +47 -0
  10. data/lib/sinclair/config_builder.rb +4 -2
  11. data/lib/sinclair/configurable.rb +2 -0
  12. data/lib/sinclair/equals_checker.rb +110 -0
  13. data/lib/sinclair/matchers/add_class_method.rb +27 -36
  14. data/lib/sinclair/matchers/add_instance_method.rb +59 -59
  15. data/lib/sinclair/matchers/add_method.rb +33 -35
  16. data/lib/sinclair/matchers/change_class_method.rb +22 -16
  17. data/lib/sinclair/matchers/change_instance_method.rb +46 -16
  18. data/lib/sinclair/matchers.rb +2 -8
  19. data/lib/sinclair/method_builder/call_method_builder.rb +49 -0
  20. data/lib/sinclair/method_builder.rb +4 -1
  21. data/lib/sinclair/method_definition/call_definition.rb +52 -0
  22. data/lib/sinclair/method_definition/string_definition.rb +0 -2
  23. data/lib/sinclair/method_definition.rb +40 -24
  24. data/lib/sinclair/method_definitions.rb +21 -1
  25. data/lib/sinclair/options/builder.rb +8 -0
  26. data/lib/sinclair/options.rb +1 -13
  27. data/lib/sinclair/version.rb +1 -1
  28. data/lib/sinclair.rb +2 -0
  29. data/sinclair.gemspec +1 -1
  30. data/spec/integration/readme/sinclair/comparable_spec.rb +24 -0
  31. data/spec/integration/yard/sinclair/comparable_spec.rb +24 -0
  32. data/spec/integration/yard/sinclair/equals_checker_spec.rb +51 -0
  33. data/spec/integration/yard/sinclair/matchers/change_class_method_spec.rb +24 -0
  34. data/spec/integration/yard/sinclair/matchers/change_instance_method_spec.rb +40 -0
  35. data/spec/lib/sinclair/comparable_spec.rb +202 -0
  36. data/spec/lib/sinclair/equals_checker_spec.rb +148 -0
  37. data/spec/lib/sinclair/method_builder/call_method_builder_spec.rb +76 -0
  38. data/spec/lib/sinclair/method_builder_spec.rb +63 -20
  39. data/spec/lib/sinclair/method_definition/call_definition_spec.rb +36 -0
  40. data/spec/lib/sinclair/method_definition_spec.rb +64 -0
  41. data/spec/lib/sinclair/method_definitions_spec.rb +79 -0
  42. data/spec/lib/sinclair/options/builder_spec.rb +13 -0
  43. data/spec/lib/sinclair/options/class_methods_spec.rb +23 -8
  44. data/spec/support/sample_model.rb +19 -0
  45. data/spec/support/shared_examples/attribute_accessor.rb +103 -0
  46. metadata +21 -5
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::EqualsChecker do
6
+ describe '#yard' do
7
+ describe '#match?' do
8
+ it 'regular usage' do
9
+ checker = Sinclair::EqualsChecker.new(:name, :age)
10
+
11
+ model1 = SampleModel.new(name: 'jack', age: 21)
12
+ model2 = SampleModel.new(name: 'rose', age: 23)
13
+
14
+ expect(checker.match?(model1, model2)).to be_falsey
15
+ end
16
+
17
+ it 'similar models' do
18
+ checker = Sinclair::EqualsChecker.new(:name, :age)
19
+
20
+ model1 = SampleModel.new(name: 'jack', age: 21)
21
+ model2 = SampleModel.new(name: 'jack', age: 21)
22
+
23
+ expect(checker.match?(model1, model2)).to be_truthy
24
+ end
25
+
26
+ it 'different classes' do
27
+ checker = Sinclair::EqualsChecker.new(:name, :age)
28
+
29
+ model1 = SampleModel.new(name: 'jack', age: 21)
30
+ model2 = OtherModel.new(name: 'jack', age: 21)
31
+
32
+ expect(checker.match?(model1, model2)).to be_falsey
33
+ end
34
+ end
35
+
36
+ describe '#add' do
37
+ it 'adding fields to equal checker' do
38
+ checker = Sinclair::EqualsChecker.new(:name)
39
+
40
+ model1 = SampleModel.new(name: 'jack', age: 21)
41
+ model2 = SampleModel.new(name: 'jack', age: 22)
42
+
43
+ expect(checker.match?(model1, model2)).to be_truthy
44
+
45
+ checker.add(:age)
46
+
47
+ expect(checker.match?(model1, model2)).to be_falsey
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Sinclair::Matchers::ChangeClassMethod do
6
+ describe 'yard' do
7
+ describe '#on' do
8
+ context 'when checking against Class' do
9
+ let(:klass) { Class.new(MyModel) }
10
+ let(:builder) { Sinclair.new(klass) }
11
+
12
+ before do
13
+ builder.add_class_method(:the_method) { 10 }
14
+ builder.build
15
+ builder.add_class_method(:the_method) { 20 }
16
+ end
17
+
18
+ it do
19
+ expect { builder.build }.to change_class_method(:the_method).on(klass)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Sinclair::Matchers::ChangeInstanceMethod do
6
+ describe 'yard' do
7
+ describe '#on' do
8
+ context 'when checking against Class' do
9
+ let(:klass) { Class.new(MyModel) }
10
+ let(:builder) { Sinclair.new(klass) }
11
+
12
+ before do
13
+ builder.add_method(:the_method) { 10 }
14
+ builder.build
15
+ builder.add_method(:the_method) { 20 }
16
+ end
17
+
18
+ it do
19
+ expect { builder.build }.to change_method(:the_method).on(klass)
20
+ end
21
+ end
22
+
23
+ context 'when checking against an intance' do
24
+ let(:klass) { Class.new(MyModel) }
25
+ let(:instance) { klass.new }
26
+ let(:builder) { Sinclair.new(klass) }
27
+
28
+ before do
29
+ builder.add_method(:the_method) { 10 }
30
+ builder.build
31
+ builder.add_method(:the_method) { 20 }
32
+ end
33
+
34
+ it do
35
+ expect { builder.build }.to change_method(:the_method).on(instance)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::Comparable do
6
+ let(:model_class) do
7
+ Class.new(SampleModel) do
8
+ include Sinclair::Comparable
9
+ end
10
+ end
11
+
12
+ let(:attributes) { %i[] }
13
+ let(:model1_class) { model_class }
14
+ let(:model2_class) { model_class }
15
+ let(:model1) { model1_class.new(**model1_attributes) }
16
+ let(:model2) { model2_class.new(**model2_attributes) }
17
+
18
+ let(:model1_attributes) { { name: name1, age: age1 } }
19
+ let(:model2_attributes) { { name: name2, age: age2 } }
20
+ let(:name1) { SecureRandom.hex(10) }
21
+ let(:name2) { SecureRandom.hex(10) }
22
+ let(:age1) { Random.rand(10..20) }
23
+ let(:age2) { Random.rand(21..50) }
24
+
25
+ describe '.comparable_by' do
26
+ let(:model2_class) { model1_class }
27
+
28
+ context 'when no field was present' do
29
+ it 'adds the field for comparison' do
30
+ expect { model1_class.comparable_by(:name) }
31
+ .to change { model1 == model2 }
32
+ .from(true).to(false)
33
+ end
34
+ end
35
+
36
+ context 'when there was a field present' do
37
+ let(:name2) { name1 }
38
+
39
+ before { model1_class.comparable_by(:name) }
40
+
41
+ it 'adds the field for comparison' do
42
+ expect { model1_class.comparable_by(:age) }
43
+ .to change { model1 == model2 }
44
+ .from(true).to(false)
45
+ end
46
+ end
47
+
48
+ context 'when there was a field present and it made a non match' do
49
+ let(:age2) { age1 }
50
+
51
+ before { model1_class.comparable_by(:name) }
52
+
53
+ it 'adds the field for comparison without forgeting the previous' do
54
+ expect { model1_class.comparable_by(:age) }
55
+ .not_to change { model1 == model2 }
56
+ .from(false)
57
+ end
58
+ end
59
+
60
+ context 'when the class parent class adds a field' do
61
+ let(:model1_class) { Class.new(model_class) }
62
+ let(:model2_class) { model1_class }
63
+
64
+ it 'takes the field into consideration' do
65
+ expect { model_class.comparable_by(:name) }
66
+ .to change { model1 == model2 }
67
+ .from(true).to(false)
68
+ end
69
+
70
+ context 'when we add a field to the class itself' do
71
+ let(:name2) { name1 }
72
+
73
+ before { model_class.comparable_by(:name) }
74
+
75
+ it 'takes all fields into consideration' do
76
+ expect { model1_class.comparable_by(:age) }
77
+ .to change { model1 == model2 }
78
+ .from(true).to(false)
79
+ end
80
+ end
81
+
82
+ context 'when we add a field to the parent class after' do
83
+ let(:name2) { name1 }
84
+
85
+ before { model1_class.comparable_by(:name) }
86
+
87
+ it 'takes all fields into consideration' do
88
+ expect { model_class.comparable_by(:age) }
89
+ .to change { model1 == model2 }
90
+ .from(true).to(false)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '#==' do
97
+ before do
98
+ model1_class.comparable_by(attributes)
99
+ model2_class.comparable_by(attributes)
100
+ end
101
+
102
+ context 'when the attributes is empty' do
103
+ context 'when they are different classes and attributes are the same' do
104
+ let(:model2_class) { Class.new(model1_class) }
105
+ let(:name2) { name1 }
106
+ let(:age2) { age1 }
107
+
108
+ it do
109
+ expect(model1).not_to eq(model2)
110
+ end
111
+ end
112
+
113
+ context 'when the models have the same attributes' do
114
+ let(:name2) { name1 }
115
+ let(:age2) { age1 }
116
+
117
+ it do
118
+ expect(model1).to eq(model2)
119
+ end
120
+ end
121
+
122
+ context 'when the models have very different attributes' do
123
+ it do
124
+ expect(model1).to eq(model2)
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'when the attributes is missing just one attribute' do
130
+ let(:attributes) { %i[name] }
131
+
132
+ context 'when they are different classes and attributes are the same' do
133
+ let(:model2_class) { Class.new(model1_class) }
134
+ let(:name2) { name1 }
135
+ let(:age2) { age1 }
136
+
137
+ it do
138
+ expect(model1).not_to eq(model2)
139
+ end
140
+ end
141
+
142
+ context 'when the models have a non listed different attribute' do
143
+ let(:name2) { name1 }
144
+
145
+ it do
146
+ expect(model1).to eq(model2)
147
+ end
148
+ end
149
+
150
+ context 'when the models have a listed different attribute' do
151
+ let(:age2) { age1 }
152
+
153
+ it do
154
+ expect(model1).not_to eq(model2)
155
+ end
156
+ end
157
+
158
+ context 'when the models have very different attributes' do
159
+ it do
160
+ expect(model1).not_to eq(model2)
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'when all attributes are included' do
166
+ let(:attributes) { %i[name age] }
167
+
168
+ context 'when they are different classes and attributes are the same' do
169
+ let(:model2_class) { Class.new(model1_class) }
170
+ let(:name2) { name1 }
171
+ let(:age2) { age1 }
172
+
173
+ it do
174
+ expect(model1).not_to eq(model2)
175
+ end
176
+ end
177
+
178
+ context 'when the models have the same attributes' do
179
+ let(:name2) { name1 }
180
+ let(:age2) { age1 }
181
+
182
+ it do
183
+ expect(model1).to eq(model2)
184
+ end
185
+ end
186
+
187
+ context 'when the models have a listed different attribute' do
188
+ let(:name) { name1 }
189
+
190
+ it do
191
+ expect(model1).not_to eq(model2)
192
+ end
193
+ end
194
+
195
+ context 'when the models have very different attributes' do
196
+ it do
197
+ expect(model1).not_to eq(model2)
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::EqualsChecker do
6
+ subject(:checker) { described_class.new(attributes) }
7
+
8
+ let(:attributes) { %i[] }
9
+
10
+ let(:model1_class) { SampleModel }
11
+ let(:model2_class) { SampleModel }
12
+ let(:model1) { model1_class.new(**model1_attributes) }
13
+ let(:model2) { model2_class.new(**model2_attributes) }
14
+
15
+ let(:model1_attributes) { { name: name1, age: age1 } }
16
+ let(:model2_attributes) { { name: name2, age: age2 } }
17
+ let(:name1) { SecureRandom.hex(10) }
18
+ let(:name2) { SecureRandom.hex(10) }
19
+ let(:age1) { Random.rand(10..20) }
20
+ let(:age2) { Random.rand(21..50) }
21
+
22
+ describe 'match?' do
23
+ context 'when the attributes is empty' do
24
+ context 'when they are different classes and attributes are the same' do
25
+ let(:model2_class) { Class.new(SampleModel) }
26
+ let(:name2) { name1 }
27
+ let(:age2) { age1 }
28
+
29
+ it do
30
+ expect(checker).not_to be_match(model1, model2)
31
+ end
32
+ end
33
+
34
+ context 'when the models have the same attributes' do
35
+ let(:name2) { name1 }
36
+ let(:age2) { age1 }
37
+
38
+ it do
39
+ expect(checker).to be_match(model1, model2)
40
+ end
41
+ end
42
+
43
+ context 'when the models have very different attributes' do
44
+ it do
45
+ expect(checker).to be_match(model1, model2)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'when the attributes is missing just one attribute' do
51
+ let(:attributes) { %i[name] }
52
+
53
+ context 'when they are different classes and attributes are the same' do
54
+ let(:model2_class) { Class.new(SampleModel) }
55
+ let(:name2) { name1 }
56
+ let(:age2) { age1 }
57
+
58
+ it do
59
+ expect(checker).not_to be_match(model1, model2)
60
+ end
61
+ end
62
+
63
+ context 'when the models have a non listed different attribute' do
64
+ let(:name2) { name1 }
65
+
66
+ it do
67
+ expect(checker).to be_match(model1, model2)
68
+ end
69
+ end
70
+
71
+ context 'when the models have a listed different attribute' do
72
+ let(:age2) { age1 }
73
+
74
+ it do
75
+ expect(checker).not_to be_match(model1, model2)
76
+ end
77
+ end
78
+
79
+ context 'when the models have very different attributes' do
80
+ it do
81
+ expect(checker).not_to be_match(model1, model2)
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'when all attributes are included' do
87
+ let(:attributes) { %i[name age] }
88
+
89
+ context 'when they are different classes and attributes are the same' do
90
+ let(:model2_class) { Class.new(SampleModel) }
91
+ let(:name2) { name1 }
92
+ let(:age2) { age1 }
93
+
94
+ it do
95
+ expect(checker).not_to be_match(model1, model2)
96
+ end
97
+ end
98
+
99
+ context 'when the models have the same attributes' do
100
+ let(:name2) { name1 }
101
+ let(:age2) { age1 }
102
+
103
+ it do
104
+ expect(checker).to be_match(model1, model2)
105
+ end
106
+ end
107
+
108
+ context 'when the models have a listed different attribute' do
109
+ let(:name) { name1 }
110
+
111
+ it do
112
+ expect(checker).not_to be_match(model1, model2)
113
+ end
114
+ end
115
+
116
+ context 'when the models have very different attributes' do
117
+ it do
118
+ expect(checker).not_to be_match(model1, model2)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ describe '#add' do
125
+ let(:attributes) { [:name] }
126
+ let(:new_attributes) { [:age] }
127
+
128
+ context 'when the new field has different values' do
129
+ let(:name2) { name1 }
130
+
131
+ it 'uses the new field to the match' do
132
+ expect { checker.add(new_attributes) }
133
+ .to change { checker.match?(model1, model2) }
134
+ .from(true).to(false)
135
+ end
136
+ end
137
+
138
+ context 'when the old field has different values' do
139
+ let(:age2) { age1 }
140
+
141
+ it 'uses the new field to the match' do
142
+ expect { checker.add(new_attributes) }
143
+ .not_to change { checker.match?(model1, model2) }
144
+ .from(false)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::MethodBuilder::CallMethodBuilder do
6
+ describe '#build' do
7
+ subject(:builder) do
8
+ described_class.new(klass, definition, type: type)
9
+ end
10
+
11
+ let(:call_name) { "attr_#{accessor_type}" }
12
+
13
+ let(:definition) do
14
+ Sinclair::MethodDefinition::CallDefinition.new(call_name, *attributes)
15
+ end
16
+
17
+ context 'when method called is attr_accessor' do
18
+ let(:accessor_type) { :accessor }
19
+
20
+ it_behaves_like 'a method builder that adds attribute reader'
21
+ it_behaves_like 'a method builder that adds attribute writer'
22
+ end
23
+
24
+ context 'when method called is attr_reader' do
25
+ let(:accessor_type) { :reader }
26
+
27
+ it_behaves_like 'a method builder that adds attribute reader' do
28
+ context 'when type is instance' do
29
+ let(:type) { Sinclair::MethodBuilder::INSTANCE_METHOD }
30
+
31
+ it 'does not add a reader' do
32
+ expect { builder.build }
33
+ .not_to add_method("#{method_name}=")
34
+ .to(instance)
35
+ end
36
+ end
37
+
38
+ context 'when type is class' do
39
+ let(:type) { Sinclair::MethodBuilder::CLASS_METHOD }
40
+
41
+ it 'does not add a reader' do
42
+ expect { builder.build }
43
+ .not_to add_class_method("#{method_name}=")
44
+ .to(klass)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'when method called is attr_writter' do
51
+ let(:accessor_type) { :writer }
52
+
53
+ it_behaves_like 'a method builder that adds attribute writer' do
54
+ context 'when type is instance' do
55
+ let(:type) { Sinclair::MethodBuilder::INSTANCE_METHOD }
56
+
57
+ it 'does not add a reader' do
58
+ expect { builder.build }
59
+ .not_to add_method(method_name)
60
+ .to(instance)
61
+ end
62
+ end
63
+
64
+ context 'when type is class' do
65
+ let(:type) { Sinclair::MethodBuilder::CLASS_METHOD }
66
+
67
+ it 'does not add a reader' do
68
+ expect { builder.build }
69
+ .not_to add_class_method(method_name)
70
+ .to(klass)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -11,41 +11,84 @@ describe Sinclair::MethodBuilder do
11
11
  let(:method_name) { :the_method }
12
12
  let(:instance) { klass.new }
13
13
 
14
- before do
15
- definitions.add(method_name, value.to_s)
16
- end
17
-
18
14
  describe '#build_methods' do
19
- context 'when building an instance method' do
20
- let(:type) { described_class::INSTANCE_METHOD }
15
+ context 'when the method is a string definition' do
16
+ before do
17
+ definitions.add(method_name, value.to_s)
18
+ end
21
19
 
22
- it do
23
- expect { builder.build_methods(definitions, type) }
24
- .to add_method(method_name).to(instance)
20
+ context 'when building an instance method' do
21
+ let(:type) { described_class::INSTANCE_METHOD }
22
+
23
+ it do
24
+ expect { builder.build_methods(definitions, type) }
25
+ .to add_method(method_name).to(instance)
26
+ end
27
+
28
+ context 'when the method is called' do
29
+ before { builder.build_methods(definitions, type) }
30
+
31
+ it do
32
+ expect(instance.the_method).to eq(value)
33
+ end
34
+ end
25
35
  end
26
36
 
27
- context 'when the method is called' do
28
- before { builder.build_methods(definitions, type) }
37
+ context 'when building a class method' do
38
+ let(:type) { described_class::CLASS_METHOD }
29
39
 
30
40
  it do
31
- expect(instance.the_method).to eq(value)
41
+ expect { builder.build_methods(definitions, type) }
42
+ .to add_class_method(method_name).to(klass)
43
+ end
44
+
45
+ context 'when the method is called' do
46
+ before { builder.build_methods(definitions, type) }
47
+
48
+ it do
49
+ expect(klass.the_method).to eq(value)
50
+ end
32
51
  end
33
52
  end
34
53
  end
35
54
 
36
- context 'when building a class method' do
37
- let(:type) { described_class::CLASS_METHOD }
55
+ context 'when the method is a block definition' do
56
+ before do
57
+ result = value
58
+ definitions.add(method_name) { result }
59
+ end
60
+
61
+ context 'when building an instance method' do
62
+ let(:type) { described_class::INSTANCE_METHOD }
63
+
64
+ it do
65
+ expect { builder.build_methods(definitions, type) }
66
+ .to add_method(method_name).to(instance)
67
+ end
38
68
 
39
- it do
40
- expect { builder.build_methods(definitions, type) }
41
- .to add_class_method(method_name).to(klass)
69
+ context 'when the method is called' do
70
+ before { builder.build_methods(definitions, type) }
71
+
72
+ it do
73
+ expect(instance.the_method).to eq(value)
74
+ end
75
+ end
42
76
  end
43
77
 
44
- context 'when the method is called' do
45
- before { builder.build_methods(definitions, type) }
78
+ context 'when building a class method' do
79
+ let(:type) { described_class::CLASS_METHOD }
46
80
 
47
81
  it do
48
- expect(klass.the_method).to eq(value)
82
+ expect { builder.build_methods(definitions, type) }
83
+ .to add_class_method(method_name).to(klass)
84
+ end
85
+
86
+ context 'when the method is called' do
87
+ before { builder.build_methods(definitions, type) }
88
+
89
+ it do
90
+ expect(klass.the_method).to eq(value)
91
+ end
49
92
  end
50
93
  end
51
94
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::MethodDefinition::CallDefinition do
6
+ subject(:definition) do
7
+ described_class.new(call_name, *attributes)
8
+ end
9
+
10
+ let(:call_name) { :method_call }
11
+ let(:attributes) { %i[key1 value2] }
12
+
13
+ describe '#code_string' do
14
+ let(:expected) { 'method_call :key1, :value2' }
15
+
16
+ it 'returns the code string' do
17
+ expect(definition.code_string)
18
+ .to eq(expected)
19
+ end
20
+ end
21
+
22
+ describe '#class_code_string' do
23
+ let(:expected) do
24
+ <<-RUBY
25
+ class << self
26
+ method_call :key1, :value2
27
+ end
28
+ RUBY
29
+ end
30
+
31
+ it 'returns the code string' do
32
+ expect(definition.class_code_string.gsub(/^ */, ''))
33
+ .to eq(expected.gsub(/^ */, ''))
34
+ end
35
+ end
36
+ end