ruote-kit 2.1.10 → 2.1.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.
Files changed (91) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGELOG.txt +10 -0
  3. data/Gemfile +21 -5
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +132 -80
  6. data/Rakefile +4 -15
  7. data/TODO.txt +12 -0
  8. data/config.ru +22 -14
  9. data/lib/ruote-kit/application.rb +46 -22
  10. data/lib/ruote-kit/helpers/json_helpers.rb +280 -0
  11. data/lib/ruote-kit/helpers/link_helpers.rb +117 -0
  12. data/lib/ruote-kit/helpers/misc_helpers.rb +35 -0
  13. data/lib/ruote-kit/helpers/pagination_helpers.rb +30 -0
  14. data/lib/ruote-kit/helpers/render_helpers.rb +39 -92
  15. data/lib/ruote-kit/public/_ruote/images/favicon.ico +0 -0
  16. data/lib/ruote-kit/public/_ruote/images/ruote.png +0 -0
  17. data/lib/ruote-kit/public/_ruote/images/ruote_buttons.png +0 -0
  18. data/lib/ruote-kit/public/_ruote/javascripts/jquery-1.4.2.min.js +154 -0
  19. data/lib/ruote-kit/public/_ruote/javascripts/rk.js +40 -0
  20. data/lib/ruote-kit/public/_ruote/javascripts/ruote-fluo-editor.js +548 -0
  21. data/lib/ruote-kit/public/_ruote/javascripts/ruote-fluo.js +32 -1
  22. data/lib/ruote-kit/public/_ruote/stylesheets/reset.css +53 -0
  23. data/lib/ruote-kit/public/_ruote/stylesheets/rk.css +355 -16
  24. data/lib/ruote-kit/public/_ruote/stylesheets/ruote-fluo-editor.css +108 -0
  25. data/lib/ruote-kit/resources/errors.rb +46 -43
  26. data/lib/ruote-kit/resources/expressions.rb +92 -35
  27. data/lib/ruote-kit/resources/participants.rb +53 -0
  28. data/lib/ruote-kit/resources/processes.rb +68 -36
  29. data/lib/ruote-kit/resources/schedules.rb +27 -0
  30. data/lib/ruote-kit/resources/workitems.rb +93 -31
  31. data/lib/ruote-kit/spec/ruote_helpers.rb +25 -16
  32. data/lib/ruote-kit/version.rb +3 -1
  33. data/lib/ruote-kit/views/_pagination.html.haml +38 -0
  34. data/lib/ruote-kit/views/_tree_editor.html.haml +52 -0
  35. data/lib/ruote-kit/views/error.html.haml +69 -0
  36. data/lib/ruote-kit/views/errors.html.haml +28 -12
  37. data/lib/ruote-kit/views/expression.html.haml +161 -30
  38. data/lib/ruote-kit/views/expressions.html.haml +26 -21
  39. data/lib/ruote-kit/views/http_error.html.haml +11 -0
  40. data/lib/ruote-kit/views/index.html.haml +4 -0
  41. data/lib/ruote-kit/views/layout.html.haml +52 -32
  42. data/lib/ruote-kit/views/participants.html.haml +79 -0
  43. data/lib/ruote-kit/views/process.html.haml +109 -42
  44. data/lib/ruote-kit/views/process_launched.html.haml +6 -2
  45. data/lib/ruote-kit/views/processes.html.haml +55 -24
  46. data/lib/ruote-kit/views/processes_new.html.haml +36 -0
  47. data/lib/ruote-kit/views/schedules.html.haml +42 -0
  48. data/lib/ruote-kit/views/workitem.html.haml +83 -29
  49. data/lib/ruote-kit/views/workitems.html.haml +42 -19
  50. data/lib/ruote-kit.rb +17 -88
  51. data/ruote-kit.gemspec +68 -80
  52. data/spec/it_has_an_engine.rb +69 -0
  53. data/spec/resources/errors_spec.rb +198 -270
  54. data/spec/resources/expressions_spec.rb +308 -52
  55. data/spec/resources/index_spec.rb +28 -16
  56. data/spec/resources/participants_spec.rb +102 -0
  57. data/spec/resources/processes_spec.rb +218 -104
  58. data/spec/resources/schedules_spec.rb +97 -0
  59. data/spec/resources/workitems_spec.rb +195 -100
  60. data/spec/ruote-kit_configure_spec.rb +32 -75
  61. data/spec/spec.opts +0 -1
  62. data/spec/spec_helper.rb +31 -92
  63. data/spec/webapp_helpers_spec.rb +309 -0
  64. metadata +87 -152
  65. data/lib/ruote-kit/configuration.rb +0 -83
  66. data/lib/ruote-kit/helpers/engine_helpers.rb +0 -24
  67. data/lib/ruote-kit/helpers/form_helpers.rb +0 -11
  68. data/lib/ruote-kit/helpers/launch_item_parser.rb +0 -59
  69. data/lib/ruote-kit/helpers/navigation_helpers.rb +0 -54
  70. data/lib/ruote-kit/helpers.rb +0 -9
  71. data/lib/ruote-kit/public/_ruote/images/bg.gif +0 -0
  72. data/lib/ruote-kit/public/_ruote/images/bg_button_left.gif +0 -0
  73. data/lib/ruote-kit/public/_ruote/images/bg_button_left_cancel.gif +0 -0
  74. data/lib/ruote-kit/public/_ruote/images/bg_button_left_submit.gif +0 -0
  75. data/lib/ruote-kit/public/_ruote/images/bg_button_right.gif +0 -0
  76. data/lib/ruote-kit/public/_ruote/javascripts/SimplyButtons.js +0 -191
  77. data/lib/ruote-kit/public/_ruote/javascripts/fluo-dial.js +0 -149
  78. data/lib/ruote-kit/public/_ruote/stylesheets/SimplyButtons.css +0 -226
  79. data/lib/ruote-kit/public/_ruote/stylesheets/base.css +0 -336
  80. data/lib/ruote-kit/public/_ruote/stylesheets/style.css +0 -393
  81. data/lib/ruote-kit/views/launch_process.html.haml +0 -19
  82. data/lib/ruote-kit/views/process_failed_to_launch.html.haml +0 -7
  83. data/lib/ruote-kit/views/resource_not_found.html.haml +0 -7
  84. data/spec/helpers/render_helpers_spec.rb +0 -214
  85. data/spec/ruote-kit_spec.rb +0 -4
  86. data/spec/views/expressions.html.haml_spec.rb +0 -31
  87. data/spec/views/launch_process.html.haml_spec.rb +0 -22
  88. data/spec/views/process.html.haml_spec.rb +0 -17
  89. data/spec/views/process_launched.html.haml_spec.rb +0 -16
  90. data/spec/views/processes.html.haml_spec.rb +0 -31
  91. data/spec/views/workitems.html.haml_spec.rb +0 -69
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..'))
2
+
1
3
  ENV['RACK_ENV'] = 'test'
