compat_resource 12.9.1 → 12.10.1

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +8 -3
  3. data/files/lib/chef_compat/copied_from_chef/chef/dsl/core.rb +56 -0
  4. data/files/lib/chef_compat/copied_from_chef/chef/dsl/declare_resource.rb +182 -7
  5. data/files/lib/chef_compat/copied_from_chef/chef/dsl/recipe.rb +5 -2
  6. data/files/lib/chef_compat/copied_from_chef/chef/mixin/lazy_module_include.rb +90 -0
  7. data/files/lib/chef_compat/copied_from_chef/chef/mixin/notifying_block.rb +66 -0
  8. data/files/lib/chef_compat/copied_from_chef/chef/mixin/powershell_out.rb +109 -0
  9. data/files/lib/chef_compat/copied_from_chef/chef/property.rb +9 -2
  10. data/files/lib/chef_compat/copied_from_chef/chef/provider.rb +2 -2
  11. data/files/lib/chef_compat/copied_from_chef/chef/resource.rb +1 -0
  12. data/files/lib/chef_compat/copied_from_chef/chef/resource/action_class.rb +3 -0
  13. data/files/lib/chef_compat/copied_from_chef/chef/resource_builder.rb +1 -1
  14. data/files/lib/chef_compat/monkeypatches.rb +7 -3
  15. data/files/lib/chef_compat/monkeypatches/chef.rb +3 -2
  16. data/files/lib/chef_compat/monkeypatches/chef/log.rb +13 -0
  17. data/files/lib/chef_compat/monkeypatches/chef/resource_collection.rb +103 -0
  18. data/files/lib/chef_compat/monkeypatches/chef/resource_collection/resource_list.rb +49 -0
  19. data/files/lib/chef_compat/monkeypatches/chef/resource_collection/resource_set.rb +49 -0
  20. data/files/lib/chef_compat/monkeypatches/chef/run_context.rb +74 -9
  21. data/files/lib/chef_compat/monkeypatches/chef/runner.rb +60 -0
  22. data/files/lib/compat_resource/version.rb +1 -1
  23. data/files/spec/cookbook_spec.rb +10 -1
  24. data/files/spec/data/.bundle/config +1 -0
  25. data/files/spec/data/Gemfile.lock +18 -50
  26. data/files/spec/data/config.rb +1 -1
  27. data/files/spec/data/cookbooks/notifications/metadata.rb +4 -0
  28. data/files/spec/data/cookbooks/notifications/recipes/default.rb +5 -0
  29. data/files/spec/data/cookbooks/notifications/resources/resource.rb +8 -0
  30. metadata +15 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cdff0f147fe02b913ab1b762bc5199525d59b5a0
4
- data.tar.gz: ba8668032f0b491c201f8a005f77bcf38d99bdc1
3
+ metadata.gz: 7c7c3400d3a6270d353403967a7285c17b4c0e01
4
+ data.tar.gz: 058a4752161e3c8e1610e80f5ff9497d3802a6b2
5
5
  SHA512:
6
- metadata.gz: 3cb3c8338706b7b803630dd53ebe6732183f91f2ed991796ea2eb42bccdb494a182e76c87132f91a612992851a3f2ae098467f07776b9468cbc4403dec45cd57
7
- data.tar.gz: 3118e30cac739d8e406316980c2b4b2f2f1ed939f15c8c75d56a063d4fac72677692d5fa4c36be6d05b7f4f660df1d3a8d002845b4ff96e2e27821990341479d
6
+ metadata.gz: 3e1d3dc00dbe9e97c67a6cc14baa7700ae7462e977130bc2bbe8f6ed75c3f35c45505de9d8082506ac68db68532afbd6443e6b9eb3c356a04d4ac216ffac8ba2
7
+ data.tar.gz: f87ce089823b777aa404cf36ab66f1222a50b9554c0bf0c1c948a6687aa85bcc2b9ca33ea4c836d7a54a3d80431cf401d4cd24156d9101e915df7ac26ecc567b
data/Rakefile CHANGED
@@ -16,15 +16,19 @@ task default: :spec
16
16
  CHEF_FILES = %w(
17
17
  chef/constants
18
18
  chef/delayed_evaluator
19
+ chef/dsl/core
19
20
  chef/dsl/declare_resource
20
21
  chef/dsl/recipe
22
+ chef/mixin/lazy_module_include
23
+ chef/mixin/notifying_block
21
24
  chef/mixin/params_validate
25
+ chef/mixin/powershell_out
22
26
  chef/mixin/properties
23
27
  chef/property
24
28
  chef/provider
25
29
  chef/resource
26
- chef/resource_builder
27
30
  chef/resource/action_class
31
+ chef/resource_builder
28
32
  )
