arbo 1.2.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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yaml +21 -0
  3. data/.github/workflows/daily.yaml +23 -0
  4. data/.gitignore +11 -0
  5. data/CHANGELOG.md +95 -0
  6. data/CONTRIBUTING.md +3 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE +20 -0
  9. data/README.md +29 -0
  10. data/Rakefile +18 -0
  11. data/arbo.gemspec +25 -0
  12. data/docs/Gemfile +2 -0
  13. data/docs/_config.yml +7 -0
  14. data/docs/_includes/footer.html +8 -0
  15. data/docs/_includes/google-analytics.html +16 -0
  16. data/docs/_includes/head.html +7 -0
  17. data/docs/_includes/toc.html +12 -0
  18. data/docs/_includes/top-menu.html +8 -0
  19. data/docs/_layouts/default.html +21 -0
  20. data/docs/index.md +106 -0
  21. data/docs/stylesheets/main.css +1152 -0
  22. data/lib/arbo/component.rb +22 -0
  23. data/lib/arbo/context.rb +118 -0
  24. data/lib/arbo/element/builder_methods.rb +83 -0
  25. data/lib/arbo/element/proxy.rb +28 -0
  26. data/lib/arbo/element.rb +225 -0
  27. data/lib/arbo/element_collection.rb +31 -0
  28. data/lib/arbo/html/attributes.rb +41 -0
  29. data/lib/arbo/html/class_list.rb +28 -0
  30. data/lib/arbo/html/document.rb +31 -0
  31. data/lib/arbo/html/html5_elements.rb +47 -0
  32. data/lib/arbo/html/tag.rb +220 -0
  33. data/lib/arbo/html/text_node.rb +43 -0
  34. data/lib/arbo/rails/forms.rb +101 -0
  35. data/lib/arbo/rails/rendering.rb +17 -0
  36. data/lib/arbo/rails/template_handler.rb +35 -0
  37. data/lib/arbo/rails.rb +5 -0
  38. data/lib/arbo/version.rb +3 -0
  39. data/lib/arbo.rb +21 -0
  40. data/spec/arbo/integration/html_spec.rb +307 -0
  41. data/spec/arbo/unit/component_spec.rb +54 -0
  42. data/spec/arbo/unit/context_spec.rb +35 -0
  43. data/spec/arbo/unit/element_finder_methods_spec.rb +116 -0
  44. data/spec/arbo/unit/element_spec.rb +272 -0
  45. data/spec/arbo/unit/html/class_list_spec.rb +16 -0
  46. data/spec/arbo/unit/html/tag_attributes_spec.rb +104 -0
  47. data/spec/arbo/unit/html/tag_spec.rb +124 -0
  48. data/spec/arbo/unit/html/text_node_spec.rb +5 -0
  49. data/spec/rails/integration/forms_spec.rb +117 -0
  50. data/spec/rails/integration/rendering_spec.rb +98 -0
  51. data/spec/rails/rails_spec_helper.rb +46 -0
  52. data/spec/rails/stub_app/config/database.yml +3 -0
  53. data/spec/rails/stub_app/config/routes.rb +3 -0
  54. data/spec/rails/stub_app/db/schema.rb +3 -0
  55. data/spec/rails/stub_app/log/.gitignore +1 -0
  56. data/spec/rails/stub_app/public/favicon.ico +0 -0
  57. data/spec/rails/support/mock_person.rb +15 -0
  58. data/spec/rails/templates/arbo/_partial.arb +1 -0
  59. data/spec/rails/templates/arbo/_partial_with_assignment.arb +1 -0
  60. data/spec/rails/templates/arbo/empty.arb +0 -0
  61. data/spec/rails/templates/arbo/page_with_arb_partial_and_assignment.arb +3 -0
  62. data/spec/rails/templates/arbo/page_with_assignment.arb +1 -0
  63. data/spec/rails/templates/arbo/page_with_erb_partial.arb +3 -0
  64. data/spec/rails/templates/arbo/page_with_helpers.arb +7 -0
  65. data/spec/rails/templates/arbo/page_with_partial.arb +3 -0
  66. data/spec/rails/templates/arbo/simple_page.arb +8 -0
  67. data/spec/rails/templates/erb/_partial.erb +1 -0
  68. data/spec/spec_helper.rb +7 -0
  69. data/spec/support/bundle.rb +4 -0
  70. metadata +169 -0