2
4
 
3
5
  require 'rubygems'
@@ -10,25 +12,33 @@ require 'rack/test'
10
12
 
11
13
  require 'webrat'
12
14
 
13
- require 'json'
15
+ begin
16
+ require 'yajl'
17
+ rescue LoadError
18
+ begin
19
+ require 'json'
20
+ rescue LoadError
21
+ puts 'Please specify "gem {yajl-ruby|json_pure|json}" in the Gemfile'
22
+ exit
23
+ end
24
+ end
14
25
 
15
26
  Test::Unit::TestCase.send :include, Rack::Test::Methods
16
27
 
17
- require File.dirname(__FILE__) + '/../vendor/gems/environment' if File.exists?( File.dirname(__FILE__) + '/../vendor/gems/environment.rb' )
18
- require File.dirname(__FILE__) + '/../lib/ruote-kit'
19
-
20
- require 'ruote/part/storage_participant'
21
-
22
- RuoteKit.configure do |config|
23
-
24
- # In memory is perfect for tests
25
- config.mode = :transient
28
+ begin
29
+ require File.join(File.dirname(__FILE__), '/../vendor/gems/environment')
30
+ rescue LoadError
26
31
  end
32
+ require File.join(File.dirname(__FILE__), '/../lib/ruote-kit')
27
33
 
28
34
  require 'ruote-kit/spec/ruote_helpers'
