xccleanup 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c11e52272b6c8b2d17d990175b113e26bc820921
4
+ data.tar.gz: 7a09e454448c8c52f7b9dabc00b81c6eb15649e1
5
+ SHA512:
6
+ metadata.gz: 35aa6833ea2c9f0829bf01a57072ce929a528f998f0cdd464a521f2c33cca67bad21d8e48ef2f1d151133e373f9d7646bf11823caf48a9261ebe517bc06ff1c4
7
+ data.tar.gz: 59daff81b5a4a7c1f378ecd315c216fc5d127a5fdfd2d4b0f8e1aa12703ef17aa87cabde6f420c5390764e19dc6ed6e2223b4033a57e8d2032c2a7bf8f523a2e
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xccleanup.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Toine Heuvelmans
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # xccleanup
2
+
3
+ A cleanup tool that assists you in cleaning up after Xcode.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'xccleanup'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install xccleanup
21
+
22
+ ## Usage
23
+
24
+ Just run it. You'll be asked which steps you would like to run.
25
+
26
+ ## Development
27
+
28
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
29
+
30
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
31
+
32
+ ## Contributing
33
+
34
+ Bug reports and pull requests are welcome on GitHub at https://github.com/toineheuvelmans/xccleanup.
35
+
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
40
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "xccleanup"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ module Xccleanup
2
+ VERSION = "0.3.0"
3
+ end
data/lib/xccleanup.rb ADDED
@@ -0,0 +1,460 @@
1
+ require "xccleanup/version"
2
+
3
+ require 'fileutils'
4
+ require 'date'
5
+ require 'rubygems'
6
+
7
+ module Xccleanup
8
+
9
+ # Helper functions
10
+
11
+ $line_length = 80
12
+
13
+ def self.line(char)
14
+ puts char * $line_length
15
+ end
16
+
17
+ def self.center(text)
18
+ if text.length > 0
19
+ puts (' ' * (($line_length - text.length) / 2)) + text
20
+ end
21
+ end
22
+
23
+ def self.prompt(*args)
24
+ print(*args)
25
+ gets
26
+ end
27
+
28
+ def self.prompt_bool(message)
29
+ message = message + " (y/n) "
30
+ input = prompt message
31
+ input = input.strip!
32
+ return input.casecmp("y") == 0 || input.casecmp("yes") == 0
33
+ end
34
+
35
+ def self.get_folders_in_dir(dir)
36
+ if File.directory?(dir)
37
+ return Dir.entries(dir).select {|entry| File.directory? File.join(dir,entry) and !(entry[0] =='.')}.map {|entry| File.join(dir,entry)}
38
+ end
39
+ return []
40
+ end
41
+
42
+ def self.get_files_in_dir(dir)
43
+ if File.directory?(dir)
44
+ return Dir.entries(dir).select {|entry| File.file? File.join(dir,entry)}.map {|entry| File.join(dir,entry)}
45
+ end
46
+ return []
47
+ end
48
+
49
+ def self.get_byte_size(file_or_dir)
50
+ file_or_dir = file_or_dir
51
+ if File.file? file_or_dir
52
+ return File.stat(file_or_dir).size
53
+ else
54
+ cmd = "du -ks '#{file_or_dir}'"
55
+ return (`#{cmd}`).split("\t").first.to_i * 1024
56
+ end
57
+ end
58
+
59
+ def self.pbs(bytes) # pretty byte size
60
+ {
61
+ 'B' => 1024,
62
+ 'KB' => 1024 * 1024,
63
+ 'MB' => 1024 * 1024 * 1024,
64
+ 'GB' => 1024 * 1024 * 1024 * 1024,
65
+ 'TB' => 1024 * 1024 * 1024 * 1024 * 1024
66
+ }.each do |e, s|
67
+ return "#{(bytes.to_f / (s / 1024)).round(1)} #{e}" if bytes < s
68
+ end
69
+ end
70
+
71
+ #######################################################################
72
+ # Cleanup functions
73
+
74
+ def self.remove_derived_data(manually)
75
+ saved_bytes = 0
76
+
77
+ dd_dir = File.expand_path('~/Library/Developer/Xcode/DerivedData')
78
+ dd_folders = get_folders_in_dir(dd_dir).sort_by{ |d| File.mtime(d) }.reverse
79
+ if dd_folders.length > 1
80
+
81
+ recent_projects = 0
82
+ if manually
83
+ recent_projects = prompt "> KEEP how many most recent projects? "
84
+ recent_projects = recent_projects.to_i
85
+ end
86
+
87
+ kept = 0
88
+ dd_folders.each do |folder_path|
89
+ folder_name = folder_path.split('/').last
90
+ if folder_name != 'ModuleCache'
91
+ project_name = folder_name.rindex('-').nil? ? folder_name : folder_name[0,folder_name.rindex('-')]
92
+ project_size = get_byte_size(folder_path)
93
+ if kept < recent_projects
94
+ puts "- Keeping #{project_name} (#{pbs(project_size)})"
95
+ else
96
+ puts "- Removing #{project_name} (#{pbs(project_size)})"
97
+ FileUtils.rm_rf(folder_path)
98
+ saved_bytes += project_size
99
+ end
100
+ kept += 1
101
+ end
102
+ end
103
+ else
104
+ puts "Skipping, no cleanup needed."
105
+ end
106
+
107
+ return saved_bytes
108
+ end
109
+
110
+ def self.remove_module_cache(manually)
111
+ saved_bytes = 0
112
+
113
+ puts "Removing Module Cache..."
114
+
115
+ dd_dir = File.expand_path('~/Library/Developer/Xcode/DerivedData')
116
+ path = File.join(dd_dir,'ModuleCache')
117
+ saved_bytes = get_byte_size(path)
118
+
119
+ get_folders_in_dir(path).each do |folder|
120
+ FileUtils.rm_rf(folder)
121
+ end
122
+
123
+ return saved_bytes
124
+ end
125
+
126
+
127
+ def self.remove_device_support(manually)
128
+ saved_bytes = 0
129
+
130
+ ds_folder = File.expand_path('~/Library/Developer/Xcode/iOS DeviceSupport/')
131
+ ds_versions = get_folders_in_dir(ds_folder)
132
+
133
+ if ds_versions.length > 0
134
+ puts "Found versions:"
135
+ ds_versions.each do |version_folder|
136
+ version_size = get_byte_size(version_folder)
137
+ version_name = version_folder.split('/').last
138
+ puts "• #{version_name} (#{pbs(version_size)})"
139
+ end
140
+
141
+ min_version = Gem::Version.new('9999.9.9')
142
+ if manually
143
+ min_version = prompt "> Miminum version to KEEP? "
144
+ min_version = Gem::Version.new(min_version)
145
+ end
146
+
147
+ unless min_version.nil?
148
+ ds_versions.each do |version_folder|
149
+ version_name = version_folder.split('/').last
150
+ version_number = version_name.split(' ').first
151
+ version_number = Gem::Version.new(version_number)
152
+ unless version_number.nil?
153
+ if version_number < min_version
154
+ puts "- Removing #{version_name}"
155
+ saved_bytes += get_byte_size(version_folder)
156
+ FileUtils.rm_rf(version_folder)
157
+ else
158
+ puts "- Keeping #{version_name}"
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ return saved_bytes
166
+ end
167
+
168
+
169
+ def self.remove_old_archives(manually)
170
+ saved_bytes = 0
171
+
172
+ arch_folder = File.expand_path('~/Library/Developer/Xcode/Archives/')
173
+ arch_date_folders = get_folders_in_dir(arch_folder)
174
+
175
+ bundle_ids = {}
176
+
177
+ arch_date_folders.each do |arch_date_folder|
178
+ date_name = arch_date_folder.split('/').last
179
+
180
+ archives = get_folders_in_dir(arch_date_folder)
181
+
182
+
183
+ if archives.length == 0
184
+ puts "- Empty archives subfolder #{date_name}, removing..."
185
+ FileUtils.rm_rf(arch_date_folder)
186
+ else
187
+ archives.each do |archive|
188
+ plist_path = File.join(archive,'Info.plist')
189
+ if File.file?(plist_path)
190
+ plist = File.read(plist_path)
191
+
192
+ bundle_id_pos = plist.index('<key>CFBundleIdentifier</key>')
193
+ unless bundle_id_pos.nil?
194
+ bundle_id_pos = plist.index('<string>', bundle_id_pos)
195
+ unless bundle_id_pos.nil?
196
+ bundle_id_pos = bundle_id_pos + 8 # <string>
197
+ bundle_id_end = plist.index('</string>', bundle_id_pos)
198
+ unless bundle_id_end.nil?
199
+ bundle_id = plist[bundle_id_pos, bundle_id_end - bundle_id_pos]
200
+ if bundle_ids[bundle_id].nil?
201
+ bundle_ids[bundle_id] = {date_name => archive }
202
+ else
203
+ bundle_ids[bundle_id][date_name] = archive
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ skip_single_archives = false
214
+ if manually
215
+ skip_single_archives = prompt_bool("Skip all bundle id's for which only a single archive is present?")
216
+ end
217
+
218
+ bundle_ids.each do |bundle_id, dates|
219
+ if dates.length > 1
220
+ dates = dates.sort_by { |date, archive| Date.parse(date) }.reverse
221
+ puts "• #{dates.length} archives for \"#{bundle_id}\":"
222
+ arch_index = 1
223
+ dates.each do |date, archive|
224
+ archive_size = get_byte_size(archive)
225
+ puts " #{arch_index}: #{date} (#{pbs(archive_size)})"
226
+ arch_index += 1
227
+ end
228
+
229
+ recent = 0
230
+ if manually
231
+ recent = prompt("> KEEP how many most recent? ").to_i
232
+ end
233
+
234
+ if recent < dates.length
235
+ kept = 0
236
+ dates.each do |date, archive|
237
+ if kept >= recent
238
+ archive_size = get_byte_size(archive)
239
+ puts "- Removing #{date}"
240
+ FileUtils.rm_rf(archive)
241
+ saved_bytes += archive_size
242
+ else
243
+ puts "- Keeping #{date}"
244
+ end
245
+ kept += 1
246
+ end
247
+ end
248
+ elsif !skip_single_archives
249
+ date, archive = dates.first
250
+ archive_size = get_byte_size(archive)
251
+
252
+ remove = !manually
253
+ if manually
254
+ remove = prompt_bool("• 1 archive for \"#{bundle_id}\" (#{date}, #{pbs(archive_size)})\n> REMOVE it?")
255
+ else
256
+ puts "• Removing 1 archive for \"#{bundle_id}\" (#{date}, #{pbs(archive_size)})"
257
+ end
258
+
259
+ if remove
260
+ FileUtils.rm_rf(archive)
261
+ saved_bytes += archive_size
262
+ end
263
+ end
264
+ end
265
+
266
+ return saved_bytes
267
+ end
268
+
269
+
270
+ def self.remove_expired_provisioning_profiles(manually)
271
+ saved_bytes = 0
272
+
273
+ pp_folder = File.expand_path('~/Library/MobileDevice/Provisioning Profiles')
274
+ profiles = get_files_in_dir(pp_folder)
275
+
276
+ today = Date.today
277
+
278
+ profiles.each do |profile|
279
+ escaped_profile = profile.gsub(/ /, '\ ')
280
+ filename = profile.split('/').last
281
+ ext = filename.split('.').last
282
+ if ext == 'mobileprovision'
283
+ cmd = "security cms -D -i #{escaped_profile}"
284
+ plist = `#{cmd}`
285
+
286
+ exp_date_pos = plist.index('<key>ExpirationDate</key>')
287
+ unless exp_date_pos.nil?
288
+ exp_date_pos = plist.index('<date>', exp_date_pos)
289
+ unless exp_date_pos.nil?
290
+ exp_date_pos = exp_date_pos + 6 # <date>
291
+ exp_date_end = plist.index('</date>', exp_date_pos)
292
+ unless exp_date_end.nil?
293
+ exp_date_str = plist[exp_date_pos, exp_date_end - exp_date_pos]
294
+ exp_date = Date.parse(exp_date_str)
295
+ if today > exp_date
296
+ puts "- Removing #{filename} (#{exp_date})"
297
+ saved_bytes += get_byte_size(profile)
298
+ FileUtils.rm(profile)
299
+ else
300
+ puts "- Skipping #{filename} (#{exp_date})"
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ return saved_bytes
309
+ end
310
+
311
+
312
+ def self.remove_simulator_devices(manually)
313
+ saved_bytes = 0
314
+
315
+ sd_dir = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
316
+
317
+ devices_output = `xcrun simctl list devices`
318
+ devices = devices_output.scan /\s\s\s\s(.*) \(([^)]+)\) (.*)/
319
+ devices.each do |device|
320
+ device_uuid = nil
321
+ device.each do |device_component|
322
+ device_uuid = /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/.match(device_component.downcase)
323
+
324
+ unless device_uuid.nil?
325
+ device_uuid = device_uuid[0].upcase
326
+ break
327
+ end
328
+ end
329
+
330
+ unavailable = false
331
+ device.each do |device_component|
332
+ if device_component.include? 'unavailable'
333
+ unavailable = true
334
+ break
335
+ end
336
+ end
337
+
338
+ path = File.join(sd_dir, device_uuid)
339
+ byte_size = get_byte_size(path)
340
+
341
+ if unavailable || !manually || prompt_bool("> REMOVE #{device[0]} (#{pbs(byte_size)})?")
342
+ if unavailable
343
+ puts "! Unavailable device found: #{device[0]} (#{pbs(byte_size)})"
344
+ end
345
+
346
+ puts "- Removing device #{device[0]}"
347
+ `xcrun simctl delete #{device_uuid}`
348
+ saved_bytes += byte_size
349
+ end
350
+ end
351
+
352
+ return saved_bytes
353
+ end
354
+
355
+
356
+
357
+ def self.remove_doc_sets(manually)
358
+ saved_bytes = 0
359
+
360
+ ds_dir = File.expand_path('~/Library/Developer/Shared/Documentation/DocSets')
361
+ ds_folders = get_folders_in_dir(ds_dir)
362
+
363
+ ds_folders.each do |ds_folder|
364
+ docset_size = get_byte_size(ds_folder)
365
+ docset_name = ds_folder.split('/').last
366
+ if docset_name.split('.').last == 'docset'
367
+ if !manually || prompt_bool("> REMOVE \"#{docset_name}\" (#{pbs(docset_size)})?")
368
+ FileUtils.rm_rf(ds_folder)
369
+ saved_bytes += docset_size
370
+ end
371
+ end
372
+ end
373
+
374
+ return saved_bytes
375
+ end
376
+
377
+
378
+ # Execution and Menu
379
+
380
+ def self.run_steps(steps, manually)
381
+ total_saved_bytes = 0
382
+
383
+ steps.each do |method|
384
+ puts "\n"
385
+ line('-')
386
+ method_name = "#{method.name}".gsub('_', ' ').capitalize
387
+ center(method_name.upcase)
388
+ line('-')
389
+ saved_bytes = method.call(manually)
390
+ line('.')
391
+ center("#{method_name} completed, #{pbs(saved_bytes)} removed.")
392
+ total_saved_bytes += saved_bytes
393
+ end
394
+
395
+ line('–')
396
+ emoji = '😞'
397
+ if total_saved_bytes > 0
398
+ emoji = '👍'
399
+ end
400
+ mb = 1024 * 1024
401
+ if total_saved_bytes > (100 * mb)
402
+ emoji = '💪'
403
+ end
404
+ if total_saved_bytes > (5000 * mb)
405
+ emoji = '🍾'
406
+ end
407
+ if total_saved_bytes > (10000 * mb)
408
+ emoji = '💥'
409
+ end
410
+ center("🎉 XCODE CLEANUP completed, #{pbs(total_saved_bytes)} removed. #{emoji}")
411
+ line('–')
412
+ end
413
+
414
+
415
+ def self.menu()
416
+ line('–')
417
+ center('🗑 XCODE CLEANUP 🗑')
418
+ line('–')
419
+ puts "This script can perform the following steps:\n"
420
+
421
+ steps = [method(:remove_derived_data),
422
+ method(:remove_module_cache),
423
+ method(:remove_device_support),
424
+ method(:remove_old_archives),
425
+ method(:remove_expired_provisioning_profiles),
426
+ method(:remove_simulator_devices),
427
+ method(:remove_doc_sets)]
428
+
429
+ step_index = 1
430
+ steps.each do |method|
431
+ method_name = "#{method.name}".gsub('_', ' ').capitalize
432
+ puts "#{step_index}: #{method_name}"
433
+ step_index += 1
434
+ end
435
+
436
+ puts "\nWhat would you like to do?\n"
437
+ puts "[m] 🖐 Run each stap and manually specify what should be removed"
438
+ puts "[n] 💥 Nuke'm, remove everything that can be removed"
439
+ puts "[1-#{steps.length}] Run a single step"
440
+
441
+ choice = prompt("(M/N/1-#{steps.length}): ").strip!
442
+
443
+ if choice.casecmp('M') == 0
444
+ puts "Running all steps manually"
445
+ run_steps(steps, true)
446
+ elsif choice.casecmp('N') == 0
447
+ if prompt_bool("Are you sure you would like to Nuke'm?")
448
+ run_steps(steps, false)
449
+ end
450
+ elsif choice.to_i > 0 && choice.to_i <= steps.length
451
+ step = steps[choice.to_i - 1]
452
+ method_name = "#{step.name}".gsub('_', ' ').capitalize
453
+ puts "Running step #{choice.to_i}: #{method_name}"
454
+ run_steps([step], true)
455
+ end
456
+ end
457
+
458
+ self.menu()
459
+
460
+ end
data/xccleanup.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'xccleanup/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "xccleanup"
8
+ spec.version = Xccleanup::VERSION
9
+ spec.authors = ["Toine Heuvelmans"]
10
+ spec.email = ["toine@algorithmic.me"]
11
+
12
+ spec.summary = "A cleanup tool that assists you in cleaning up after Xcode."
13
+ spec.homepage = "https://github.com/toineheuvelmans/xccleanup"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.13"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xccleanup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Toine Heuvelmans
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - toine@algorithmic.me
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - bin/console
54
+ - bin/setup
55
+ - lib/xccleanup.rb
56
+ - lib/xccleanup/version.rb
57
+ - xccleanup.gemspec
58
+ homepage: https://github.com/toineheuvelmans/xccleanup
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.5.1
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: A cleanup tool that assists you in cleaning up after Xcode.
82
+ test_files: []