@@ -0,0 +1,272 @@
1
+ require 'spec_helper'
2
+ require 'timeout'
3
+
4
+ describe Arbo::Element do
5
+
6
+ let(:element){ Arbo::Element.new }
7
+
8
+ context "when initialized" do
9
+
10
+ it "should have no children" do
11
+ expect(element.children).to be_empty
12
+ end
13
+
14
+ it "should have no parent" do
15
+ expect(element.parent).to be_nil
16
+ end
17
+
18
+ it "should respond to the HTML builder methods" do
19
+ expect(element).to respond_to(:span)
20
+ end
21
+
22
+ it "should have a set of local assigns" do
23
+ context = Arbo::Context.new hello: "World"
24
+ element = Arbo::Element.new(context)
25
+ expect(element.assigns[:hello]).to eq("World")
26
+ end
27
+
28
+ it "should have an empty hash with no local assigns" do
29
+ expect(element.assigns).to eq({})
30
+ end
31
+
32
+ end
33
+
34
+ describe "passing in a helper object" do
35
+
36
+ let(:helper) do
37
+ Class.new do
38
+ def helper_method
39
+ "helper method"
40
+ end
41
+ end
42
+ end
43
+
44
+ let(:element){ Arbo::Element.new(Arbo::Context.new(nil, helper.new)) }
45
+
46
+ it "should call methods on the helper object and return TextNode objects" do
47
+ expect(element.helper_method).to eq("helper method")
48
+ end
49
+
50
+ it "should raise a NoMethodError if not found" do
51
+ expect {
52
+ element.a_method_that_doesnt_exist
53
+ }.to raise_error(NoMethodError)
54
+ end
55
+
56
+ end
57
+
58
+ describe "passing in assigns" do
59
+ let(:post){ double }
60
+ let(:assigns){ {post: post} }
61
+
62
+ it "should be accessible via a method call" do
63
+ element = Arbo::Element.new(Arbo::Context.new(assigns))
64
+ expect(element.post).to eq(post)
65
+ end
66
+
67
+ end
68
+
69
+ it "to_a.flatten should not infinitely recurse" do
70
+ Timeout.timeout(1) do
71
+ element.to_a.flatten
72
+ end
73
+ end
74
+
75
+ describe "adding a child" do
76
+
77
+ let(:child){ Arbo::Element.new }
78
+
79
+ before do
80
+ element.add_child child
81
+ end
82
+
83
+ it "should add the child to the parent" do
84
+ expect(element.children.first).to eq(child)
85
+ end
86
+
87
+ it "should set the parent of the child" do
88
+ expect(child.parent).to eq(element)
89
+ end
90
+
91
+ context "when the child is nil" do
92
+
93
+ let(:child){ nil }
94
+
95
+ it "should not add the child" do
96
+ expect(element.children).to be_empty
97
+ end
98
+
99
+ end
100
+
101
+ context "when the child is a string" do
102
+
103
+ let(:child){ "Hello World" }
104
+
105
+ it "should add as a TextNode" do
106
+ expect(element.children.first).to be_instance_of(Arbo::HTML::TextNode)
107
+ expect(element.children.first.to_s).to eq("Hello World")
108
+ end
109
+
110
+ end
111
+ end
112
+
113
+ describe "setting the content" do
114
+
115
+ context "when a string" do
116
+
117
+ before do
118
+ element.add_child "Hello World"
119
+ element.content = "Goodbye"
120
+ end
121
+
122
+ it "should clear the existing children" do
123
+ expect(element.children.size).to eq(1)
124
+ end
125
+
126
+ it "should add the string as a child" do
127
+ expect(element.children.first.to_s).to eq("Goodbye")
128
+ end
129
+
130
+ it "should html escape the string" do
131
+ string = "Goodbye <br />"
132
+ element.content = string
133
+ expect(element.content.to_s).to eq("Goodbye &lt;br /&gt;")
134
+ end
135
+ end
136
+
137
+ context "when an element" do
138
+ let(:content_element){ Arbo::Element.new }
139
+
140
+ before do
141
+ element.content = content_element
142
+ end
143
+
144
+ it "should set the content tag" do
145
+ expect(element.children.first).to eq(content_element)
146
+ end
147
+
148
+ it "should set the tags parent" do
149
+ expect(content_element.parent).to eq(element)
150
+ end
151
+ end
152
+
153
+ context "when an array of tags" do
154
+ let(:first){ Arbo::Element.new }
155
+ let(:second){ Arbo::Element.new }
156
+
157
+ before do
158
+ element.content = [first, second]
159
+ end
160
+
161
+ it "should set the content tag" do
162
+ expect(element.children.first).to eq(first)
163
+ end
164
+
165
+ it "should set the tags parent" do
166
+ expect(element.children.first.parent).to eq(element)
167
+ end
168
+ end
169
+
170
+ end
171
+
172
+ describe "rendering to html" do
173
+
174
+ before { @separator = $, }
175
+ after { $, = @separator }
176
+ let(:collection){ element + "hello world" }
177
+
178
+ it "should render the children collection" do
179
+ expect(element.children).to receive(:to_s).and_return("content")
180
+ expect(element.to_s).to eq("content")
181
+ end
182
+
183
+ it "should render collection when is set the default separator" do
184
+ $, = "_"
185
+ expect(collection.to_s).to eq("hello world")
186
+ end
187
+
188
+ it "should render collection when is not set the default separator" do
189
+ expect(collection.to_s).to eq("hello world")
190
+ end
191
+
192
+ end
193
+
194
+ describe "adding elements together" do
195
+
196
+ context "when both elements are tags" do
197
+ let(:first){ Arbo::Element.new }
198
+ let(:second){ Arbo::Element.new }
199
+ let(:collection){ first + second }
200
+
201
+ it "should return an instance of Collection" do
202
+ expect(collection).to be_an_instance_of(Arbo::ElementCollection)
203
+ end
204
+
205
+ it "should return the elements in the collection" do
206
+ expect(collection.size).to eq(2)
207
+ expect(collection.first).to eq(first)
208
+ expect(collection[1]).to eq(second)
209
+ end
210
+ end
211
+
212
+ context "when the left is a collection and the right is a tag" do
213
+ let(:first){ Arbo::Element.new }
214
+ let(:second){ Arbo::Element.new }
215
+ let(:third){ Arbo::Element.new }
216
+ let(:collection){ Arbo::ElementCollection.new([first, second]) + third}
217
+
218
+ it "should return an instance of Collection" do
219
+ expect(collection).to be_an_instance_of(Arbo::ElementCollection)
220
+ end
221
+
222
+ it "should return the elements in the collection flattened" do
223
+ expect(collection.size).to eq(3)
224
+ expect(collection[0]).to eq(first)
225
+ expect(collection[1]).to eq(second)
226
+ expect(collection[2]).to eq(third)
227
+ end
228
+ end
229
+
230
+ context "when the right is a collection and the left is a tag" do
231
+ let(:first){ Arbo::Element.new }
232
+ let(:second){ Arbo::Element.new }
233
+ let(:third){ Arbo::Element.new }
234
+ let(:collection){ first + Arbo::ElementCollection.new([second,third]) }
235
+
236
+ it "should return an instance of Collection" do
237
+ expect(collection).to be_an_instance_of(Arbo::ElementCollection)
238
+ end
239
+
240
+ it "should return the elements in the collection flattened" do
241
+ expect(collection.size).to eq(3)
242
+ expect(collection[0]).to eq(first)
243
+ expect(collection[1]).to eq(second)
244
+ expect(collection[2]).to eq(third)
245
+ end
246
+ end
247
+
248
+ context "when the left is a tag and the right is a string" do
249
+ let(:element){ Arbo::Element.new }
250
+ let(:collection){ element + "Hello World"}
251
+
252
+ it "should return an instance of Collection" do
253
+ expect(collection).to be_an_instance_of(Arbo::ElementCollection)
254
+ end
255
+
256
+ it "should return the elements in the collection" do
257
+ expect(collection.size).to eq(2)
258
+ expect(collection[0]).to eq(element)
259
+ expect(collection[1]).to be_an_instance_of(Arbo::HTML::TextNode)
260
+ end
261
+ end
262
+
263
+ context "when the left is a string and the right is a tag" do
264
+ let(:collection){ "hello World" + Arbo::Element.new}
265
+
266
+ it "should return a string" do
267
+ expect(collection.strip.chomp).to eq("hello World")
268
+ end
269
+ end
270
+ end
271
+
272
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Arbo::HTML::ClassList do
4
+
5
+ describe ".build_from_string" do
6
+
7
+ it "should build a new list from a string of classes" do
8
+ list = Arbo::HTML::ClassList.build_from_string("first second")
9
+ expect(list.size).to eq(2)
10
+
11
+ expect(list).to match_array(%w{first second})
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe Arbo::HTML::Tag, "Attributes" do
4
+
5
+ let(:tag){ Arbo::HTML::Tag.new }
6
+
7
+ describe "attributes" do
8
+
9
+ before { tag.build id: "my_id" }
10
+
11
+ it "should have an attributes hash" do
12
+ expect(tag.attributes).to eq({id: "my_id"})
13
+ end
14
+
15
+ describe "#to_s" do
16
+ it "should render the attributes to html" do
17
+ expect(tag.to_s).to eq "<tag id=\"my_id\"></tag>\n"
18
+ end
19
+
20
+ it "shouldn't render attributes that are empty" do
21
+ tag.class_list # initializes an empty ClassList
22
+ tag.set_attribute :foo, ''
23
+ tag.set_attribute :bar, nil
24
+
25
+ expect(tag.to_s).to eq "<tag id=\"my_id\"></tag>\n"
26
+ end
27
+
28
+ context "with hyphenated attributes" do
29
+ before { tag.build id: "my_id", "data-method" => "get", "data-remote" => true }
30
+
31
+ it "should render the attributes to html" do
32
+ expect(tag.to_s).to eq "<tag id=\"my_id\" data-method=\"get\" data-remote=\"true\"></tag>\n"
33
+ end
34
+ end
35
+
36
+ context "when there is a nested attribute" do
37
+ before { tag.build id: "my_id", data: { action: 'some_action' } }
38
+
39
+ it "should flatten the attributes when rendering to html" do
40
+ expect(tag.to_s).to eq "<tag id=\"my_id\" data-action=\"some_action\"></tag>\n"
41
+ end
42
+
43
+ it "shouldn't render attributes that are empty" do
44
+ tag.class_list # initializes an empty ClassList
45
+ tag.set_attribute :foo, { bar: '' }
46
+ tag.set_attribute :bar, { baz: nil }
47
+
48
+ expect(tag.to_s).to eq "<tag id=\"my_id\" data-action=\"some_action\"></tag>\n"
49
+ end
50
+ end
51
+
52
+ context "when there is a deeply nested attribute" do
53
+ before { tag.build id: "my_id", foo: { bar: { baz: 'foozle' } } }
54
+
55
+ it "should flatten the attributes when rendering to html" do
56
+ expect(tag.to_s).to eq "<tag id=\"my_id\" foo-bar-baz=\"foozle\"></tag>\n"
57
+ end
58
+ end
59
+
60
+ context "when there are multiple nested attributes" do
61
+ before { tag.build id: "my_id", foo: { bar: 'foozle1', baz: 'foozle2' } }
62
+
63
+ it "should flatten the attributes when rendering to html" do
64
+ expect(tag.to_s).to eq "<tag id=\"my_id\" foo-bar=\"foozle1\" foo-baz=\"foozle2\"></tag>\n"
65
+ end
66
+ end
67
+ end
68
+
69
+ it "should get an attribute value" do
70
+ expect(tag.attr(:id)).to eq("my_id")
71
+ end
72
+
73
+ describe "#has_attribute?" do
74
+ context "when the attribute exists" do
75
+ it "should return true" do
76
+ expect(tag.has_attribute?(:id)).to eq(true)
77
+ end
78
+ end
79
+
80
+ context "when the attribute does not exist" do
81
+ it "should return false" do
82
+ expect(tag.has_attribute?(:class)).to eq(false)
83
+ end
84
+ end
85
+ end
86
+
87
+ it "should remove an attribute" do
88
+ expect(tag.attributes).to eq({id: "my_id"})
89
+ expect(tag.remove_attribute(:id)).to eq("my_id")
90
+ expect(tag.attributes).to eq({})
91
+ end
92
+ end
93
+
94
+ describe "rendering attributes" do
95
+ it "should html safe the attribute values" do
96
+ tag.set_attribute(:class, '">bad things!')
97
+ expect(tag.to_s).to eq "<tag class=\"&quot;&gt;bad things!\"></tag>\n"
98
+ end
99
+ it "should should escape the attribute names" do
100
+ tag.set_attribute(">bad", "things")
101
+ expect(tag.to_s).to eq "<tag &gt;bad=\"things\"></tag>\n"
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ describe Arbo::HTML::Tag do
4
+
5
+ let(:tag){ Arbo::HTML::Tag.new }
6
+
7
+ describe "building a new tag" do
8
+ before { tag.build "Hello World", id: "my_id" }
9
+
10
+ it "should set the contents to a string" do
11
+ expect(tag.content).to eq("Hello World")
12
+ end
13
+
14
+ it "should set the hash of options to the attributes" do
15
+ expect(tag.attributes).to eq({ id: "my_id" })
16
+ end
17
+ end
18
+
19
+ describe "creating a tag 'for' an object" do
20
+ let(:model_name){ double(singular: "resource_class")}
21
+ let(:resource_class){ double(model_name: model_name) }
22
+ let(:resource){ double(class: resource_class, to_key: ['5'])}
23
+
24
+ before do
25
+ tag.build for: resource
26
+ end
27
+ it "should set the id to the type and id" do
28
+ expect(tag.id).to eq("resource_class_5")
29
+ end
30
+
31
+ it "should add a class name" do
32
+ expect(tag.class_list).to include("resource_class")
33
+ end
34
+
35
+
36
+ describe "for an object that doesn't have a model_name" do
37
+ let(:resource_class){ double(name: 'ResourceClass') }
38
+
39
+ before do
40
+ tag.build for: resource
41
+ end
42
+
43
+ it "should set the id to the type and id" do
44
+ expect(tag.id).to eq("resource_class_5")
45
+ end
46
+
47
+ it "should add a class name" do
48
+ expect(tag.class_list).to include("resource_class")
49
+ end
50
+ end
51
+
52
+ describe "with a default_id_for_prefix" do
53
+
54
+ let(:tag) do
55
+ Class.new(Arbo::HTML::Tag) do
56
+ def default_id_for_prefix
57
+ "a_prefix"
58
+ end
59
+ end.new
60
+ end
61
+
62
+ it "should set the id to the type and id" do
63
+ expect(tag.id).to eq("a_prefix_resource_class_5")
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ describe "creating a tag with a for attribute" do
70
+ it "sets the `for` attribute when a string is given" do
71
+ tag.build for: "email"
72
+ expect(tag.attributes[:for]).to eq "email"
73
+ end
74
+
75
+ it "sets the `for` attribute when a symbol is given" do
76
+ tag.build for: :email
77
+ expect(tag.attributes[:for]).to eq :email
78
+ end
79
+ end
80
+
81
+ describe "css class names" do
82
+
83
+ it "should add a class" do
84
+ tag.add_class "hello_world"
85
+ expect(tag.class_names).to eq("hello_world")
86
+ end
87
+
88
+ it "should remove_class" do
89
+ tag.add_class "hello_world"
90
+ expect(tag.class_names).to eq("hello_world")
91
+ tag.remove_class "hello_world"
92
+ expect(tag.class_names).to eq("")
93
+ end
94
+
95
+ it "should not add a class if it already exists" do
96
+ tag.add_class "hello_world"
97
+ tag.add_class "hello_world"
98
+ expect(tag.class_names).to eq("hello_world")
99
+ end
100
+
101
+ it "should seperate classes with space" do
102
+ tag.add_class "hello world"
103
+ expect(tag.class_list.size).to eq(2)
104
+ end
105
+
106
+ it "should create a class list from a string" do
107
+ tag = Arbo::HTML::Tag.new
108
+ tag.build(class: "first-class")
109
+ tag.add_class "second-class"
110
+ expect(tag.class_list.size).to eq(2)
111
+ end
112
+
113
+ end
114
+
115
+ describe "#render_in" do
116
+ before { tag.build "Hello World", id: "my_id" }
117
+
118
+ it "renders tag" do
119
+ html = tag.render_in(Arbo::Context.new)
120
+
121
+ expect(html).to eq "<tag id=\"my_id\">Hello World</tag>\n"
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Arbo::HTML::TextNode do
4
+
5
+ end
@@ -0,0 +1,117 @@
1
+ require 'rails/rails_spec_helper'
2
+
3
+ describe "Building forms" do
4
+
5
+ let(:assigns){ {} }
6
+ let(:helpers){ mock_action_view }
7
+ let(:html) { form.to_s }
8
+ let(:output_buffer) { form.render_in(form) }
9
+
10
+ describe "building a simple form for" do
11
+
12
+ let(:form) do
13
+ arbo do
14
+ form_for MockPerson.new, url: "/" do |f|
15
+ f.label :name
16
+ f.text_field :name
17
+ end
18
+ end
19
+ end
20
+
21
+ it "should build a form" do
22
+ expect(html).to have_selector("form")
23
+ end
24
+
25
+ it "should include the hidden authenticity token" do
26
+ expect(html).to have_selector('input[type="hidden"][name="authenticity_token"][value="AUTH_TOKEN"]', visible: :hidden)
27
+ end
28
+
29
+ it "should create a label" do
30
+ expect(html).to have_selector("form label[for=mock_person_name]")
31
+ end
32
+
33
+ it "should create a text field" do
34
+ expect(html).to have_selector("form input[type=text]")
35
+ end
36
+
37
+ it "should render equally with each strategy" do
38
+ expect(output_buffer).to eq html
39
+ end
40
+ end
41
+
42
+ describe "building a form with fields for" do
43
+
44
+ let(:form) do
45
+ arbo do
46
+ form_for MockPerson.new, url: "/" do |f|
47
+ f.label :name
48
+ f.text_field :name
49
+ f.fields_for :permission do |pf|
50
+ pf.label :admin
51
+ pf.check_box :admin
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ it "should render nested label" do
58
+ expect(html).to have_selector("form label[for=mock_person_permission_admin]", text: "Admin")
59
+ end
60
+
61
+ it "should render nested label" do
62
+ expect(html).to have_selector("form input[type=checkbox][name='mock_person[permission][admin]']")
63
+ end
64
+
65
+ it "should not render a div for the proxy" do
66
+ expect(html).not_to have_selector("form div.fields_for_proxy")
67
+ end
68
+
69
+ it "should render equally with each strategy" do
70
+ expect(output_buffer).to eq html
71
+ end
72
+
73
+ end
74
+
75
+ describe "forms with other elements" do
76
+ let(:form) do
77
+ arbo do
78
+ form_for MockPerson.new, url: "/" do |f|
79
+
80
+ div do
81
+ f.label :name
82
+ f.text_field :name
83
+ end
84
+
85
+ para do
86
+ f.label :name
87
+ f.text_field :name
88
+ end
89
+
90
+ div class: "permissions" do
91
+ f.fields_for :permission do |pf|
92
+ div class: "permissions_label" do
93
+ pf.label :admin
94
+ end
95
+ pf.check_box :admin
96
+ end
97
+ end
98
+
99
+ end
100
+ end
101
+ end
102
+
103
+ it "should correctly nest elements" do
104
+ expect(html).to have_selector("form > p > label")
105
+ end
106
+
107
+ it "should correnctly nest elements within fields for" do
108
+ expect(html).to have_selector("form > div.permissions > div.permissions_label label")
109
+ end
110
+
111
+ it "should render equally with each strategy" do
112
+ expect(output_buffer).to eq html
113
+ end
114
+ end
115
+
116
+
117
+ end