garcun 0.0.2

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 (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,114 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
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 Stash
22
+ # Database format serializer and deserializer. You can create your own
23
+ # implementation of this class and define your own database format!
24
+ #
25
+ # @api public
26
+ class Format
27
+ # Read database header from input stream
28
+ #
29
+ # @param [#read] input
30
+ # the input stream
31
+ #
32
+ # @return void
33
+ #
34
+ def read_header(input)
35
+ raise 'Not a stash' if input.read(MAGIC.bytesize) != MAGIC
36
+ ver = input.read(2).unpack('n').first
37
+ raise "Expected stash version #{VERSION}, got #{ver}" if ver != VERSION
38
+ end
39
+
40
+ # Return database header as string
41
+ #
42
+ # @return [String] database file header
43
+ #
44
+ def header
45
+ MAGIC + [VERSION].pack('n')
46
+ end
47
+
48
+ # Serialize record and return string.
49
+ #
50
+ # @param [Array] rec
51
+ # an array with [key, value] or [key] if the record is deleted
52
+ #
53
+ # @return [String] serialized record
54
+ #
55
+ def dump(rec)
56
+ data = if rec.size == 1
57
+ [rec[0].bytesize, DELETE].pack('NN') << rec[0]
58
+ else
59
+ [rec[0].bytesize, rec[1].bytesize].pack('NN') << rec[0] << rec[1]
60
+ end
61
+ data << crc32(data)
62
+ end
63
+
64
+ # Deserialize records from buffer, and yield them.
65
+ #
66
+ # @param [String] buf
67
+ # the buffer to read from
68
+ #
69
+ # @yield [Array] block
70
+ # deserialized record [key, value] or [key] if the record is deleted
71
+ #
72
+ # @return [Fixnum] number of records
73
+ #
74
+ def parse(buf)
75
+ n, count = 0, 0
76
+ while n < buf.size
77
+ key_size, value_size = buf[n, 8].unpack('NN')
78
+ data_size = key_size + 8
79
+ data_size += value_size if value_size != DELETE
80
+ data = buf[n, data_size]
81
+ n += data_size
82
+ unless buf[n, 4] == crc32(data)
83
+ raise 'CRC mismatch: your stash might be corrupted!'
84
+ end
85
+ n += 4
86
+ yield(value_size == DELETE ? [data[8, key_size]] : [data[8, key_size], data[8 + key_size, value_size]])
87
+ count += 1
88
+ end
89
+ count
90
+ end
91
+
92
+ protected # A T T E N Z I O N E A R E A P R O T E T T A
93
+
94
+ # Magic string of the file header.
95
+ MAGIC = 'ABRACADABRA'
96
+
97
+ # Database file format version (it is a hash after all).
98
+ VERSION = 420
99
+
100
+ # Special value size used for deleted records
101
+ DELETE = (1 << 32) - 1
102
+
103
+ # Compute crc32 of string
104
+ #
105
+ # @param [String] s a string
106
+ #
107
+ # @return [Fixnum]
108
+ #
109
+ def crc32(s)
110
+ [Zlib.crc32(s, 0)].pack('N')
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,226 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
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 Stash
22
+ # Stash::Io handles background io, compaction and is the arbiter
23
+ # of multiprocess safety.
24
+ #
25
+ # @api private
26
+ class Journal < Queue
27
+ attr_reader :size, :file
28
+
29
+ def initialize(file, format, serializer, &block)
30
+ super()
31
+ @file, @format, @serializer, @emit = file, format, serializer, block
32
+ open
33
+ @worker = Thread.new(&method(:worker))
34
+ @worker.priority = -1
35
+ load
36
+ end
37
+
38
+ # Is the journal closed?
39
+ #
40
+ def closed?
41
+ @fd.closed?
42
+ end
43
+
44
+ # Clear the queue and close the file handler
45
+ #
46
+ def close
47
+ self << nil
48
+ @worker.join
49
+ @fd.close
50
+ super
51
+ end
52
+
53
+ # Load new journal entries
54
+ #
55
+ def load
56
+ flush
57
+ replay
58
+ end
59
+
60
+ # Lock the logfile across thread and process boundaries
61
+ #
62
+ def lock
63
+ flush
64
+ with_flock(File::LOCK_EX) do
65
+ replay
66
+ result = yield
67
+ flush
68
+ result
69
+ end
70
+ end
71
+
72
+ # Clear the database log and yield
73
+ #
74
+ def clear
75
+ flush
76
+ with_tmpfile do |path, file|
77
+ file.write(@format.header)
78
+ file.close
79
+ with_flock(File::LOCK_EX) do
80
+ File.rename(path, @file)
81
+ end
82
+ end
83
+ open
84
+ end
85
+
86
+ # Compact the logfile to represent the in-memory state
87
+ #
88
+ def compact
89
+ load
90
+ with_tmpfile do |path, file|
91
+ # Compactified database has the same size -> return
92
+ return self if @pos == file.write(dump(yield, @format.header))
93
+ with_flock(File::LOCK_EX) do
94
+ if @pos != nil
95
+ file.write(read)
96
+ file.close
97
+ File.rename(path, @file)
98
+ end
99
+ end
100
+ end
101
+ open
102
+ replay
103
+ end
104
+
105
+ # Return byte size of journal
106
+ #
107
+ def bytesize
108
+ @fd.stat.size
109
+ end
110
+
111
+ private # P R O P R I E T À P R I V A T A Vietato L'accesso
112
+
113
+ # Emit records as we parse them
114
+ def replay
115
+ buf = read
116
+ @size += @format.parse(buf, &@emit)
117
+ end
118
+
119
+ # Open or reopen file
120
+ #
121
+ def open
122
+ @fd.close if @fd
123
+ @fd = File.open(@file, 'ab+')
124
+ @fd.advise(:sequential) if @fd.respond_to? :advise
125
+ stat = @fd.stat
126
+ @inode = stat.ino
127
+ write(@format.header) if stat.size == 0
128
+ @pos = nil
129
+ end
130
+
131
+ # Read new file content
132
+ #
133
+ def read
134
+ with_flock(File::LOCK_SH) do
135
+ unless @pos
136
+ @fd.pos = 0
137
+ @format.read_header(@fd)
138
+ @size = 0
139
+ @emit.call(nil)
140
+ else
141
+ @fd.pos = @pos
142
+ end
143
+ buf = @fd.read
144
+ @pos = @fd.pos
145
+ buf
146
+ end
147
+ end
148
+
149
+ # Return database dump as string
150
+ #
151
+ def dump(records, dump = '')
152
+ records.each do |record|
153
+ record[1] = @serializer.dump(record.last)
154
+ dump << @format.dump(record)
155
+ end
156
+ dump
157
+ end
158
+
159
+ # Worker thread
160
+ #
161
+ def worker
162
+ while (record = first)
163
+ tries = 0
164
+ begin
165
+ if Hash === record
166
+ write(dump(record))
167
+ @size += record.size
168
+ else
169
+ record[1] = @serializer.dump(record.last) if record.size > 1
170
+ write(@format.dump(record))
171
+ @size += 1
172
+ end
173
+ rescue Exception => e
174
+ tries += 1
175
+ warn "Stash worker, try #{tries}: #{e.message}"
176
+ tries <= 3 ? retry : raise
177
+ ensure
178
+ pop
179
+ end
180
+ end
181
+ rescue Exception => e
182
+ warn "Stash worker terminated: #{e.message}"
183
+ end
184
+
185
+ # Write data to output stream and advance @pos
186
+ #
187
+ def write(dump)
188
+ with_flock(File::LOCK_EX) do
189
+ @fd.write(dump)
190
+ @fd.flush
191
+ end
192
+ @pos = @fd.pos if @pos && @fd.pos == @pos + dump.bytesize
193
+ end
194
+
195
+ # Block with file lock
196
+ #
197
+ def with_flock(mode)
198
+ return yield if @locked
199
+ begin
200
+ loop do
201
+ Thread.pass until @fd.flock(mode)
202
+ stat = @fd.stat
203
+ break if stat.nlink > 0 && stat.ino == @inode
204
+ open
205
+ end
206
+ @locked = true
207
+ yield
208
+ ensure
209
+ @fd.flock(File::LOCK_UN)
210
+ @locked = false
211
+ end
212
+ end
213
+
214
+ # Open temporary file and pass it to the block
215
+ #
216
+ def with_tmpfile
217
+ path = [@file, $$.to_s(36), Thread.current.object_id.to_s(36)].join
218
+ file = File.open(path, 'wb')
219
+ yield(path, file)
220
+ ensure
221
+ file.close unless file.closed?
222
+ File.unlink(path) if File.exists?(path)
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
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 Stash
22
+ class Queue
23
+ def initialize
24
+ @queue, @full, @empty = [], [], []
25
+ @stop = false
26
+ @heartbeat = Thread.new(&method(:heartbeat))
27
+ @heartbeat.priority = -9
28
+ end
29
+
30
+ def <<(x)
31
+ @queue << x
32
+ thread = @full.first
33
+ thread.wakeup if thread
34
+ end
35
+
36
+ def pop
37
+ @queue.shift
38
+ if @queue.empty?
39
+ thread = @empty.first
40
+ thread.wakeup if thread
41
+ end
42
+ end
43
+
44
+ def first
45
+ while @queue.empty?
46
+ begin
47
+ @full << Thread.current
48
+ Thread.stop while @queue.empty?
49
+ ensure
50
+ @full.delete(Thread.current)
51
+ end
52
+ end
53
+ @queue.first
54
+ end
55
+
56
+ def flush
57
+ until @queue.empty?
58
+ begin
59
+ @empty << Thread.current
60
+ Thread.stop until @queue.empty?
61
+ ensure
62
+ @empty.delete(Thread.current)
63
+ end
64
+ end
65
+ end
66
+
67
+ def close
68
+ @stop = true
69
+ @heartbeat.join
70
+ end
71
+
72
+ private # P R O P R I E T À P R I V A T A Vietato L'accesso
73
+
74
+ def heartbeat
75
+ until @stop
76
+ @empty.each(&:wakeup)
77
+ @full.each(&:wakeup)
78
+ sleep 0.1
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Author: Stefano Harding <riddopic@gmail.com>
4
+ #
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 Stash
22
+ module Serializer
23
+ # Serializer which only encodes key in binary
24
+ #
25
+ # @api public
26
+ class None
27
+ # (see Stash::Serializer::Default#key_for)
28
+ if ''.respond_to? :force_encoding
29
+ def key_for(key)
30
+ if key.encoding != Encoding::BINARY
31
+ key = key.dup if key.frozen?
32
+ key.force_encoding(Encoding::BINARY)
33
+ end
34
+ key
35
+ end
36
+ else
37
+ def key_for(key)
38
+ key
39
+ end
40
+ end
41
+
42
+ # (see Stash::Serializer::Default#dump)
43
+ def dump(value)
44
+ value
45
+ end
46
+
47
+ # (see Stash::Serializer::Default#load)
48
+ def load(value)
49
+ value
50
+ end
51
+ end
52
+
53
+ # Default serializer which converts keys to strings and marshalls values.
54
+ #
55
+ # @api public
56
+ class Default < None
57
+ # Transform the key to a string.
58
+ #
59
+ # @param [Object] key
60
+ # @return [String]
61
+ # The key transformed to string.
62
+ def key_for(key)
63
+ super(key.to_s)
64
+ end
65
+
66
+ # Serialize a value.
67
+ #
68
+ # @param [Object] value
69
+ # @return [String]
70
+ # The value transformed to string.
71
+ def dump(value)
72
+ Marshal.dump(value)
73
+ end
74
+
75
+ # Parse a value.
76
+ #
77
+ # @param [String] value
78
+ # @return [Object]
79
+ # The deserialized value.
80
+ def load(value)
81
+ Marshal.load(value)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end