cocoapods-square-stable 0.19.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1296 -0
  3. data/LICENSE +20 -0
  4. data/README.md +94 -0
  5. data/bin/pod +16 -0
  6. data/bin/sandbox-pod +120 -0
  7. data/lib/cocoapods.rb +77 -0
  8. data/lib/cocoapods/command.rb +116 -0
  9. data/lib/cocoapods/command/help.rb +23 -0
  10. data/lib/cocoapods/command/inter_process_communication.rb +178 -0
  11. data/lib/cocoapods/command/list.rb +77 -0
  12. data/lib/cocoapods/command/outdated.rb +56 -0
  13. data/lib/cocoapods/command/podfile_info.rb +91 -0
  14. data/lib/cocoapods/command/project.rb +88 -0
  15. data/lib/cocoapods/command/push.rb +172 -0
  16. data/lib/cocoapods/command/repo.rb +145 -0
  17. data/lib/cocoapods/command/search.rb +61 -0
  18. data/lib/cocoapods/command/setup.rb +134 -0
  19. data/lib/cocoapods/command/spec.rb +590 -0
  20. data/lib/cocoapods/config.rb +231 -0
  21. data/lib/cocoapods/downloader.rb +59 -0
  22. data/lib/cocoapods/executable.rb +118 -0
  23. data/lib/cocoapods/external_sources.rb +363 -0
  24. data/lib/cocoapods/file_list.rb +36 -0
  25. data/lib/cocoapods/gem_version.rb +7 -0
  26. data/lib/cocoapods/generator/acknowledgements.rb +107 -0
  27. data/lib/cocoapods/generator/acknowledgements/markdown.rb +40 -0
  28. data/lib/cocoapods/generator/acknowledgements/plist.rb +64 -0
  29. data/lib/cocoapods/generator/bridge_support.rb +22 -0
  30. data/lib/cocoapods/generator/copy_resources_script.rb +54 -0
  31. data/lib/cocoapods/generator/dummy_source.rb +22 -0
  32. data/lib/cocoapods/generator/prefix_header.rb +82 -0
  33. data/lib/cocoapods/generator/target_environment_header.rb +86 -0
  34. data/lib/cocoapods/generator/xcconfig.rb +185 -0
  35. data/lib/cocoapods/hooks/installer_representation.rb +134 -0
  36. data/lib/cocoapods/hooks/library_representation.rb +94 -0
  37. data/lib/cocoapods/hooks/pod_representation.rb +74 -0
  38. data/lib/cocoapods/installer.rb +571 -0
  39. data/lib/cocoapods/installer/analyzer.rb +559 -0
  40. data/lib/cocoapods/installer/analyzer/sandbox_analyzer.rb +253 -0
  41. data/lib/cocoapods/installer/file_references_installer.rb +179 -0
  42. data/lib/cocoapods/installer/pod_source_installer.rb +248 -0
  43. data/lib/cocoapods/installer/target_installer.rb +379 -0
  44. data/lib/cocoapods/installer/user_project_integrator.rb +180 -0
  45. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +224 -0
  46. data/lib/cocoapods/library.rb +202 -0
  47. data/lib/cocoapods/open_uri.rb +24 -0
  48. data/lib/cocoapods/project.rb +209 -0
  49. data/lib/cocoapods/resolver.rb +212 -0
  50. data/lib/cocoapods/sandbox.rb +343 -0
  51. data/lib/cocoapods/sandbox/file_accessor.rb +217 -0
  52. data/lib/cocoapods/sandbox/headers_store.rb +96 -0
  53. data/lib/cocoapods/sandbox/path_list.rb +208 -0
  54. data/lib/cocoapods/sources_manager.rb +276 -0
  55. data/lib/cocoapods/user_interface.rb +304 -0
  56. data/lib/cocoapods/user_interface/error_report.rb +101 -0
  57. data/lib/cocoapods/validator.rb +350 -0
  58. metadata +238 -0