29
33
  SPEC_FILES = %w(
30
34
  unit/mixin/properties_spec.rb
@@ -69,8 +73,8 @@ KEEP_FUNCTIONS = {
69
73
  }
70
74
  KEEP_INCLUDES = {
71
75
  'chef/resource' => %w(Chef::Mixin::ParamsValidate Chef::Mixin::Properties),
72
- 'chef/provider' => %w(Chef::DSL::Recipe::FullDSL),
73
- 'chef/dsl/recipe' => %w(Chef::DSL::DeclareResource Chef::DSL::Recipe),
76
+ 'chef/provider' => %w(Chef::DSL::Core),
77
+ 'chef/dsl/recipe' => %w(Chef::DSL::Core Chef::DSL::Recipe Chef::Mixin::LazyModuleInclude),
74
78
  }
75
79
  KEEP_CLASSES = {
76
80
  'chef/provider' => %w(Chef::Provider Chef::Provider::InlineResources Chef::Provider::InlineResources::ClassMethods)
@@ -83,6 +87,7 @@ PROCESS_LINES = {
83
87
  # See chef_compat/resource for def. of resource_name and provider
84
88
  # See chef_compat/monkeypatches/chef/resource for def. of current_value
85
89
 
90
+ desc "Pull new files from the chef client this is bundled with and update this cookbook"
86
91
  task :update do
87
92
  # Copy files from chef to chef_compat/chef, with a few changes
88
93
  target_path = File.expand_path("../files/lib/chef_compat/copied_from_chef", __FILE__)
@@ -0,0 +1,56 @@
1
+ begin
2
+ require 'chef/dsl/core'
3
+ rescue LoadError; end
4
+
5
+ require 'chef_compat/copied_from_chef'
6
+ class Chef
7
+ module ::ChefCompat
8
+ module CopiedFromChef
9
+ #--
10
+ # Author:: Adam Jacob (<adam@chef.io>)
11
+ # Author:: Christopher Walters (<cw@chef.io>)
12
+ # Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc.
13
+ # License:: Apache License, Version 2.0
14
+ #
15
+ # Licensed under the Apache License, Version 2.0 (the "License");
16
+ # you may not use this file except in compliance with the License.
17
+ # You may obtain a copy of the License at
18
+ #
19
+ # http://www.apache.org/licenses/LICENSE-2.0
20
+ #
21
+ # Unless required by applicable law or agreed to in writing, software
22
+ # distributed under the License is distributed on an "AS IS" BASIS,
23
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
+ # See the License for the specific language governing permissions and
25
+ # limitations under the License.
26
+ #
27
+
28
+ require "chef_compat/copied_from_chef/chef/dsl/declare_resource"
29
+ require "chef_compat/copied_from_chef/chef/mixin/notifying_block"
30
+ require "chef_compat/copied_from_chef/chef/mixin/powershell_out"
31
+
32
+ class Chef < (defined?(::Chef) ? ::Chef : Object)
33
+ module DSL
34
+ CopiedFromChef.extend_chef_module(::Chef::DSL, self) if defined?(::Chef::DSL)
35
+ # This is the "Core DSL" with various bits of Sugar that are mixed into core providers as well
36
+ # as user LWRPs. This module deliberately does not mixin the Resources or Defintions DSL bits
37
+ # so that cookbooks are not injeting random things into the namespace of core providers.
38
+ #
39
+ # - If you are writing cookbooks: you have come to the wrong place, please inject things into
40
+ # Chef::DSL::Recipe instead.
41
+ #
42
+ # - If you are writing core chef: you have come to the right place, please drop your DSL modules
43
+ # into here.
44
+ #
45
+ module Core
46
+ CopiedFromChef.extend_chef_module(::Chef::DSL::Core, self) if defined?(::Chef::DSL::Core)
47
+ include Chef::DSL::DeclareResource
48
+ include Chef::Mixin::NotifyingBlock
49
+ include Chef::Mixin::PowershellOut
50
+ include Chef::Mixin::ShellOut
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -8,7 +8,7 @@ module ::ChefCompat
8
8
  module CopiedFromChef
9
9
  #--
10
10
  # Author:: Adam Jacob (<adam@chef.io>)
11
- # Author:: Christopher Walters (<cw@chef.io>)
11
+ # Author:: Christopher Walters
12
12
  # Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc.
13
13
  # License:: Apache License, Version 2.0
14
14
  #
@@ -32,7 +32,180 @@ class Chef < (defined?(::Chef) ? ::Chef : Object)
32
32
  module DeclareResource
33
33
  CopiedFromChef.extend_chef_module(::Chef::DSL::DeclareResource, self) if defined?(::Chef::DSL::DeclareResource)
34
34
 
35
+ # Helper for switching run_contexts. Allows for using :parent or :root in place of
36
+ # passing the run_context. Executes the block in the run_context. Returns the return
37
+ # value of the passed block.
35
38
  #
39
+ # @param rc [Chef::RunContext,Symbol] Either :root, :parent or a Chef::RunContext
40
+ #
41
+ # @return return value of the block
42
+ #
43
+ # @example
44
+ # # creates/returns a 'service[foo]' resource in the root run_context
45
+ # resource = with_run_context(:root)
46
+ # edit_resource(:service, "foo") do
47
+ # action :nothing
48
+ # end
49
+ # end
50
+ #
51
+ def with_run_context(rc)
52
+ raise ArgumentError, "with_run_context is useless without a block" unless block_given?
53
+ old_run_context = @run_context
54
+ @run_context =
55
+ case rc
56
+ when Chef::RunContext
57
+ rc
58
+ when :root
59
+ Chef.run_context
60
+ when :parent
61
+ run_context.parent_run_context
62
+ else
63
+ raise ArgumentError, "bad argument to run_context helper, must be :root, :parent, or a Chef::RunContext"
64
+ end
65
+ yield
66
+ ensure
67
+ @run_context = old_run_context
68
+ end
69
+
70
+ # Lookup a resource in the resource collection by name and delete it. This
71
+ # will raise Chef::Exceptions::ResourceNotFound if the resource is not found.
72
+ #
73
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
74
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
75
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
76
+ #
77
+ # @return [Chef::Resource] The resource
78
+ #
79
+ # @example
80
+ # delete_resource!(:template, '/x/y.txy')
81
+ #
82
+ def delete_resource!(type, name, run_context: self.run_context)
83
+ run_context.resource_collection.delete("#{type}[#{name}]")
84
+ end
85
+
86
+ # Lookup a resource in the resource collection by name and delete it. Returns
87
+ # nil if the resource is not found and should not fail.
88
+ #
89
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
90
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
91
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
92
+ #
93
+ # @return [Chef::Resource] The resource
94
+ #
95
+ # @example
96
+ # delete_resource(:template, '/x/y.txy')
97
+ #
98
+ def delete_resource(type, name, run_context: self.run_context)
99
+ delete_resource!(type, name, run_context: run_context)
100
+ rescue Chef::Exceptions::ResourceNotFound
101
+ nil
102
+ end
103
+
104
+ # Lookup a resource in the resource collection by name and edit the resource. If the resource is not
105
+ # found this will raise Chef::Exceptions::ResourceNotFound. This is the correct API to use for
106
+ # "chef_rewind" functionality.
107
+ #
108
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
109
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
110
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
111
+ # @param resource_attrs_block A block that lets you set attributes of the
112
+ # resource (it is instance_eval'd on the resource instance).
113
+ #
114
+ # @return [Chef::Resource] The updated resource
115
+ #
116
+ # @example
117
+ # edit_resource!(:template, '/x/y.txy') do
118
+ # cookbook_name: cookbook_name
119
+ # end
120
+ #
121
+ def edit_resource!(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block)
122
+ resource = find_resource!(type, name, run_context: run_context)
123
+ resource.instance_eval(&resource_attrs_block) if block_given?
124
+ resource
125
+ end
126
+
127
+ # Lookup a resource in the resource collection by name. If it exists,
128
+ # return it. If it does not exist, create it. This is a useful function
129
+ # for accumulator patterns. In CRUD terminology this is an "upsert" operation and is
130
+ # used to assert that the resource must exist with the specified properties.
131
+ #
132
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
133
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
134
+ # @param created_at [String] The caller of the resource. Use `caller[0]`
135
+ # to get the caller of your function. Defaults to the caller of this
136
+ # function.
137
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
138
+ # @param resource_attrs_block A block that lets you set attributes of the
139
+ # resource (it is instance_eval'd on the resource instance).
140
+ #
141
+ # @return [Chef::Resource] The updated or created resource
142
+ #
143
+ # @example
144
+ # resource = edit_resource(:template, '/x/y.txy') do
145
+ # source "y.txy.erb"
146
+ # variables {}
147
+ # end
148
+ # resource.variables.merge!({ home: "/home/klowns" })
149
+ #
150
+ def edit_resource(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block)
151
+ edit_resource!(type, name, created_at, run_context: run_context, &resource_attrs_block)
152
+ rescue Chef::Exceptions::ResourceNotFound
153
+ declare_resource(type, name, created_at, run_context: run_context, &resource_attrs_block)
154
+ end
155
+
156
+ # Lookup a resource in the resource collection by name. If the resource is not
157
+ # found this will raise Chef::Exceptions::ResourceNotFound. This API is identical to the
158
+ # resources() call and while it is a synonym it is not intended to deprecate that call.
159
+ #
160
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
161
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
162
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
163
+ #
164
+ # @return [Chef::Resource] The updated resource
165
+ #
166
+ # @example
167
+ # resource = find_resource!(:template, '/x/y.txy')
168
+ #
169
+ def find_resource!(type, name, run_context: self.run_context)
170
+ raise ArgumentError, "find_resource! does not take a block" if block_given?
171
+ run_context.resource_collection.find(type => name)
172
+ end
173
+
174
+ # Lookup a resource in the resource collection by name. If the resource is not found
175
+ # the will be no exception raised and the call will return nil. If a block is given and
176
+ # no resource is found it will create the resource using the block, if the resource is
177
+ # found then the block will not be applied. The block version is similar to create_if_missing
178
+ #
179
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
180
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
181
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
182
+ #
183
+ # @return [Chef::Resource] The updated resource
184
+ #
185
+ # @example
186
+ # if ( find_resource(:template, '/x/y.txy') )
187
+ # # do something
188
+ # else
189
+ # # don't worry about the error
190
+ # end
191
+ #
192
+ # @example
193
+ # # this API can be used to return a resource from an outer run context, and will only create
194
+ # # an action :nothing service if one does not already exist.
195
+ # resource = with_run_context(:root) do
196
+ # find_resource(:service, 'whatever') do
197
+ # action :nothing
198
+ # end
199
+ # end
200
+ #
201
+ def find_resource(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block)
202
+ find_resource!(type, name, run_context: run_context)
203
+ rescue Chef::Exceptions::ResourceNotFound
204
+ if block_given?
205
+ declare_resource(type, name, created_at, run_context: run_context, &resource_attrs_block)
206
+ end # returns nil otherwise
207
+ end
208
+
36
209
  # Instantiates a resource (via #build_resource), then adds it to the
37
210
  # resource collection. Note that resource classes are looked up directly,
38
211
  # so this will create the resource you intended even if the method name
@@ -43,6 +216,7 @@ class Chef < (defined?(::Chef) ? ::Chef : Object)
43
216
  # @param created_at [String] The caller of the resource. Use `caller[0]`
44
217
  # to get the caller of your function. Defaults to the caller of this
45
218
  # function.
219
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
46
220
  # @param resource_attrs_block A block that lets you set attributes of the
47
221
  # resource (it is instance_eval'd on the resource instance).
48
222
  #
@@ -61,11 +235,9 @@ class Chef < (defined?(::Chef) ? ::Chef : Object)
61
235
  created_at ||= caller[0]
62
236
 
63
237
  if create_if_missing
64
- begin
65
- resource = run_context.resource_collection.find(type => name)
66
- return resource
67
- rescue Chef::Exceptions::ResourceNotFound
68
- end
238
+ Chef::Log.deprecation "build_resource with a create_if_missing flag is deprecated, use edit_resource instead"
239
+ # midly goofy since we call edit_resource only to re-call ourselves, but that's why its deprecated...
240
+ return edit_resource(type, name, created_at, run_context: run_context, &resource_attrs_block)
69
241
  end
70
242
 
71
243
  resource = build_resource(type, name, created_at, &resource_attrs_block)
@@ -74,7 +246,6 @@ class Chef < (defined?(::Chef) ? ::Chef : Object)
74
246
  resource
75
247
  end
76
248
 
77
- #
78
249
  # Instantiate a resource of the given +type+ with the given +name+ and
79
250
  # attributes as given in the +resource_attrs_block+.
80
251
  #
@@ -85,6 +256,7 @@ class Chef < (defined?(::Chef) ? ::Chef : Object)
85
256
  # @param created_at [String] The caller of the resource. Use `caller[0]`
86
257
  # to get the caller of your function. Defaults to the caller of this
87
258
  # function.
259
+ # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on
88
260
  # @param resource_attrs_block A block that lets you set attributes of the
89
261
  # resource (it is instance_eval'd on the resource instance).
90
262
  #
@@ -97,6 +269,9 @@ class Chef < (defined?(::Chef) ? ::Chef : Object)
97
269
  #
98
270
  def build_resource(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block)
99
271
  created_at ||= caller[0]
272
+
273
+ # this needs to be lazy in order to avoid circular dependencies since ResourceBuilder
274
+ # will requires the entire provider+resolver universe
100
275
  require "chef_compat/copied_from_chef/chef/resource_builder" unless defined?(Chef::ResourceBuilder)
101
276
 
102
277
  Chef::ResourceBuilder.new(
@@ -6,16 +6,19 @@ require 'chef_compat/copied_from_chef'
6
6
  class Chef
7
7
  module ::ChefCompat
8
8
  module CopiedFromChef
9
- require "chef_compat/copied_from_chef/chef/dsl/declare_resource"
9
+ require "chef_compat/copied_from_chef/chef/dsl/core"
10
+ require "chef_compat/copied_from_chef/chef/mixin/lazy_module_include"
10
11
  class Chef < (defined?(::Chef) ? ::Chef : Object)
11
12
  module DSL
12
13
  CopiedFromChef.extend_chef_module(::Chef::DSL, self) if defined?(::Chef::DSL)
13
14
  module Recipe
14
15
  CopiedFromChef.extend_chef_module(::Chef::DSL::Recipe, self) if defined?(::Chef::DSL::Recipe)
15
- include Chef::DSL::DeclareResource
16
+ include Chef::DSL::Core
17
+ extend Chef::Mixin::LazyModuleInclude
16
18
  module FullDSL
17
19
  CopiedFromChef.extend_chef_module(::Chef::DSL::Recipe::FullDSL, self) if defined?(::Chef::DSL::Recipe::FullDSL)
18
20
  include Chef::DSL::Recipe
21
+ extend Chef::Mixin::LazyModuleInclude
19
22
  end
20
23
  end
21
24
  end
@@ -0,0 +1,90 @@
1
+ begin
2
+ require 'chef/mixin/lazy_module_include'
3
+ rescue LoadError; end
4
+
5
+ require 'chef_compat/copied_from_chef'
6
+ class Chef
7
+ module ::ChefCompat
8
+ module CopiedFromChef
9
+ #
10
+ # Copyright:: Copyright 2011-2016, Chef Software Inc.
11
+ # License:: Apache License, Version 2.0
12
+ #
13
+ # Licensed under the Apache License, Version 2.0 (the "License");
14
+ # you may not use this file except in compliance with the License.
15
+ # You may obtain a copy of the License at
16
+ #
17
+ # http://www.apache.org/licenses/LICENSE-2.0
18
+ #
19
+ # Unless required by applicable law or agreed to in writing, software
20
+ # distributed under the License is distributed on an "AS IS" BASIS,
21
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ # See the License for the specific language governing permissions and
23
+ # limitations under the License.
24
+ #
25
+
26
+ class Chef < (defined?(::Chef) ? ::Chef : Object)
27
+ module Mixin
28
+ CopiedFromChef.extend_chef_module(::Chef::Mixin, self) if defined?(::Chef::Mixin)
29
+ # If you have:
30
+ #
31
+ # module A
32
+ # extend LazyModuleInclude
33
+ # end
34
+ #
35
+ # module B
36
+ # include A
37
+ # end
38
+ #
39
+ # module C
40
+ # include B
41
+ # end
42
+ #
43
+ # module Monkeypatches
44
+ # def monkey
45
+ # puts "monkey!"
46
+ # end
47
+ # end
48
+ #
49
+ # A.send(:include, Monkeypatches)
50
+ #
51
+ # Then B and C and any classes that they're included in will also get the #monkey method patched into them.
52
+ #
53
+ module LazyModuleInclude
54
+ CopiedFromChef.extend_chef_module(::Chef::Mixin::LazyModuleInclude, self) if defined?(::Chef::Mixin::LazyModuleInclude)
55
+
56
+ # Most of the magick is in this hook which creates a closure over the parent class and then builds an
57
+ # "infector" module which infects all descendants and which is responsible for updating the list of
58
+ # descendants in the parent class.
59
+ def included(klass)
60
+ super
61
+ parent_klass = self
62
+ infector = Module.new do
63
+ define_method(:included) do |subklass|
64
+ super(subklass)
65
+ subklass.extend(infector)
66
+ parent_klass.descendants.push(subklass)
67
+ end
68
+ end
69
+ klass.extend(infector)
70
+ parent_klass.descendants.push(klass)
71
+ end
72
+
73
+ def descendants
74
+ @descendants ||= []
75
+ end
76
+
77
+ def include(*classes)
78
+ super
79
+ classes.each do |klass|
80
+ descendants.each do |descendant|
81
+ descendant.send(:include, klass)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end