garcun 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +17 -0
  3. data/.gitignore +197 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +22 -0
  6. data/LICENSE +201 -0
  7. data/README.md +521 -0
  8. data/Rakefile +47 -0
  9. data/garcun.gemspec +83 -0
  10. data/lib/garcon.rb +290 -0
  11. data/lib/garcon/chef/chef_helpers.rb +343 -0
  12. data/lib/garcon/chef/coerce/coercer.rb +134 -0
  13. data/lib/garcon/chef/coerce/coercions/boolean_definitions.rb +34 -0
  14. data/lib/garcon/chef/coerce/coercions/date_definitions.rb +32 -0
  15. data/lib/garcon/chef/coerce/coercions/date_time_definitions.rb +32 -0
  16. data/lib/garcon/chef/coerce/coercions/fixnum_definitions.rb +34 -0
  17. data/lib/garcon/chef/coerce/coercions/float_definitions.rb +32 -0
  18. data/lib/garcon/chef/coerce/coercions/hash_definitions.rb +29 -0
  19. data/lib/garcon/chef/coerce/coercions/integer_definitions.rb +31 -0
  20. data/lib/garcon/chef/coerce/coercions/string_definitions.rb +45 -0
  21. data/lib/garcon/chef/coerce/coercions/time_definitions.rb +32 -0
  22. data/lib/garcon/chef/handler/devreporter.rb +127 -0
  23. data/lib/garcon/chef/log.rb +64 -0
  24. data/lib/garcon/chef/node.rb +100 -0
  25. data/lib/garcon/chef/provider/civilize.rb +209 -0
  26. data/lib/garcon/chef/provider/development.rb +159 -0
  27. data/lib/garcon/chef/provider/download.rb +420 -0
  28. data/lib/garcon/chef/provider/house_keeping.rb +265 -0
  29. data/lib/garcon/chef/provider/node_cache.rb +31 -0
  30. data/lib/garcon/chef/provider/partial.rb +183 -0
  31. data/lib/garcon/chef/provider/recovery.rb +80 -0
  32. data/lib/garcon/chef/provider/zip_file.rb +271 -0
  33. data/lib/garcon/chef/resource/attribute.rb +52 -0
  34. data/lib/garcon/chef/resource/base_dsl.rb +174 -0
  35. data/lib/garcon/chef/resource/blender.rb +140 -0
  36. data/lib/garcon/chef/resource/lazy_eval.rb +66 -0
  37. data/lib/garcon/chef/resource/resource_name.rb +109 -0
  38. data/lib/garcon/chef/secret_bag.rb +204 -0
  39. data/lib/garcon/chef/validations.rb +76 -0
  40. data/lib/garcon/chef_inclusions.rb +151 -0
  41. data/lib/garcon/configuration.rb +138 -0
  42. data/lib/garcon/core_ext.rb +39 -0
  43. data/lib/garcon/core_ext/array.rb +27 -0
  44. data/lib/garcon/core_ext/binding.rb +64 -0
  45. data/lib/garcon/core_ext/boolean.rb +66 -0
  46. data/lib/garcon/core_ext/duration.rb +271 -0
  47. data/lib/garcon/core_ext/enumerable.rb +34 -0
  48. data/lib/garcon/core_ext/file.rb +127 -0
  49. data/lib/garcon/core_ext/filetest.rb +62 -0
  50. data/lib/garcon/core_ext/hash.rb +279 -0
  51. data/lib/garcon/core_ext/kernel.rb +159 -0
  52. data/lib/garcon/core_ext/lazy.rb +222 -0
  53. data/lib/garcon/core_ext/method_access.rb +243 -0
  54. data/lib/garcon/core_ext/module.rb +92 -0
  55. data/lib/garcon/core_ext/nil.rb +53 -0
  56. data/lib/garcon/core_ext/numeric.rb +44 -0
  57. data/lib/garcon/core_ext/object.rb +342 -0
  58. data/lib/garcon/core_ext/pathname.rb +152 -0
  59. data/lib/garcon/core_ext/process.rb +41 -0
  60. data/lib/garcon/core_ext/random.rb +497 -0
  61. data/lib/garcon/core_ext/string.rb +312 -0
  62. data/lib/garcon/core_ext/struct.rb +49 -0
  63. data/lib/garcon/core_ext/symbol.rb +170 -0
  64. data/lib/garcon/core_ext/time.rb +234 -0
  65. data/lib/garcon/exceptions.rb +101 -0
  66. data/lib/garcon/inflections.rb +237 -0
  67. data/lib/garcon/inflections/defaults.rb +79 -0
  68. data/lib/garcon/inflections/inflections.rb +182 -0
  69. data/lib/garcon/inflections/rules_collection.rb +37 -0
  70. data/lib/garcon/secret.rb +271 -0
  71. data/lib/garcon/stash/format.rb +114 -0
  72. data/lib/garcon/stash/journal.rb +226 -0
  73. data/lib/garcon/stash/queue.rb +83 -0
  74. data/lib/garcon/stash/serializer.rb +86 -0
  75. data/lib/garcon/stash/store.rb +435 -0
  76. data/lib/garcon/task.rb +31 -0
  77. data/lib/garcon/task/atomic.rb +151 -0
  78. data/lib/garcon/task/atomic_boolean.rb +127 -0
  79. data/lib/garcon/task/condition.rb +99 -0
  80. data/lib/garcon/task/copy_on_notify_observer_set.rb +154 -0
  81. data/lib/garcon/task/copy_on_write_observer_set.rb +153 -0
  82. data/lib/garcon/task/count_down_latch.rb +92 -0
  83. data/lib/garcon/task/delay.rb +196 -0
  84. data/lib/garcon/task/dereferenceable.rb +144 -0
  85. data/lib/garcon/task/event.rb +119 -0
  86. data/lib/garcon/task/executor.rb +275 -0
  87. data/lib/garcon/task/executor_options.rb +59 -0
  88. data/lib/garcon/task/future.rb +107 -0
  89. data/lib/garcon/task/immediate_executor.rb +84 -0
  90. data/lib/garcon/task/ivar.rb +171 -0
  91. data/lib/garcon/task/lazy_reference.rb +74 -0
  92. data/lib/garcon/task/monotonic_time.rb +69 -0
  93. data/lib/garcon/task/obligation.rb +256 -0
  94. data/lib/garcon/task/observable.rb +101 -0
  95. data/lib/garcon/task/priority_queue.rb +234 -0
  96. data/lib/garcon/task/processor_count.rb +128 -0
  97. data/lib/garcon/task/read_write_lock.rb +304 -0
  98. data/lib/garcon/task/safe_task_executor.rb +58 -0
  99. data/lib/garcon/task/single_thread_executor.rb +97 -0
  100. data/lib/garcon/task/thread_pool/cached.rb +71 -0
  101. data/lib/garcon/task/thread_pool/executor.rb +294 -0
  102. data/lib/garcon/task/thread_pool/fixed.rb +61 -0
  103. data/lib/garcon/task/thread_pool/worker.rb +90 -0
  104. data/lib/garcon/task/timer.rb +44 -0
  105. data/lib/garcon/task/timer_set.rb +194 -0
  106. data/lib/garcon/task/timer_task.rb +377 -0
  107. data/lib/garcon/task/waitable_list.rb +58 -0
  108. data/lib/garcon/utility/ansi.rb +199 -0
  109. data/lib/garcon/utility/at_random.rb +77 -0
  110. data/lib/garcon/utility/crypto.rb +292 -0
  111. data/lib/garcon/utility/equalizer.rb +146 -0
  112. data/lib/garcon/utility/faker/extensions/array.rb +22 -0
  113. data/lib/garcon/utility/faker/extensions/symbol.rb +9 -0
  114. data/lib/garcon/utility/faker/faker.rb +164 -0
  115. data/lib/garcon/utility/faker/faker/company.rb +17 -0
  116. data/lib/garcon/utility/faker/faker/hacker.rb +30 -0
  117. data/lib/garcon/utility/faker/faker/version.rb +3 -0
  118. data/lib/garcon/utility/faker/locales/en-US.yml +83 -0
  119. data/lib/garcon/utility/faker/locales/en.yml +21 -0
  120. data/lib/garcon/utility/file_helper.rb +170 -0
  121. data/lib/garcon/utility/hookers.rb +178 -0
  122. data/lib/garcon/utility/interpolation.rb +90 -0
  123. data/lib/garcon/utility/memstash.rb +364 -0
  124. data/lib/garcon/utility/misc.rb +54 -0
  125. data/lib/garcon/utility/msg_from_god.rb +62 -0
  126. data/lib/garcon/utility/retry.rb +238 -0
  127. data/lib/garcon/utility/timeout.rb +58 -0
  128. data/lib/garcon/utility/uber/builder.rb +91 -0
  129. data/lib/garcon/utility/uber/callable.rb +7 -0
  130. data/lib/garcon/utility/uber/delegates.rb +13 -0
  131. data/lib/garcon/utility/uber/inheritable_attr.rb +37 -0
  132. data/lib/garcon/utility/uber/options.rb +101 -0
  133. data/lib/garcon/utility/uber/uber_version.rb +3 -0
  134. data/lib/garcon/utility/uber/version.rb +33 -0
  135. data/lib/garcon/utility/url_helper.rb +100 -0
  136. data/lib/garcon/utils.rb +29 -0
  137. data/lib/garcon/version.rb +62 -0
  138. data/lib/garcun.rb +24 -0
  139. metadata +680 -0