35
+ require 'spec/it_has_an_engine'
36
+
29
37
  require 'ruote/log/test_logger'
30
38
 
39
+
31
40
  Spec::Runner.configure do |config|
41
+
32
42
  # == Mock Framework
33
43
  #
34
44
  # RSpec uses it's own mocking framework by default. If you prefer to
@@ -42,40 +52,15 @@ Spec::Runner.configure do |config|
42
52
  config.include Webrat::Matchers, :type => :views
43
53
  config.include RuoteKit::Spec::RuoteHelpers
44
54
 
45
- config.before(:each, :type => :with_engine) do
46
- RuoteKit.configure do |conf|
47
- conf.register do
48
- catchall Ruote::StorageParticipant
49
- end
50
- end
51
- RuoteKit.run_engine!
52
-
53
- @tracer = Tracer.new
54
- RuoteKit.engine.add_service( 'tracer', @tracer )
55
-
56
- # Specs use their own worker since we need the trace
57
- @_spec_worker = Ruote::Worker.new( RuoteKit.engine.storage )
58
- @_spec_worker.context.add_service( 'tracer', @tracer )
59
- @_spec_worker.run_in_thread
55
+ def it_has_an_engine
56
+ it_should_behave_like 'it has an engine'
60
57
  end
61
-
62
- # Purge the engine after every run
63
- config.after(:each, :type => :with_engine) do
64
- @_spec_worker.shutdown
65
-
66
- # Seems in some rubies this block gets called multiple times
67
- unless RuoteKit.engine.nil?
68
- RuoteKit.storage_participant.purge!
69
- RuoteKit.engine.storage.purge! unless RuoteKit.engine.storage.nil?
70
-
71
- RuoteKit.shutdown_engine( true )
72
- end
58
+ def it_has_an_engine_with_no_participants
59
+ it_should_behave_like 'it has an engine with no participants'
73
60
  end
74
61
 
75
62
  RuoteKit::Application.included_modules.each do |klass|
76
- if klass.name =~ /RuoteKit::Helpers::\w+Helpers/
77
- config.include klass
78
- end
63
+ config.include(klass) if klass.name =~ /RuoteKit::Helpers::\w+Helpers/
79
64
  end
80
65
  end
81
66
 
@@ -83,44 +68,16 @@ def app
83
68
  RuoteKit::Application
84
69
  end
85
70
 
86
- # Sets the local variables that will be accessible in the HAML
87
- # template
88
- def assigns
89
- @assigns ||= { }
90
- end
91
-
92
- # Renders the supplied template with Haml::Engine and assigns the
93
- # @response instance variable
94
- def render(template_path)
95
- template = File.read("#{app.views}/#{template_path.sub(/^\//, '')}")
71
+ def render(template, scope=nil, locals={}, &block)
72
+ template = File.read(File.join(app.views, template.to_s))
96
73
  engine = Haml::Engine.new(template)
97
- @response = engine.render(self, assigns_for_template)
98
- end
99
-
100
- # Convenience method to access the @response instance variable set in
101
- # the render call
102
- def response
103
- @response
104
- end
105
-
106
- # Sets the local variables that will be accessible in the HAML
107
- # template
108
- def assigns
109
- @assigns ||= { }
110
- end
111
-
112
- # Prepends the assigns keywords with an "@" so that they will be
113
- # instance variables when the template is rendered.
114
- def assigns_for_template
115
- assigns.inject({}) do |memo, kv|
116
- memo["@#{kv[0].to_s}".to_sym] = kv[1]
117
- memo
118
- end
74
+ engine.render(scope, locals, &block)
119
75
  end
120
76
 
121
77
  class Rack::MockResponse
78
+
122
79
  def json_body
123
- Rufus::Json.decode( body )
80
+ Rufus::Json.decode(body)
124
81
  end
125
82
 
126
83
  def json?
@@ -133,25 +90,7 @@ class Rack::MockResponse
133
90
  end
134
91
 
135
92
  def html?
136
- !json?
93
+ ! json?
137
94
  end
138
95
  end
139
96
 