@@ -0,0 +1,559 @@
1
+ module Pod
2
+ class Installer
3
+
4
+ # Analyzes the Podfile, the Lockfile, and the sandbox manifest to generate
5
+ # the information relative to a CocoaPods installation.
6
+ #
7
+ class Analyzer
8
+
9
+ include Config::Mixin
10
+
11
+ autoload :SandboxAnalyzer, 'cocoapods/installer/analyzer/sandbox_analyzer'
12
+
13
+ # @return [Sandbox] The sandbox where the Pods should be installed.
14
+ #
15
+ attr_reader :sandbox
16
+
17
+ # @return [Podfile] The Podfile specification that contains the
18
+ # information of the Pods that should be installed.
19
+ #
20
+ attr_reader :podfile
21
+
22
+ # @return [Lockfile] The Lockfile that stores the information about the
23
+ # Pods previously installed on any machine.
24
+ #
25
+ attr_reader :lockfile
26
+
27
+ # @param [Sandbox] sandbox @see sandbox
28
+ # @param [Podfile] podfile @see podfile
29
+ # @param [Lockfile] lockfile @see lockfile
30
+ #
31
+ def initialize(sandbox, podfile, lockfile = nil)
32
+ @sandbox = sandbox
33
+ @podfile = podfile
34
+ @lockfile = lockfile
35
+
36
+ @update_mode = false
37
+ @allow_pre_downloads = true
38
+ end
39
+
40
+ # Performs the analysis.
41
+ #
42
+ # The Podfile and the Lockfile provide the information necessary to
43
+ # compute which specification should be installed. The manifest of the
44
+ # sandbox returns which specifications are installed.
45
+ #
46
+ # @return [AnalysisResult]
47
+ #
48
+ def analyze(allow_fetches = true)
49
+ update_repositories_if_needed if allow_fetches
50
+ @result = AnalysisResult.new
51
+ @result.podfile_state = generate_podfile_state
52
+ @locked_dependencies = generate_version_locking_dependencies
53
+
54
+ @result.libraries = generated_libraries
55
+ fetch_external_sources if allow_fetches
56
+ @result.specs_by_target = resolve_dependencies
57
+ @result.specifications = generate_specifications
58
+ @result.sandbox_state = generate_sandbox_state
59
+ @result
60
+ end
61
+
62
+ attr_accessor :result
63
+
64
+ # @return [Bool] Whether an installation should be performed or this
65
+ # CocoaPods project is already up to date.
66
+ #
67
+ def needs_install?
68
+ analysis_result = analyze(false)
69
+ podfile_needs_install?(analysis_result) || sandbox_needs_install?(analysis_result)
70
+ end
71
+
72
+ # @return [Bool] Whether the podfile has changes respect to the lockfile.
73
+ #
74
+ def podfile_needs_install?(analysis_result)
75
+ state = analysis_result.podfile_state
76
+ needing_install = state.added + state.changed + state.deleted
77
+ !needing_install.empty?
78
+ end
79
+
80
+ # @return [Bool] Whether the sandbox is in synch with the lockfile.
81
+ #
82
+ def sandbox_needs_install?(analysis_result)
83
+ state = analysis_result.sandbox_state
84
+ needing_install = state.added + state.changed + state.deleted
85
+ !needing_install.empty?
86
+ end
87
+
88
+ #-----------------------------------------------------------------------#
89
+
90
+ # @!group Configuration
91
+
92
+ # @return [Bool] Whether the version of the dependencies which did non
93
+ # change in the Podfile should be locked.
94
+ #
95
+ attr_accessor :update_mode
96
+ alias_method :update_mode?, :update_mode
97
+
98
+ # @return [Bool] Whether the analysis allows pre-downloads and thus
99
+ # modifications to the sandbox.
100
+ #
101
+ # @note This flag should not be used in installations.
102
+ #
103
+ # @note This is used by the `pod outdated` command to prevent
104
+ # modification of the sandbox in the resolution process.
105
+ #
106
+ attr_accessor :allow_pre_downloads
107
+ alias_method :allow_pre_downloads?, :allow_pre_downloads
108
+
109
+ #-----------------------------------------------------------------------#
110
+
111
+ private
112
+
113
+ # @!group Analysis steps
114
+
115
+ # Compares the {Podfile} with the {Lockfile} in order to detect which
116
+ # dependencies should be locked.
117
+ #
118
+ # @return [SpecsState] the states of the Podfile specs.
119
+ #
120
+ # @note As the target definitions share the same sandbox they should have
121
+ # the same version of a Pod. For this reason this method returns
122
+ # the name of the Pod (root name of the dependencies) and doesn't
123
+ # group them by target definition.
124
+ #
125
+ # @todo [CocoaPods > 0.18] If there isn't a Lockfile all the Pods should
126
+ # be marked as added.
127
+ #
128
+ def generate_podfile_state
129
+ if lockfile
130
+ pods_state = nil
131
+ UI.section "Finding Podfile changes" do
132
+ pods_by_state = lockfile.detect_changes_with_podfile(podfile)
133
+ pods_by_state.dup.each do |state, full_names|
134
+ pods_by_state[state] = full_names.map { |fn| Specification.root_name(fn) }
135
+ end
136
+ pods_state = SpecsState.new(pods_by_state)
137
+ pods_state.print
138
+ end
139
+ pods_state
140
+ else
141
+ state = SpecsState.new
142
+ state.added.concat(podfile.dependencies.map(&:root_name).uniq)
143
+ state
144
+ end
145
+ end
146
+
147
+ # Updates the source repositories unless the config indicates to skip it.
148
+ #
149
+ # @return [void]
150
+ #
151
+ def update_repositories_if_needed
152
+ unless config.skip_repo_update?
153
+ UI.section 'Updating spec repositories' do
154
+ SourcesManager.update
155
+ end
156
+ end
157
+ end
158
+
159
+ # Creates the models that represent the libraries generated by CocoaPods.
160
+ #
161
+ # @note The libraries are generated before the resolution process
162
+ # because it might be necessary to infer the platform from the
163
+ # user targets, which in turns requires to identify the user
164
+ # project.
165
+ #
166
+ # @note The specification of the libraries are added in the
167
+ # {#resolve_dependencies} step.
168
+ #
169
+ # @return [Array<Libraries>] the generated libraries.
170
+ #
171
+ def generated_libraries
172
+ libraries = []
173
+ podfile.target_definition_list.each do |target_definition|
174
+ lib = Library.new(target_definition)
175
+ lib.support_files_root = sandbox.library_support_files_dir(lib.name)
176
+
177
+ if config.integrate_targets?
178
+ project_path = compute_user_project_path(target_definition)
179
+ user_project = Xcodeproj::Project.new(project_path)
180
+ targets = compute_user_project_targets(target_definition, user_project)
181
+
182
+ lib.user_project_path = project_path
183
+ lib.client_root = project_path.dirname
184
+ lib.user_target_uuids = targets.map(&:uuid)
185
+ lib.user_build_configurations = compute_user_build_configurations(target_definition, targets)
186
+ lib.platform = compute_platform_for_target_definition(target_definition, targets)
187
+ else
188
+ unless target_definition.platform
189
+ raise Informative, "It is necessary to specify the platform in the Podfile if not integrating."
190
+ end
191
+ lib.client_root = config.installation_root
192
+ lib.user_target_uuids = []
193
+ lib.user_build_configurations = {}
194
+ lib.platform = target_definition.platform
195
+ end
196
+ libraries << lib
197
+ end
198
+ libraries
199
+ end
200
+
201
+ # Generates dependencies that require the specific version of the Pods
202
+ # that haven't changed in the {Lockfile}.
203
+ #
204
+ # These dependencies are passed to the {Resolver}, unless the installer
205
+ # is in update mode, to prevent it from upgrading the Pods that weren't
206
+ # changed in the {Podfile}.
207
+ #
208
+ # @return [Array<Dependency>] the dependencies generate by the lockfile
209
+ # that prevent the resolver to update a Pod.
210
+ #
211
+ def generate_version_locking_dependencies
212
+ if update_mode?
213
+ []
214
+ else
215
+ result.podfile_state.unchanged.map do |pod|
216
+ lockfile.dependency_to_lock_pod_named(pod)
217
+ end
218
+ end
219
+ end
220
+
221
+ # Fetches the podspecs of external sources if modifications to the
222
+ # sandbox are allowed.
223
+ #
224
+ # @note In update mode all the external sources are refreshed while in
225
+ # normal mode they are refreshed only if added or changed in the
226
+ # Podfile. Moreover, in normal specifications for unchanged Pods
227
+ # which are missing or are generated from an local source are
228
+ # fetched as well.
229
+ #
230
+ # @note It is possible to perform this step before the resolution
231
+ # process because external sources identify a single specific
232
+ # version (checkout). If the other dependencies are not
233
+ # compatible with the version reported by the podspec of the
234
+ # external source the resolver will raise.
235
+ #
236
+ # @return [void]
237
+ #
238
+ # TODO Specs
239
+ #
240
+ def fetch_external_sources
241
+ return unless allow_pre_downloads?
242
+ deps_to_fetch = []
243
+ deps_to_fetch_if_needed = []
244
+ deps_with_external_source = podfile.dependencies.select { |dep| dep.external_source }
245
+ if update_mode?
246
+ deps_to_fetch = deps_with_external_source
247
+ else
248
+ pods_to_fetch = result.podfile_state.added + result.podfile_state.changed
249
+ deps_to_fetch = deps_with_external_source.select { |dep| pods_to_fetch.include?(dep.root_name) }
250
+ deps_to_fetch_if_needed = deps_with_external_source.select { |dep| result.podfile_state.unchanged.include?(dep.root_name) }
251
+ deps_to_fetch += deps_to_fetch_if_needed.select { |dep| sandbox.specification(dep.root_name).nil? || !dep.external_source[:local].nil? || !dep.external_source[:path].nil? }
252
+ end
253
+
254
+ unless deps_to_fetch.empty?
255
+ UI.section "Fetching external sources" do
256
+ deps_to_fetch.uniq.sort.each do |dependency|
257
+ source = ExternalSources.from_dependency(dependency, podfile.defined_in_file)
258
+ source.fetch(sandbox)
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ # Converts the Podfile in a list of specifications grouped by target.
265
+ #
266
+ # @note In this step the specs are added to the libraries.
267
+ #
268
+ # @note As some dependencies might have external sources the resolver
269
+ # is aware of the {Sandbox} and interacts with it to download the
270
+ # podspecs of the external sources. This is necessary because the
271
+ # resolver needs their specifications to analyze their
272
+ # dependencies.
273
+ #
274
+ # @note The specifications of the external sources which are added,
275
+ # modified or removed need to deleted from the sandbox before the
276
+ # resolution process. Otherwise the resolver might use an
277
+ # incorrect specification instead of pre-downloading it.
278
+ #
279
+ # @note In update mode the resolver is set to always update the specs
280
+ # from external sources.
281
+ #
282
+ # @return [Hash{TargetDefinition => Array<Spec>}] the specifications
283
+ # grouped by target.
284
+ #
285
+ def resolve_dependencies
286
+ specs_by_target = nil
287
+
288
+ UI.section "Resolving dependencies of #{UI.path podfile.defined_in_file}" do
289
+ resolver = Resolver.new(sandbox, podfile, locked_dependencies)
290
+ specs_by_target = resolver.resolve
291
+ end
292
+
293
+ specs_by_target.each do |target_definition, specs|
294
+ lib = result.libraries.find { |l| l.target_definition == target_definition}
295
+ lib.specs = specs
296
+ end
297
+
298
+ specs_by_target
299
+ end
300
+
301
+ # Returns the list of all the resolved the resolved specifications.
302
+ #
303
+ # @return [Array<Specification>] the list of the specifications.
304
+ #
305
+ def generate_specifications
306
+ result.specs_by_target.values.flatten.uniq
307
+ end
308
+
309
+ # Computes the state of the sandbox respect to the resolved
310
+ # specifications.
311
+ #
312
+ # @return [SpecsState] the representation of the state of the manifest
313
+ # specifications.
314
+ #
315
+ def generate_sandbox_state
316
+ sandbox_state = nil
317
+ UI.section "Comparing resolved specification to the sandbox manifest" do
318
+ sandbox_analyzer = SandboxAnalyzer.new(sandbox, result.specifications, update_mode, lockfile)
319
+ sandbox_state = sandbox_analyzer.analyze
320
+ sandbox_state.print
321
+ end
322
+ sandbox_state
323
+ end
324
+
325
+ #-----------------------------------------------------------------------#
326
+
327
+ # @!group Analysis internal products
328
+
329
+ # @return [Array<Dependency>] the dependencies generate by the lockfile
330
+ # that prevent the resolver to update a Pod.
331
+ #
332
+ attr_reader :locked_dependencies
333
+
334
+ #-----------------------------------------------------------------------#
335
+
336
+ private
337
+
338
+ # @!group Analysis sub-steps
339
+
340
+ # Returns the path of the user project that the {TargetDefinition}
341
+ # should integrate.
342
+ #
343
+ # @raise If the project is implicit and there are multiple projects.
344
+ #
345
+ # @raise If the path doesn't exits.
346
+ #
347
+ # @return [Pathname] the path of the user project.
348
+ #
349
+ def compute_user_project_path(target_definition)
350
+ if target_definition.user_project_path
351
+ path = config.installation_root + target_definition.user_project_path
352
+ path = "#{path}.xcodeproj" unless File.extname(path) == '.xcodeproj'
353
+ path = Pathname.new(path)
354
+ unless path.exist?
355
+ raise Informative, "Unable to find the Xcode project " \
356
+ "`#{path}` for the target `#{target_definition.label}`."
357
+ end
358
+
359
+ else
360
+ xcodeprojs = Pathname.glob(config.installation_root + '*.xcodeproj')
361
+ if xcodeprojs.size == 1
362
+ path = xcodeprojs.first
363
+ else
364
+ raise Informative, "Could not automatically select an Xcode project. " \
365
+ "Specify one in your Podfile like so:\n\n" \
366
+ " xcodeproj 'path/to/Project.xcodeproj'\n"
367
+ end
368
+ end
369
+ path
370
+ end
371
+
372
+ # Returns a list of the targets from the project of {TargetDefinition}
373
+ # that needs to be integrated.
374
+ #
375
+ # @note The method first looks if there is a target specified with
376
+ # the `link_with` option of the {TargetDefinition}. Otherwise
377
+ # it looks for the target that has the same name of the target
378
+ # definition. Finally if no target was found the first
379
+ # encountered target is returned (it is assumed to be the one
380
+ # to integrate in simple projects).
381
+ #
382
+ # @note This will only return targets that do **not** already have
383
+ # the Pods library in their frameworks build phase.
384
+ #
385
+ #
386
+ def compute_user_project_targets(target_definition, user_project)
387
+ if link_with = target_definition.link_with
388
+ targets = native_targets(user_project).select { |t| link_with.include?(t.name) }
389
+ raise Informative, "Unable to find the targets named `#{link_with.to_sentence}` to link with target definition `#{target_definition.name}`" if targets.empty?
390
+ elsif target_definition.link_with_first_target?
391
+ targets = [ native_targets(user_project).first ].compact
392
+ raise Informative, "Unable to find a target" if targets.empty?
393
+ else
394
+ target = native_targets(user_project).find { |t| t.name == target_definition.name.to_s }
395
+ targets = [ target ].compact
396
+ raise Informative, "Unable to find a target named `#{target_definition.name.to_s}`" if targets.empty?
397
+ end
398
+ targets
399
+ end
400
+
401
+ # @return [Array<PBXNativeTarget>] Returns the user’s targets, excluding
402
+ # aggregate targets.
403
+ #
404
+ def native_targets(user_project)
405
+ user_project.targets.reject do |target|
406
+ target.is_a? Xcodeproj::Project::Object::PBXAggregateTarget
407
+ end
408
+ end
409
+
410
+ # @return [Hash{String=>Symbol}] A hash representing the user build
411
+ # configurations where each key corresponds to the name of a
412
+ # configuration and its value to its type (`:debug` or `:release`).
413
+ #
414
+ def compute_user_build_configurations(target_definition, user_targets)
415
+ if user_targets
416
+ user_targets.map { |t| t.build_configurations.map(&:name) }.flatten.inject({}) do |hash, name|
417
+ unless name == 'Debug' || name == 'Release'
418
+ hash[name] = :release
419
+ end
420
+ hash
421
+ end.merge(target_definition.build_configurations || {})
422
+ else
423
+ target_definition.build_configurations || {}
424
+ end
425
+ end
426
+
427
+ # @return [Platform] The platform for the library.
428
+ #
429
+ # @note This resolves to the lowest deployment target across the user
430
+ # targets.
431
+ #
432
+ # @todo Is assigning the platform to the target definition the best way
433
+ # to go?
434
+ #
435
+ def compute_platform_for_target_definition(target_definition, user_targets)
436
+ return target_definition.platform if target_definition.platform
437
+ name = nil
438
+ deployment_target = nil
439
+
440
+ user_targets.each do |target|
441
+ name ||= target.platform_name
442
+ raise Informative, "Targets with different platforms" unless name == target.platform_name
443
+ if !deployment_target || deployment_target > Version.new(target.deployment_target)
444
+ deployment_target = Version.new(target.deployment_target)
445
+ end
446
+ end
447
+
448
+ target_definition.set_platform(name, deployment_target)
449
+ Platform.new(name, deployment_target)
450
+ end
451
+
452
+ #-----------------------------------------------------------------------#
453
+
454
+ class AnalysisResult
455
+
456
+ # @return [SpecsState] the states of the Podfile specs.
457
+ #
458
+ attr_accessor :podfile_state
459
+
460
+ # @return [Hash{TargetDefinition => Array<Spec>}] the specifications
461
+ # grouped by target.
462
+ #
463
+ attr_accessor :specs_by_target
464
+
465
+ # @return [Array<Specification>] the specifications of the resolved
466
+ # version of Pods that should be installed.
467
+ #
468
+ attr_accessor :specifications
469
+
470
+ # @return [SpecsState] the states of the {Sandbox} respect the resolved
471
+ # specifications.
472
+ #
473
+ attr_accessor :sandbox_state
474
+
475
+ # @return [Array<Library>] the libraries generated by the target
476
+ # definitions.
477
+ #
478
+ attr_accessor :libraries
479
+
480
+ end
481
+
482
+ #-----------------------------------------------------------------------#
483
+
484
+ # This class represents the state of a collection of Pods.
485
+ #
486
+ # @note The names of the pods stored by this class are always the **root**
487
+ # name of the specification.
488
+ #
489
+ # @note The motivation for this class is to ensure that the names of the
490
+ # subspecs are added instead of the name of the Pods.
491
+ #
492
+ class SpecsState
493
+
494
+ # @param [Hash{Symbol=>String}] pods_by_state
495
+ # The **root** name of the pods grouped by their state
496
+ # (`:added`, `:removed`, `:changed` or `:unchanged`).
497
+ #
498
+ def initialize(pods_by_state = nil)
499
+ @added = []
500
+ @deleted = []
501
+ @changed = []
502
+ @unchanged = []
503
+
504
+ if pods_by_state
505
+ @added = pods_by_state[:added] || []
506
+ @deleted = pods_by_state[:removed] || []
507
+ @changed = pods_by_state[:changed] || []
508
+ @unchanged = pods_by_state[:unchanged] || []
509
+ end
510
+ end
511
+
512
+ # @return [Array<String>] the names of the pods that were added.
513
+ #
514
+ attr_accessor :added
515
+
516
+ # @return [Array<String>] the names of the pods that were changed.
517
+ #
518
+ attr_accessor :changed
519
+
520
+ # @return [Array<String>] the names of the pods that were deleted.
521
+ #
522
+ attr_accessor :deleted
523
+
524
+ # @return [Array<String>] the names of the pods that were unchanged.
525
+ #
526
+ attr_accessor :unchanged
527
+
528
+ # Displays the state of each pod.
529
+ #
530
+ # @return [void]
531
+ #
532
+ def print
533
+ added .sort.each { |pod| UI.message("A".green + " #{pod}", '', 2) }
534
+ deleted .sort.each { |pod| UI.message("R".red + " #{pod}", '', 2) }
535
+ changed .sort.each { |pod| UI.message("M".yellow + " #{pod}", '', 2) }
536
+ unchanged.sort.each { |pod| UI.message("-" + " #{pod}", '', 2) }
537
+ end
538
+
539
+ # Adds the name of a Pod to the give state.
540
+ #
541
+ # @param [String]
542
+ # the name of the Pod.
543
+ #
544
+ # @param [Symbol]
545
+ # the state of the Pod.
546
+ #
547
+ # @raise If there is an attempt to add the name of a subspec.
548
+ #
549
+ # @return [void]
550
+ #
551
+ def add_name(name, state)
552
+ raise "[Bug] Attempt to add subspec to the pods state" if name.include?('/')
553
+ self.send(state) << name
554
+ end
555
+
556
+ end
557
+ end
558
+ end
559
+ end