@@ -0,0 +1,222 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ # Lazy ass Ruby.
21
+ #
22
+ module Lazy
23
+ # Raised when a demanded computation diverges (e.g. if it tries to directly
24
+ # use its own result)
25
+ #
26
+ class DivergenceError < Exception
27
+ def initialize(message = 'Computation diverges')
28
+ super(message)
29
+ end
30
+ end
31
+
32
+ # Wraps an exception raised by a lazy computation.
33
+ #
34
+ # The reason we wrap such exceptions in LazyException is that they need to
35
+ # be distinguishable from similar exceptions which might normally be raised
36
+ # by whatever strict code we happen to be in at the time.
37
+ #
38
+ class LazyException < DivergenceError
39
+ attr_reader :reason
40
+
41
+ def initialize(reason)
42
+ @reason = reason
43
+ super "Exception in lazy computation: #{reason} (#{reason.class})"
44
+ set_backtrace(reason.backtrace.dup) if reason
45
+ end
46
+ end
47
+
48
+ # A handle for a promised computation. They are transparent, so that in
49
+ # most cases, a promise can be used as a proxy for the computation's result
50
+ # object. The one exception is truth testing -- a promise will always look
51
+ # true to Ruby, even if the actual result object is nil or false.
52
+ #
53
+ # If you want to test the result for truth, get the unwrapped result object
54
+ # via Kernel.demand.
55
+ #
56
+ class Promise
57
+ alias __class__ class
58
+ instance_methods.each do |method|
59
+ undef_method method unless method =~ /^(__|object_|instance_)/
60
+ end
61
+
62
+ def initialize(&computation)
63
+ @computation = computation
64
+ end
65
+
66
+ def __synchronize__
67
+ yield
68
+ end
69
+
70
+ # Create this once here, rather than creating a proc object for every
71
+ # evaluation.
72
+ DIVERGES = lambda { raise DivergenceError.new }
73
+
74
+ # Differentiate inspection of DIVERGES lambda.
75
+ def DIVERGES.inspect
76
+ 'DIVERGES'
77
+ end
78
+
79
+ def __result__
80
+ __synchronize__ do
81
+ if @computation
82
+ raise LazyException.new(@exception) if @exception
83
+
84
+ computation = @computation
85
+ @computation = DIVERGES
86
+
87
+ begin
88
+ @result = demand(computation.call(self))
89
+ @computation = nil
90
+ rescue DivergenceError
91
+ raise
92
+ rescue Exception => e
93
+ @exception = e
94
+ raise LazyException.new(@exception)
95
+ end
96
+ end
97
+
98
+ @result
99
+ end
100
+ end
101
+
102
+ def inspect
103
+ __synchronize__ do
104
+ if @computation
105
+ "#<#{__class__} computation=#{@computation.inspect}>"
106
+ else
107
+ @result.inspect
108
+ end
109
+ end
110
+ end
111
+
112
+ def respond_to?(message)
113
+ message = message.to_sym
114
+ message == :__result__ or
115
+ message == :inspect or
116
+ __result__.respond_to? message
117
+ end
118
+
119
+ def method_missing(*args, &block)
120
+ __result__.__send__(*args, &block)
121
+ end
122
+ end
123
+
124
+ # Thread safe version of Promise class.
125
+ #
126
+ class PromiseSafe < Promise
127
+ def __synchronize__
128
+ current = Thread.current
129
+
130
+ Thread.critical = true
131
+ unless @computation
132
+ Thread.critical = false
133
+ yield
134
+ else
135
+ if @owner == current
136
+ Thread.critical = false
137
+ raise DivergenceError.new
138
+ end
139
+ while @owner
140
+ Thread.critical = false
141
+ Thread.pass
142
+ Thread.critical = true
143
+ end
144
+ @owner = current
145
+ Thread.critical = false
146
+
147
+ begin
148
+ yield
149
+ ensure
150
+ @owner = nil
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Future class subclasses PromiseSafe.
157
+ #
158
+ class Future < PromiseSafe
159
+ def initialize(&computation)
160
+ result = nil
161
+ exception = nil
162
+ thread = Thread.new do
163
+ begin
164
+ result = computation.call(self)
165
+ rescue Exception => exception
166
+ end
167
+ end
168
+
169
+ super do
170
+ raise DivergenceError.new if Thread.current == thread
171
+ thread.join
172
+ raise exception if exception
173
+ result
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ module Kernel
180
+ # The promise() function is used together with demand() to implement
181
+ # lazy evaluation. It returns a promise to evaluate the provided
182
+ # block at a future time. Evaluation can be demanded and the block's
183
+ # result obtained via the demand() function.
184
+ #
185
+ # Implicit evaluation is also supported: the first message sent to it will
186
+ # demand evaluation, after which that message and any subsequent messages
187
+ # will be forwarded to the result object.
188
+ #
189
+ # As an aid to circular programming, the block will be passed a promise
190
+ # for its own result when it is evaluated. Be careful not to force
191
+ # that promise during the computation, lest the computation diverge.
192
+ #
193
+ def promise(&computation)
194
+ Lazy::Promise.new(&computation)
195
+ end
196
+
197
+ # Forces the result of a promise to be computed (if necessary) and returns
198
+ # the bare result object. Once evaluated, the result of the promise will
199
+ # be cached. Nested promises will be evaluated together, until the first
200
+ # non-promise result.
201
+ #
202
+ # If called on a value that is not a promise, it will simply return it.
203
+ #
204
+ def demand(promise)
205
+ if promise.respond_to? :__result__
206
+ promise.__result__
207
+ else
208
+ promise
209
+ end
210
+ end
211
+
212
+ # Schedules a computation to be run asynchronously in a background thread
213
+ # and returns a promise for its result. An attempt to demand the result of
214
+ # the promise will block until the computation finishes.
215
+ #
216
+ # As with Kernel.promise, this passes the block a promise for its own result.
217
+ # Use wisely.
218
+ #
219
+ def future(&computation)
220
+ Lazy::Future.new(&computation)
221
+ end
222
+ end
@@ -0,0 +1,243 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ # License: Apache License, Version 2.0
5
+ # Copyright: (C) 2014-2015 Stefano Harding
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ module Garcon
21
+ module Extensions
22
+ # MethodReader allows you to access keys of the hash via method calls. This
23
+ # gives you an OStruct like way to access your hash's keys. It will
24
+ # recognize keys either as strings or symbols.
25
+ #
26
+ # @note
27
+ # That while nil keys will be returned as nil, undefined keys will raise
28
+ # NoMethodErrors. Also note that #respond_to? has been patched to
29
+ # appropriately recognize key methods.
30
+ #
31
+ # @example
32
+ # class StashCache < Hash
33
+ # include Garcon::Extensions::MethodReader
34
+ # end
35
+ #
36
+ # cash = StashCache.new
37
+ # cash['box'] = 'full'
38
+ # cash.box # => 'full'
39
+ #
40
+ module MethodReader
41
+ def respond_to?(name, include_private = false)
42
+ return true if key?(name.to_s) || key?(name.to_sym)
43
+ super
44
+ end
45
+
46
+ def method_missing(name, *args)
47
+ if key?(name)
48
+ self[name]
49
+ else
50
+ sname = name.to_s
51
+ if key?(sname)
52
+ self[sname]
53
+ elsif sname[-1] == '?'
54
+ kname = sname[0..-2]
55
+ key?(kname) || key?(kname.to_sym)
56
+ else
57
+ super
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ # MethodWriter gives you #key_name= shortcuts for writing to your hash.
64
+ # Keys are written as strings, override #convert_key if you would like to
65
+ # have symbols or something else.
66
+ #
67
+ # @note
68
+ # That MethodWriter also overrides #respond_to such that any
69
+ # #method_name= will respond appropriately as true.
70
+ #
71
+ # @example
72
+ # class MyHash < Hash
73
+ # include Garcon::Extensions::MethodWriter
74
+ # end
75
+ #
76
+ # h = MyHash.new
77
+ # h.awesome = 'sauce'
78
+ # h['awesome'] # => 'sauce'
79
+ #
80
+ module MethodWriter
81
+ def respond_to?(name, include_private = false)
82
+ return true if name.to_s =~ /=$/
83
+ super
84
+ end
85
+
86
+ def method_missing(name, *args)
87
+ if args.size == 1 && name.to_s =~ /(.*)=$/
88
+ return self[convert_key(Regexp.last_match[1])] = args.first
89
+ end
90
+
91
+ super
92
+ end
93
+
94
+ def convert_key(key)
95
+ key.to_s
96
+ end
97
+ end
98
+
99
+ # MethodQuery gives you the ability to check for the truthiness of a key
100
+ # via method calls. Note that it will return false if the key is set to a
101
+ # non-truthful value, not if the key isn't set at all. Use #key? for
102
+ # checking if a key has been set.
103
+ #
104
+ # MethodQuery will check against both string and symbol names of the method
105
+ # for existing keys. It also patches #respond_to to appropriately detect
106
+ # the query methods.
107
+ #
108
+ # @example
109
+ # class MyHash < Hash
110
+ # include Garcon::Extensions::MethodQuery
111
+ # end
112
+ #
113
+ # h = MyHash.new
114
+ # h['abc'] = 123
115
+ # h.abc? # => true
116
+ # h['def'] = nil
117
+ # h.def? # => false
118
+ # h.hji? # => NoMethodError
119
+ #
120
+ module MethodQuery
121
+ def respond_to?(name, include_private = false)
122
+ if name.to_s =~ /(.*)\?$/ && (key?(Regexp.last_match[1]) ||
123
+ key?(Regexp.last_match[1].to_sym))
124
+ return true
125
+ end
126
+ super
127
+ end
128
+
129
+ def method_missing(name, *args)
130
+ if args.empty? && name.to_s =~ /(.*)\?$/ &&
131
+ (key?(Regexp.last_match[1]) ||
132
+ key?(Regexp.last_match[1].to_sym))
133
+ return self[Regexp.last_match[1]] ||
134
+ self[Regexp.last_match[1].to_sym]
135
+ end
136
+ super
137
+ end
138
+ end
139
+
140
+ # A macro module that will automatically include MethodReader,
141
+ # MethodWriter, and MethodQuery, giving you the ability to read, write,
142
+ # and query keys in a hash using method call shortcuts.
143
+ #
144
+ module MethodAccess
145
+ def self.included(base)
146
+ [MethodReader, MethodWriter, MethodQuery].each do |mod|
147
+ base.send :include, mod
148
+ end
149
+ end
150
+ end
151
+
152
+ # MethodOverridingWriter gives you #key_name= shortcuts for writing to your
153
+ # hash. It allows methods to be overridden by #key_name= shortcuts and
154
+ # aliases those methods with two leading underscores.
155
+ #
156
+ # Keys are written as strings. Override #convert_key if you would like to
157
+ # have symbols or something else.
158
+ #
159
+ # Note that MethodOverridingWriter also overrides #respond_to_missing? such
160
+ # that any #method_name= will respond appropriately as true.
161
+ #
162
+ # @example
163
+ # class MyHash < Hash
164
+ # include Garcon::Extensions::MethodOverridingWriter
165
+ # end
166
+ #
167
+ # h = MyHash.new
168
+ # h.awesome = 'sauce'
169
+ # h['awesome'] # => 'sauce'
170
+ # h.zip = 'a-dee-doo-dah'
171
+ # h.zip # => 'a-dee-doo-dah'
172
+ # h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]
173
+ #
174
+ module MethodOverridingWriter
175
+ def convert_key(key)
176
+ key.to_s
177
+ end
178
+
179
+ def method_missing(name, *args)
180
+ if args.size == 1 && name.to_s =~ /(.*)=$/
181
+ key = Regexp.last_match[1]
182
+ redefine_method(key) if method?(key) && !already_overridden?(key)
183
+ return self[convert_key(key)] = args.first
184
+ end
185
+
186
+ super
187
+ end
188
+
189
+ def respond_to_missing?(name, include_private = false)
190
+ return true if name.to_s.end_with?('=')
191
+ super
192
+ end
193
+
194
+ protected
195
+
196
+ def already_overridden?(name)
197
+ method?("__#{name}")
198
+ end
199
+
200
+ def method?(name)
201
+ methods.map(&:to_s).include?(name)
202
+ end
203
+
204
+ def redefine_method(method_name)
205
+ eigenclass = class << self; self; end
206
+ eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
207
+ eigenclass.__send__(:define_method, method_name, -> {self[method_name]})
208
+ end
209
+ end
210
+
211
+ # A macro module that will automatically include MethodReader,
212
+ # MethodOverridingWriter, and MethodQuery, giving you the ability to read,
213
+ # write, and query keys in a hash using method call shortcuts that can
214
+ # override object methods. Any overridden object method is automatically
215
+ # aliased with two leading underscores.
216
+ #
217
+ module MethodAccessWithOverride
218
+ def self.included(base)
219
+ [MethodReader, MethodOverridingWriter, MethodQuery].each do |mod|
220
+ base.send :include, mod
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+ module Extensions
227
+ module PrettyInspect
228
+ def self.included(base)
229
+ base.send :alias_method, :hash_inspect, :inspect
230
+ base.send :alias_method, :inspect, :stash_inspect
231
+ end
232
+
233
+ def stash_inspect
234
+ ret = "#<#{self.class}"
235
+ keys.sort_by(&:to_s).each do |key|
236
+ ret << " #{key}=#{self[key].inspect}"
237
+ end
238
+ ret << '>'
239
+ ret
240
+ end
241
+ end
242
+ end
243
+ end