140
-
141
- class Tracer
142
- def initialize
143
- @trace = ''
144
- end
145
- def to_s
146
- @trace.to_s.strip
147
- end
148
- def << s
149
- @trace << s
150
- end
151
- def clear
152
- @trace = ''
153
- end
154
- def puts s
155
- @trace << "#{s}\n"
156
- end
157
- end
@@ -0,0 +1,309 @@
1
+
2
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
3
+
4
+
5
+ describe RuoteKit::Helpers::RenderHelpers do
6
+
7
+ describe 'alink()' do
8
+
9
+ it 'link to top resources' do
10
+
11
+ alink(:processes).should ==
12
+ '<a href="/_ruote/processes" rel="http://ruote.rubyforge.org/rels.html#processes" title="/_ruote/processes">/_ruote/processes</a>'
13
+ end
14
+
15
+ it 'should link to identified resources' do
16
+
17
+ alink(:processes, '20120808-tokushima').should ==
18
+ '<a href="/_ruote/processes/20120808-tokushima" rel="http://ruote.rubyforge.org/rels.html#process" title="/_ruote/processes/20120808-tokushima">/_ruote/processes/20120808-tokushima</a>'
19
+ end
20
+
21
+ it 'should accept a custom :text' do
22
+
23
+ alink(:processes, :text => 'processes').should ==
24
+ '<a href="/_ruote/processes" rel="http://ruote.rubyforge.org/rels.html#processes" title="/_ruote/processes">processes</a>'
25
+ end
26
+
27
+ it 'should aggregate non-:text options into the query string' do
28
+
29
+ alink(:processes, :skip => 3, :limit => 4).should ==
30
+ '<a href="/_ruote/processes?limit=4&skip=3" rel="http://ruote.rubyforge.org/rels.html#processes" title="/_ruote/processes?limit=4&skip=3">/_ruote/processes?limit=4&skip=3</a>'
31
+ end
32
+
33
+ it 'should accept a custom :rel' do
34
+
35
+ alink(:processes, :rel => 'last').should ==
36
+ '<a href="/_ruote/processes" rel="last" title="/_ruote/processes">/_ruote/processes</a>'
37
+ end
38
+
39
+ it 'should accept a :head id for processes, errors, schedules and workitems' do
40
+
41
+ self.instance_eval do
42
+ def settings; OpenStruct.new(:limit => 100); end
43
+ end
44
+ # tricking the helper...
45
+
46
+ alink(:processes, :head, :text => 'processes').should ==
47
+ '<a href="/_ruote/processes?limit=100&skip=0" rel="http://ruote.rubyforge.org/rels.html#processes" title="/_ruote/processes?limit=100&skip=0">processes</a>'
48
+ end
49
+ end
50
+
51
+ describe 'links(resource) (JSON)' do
52
+
53
+ before(:each) do
54
+
55
+ @resource = Object.new
56
+ class << @resource
57
+ include RuoteKit::Helpers::LinkHelpers
58
+ include RuoteKit::Helpers::JsonHelpers
59
+ attr_accessor :processes, :count, :skip, :limit, :request
60
+ def settings
61
+ OpenStruct.new(:limit => @limit)
62
+ end
63
+ end
64
+ @resource.request = OpenStruct.new(:fullpath => '/_ruote/processes')
65
+ end
66
+
67
+ it 'should paginate correctly' do
68
+
69
+ @resource.processes = (1..201).to_a
70
+ @resource.count = @resource.processes.size
71
+
72
+ @resource.skip = 5
73
+ @resource.limit = 5
74
+
75
+ @resource.links(:processes)[8..-1].should == [
76
+ { 'href' => '/_ruote/processes',
77
+ 'rel' => 'all' },
78
+ { 'href' => '/_ruote/processes?limit=5&skip=0',
79
+ 'rel' => 'first' },
80
+ { 'href' => '/_ruote/processes?limit=5&skip=200',
81
+ 'rel' => 'last' },
82
+ { 'href' => '/_ruote/processes?limit=5&skip=0',
83
+ 'rel' => 'previous' },
84
+ { 'href' => '/_ruote/processes?limit=5&skip=10',
85
+ 'rel' => 'next' }
86
+ ]
87
+ end
88
+ end
89
+
90
+ describe '_pagination.html.haml (HTML)' do
91
+
92
+ before(:each) do
93
+ @resource = Object.new
94
+ class << @resource
95
+ attr_accessor :count, :skip, :limit, :request
96
+ def to_html
97
+ '<div>' + render('_pagination.html.haml', self) + '</div>'
98
+ end
99
+ def settings
100
+ OpenStruct.new(:limit => @limit)
101
+ end
102
+ end
103
+ @resource.request = OpenStruct.new(:path => '/_ruote/processes')
104
+ end
105
+
106
+ it 'should paginate correctly (1st page)' do
107
+
108
+ @resource.count = 201
109
+ @resource.skip = 0
110
+ @resource.limit = 7
111
+
112
+ html = @resource.to_html
113
+
114
+ html.index(" 1\n to\n 7\n of\n 201\n processes").should > 0
115
+
116
+ html.should have_selector(
117
+ 'a', :href => '/_ruote/processes', :rel => 'all')
118
+ #html.should have_selector(
119
+ # 'a', :href => '/_ruote/processes?limit=7&skip=0', :rel => 'first')
120
+ #html.should have_selector(
121
+ # 'a', :href => '/_ruote/processes?limit=7&skip=0', :rel => 'previous')
122
+ html.should have_selector(
123
+ 'a', :href => '/_ruote/processes?limit=7&skip=7', :rel => 'next')
124
+ html.should have_selector(
125
+ 'a', :href => '/_ruote/processes?limit=7&skip=196', :rel => 'last')
126
+ end
127
+
128
+ it 'should paginate correctly (2nd page)' do
129
+
130
+ @resource.count = 201
131
+ @resource.skip = 7
132
+ @resource.limit = 7
133
+
134
+ html = @resource.to_html
135
+
136
+ html.index(" 8\n to\n 14\n of\n 201\n processes").should_not be(nil)
137
+
138
+ html.should have_selector(
139
+ 'a', :href => '/_ruote/processes', :rel => 'all')
140
+ html.should have_selector(
141
+ 'a', :href => '/_ruote/processes?limit=7&skip=0', :rel => 'first')
142
+ #html.should have_selector(
143
+ # 'a', :href => '/_ruote/processes?limit=7&skip=0', :rel => 'previous')
144
+ html.should have_selector(
145
+ 'a', :href => '/_ruote/processes?limit=7&skip=14', :rel => 'next')
146
+ html.should have_selector(
147
+ 'a', :href => '/_ruote/processes?limit=7&skip=196', :rel => 'last')
148
+ end
149
+
150
+ it 'should paginate correctly (3rd page)' do
151
+
152
+ @resource.count = 201
153
+ @resource.skip = 14
154
+ @resource.limit = 7
155
+
156
+ html = @resource.to_html
157
+
158
+ html.index(" 15\n to\n 21\n of\n 201\n processes").should_not be(nil)
159
+
160
+ html.should have_selector(
161
+ 'a', :href => '/_ruote/processes', :rel => 'all')
162
+ html.should have_selector(
163
+ 'a', :href => '/_ruote/processes?limit=7&skip=0', :rel => 'first')
164
+ html.should have_selector(
165
+ 'a', :href => '/_ruote/processes?limit=7&skip=7', :rel => 'previous')
166
+ html.should have_selector(
167
+ 'a', :href => '/_ruote/processes?limit=7&skip=21', :rel => 'next')
168
+ html.should have_selector(
169
+ 'a', :href => '/_ruote/processes?limit=7&skip=196', :rel => 'last')
170
+ end
171
+
172
+ it 'should paginate correctly (right before last page)' do
173
+
174
+ @resource.count = 201
175
+ @resource.skip = 189
176
+ @resource.limit = 7
177
+
178
+ html = @resource.to_html
179
+
180
+ html.index(" 190\n to\n 196\n of\n 201\n processes").should_not be(nil)
181
+
182
+ html.should have_selector(
183
+ 'a', :href => '/_ruote/processes', :rel => 'all')
184
+ html.should have_selector(
185
+ 'a', :href => '/_ruote/processes?limit=7&skip=0', :rel => 'first')
186
+ html.should have_selector(
187
+ 'a', :href => '/_ruote/processes?limit=7&skip=182', :rel => 'previous')
188
+ #html.should have_selector(
189
+ # 'a', :href => '/_ruote/processes?limit=7&skip=21', :rel => 'next')
190
+ html.should have_selector(
191
+ 'a', :href => '/_ruote/processes?limit=7&skip=196', :rel => 'last')
192
+ end
193
+
194
+ it 'should paginate correctly (last page)' do
195
+
196
+ @resource.count = 201
197
+ @resource.skip = 196
198
+ @resource.limit = 7
199
+
200
+ html = @resource.to_html
201
+
202
+ html.index(" 197\n to\n 201\n of\n 201\n processes").should_not be(nil)
203
+
204
+ html.should have_selector(
205
+ 'a', :href => '/_ruote/processes', :rel => 'all')
206
+ html.should have_selector(
207
+ 'a', :href => '/_ruote/processes?limit=7&skip=0', :rel => 'first')
208
+ html.should have_selector(
209
+ 'a', :href => '/_ruote/processes?limit=7&skip=189', :rel => 'previous')
210
+ #html.should have_selector(
211
+ # 'a', :href => '/_ruote/processes?limit=7&skip=21', :rel => 'next')
212
+ #html.should have_selector(
213
+ # 'a', :href => '/_ruote/processes?limit=7&skip=196', :rel => 'last')
214
+ end
215
+
216
+ it 'should paginate correctly (corner case 3 items, limit 3)' do
217
+
218
+ @resource.count = 3
219
+ @resource.skip = 0
220
+ @resource.limit = 3
221
+
222
+ html = @resource.to_html
223
+
224
+ html.index(" 1\n to\n 3\n of\n 3\n processes").should_not be(nil)
225
+
226
+ html.should have_selector(
227
+ 'a', :href => '/_ruote/processes', :rel => 'all')
228
+ html.should_not have_selector(
229
+ 'a', :rel => 'first')
230
+ html.should_not have_selector(
231
+ 'a', :rel => 'previous')
232
+ html.should_not have_selector(
233
+ 'a', :rel => 'next')
234
+ html.should_not have_selector(
235
+ 'a', :rel => 'last')
236
+
237
+ end
238
+
239
+ it 'should paginate correctly (corner case 30 items, limit 10, skip 20)' do
240
+
241
+ @resource.count = 30
242
+ @resource.skip = 20
243
+ @resource.limit = 10
244
+
245
+ html = @resource.to_html
246
+
247
+ html.should_not have_selector(
248
+ 'a', :rel => 'next')
249
+ html.should_not have_selector(
250
+ 'a', :rel => 'last')
251
+
252
+ end
253
+
254
+ it 'should not show there is an item if there is none' do
255
+
256
+ @resource.count = 0
257
+ @resource.skip = 0
258
+ @resource.limit = 100
259
+
260
+ html = @resource.to_html
261
+
262
+ html.index(" 0\n to\n 0\n of\n 0\n processes").should_not be(nil)
263
+
264
+ html.should have_selector(
265
+ 'a', :href => '/_ruote/processes', :rel => 'all')
266
+ html.should_not have_selector(
267
+ 'a', :rel => 'first')
268
+ html.should_not have_selector(
269
+ 'a', :rel => 'previous')
270
+ html.should_not have_selector(
271
+ 'a', :rel => 'next')
272
+ html.should_not have_selector(
273
+ 'a', :rel => 'last')
274
+
275
+ end
276
+
277
+ it 'should ignore negative values for skip param' do
278
+
279
+ @resource.count = 10
280
+ @resource.skip = -5
281
+ @resource.limit = 100
282
+
283
+ html = @resource.to_html
284
+
285
+ html.index(" 1\n to\n 10\n of\n 10\n processes").should_not be(nil)
286
+
287
+ # perhaps a redirection to the same url but with a sane param would be
288
+ # more appropriate?
289
+
290
+ end
291
+
292
+ it 'should ignore values for the skip param larger than the number of items' do
293
+
294
+ @resource.count = 10
295
+ @resource.skip = 20
296
+ @resource.limit = 5
297
+
298
+ html = @resource.to_html
299
+
300
+ html.index(" 6\n to\n 10\n of\n 10\n processes").should_not be(nil)
301
+
302
+ # perhaps a redirection to the same url but with a sane param would be
303
+ # more appropriate?
304
+
305
+ end
306
+
307
+ end
308
+ end
309
+