hanami 2.0.2 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +25 -9
  5. data/hanami.gemspec +2 -2
  6. data/lib/hanami/config/actions.rb +0 -4
  7. data/lib/hanami/config/logger.rb +1 -1
  8. data/lib/hanami/config/views.rb +0 -4
  9. data/lib/hanami/config.rb +54 -0
  10. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  11. data/lib/hanami/extensions/action.rb +4 -4
  12. data/lib/hanami/extensions/router/errors.rb +58 -0
  13. data/lib/hanami/extensions/view/context.rb +129 -60
  14. data/lib/hanami/extensions/view/part.rb +26 -0
  15. data/lib/hanami/extensions/view/scope.rb +26 -0
  16. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  17. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  18. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  19. data/lib/hanami/extensions/view/standard_helpers.rb +14 -0
  20. data/lib/hanami/extensions.rb +10 -3
  21. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  22. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  23. data/lib/hanami/helpers/form_helper.rb +213 -0
  24. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  25. data/lib/hanami/middleware/render_errors.rb +93 -0
  26. data/lib/hanami/slice.rb +28 -2
  27. data/lib/hanami/slice_configurable.rb +3 -2
  28. data/lib/hanami/version.rb +1 -1
  29. data/lib/hanami/web/rack_logger.rb +8 -20
  30. data/lib/hanami.rb +1 -1
  31. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  32. data/spec/integration/action/view_rendering_spec.rb +0 -18
  33. data/spec/integration/rack_app/middleware_spec.rb +23 -23
  34. data/spec/integration/rack_app/rack_app_spec.rb +5 -1
  35. data/spec/integration/slices/slice_registrations_spec.rb +80 -0
  36. data/spec/integration/view/config/default_context_spec.rb +149 -0
  37. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  38. data/spec/integration/view/config/part_class_spec.rb +147 -0
  39. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  40. data/spec/integration/view/config/paths_spec.rb +119 -0
  41. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  42. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  43. data/spec/integration/view/config/template_spec.rb +38 -0
  44. data/spec/integration/view/context/request_spec.rb +3 -7
  45. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  46. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  47. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  48. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  49. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  50. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  51. data/spec/integration/web/render_detailed_errors_spec.rb +90 -0
  52. data/spec/integration/web/render_errors_spec.rb +240 -0
  53. data/spec/spec_helper.rb +1 -1
  54. data/spec/support/matchers.rb +32 -0
  55. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -4
  56. data/spec/unit/hanami/config/logger_spec.rb +9 -0
  57. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  58. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  59. data/spec/unit/hanami/config/views_spec.rb +0 -18
  60. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  61. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  62. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  63. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  64. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  65. data/spec/unit/hanami/version_spec.rb +1 -1
  66. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  67. metadata +67 -33
  68. data/spec/integration/action/view_integration_spec.rb +0 -165
  69. data/spec/integration/view/part_namespace_spec.rb +0 -96
  70. data/spec/integration/view/path_spec.rb +0 -56
  71. data/spec/integration/view/template_spec.rb +0 -68
  72. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  73. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  74. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  75. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  76. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  77. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  78. data/spec/isolation/hanami/boot/success_spec.rb +0 -50
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+ require "stringio"
5
+
6
+ RSpec.describe "Helpers / FormHelper", :app_integration do
7
+ include Rack::Test::Methods
8
+ let(:app) { Hanami.app }
9
+
10
+ before do
11
+ with_directory(make_tmp_directory) do
12
+ write "config/app.rb", <<~RUBY
13
+ module TestApp
14
+ class App < Hanami::App
15
+ config.logger.stream = StringIO.new
16
+ end
17
+ end
18
+ RUBY
19
+
20
+ write "config/routes.rb", <<~RUBY
21
+ module TestApp
22
+ class Routes < Hanami::Routes
23
+ get "posts/:id/edit", to: "posts.edit"
24
+ put "posts/:id", to: "posts.update"
25
+ end
26
+ end
27
+ RUBY
28
+
29
+ write "app/action.rb", <<~RUBY
30
+ # auto_register: false
31
+
32
+ require "hanami/action"
33
+
34
+ module TestApp
35
+ class Action < Hanami::Action
36
+ end
37
+ end
38
+ RUBY
39
+
40
+ write "app/view.rb", <<~RUBY
41
+ # auto_register: false
42
+
43
+ require "hanami/view"
44
+
45
+ module TestApp
46
+ class View < Hanami::View
47
+ config.layout = nil
48
+ end
49
+ end
50
+ RUBY
51
+
52
+ write "app/actions/posts/edit.rb", <<~RUBY
53
+ module TestApp
54
+ module Actions
55
+ module Posts
56
+ class Edit < TestApp::Action
57
+ end
58
+ end
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ write "app/actions/posts/update.rb", <<~RUBY
64
+ module TestApp
65
+ module Actions
66
+ module Posts
67
+ class Update < TestApp::Action
68
+ def handle(request, response)
69
+ if valid?(request.params[:post])
70
+ response.redirect_to "/posts/x/edit"
71
+ else
72
+ response.render view
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def valid?(post)
79
+ post.to_h[:title].to_s.length > 0
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ RUBY
86
+
87
+ write "app/views/posts/edit.rb", <<~RUBY
88
+ module TestApp
89
+ module Views
90
+ module Posts
91
+ class Edit < TestApp::View
92
+ expose :post do
93
+ Struct.new(:title, :body).new("Hello <world>", "This is the post.")
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ RUBY
100
+
101
+ write "app/templates/posts/edit.html.erb", <<~ERB
102
+ <h1>Edit post</h1>
103
+
104
+ <%= form_for("/posts") do |f| %>
105
+ <div>
106
+ Title:
107
+ <%= f.text_field "post.title" %>
108
+ </div>
109
+ <div>
110
+ Body:
111
+ <%= f.text_area "post.body" %>
112
+ </div>
113
+ <% end %>
114
+ ERB
115
+
116
+ before_prepare if respond_to?(:before_prepare)
117
+ require "hanami/prepare"
118
+ end
119
+ end
120
+
121
+ it "does not have a _csrf_token field when no sessions are configured" do
122
+ get "/posts/123/edit"
123
+
124
+ html = Capybara.string(last_response.body)
125
+
126
+ expect(html).to have_no_selector("input[type='hidden'][name='_csrf_token']")
127
+ end
128
+
129
+ it "uses the value from the view's locals" do
130
+ get "/posts/123/edit"
131
+
132
+ html = Capybara.string(last_response.body)
133
+
134
+ title_field = html.find("input[name='post[title]']")
135
+ expect(title_field.value).to eq "Hello <world>"
136
+
137
+ body_field = html.find("textarea[name='post[body]']")
138
+ expect(body_field.value).to eq "This is the post."
139
+ end
140
+
141
+ it "prefers the values from the request params" do
142
+ put "/posts/123", post: {title: "", body: "This is the UPDATED post."}
143
+
144
+ html = Capybara.string(last_response.body)
145
+
146
+ title_field = html.find("input[name='post[title]']")
147
+ expect(title_field.value).to eq ""
148
+
149
+ body_field = html.find("textarea[name='post[body]']")
150
+ expect(body_field.value).to eq "This is the UPDATED post."
151
+ end
152
+
153
+ context "sessions enabled" do
154
+ def before_prepare
155
+ write "config/app.rb", <<~RUBY
156
+ module TestApp
157
+ class App < Hanami::App
158
+ config.logger.stream = StringIO.new
159
+ config.actions.sessions = :cookie, {secret: "xyz"}
160
+ end
161
+ end
162
+ RUBY
163
+ end
164
+
165
+ it "inserts a CSRF token field" do
166
+ get "/posts/123/edit"
167
+
168
+ html = Capybara.string(last_response.body)
169
+
170
+ csrf_field = html.find("input[type='hidden'][name='_csrf_token']", visible: false)
171
+ expect(csrf_field.value).to match(/[a-z0-9]{10,}/i)
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+
5
+ # rubocop:disable Style/OpenStructUse
6
+
7
+ RSpec.describe "App view / Helpers / Part helpers", :app_integration do
8
+ before do
9
+ with_directory(make_tmp_directory) do
10
+ write "config/app.rb", <<~RUBY
11
+ module TestApp
12
+ class App < Hanami::App
13
+ end
14
+ end
15
+ RUBY
16
+
17
+ write "app/view.rb", <<~RUBY
18
+ # auto_register: false
19
+
20
+ require "hanami/view"
21
+
22
+ module TestApp
23
+ class View < Hanami::View
24
+ config.layout = nil
25
+ end
26
+ end
27
+ RUBY
28
+
29
+ before_prepare if respond_to?(:before_prepare)
30
+ require "hanami/prepare"
31
+ end
32
+ end
33
+
34
+ describe "app view and parts" do
35
+ def before_prepare
36
+ write "app/views/posts/show.rb", <<~RUBY
37
+ module TestApp
38
+ module Views
39
+ module Posts
40
+ class Show < TestApp::View
41
+ expose :post
42
+ end
43
+ end
44
+ end
45
+ end
46
+ RUBY
47
+
48
+ write "app/views/parts/post.rb", <<~RUBY
49
+ module TestApp
50
+ module Views
51
+ module Parts
52
+ class Post < TestApp::Views::Part
53
+ def number
54
+ format_number(value.number)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ RUBY
61
+
62
+ write "app/templates/posts/show.html.erb", <<~ERB
63
+ <h1><%= post.number %></h1>
64
+ ERB
65
+ end
66
+
67
+ it "makes default helpers available in parts" do
68
+ post = OpenStruct.new(number: 12_345)
69
+ output = TestApp::App["views.posts.show"].call(post: post).to_s.strip
70
+
71
+ expect(output).to eq "<h1>12,345</h1>"
72
+ end
73
+ end
74
+
75
+ describe "slice view and parts" do
76
+ def before_prepare
77
+ write "slices/main/view.rb", <<~RUBY
78
+ module Main
79
+ class View < TestApp::View
80
+ end
81
+ end
82
+ RUBY
83
+
84
+ write "slices/main/views/posts/show.rb", <<~RUBY
85
+ module Main
86
+ module Views
87
+ module Posts
88
+ class Show < Main::View
89
+ expose :post
90
+ end
91
+ end
92
+ end
93
+ end
94
+ RUBY
95
+
96
+ write "slices/main/views/parts/post.rb", <<~RUBY
97
+ module Main
98
+ module Views
99
+ module Parts
100
+ class Post < Main::Views::Part
101
+ def number
102
+ format_number(value.number)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ RUBY
109
+
110
+ write "slices/main/templates/posts/show.html.erb", <<~ERB
111
+ <h1><%= post.number %></h1>
112
+ ERB
113
+ end
114
+
115
+ it "makes default helpers available in parts" do
116
+ post = OpenStruct.new(number: 12_345)
117
+ output = Main::Slice["views.posts.show"].call(post: post).to_s.strip
118
+
119
+ expect(output).to eq "<h1>12,345</h1>"
120
+ end
121
+ end
122
+ end
123
+
124
+ # rubocop:enable Style/OpenStructUse
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App view / Helpers / Scope helpers", :app_integration do
4
+ before do
5
+ with_directory(make_tmp_directory) do
6
+ write "config/app.rb", <<~RUBY
7
+ module TestApp
8
+ class App < Hanami::App
9
+ end
10
+ end
11
+ RUBY
12
+
13
+ write "app/view.rb", <<~RUBY
14
+ # auto_register: false
15
+
16
+ require "hanami/view"
17
+
18
+ module TestApp
19
+ class View < Hanami::View
20
+ config.layout = nil
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ before_prepare if respond_to?(:before_prepare)
26
+ require "hanami/prepare"
27
+ end
28
+ end
29
+
30
+ describe "app view" do
31
+ def before_prepare
32
+ write "app/views/posts/show.rb", <<~RUBY
33
+ module TestApp
34
+ module Views
35
+ module Posts
36
+ class Show < TestApp::View
37
+ end
38
+ end
39
+ end
40
+ end
41
+ RUBY
42
+
43
+ write "app/templates/posts/show.html.erb", <<~ERB
44
+ <h1><%= format_number(12_345) %></h1>
45
+ ERB
46
+ end
47
+
48
+ it "makes default helpers available in templates" do
49
+ output = TestApp::App["views.posts.show"].call.to_s.strip
50
+ expect(output).to eq "<h1>12,345</h1>"
51
+ end
52
+ end
53
+
54
+ describe "slice view" do
55
+ def before_prepare
56
+ write "slices/main/view.rb", <<~RUBY
57
+ module Main
58
+ class View < TestApp::View
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ write "slices/main/views/posts/show.rb", <<~RUBY
64
+ module Main
65
+ module Views
66
+ module Posts
67
+ class Show < Main::View
68
+ end
69
+ end
70
+ end
71
+ end
72
+ RUBY
73
+
74
+ write "slices/main/templates/posts/show.html.erb", <<~ERB
75
+ <h1><%= format_number(12_345) %></h1>
76
+ ERB
77
+ end
78
+
79
+ it "makes default helpers available in templates" do
80
+ output = Main::Slice["views.posts.show"].call.to_s.strip
81
+ expect(output).to eq "<h1>12,345</h1>"
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+
5
+ # rubocop:disable Style/OpenStructUse
6
+
7
+ RSpec.describe "App view / Helpers / User-defined helpers / Scope helpers", :app_integration do
8
+ before do
9
+ with_directory(make_tmp_directory) do
10
+ write "config/app.rb", <<~RUBY
11
+ module TestApp
12
+ class App < Hanami::App
13
+ end
14
+ end
15
+ RUBY
16
+
17
+ write "app/view.rb", <<~RUBY
18
+ # auto_register: false
19
+
20
+ require "hanami/view"
21
+
22
+ module TestApp
23
+ class View < Hanami::View
24
+ config.layout = nil
25
+ end
26
+ end
27
+ RUBY
28
+
29
+ write "app/views/helpers.rb", <<~'RUBY'
30
+ # auto_register: false
31
+
32
+ module TestApp
33
+ module Views
34
+ module Helpers
35
+ def exclaim_from_app(str)
36
+ tag.h1("#{str}! (app helper)")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ RUBY
42
+
43
+ before_prepare if respond_to?(:before_prepare)
44
+ require "hanami/prepare"
45
+ end
46
+ end
47
+
48
+ describe "app view and parts" do
49
+ def before_prepare
50
+ write "app/views/posts/show.rb", <<~RUBY
51
+ module TestApp
52
+ module Views
53
+ module Posts
54
+ class Show < TestApp::View
55
+ expose :post
56
+ end
57
+ end
58
+ end
59
+ end
60
+ RUBY
61
+
62
+ write "app/views/parts/post.rb", <<~RUBY
63
+ module TestApp
64
+ module Views
65
+ module Parts
66
+ class Post < TestApp::Views::Part
67
+ def title
68
+ exclaim_from_app(value.title)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ RUBY
75
+
76
+ write "app/templates/posts/show.html.erb", <<~ERB
77
+ <%= post.title %>
78
+ ERB
79
+ end
80
+
81
+ it "makes user-defined helpers available in parts" do
82
+ post = OpenStruct.new(title: "Hello world")
83
+ output = TestApp::App["views.posts.show"].call(post: post).to_s.strip
84
+
85
+ expect(output).to eq "<h1>Hello world! (app helper)</h1>"
86
+ end
87
+ end
88
+
89
+ describe "slice view and parts" do
90
+ def before_prepare
91
+ write "slices/main/view.rb", <<~RUBY
92
+ module Main
93
+ class View < TestApp::View
94
+ # FIXME: base slice views should override paths from the base app view
95
+ config.paths = [File.join(File.expand_path(__dir__), "templates")]
96
+ end
97
+ end
98
+ RUBY
99
+
100
+ write "slices/main/views/helpers.rb", <<~'RUBY'
101
+ # auto_register: false
102
+
103
+ module Main
104
+ module Views
105
+ module Helpers
106
+ def exclaim_from_slice(str)
107
+ tag.h1("#{str}! (slice helper)")
108
+ end
109
+ end
110
+ end
111
+ end
112
+ RUBY
113
+
114
+ write "slices/main/views/posts/show.rb", <<~RUBY
115
+ module Main
116
+ module Views
117
+ module Posts
118
+ class Show < Main::View
119
+ expose :post
120
+ end
121
+ end
122
+ end
123
+ end
124
+ RUBY
125
+
126
+ write "slices/main/views/parts/post.rb", <<~RUBY
127
+ module Main
128
+ module Views
129
+ module Parts
130
+ class Post < Main::Views::Part
131
+ def title
132
+ exclaim_from_slice(value.title)
133
+ end
134
+
135
+ def title_from_app
136
+ exclaim_from_app(value.title)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ RUBY
143
+
144
+ write "slices/main/templates/posts/show.html.erb", <<~ERB
145
+ <%= post.title %>
146
+ <%= post.title_from_app %>
147
+ ERB
148
+ end
149
+
150
+ it "makes user-defined helpers (from app as well as slice) available in parts" do
151
+ post = OpenStruct.new(title: "Hello world")
152
+ output = Main::Slice["views.posts.show"].call(post: post).to_s
153
+
154
+ expect(output).to eq <<~HTML
155
+ <h1>Hello world! (slice helper)</h1>
156
+ <h1>Hello world! (app helper)</h1>
157
+ HTML
158
+ end
159
+ end
160
+ end
161
+
162
+ # rubocop:enable Style/OpenStructUse
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App view / Helpers / User-defined helpers / Scope helpers", :app_integration do
4
+ before do
5
+ with_directory(make_tmp_directory) do
6
+ write "config/app.rb", <<~RUBY
7
+ module TestApp
8
+ class App < Hanami::App
9
+ end
10
+ end
11
+ RUBY
12
+
13
+ write "app/view.rb", <<~RUBY
14
+ # auto_register: false
15
+
16
+ require "hanami/view"
17
+
18
+ module TestApp
19
+ class View < Hanami::View
20
+ config.layout = nil
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ write "app/views/helpers.rb", <<~'RUBY'
26
+ # auto_register: false
27
+
28
+ module TestApp
29
+ module Views
30
+ module Helpers
31
+ def exclaim_from_app(str)
32
+ tag.h1("#{str}! (app helper)")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ RUBY
38
+
39
+ before_prepare if respond_to?(:before_prepare)
40
+ require "hanami/prepare"
41
+ end
42
+ end
43
+
44
+ describe "app view" do
45
+ def before_prepare
46
+ write "app/views/posts/show.rb", <<~RUBY
47
+ module TestApp
48
+ module Views
49
+ module Posts
50
+ class Show < TestApp::View
51
+ end
52
+ end
53
+ end
54
+ end
55
+ RUBY
56
+
57
+ write "app/templates/posts/show.html.erb", <<~ERB
58
+ <%= exclaim_from_app("Hello world") %>
59
+ ERB
60
+ end
61
+
62
+ it "makes user-defined helpers available in templates" do
63
+ output = TestApp::App["views.posts.show"].call.to_s.strip
64
+ expect(output).to eq "<h1>Hello world! (app helper)</h1>"
65
+ end
66
+ end
67
+
68
+ describe "slice view" do
69
+ def before_prepare
70
+ write "slices/main/view.rb", <<~RUBY
71
+ module Main
72
+ class View < TestApp::View
73
+ # FIXME: base slice views should override paths from the base app view
74
+ config.paths = [File.join(File.expand_path(__dir__), "templates")]
75
+ end
76
+ end
77
+ RUBY
78
+
79
+ write "slices/main/views/helpers.rb", <<~'RUBY'
80
+ # auto_register: false
81
+
82
+ module Main
83
+ module Views
84
+ module Helpers
85
+ def exclaim_from_slice(str)
86
+ tag.h1("#{str}! (slice helper)")
87
+ end
88
+ end
89
+ end
90
+ end
91
+ RUBY
92
+
93
+ write "slices/main/views/posts/show.rb", <<~RUBY
94
+ module Main
95
+ module Views
96
+ module Posts
97
+ class Show < Main::View
98
+ end
99
+ end
100
+ end
101
+ end
102
+ RUBY
103
+
104
+ write "slices/main/templates/posts/show.html.erb", <<~ERB
105
+ <%= exclaim_from_slice("Hello world") %>
106
+ <%= exclaim_from_app("Hello world") %>
107
+ ERB
108
+ end
109
+
110
+ it "makes user-defined helpers (from app as well as slice) available in templates" do
111
+ output = Main::Slice["views.posts.show"].call.to_s
112
+
113
+ expect(output).to eq <<~HTML
114
+ <h1>Hello world! (slice helper)</h1>
115
+ <h1>Hello world! (app helper)</h1>
116
+ HTML
117
+ end
118
+ end
119
+ end