xcsim 1.0.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: 7001c7d48bafa2526204efa6fe004587f6881890
4
+ data.tar.gz: 364f09686a392b1d893f3c3dcde0a3ea12be1974
5
+ SHA512:
6
+ metadata.gz: 1409c1748ae77efb92f9d8ce41fdd7b12b9a6569eb3880428e406ab9b46354e0d41ca34ffaefc76192e18ea69cda43e26aea67ec092cedad688a8df1a96e8b41
7
+ data.tar.gz: 188424f9644888129ecaea27b6c6c497885bf458af282558f9f98f3422c9649fd880f42b3f76869d6afe69f962e19c9b1c3dcfd70baae06b83273b2c162a59f3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Egor Chiglintsev.
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,37 @@
1
+ # xcsim
2
+
3
+ xcsim is a command-line utility to simplify opening iOS Simulator application directories in Finder.
4
+ iOS Simulator uses UUIDs heavily for naming its directories, which makes them not really readable
5
+ by human eye. Consider for example:
6
+
7
+ /Users/User/Library/Developer/CoreSimulator/Devices/FC138577-990F-4069-9AB1-6D847B8529BD/data/
8
+ Containers/Data/Application/E34BF950-B343-4C73-B50F-EEEA0E34BFAC
9
+
10
+ Suppose you have an iOS application with bundle ID `com.yourcompany.appname` and want to peek
11
+ into its Documents folder, which is stored in iPhone 5s (iOS 9.2) simulator. Finding the right
12
+ directory could be a tedious task. With xcsim it is as easy as
13
+
14
+ xcsim --os "iOS 9.2" --device "iPhone 5s" com.yourcompany.appname
15
+
16
+ or even as just
17
+
18
+ xcsim com.yourcompany.appname
19
+
20
+ if it happens that default OS and device values are enough for your needs.
21
+
22
+ In case you're not sure which exact simulator your app is installed on, you can use xcsim to list
23
+ all present simulators by OS type, device name and view bundle IDs of the applications installed
24
+ on them.
25
+
26
+ By default xcsim invokes the `open` command to open the application data directory in Finder. By
27
+ providing the `--app` option you can open application bundle directory instead. Using `--echo`
28
+ option will print the full path to the selected directory instead of opening Finder.
29
+
30
+ Usage: `xcsim [options] [bundleID]`
31
+
32
+ -l, --list [OS], [DEVICE] List simulator OS, devices, app bundle IDs
33
+ -o, --os 'TYPE VERSION' Select simulator OS (default: 'iOS 9.2')
34
+ -d, --device 'TYPE' Select simulator device (default: 'iPhone 5s')
35
+ -a, --app Select application container directory instead of data directory
36
+ -e, --echo Echo the selected directory instead of opening it
37
+ -h, --help [OPTION] Print this message / help for individual options
data/bin/xcsim ADDED
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/ruby
2
+ require 'optparse'
3
+
4
+ begin
5
+ require 'cfpropertylist'
6
+ rescue LoadError
7
+ puts "require 'cfpropertylist' failed! Consider running 'gem install CFPropertyList'"
8
+ exit
9
+ end
10
+
11
+ require 'xcsim'
12
+
13
+ def banner
14
+ <<-END_BANNER
15
+ xcsim is a command-line utility to simplify opening iOS Simulator application directories in Finder.
16
+ iOS Simulator uses UUIDs heavily for naming its directories, which makes them not really readable
17
+ by human eye. Consider for example:
18
+
19
+ /Users/User/Library/Developer/CoreSimulator/Devices/FC138577-990F-4069-9AB1-6D847B8529BD/data/
20
+ Containers/Data/Application/E34BF950-B343-4C73-B50F-EEEA0E34BFAC
21
+
22
+ Suppose you have an iOS application with bundle ID 'com.yourcompany.appname' and want to peek
23
+ into its Documents folder, which is stored in iPhone 5s (iOS 9.2) simulator. Finding the right
24
+ directory could be a tedious task. With xcsim it is as easy as
25
+
26
+ xcsim --os "iOS 9.2" --device "iPhone 5s" com.yourcompany.appname
27
+
28
+ or even as just
29
+
30
+ xcsim com.yourcompany.appname
31
+
32
+ if it happens that default OS and device values are enough for your needs.
33
+
34
+ In case you're not sure which exact simulator your app is installed on, you can use xcsim to list
35
+ all present simulators by OS type, device name and view bundle IDs of the applications installed
36
+ on them.
37
+
38
+ By default xcsim invokes the `open` command to open the application data directory in Finder. By
39
+ providing the `--app` option you can open application bundle directory instead. Using `--echo`
40
+ option will print the full path to the selected directory instead of opening Finder.
41
+
42
+ Usage: xcsim [options] [bundleID]
43
+
44
+ END_BANNER
45
+ end
46
+
47
+
48
+
49
+ def help_list
50
+ <<-END_LIST_HELP
51
+ Usage: xcsim --list [OS], [DEVICE]
52
+
53
+ List option allows listing available simulators/OS versions or bundle IDs of installed apps.
54
+ Actual output of the list option depends on the parameters provided. Both parameters are optional,
55
+ and if both present, should be separated by a comma.
56
+
57
+ Without parameters `xcsim --list` prints all iOS simulator OS versions available with respective
58
+ count of installed devices for each of the OS:
59
+
60
+ xcsim --list
61
+
62
+ iOS 8.0 (6 devices)
63
+ iOS 8.3 (5 devices)
64
+ iOS 9.0 (6 devices)
65
+ iOS 9.1 (4 devices)
66
+ iOS 9.2 (10 devices)
67
+
68
+ Both OS and DEVICE parameters can be used to partially match existing simulators and print the
69
+ matching results. Considering the example above, we could receive the following outputs:
70
+
71
+ xcsim --list 9 # note partial match on OS version
72
+
73
+ iOS 9.0 (6 devices)
74
+ iOS 9.1 (4 devices)
75
+ iOS 9.2 (10 devices)
76
+
77
+ xcsim --list 9.1 # exact match yields list of devices for the given OS version
78
+
79
+ iPhone 4s
80
+ iPhone 5s
81
+ iPhone 6 Plus
82
+ iPhone 6s Plus
83
+
84
+ xcsim --list iPad # partial match on device type, lists devices with OS versions
85
+
86
+ iPad 2 (iOS 8.0)
87
+ iPad Air (iOS 8.0)
88
+ iPad Air (iOS 8.3)
89
+ iPad Air (iOS 9.0)
90
+ iPad 2 (iOS 9.2)
91
+ iPad Air (iOS 9.2)
92
+ iPad Air 2 (iOS 9.2)
93
+
94
+ xcsim --list iOS 9.2, iPad Air 2 # exact device match lists bundle IDs of installed apps
95
+
96
+ com.yourcompany.helloapp
97
+ com.yourcompany.otherapp
98
+ END_LIST_HELP
99
+ end
100
+
101
+
102
+ def help_os
103
+ <<-END_OS_HELP
104
+ Usage: xcsim --os "iOS 9.2" com.yourcompany.appname
105
+
106
+ Sets the OS version to work with when searching for bundle ID (needs to be an exact match, i.e.
107
+ partial matches such as "iOS" or "iOS 9" are not allowed)
108
+
109
+ If omitted, a default OS will be used instead (#{XCSim::defaultOSName})
110
+ END_OS_HELP
111
+ end
112
+
113
+
114
+ def help_device
115
+ <<-END_DEVICE_HELP
116
+ Usage: xcsim --device "iPhone 5s" com.yourcompany.appname
117
+
118
+ Sets the device type to work with when searching for bundle ID (needs to be an exact match, i.e.
119
+ partial matches such as "iPhone" are not allowed)
120
+
121
+ If omitted, a default device will be used instead (#{XCSim::defaultDeviceName})
122
+ END_DEVICE_HELP
123
+ end
124
+
125
+
126
+ def help_app
127
+ <<-END_APP_HELP
128
+ Usage: xcsim --app com.yourcompany.appname
129
+
130
+ Selects application bundle directory instead of application data directory (which is default).
131
+ END_APP_HELP
132
+ end
133
+
134
+
135
+ def help_echo
136
+ <<-END_ECHO_HELP
137
+ Usage: xcsim --echo com.yourcompany.appname
138
+
139
+ Prints the selected directory path instead of opening it.
140
+ END_ECHO_HELP
141
+ end
142
+
143
+
144
+ parser = OptionParser.new do |opts|
145
+ opts.banner = banner
146
+
147
+ opts.on_tail("-h", "--help [OPTION]", "Print this message / help for individual options") do |v|
148
+ if v == "l" || v == "list"
149
+ puts help_list
150
+ elsif v == "o" || v == "os"
151
+ puts help_os
152
+ elsif v == "d" || v == "device"
153
+ puts help_device
154
+ elsif v == "a" || v == "app"
155
+ puts help_app
156
+ elsif v == "e" || v == "echo"
157
+ puts help_echo
158
+ else
159
+ puts opts
160
+ end
161
+ exit
162
+ end
163
+
164
+ opts.on("-l", "--list [OS], [DEVICE]",
165
+ "List simulator OS, devices, app bundle IDs") do |v|
166
+ @command = :list
167
+ @argument = ([v] + ARGV).join(" ")
168
+ end
169
+
170
+ opts.on("-o", "--os 'TYPE VERSION'",
171
+ "Select simulator OS (default: '#{XCSim::defaultOSName}')") do |v|
172
+ @argument ||= {}
173
+ @argument[:os] = v
174
+ end
175
+
176
+ opts.on("-d", "--device 'TYPE'",
177
+ "Select simulator device (default: '#{XCSim::defaultDeviceName}')") do |v|
178
+ @argument ||= {}
179
+ @argument[:device] = v
180
+ end
181
+
182
+ opts.on("-a", "--app", "Select application container directory instead of data directory") do |v|
183
+ @argument ||= {}
184
+ @argument[:dir] = :app
185
+ end
186
+
187
+ opts.on("-e", "--echo", "Echo the selected directory instead of opening it") do |v|
188
+ @argument ||= {}
189
+ @argument[:output] = :echo
190
+ end
191
+ end
192
+
193
+ parser.parse!
194
+
195
+ begin
196
+ if @command == nil
197
+ if ARGV.count > 1
198
+ raise ArgumentError
199
+ end
200
+
201
+ @command ||= :bundle
202
+ @argument ||= {}
203
+
204
+ @argument[:bundleID] ||= ARGV.first
205
+ end
206
+
207
+ result = xcsim(@command, @argument)
208
+
209
+ case @command
210
+ when :list
211
+ unless result.empty?
212
+ puts XCSim::reportFromDeviceList(result)
213
+ else
214
+ puts "ERROR: Could not find simulators matching '#{@argument}'!\n"
215
+ puts parser
216
+ exit 1
217
+ end
218
+
219
+ when :bundle
220
+ path = (@argument[:dir] == :app) ? result.bundlePath : result.dataPath
221
+
222
+ if @argument[:output] == :echo
223
+ puts path
224
+ else
225
+ `open #{path}`
226
+ end
227
+ end
228
+
229
+ exit
230
+
231
+ rescue XCSim::NonUniqueBundleIDError => error
232
+ puts "ERROR: Multiple data directories matching bundle ID '#{error.bundleID}' on #{error.deviceID} " +
233
+ "found:"
234
+ puts error.directories
235
+ exit 1
236
+
237
+ rescue XCSim::OSNotFoundError => error
238
+ puts "ERROR: Unknown OS '#{error.name}'"
239
+ exit 1
240
+
241
+ rescue XCSim::DeviceNotFoundError => error
242
+ puts "ERROR: Unknown device '#{error.name}' for OS '#{error.os.id}'"
243
+ exit 1
244
+
245
+ rescue XCSim::BundleNotFoundError => error
246
+ puts "Unknown bundle ID '#{error.bundleID}' on #{error.device.name} (#{error.os.id})"
247
+ exit 1
248
+
249
+ rescue ArgumentError
250
+ puts parser
251
+ exit
252
+ end
@@ -0,0 +1,124 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'cfpropertylist'
4
+ require_relative 'rbBundleInfo'
5
+
6
+ module XCSim
7
+
8
+ # An error raised by XCSim::findBundleDataPath function when multiple directories matching
9
+ # the given bundle ID are found on the same simulator device.
10
+ class NonUniqueBundleIDError < RuntimeError
11
+
12
+ # A DeviceID object representing the iOS Simulator on which the error has occurred
13
+ attr_reader :deviceID
14
+
15
+ # A bundle ID string, which caused the error
16
+ attr_reader :bundleID
17
+
18
+ # An array of strings containing absolute paths for directories matching the given bundle ID
19
+ attr_reader :directories
20
+
21
+ # Initializes a NonUniqueBundleIDError object with the given parameters
22
+ def initialize(deviceID, bundleID, directories)
23
+ @bundleID = bundleID
24
+ @deviceID = deviceID
25
+ @directories = directories
26
+ end
27
+ end
28
+
29
+
30
+
31
+ # call-seq:
32
+ # findBundleDataPath(deviceID, bundleID) => String
33
+ #
34
+ # +deviceID+:: A DeviceID object representing the device simulator on which the application
35
+ # is installed
36
+ # +bundleID+:: Bundle ID of the application to search for
37
+ #
38
+ # Finds an absolute path for application data directory of an application with a given bundle ID
39
+ # installed on a given device.
40
+ #
41
+ # Searches for directories matching +bundleID+ inside the application data directory of the
42
+ # device. Raises a NonUniqueBundleIDError if multiple matching directories
43
+ # are found (bundle IDs are expected to be unique). Returns the found directory or +nil+
44
+ # otherwise.
45
+ def self.findBundleDataPath(deviceID, bundleID)
46
+ path = deviceID.appDataPath
47
+ subdirs = Dir.entries(path).select do |entry|
48
+ File.directory? File.join(path, entry) and !(entry =='.' || entry == '..')
49
+ end
50
+
51
+ metadataPairs = subdirs.map do |dir|
52
+ metadataPath = "#{path}/#{dir}/#{BUNDLE_METADATA_PLIST}"
53
+
54
+ if File.exists? metadataPath
55
+ plist = CFPropertyList::List.new(:file => metadataPath)
56
+ plist = CFPropertyList.native_types(plist.value)
57
+
58
+ { :plist => plist, :dir => "#{path}/#{dir}" }
59
+ else
60
+ nil
61
+ end
62
+ end
63
+ .select{ |pair| pair != nil }
64
+
65
+ result = metadataPairs.select{ |pair| pair[:plist][METADATA_ID] == bundleID }
66
+
67
+ if result.count > 1
68
+ raise NonUniqueBundleIDError.new(deviceID, bundleID, result.map{|pair| pair[:dir]})
69
+ elsif result.empty?
70
+ return nil
71
+ else
72
+ result.first[:dir]
73
+ end
74
+ end
75
+
76
+
77
+
78
+ # call-seq:
79
+ # parseInstalledBundles(deviceID) => Hash
80
+ #
81
+ # +deviceID+:: A DeviceID object to search installed app bundles for
82
+ #
83
+ # Searches the given iOS Simulator device for installed application bundles (excluding the
84
+ # system applications such as Safari) and returns a Hash of <tt>bundle ID string =>
85
+ # BundleInfo</tt> containing BundleInfo instances corresponding the found bundles.
86
+ #
87
+ # May raise a NonUniqueBundleIDError if multiple data directories are found for one
88
+ # of the bundles.
89
+ def self.parseInstalledBundles(deviceID)
90
+ path = deviceID.appBundlesPath
91
+ subdirs = Dir.entries(path).select do |entry|
92
+ File.directory? File.join(path, entry) and !(entry =='.' || entry == '..')
93
+ end
94
+
95
+ bundlePlists = subdirs.map do |dir|
96
+ plistPath = "#{path}/#{dir}/#{BUNDLE_METADATA_PLIST}"
97
+
98
+ if File.exists? plistPath
99
+ plist = CFPropertyList::List.new(:file => plistPath)
100
+ plist = CFPropertyList.native_types(plist.value)
101
+
102
+ { :plist => plist, :dir => "#{path}/#{dir}" }
103
+ else
104
+ nil
105
+ end
106
+ end
107
+ .select { |plist| plist != nil }
108
+
109
+ bundleInfos = bundlePlists.map do |pair|
110
+ bundleID = pair[:plist][METADATA_ID]
111
+ bundlePath = pair[:dir]
112
+ dataPath = findBundleDataPath(deviceID, bundleID)
113
+ BundleInfo.new(bundleID, bundlePath, dataPath)
114
+ end
115
+
116
+ bundleInfosHash = {}
117
+ bundleInfos.each{ |info| bundleInfosHash[info.bundleID] = info }
118
+
119
+ bundleInfosHash
120
+ end
121
+
122
+ end # module XCSim
123
+
124
+ # eof
@@ -0,0 +1,142 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module XCSim
4
+ # An error, which is raised by GetBundle class when iOS Simulator OS version specified by
5
+ # the +:os+ option could not be found.
6
+ class OSNotFoundError < RuntimeError
7
+
8
+ # Name of the OS provided in +:os+ option
9
+ attr_reader :name
10
+
11
+ # Initializes an OSNotFoundError instance with the given OS name
12
+ def initialize(name)
13
+ @name = name
14
+ end
15
+ end
16
+
17
+
18
+
19
+ # An error, which is raised by GetBundle class when iOS Simulator device model specified by
20
+ # the +:device+ option could not be found.
21
+ class DeviceNotFoundError < RuntimeError
22
+
23
+ # An OSDevices object corresponding to the OS version, which was used when searching
24
+ # for the device
25
+ attr_reader :os
26
+
27
+ # Device name specified in the +:device+ option
28
+ attr_reader :name
29
+
30
+ # Initializes a DeviceNotFoundError instance with a given os and name
31
+ def initialize(os, name)
32
+ @os = os
33
+ @name = name
34
+ end
35
+ end
36
+
37
+
38
+
39
+ # An error, which is raised by GetBundle class when bundle ID specified by +:bundleID+ option
40
+ # has not been found (even with partial match) on the device
41
+ class BundleNotFoundError < RuntimeError
42
+
43
+ # An OSDevices object corresponding to the OS version, which was used when searching
44
+ # for the application bundle
45
+ attr_reader :os
46
+
47
+ # A DeviceID object corresponding to the device, which was used when searching for the
48
+ # application bundle
49
+ attr_reader :device
50
+
51
+ # Bundle ID provided in +:bundleID+ option
52
+ attr_reader :bundleID
53
+
54
+ # Initializes a BundleNotFoundError instance with a given os, device and bundleID
55
+ def initialize(os, device, bundleID)
56
+ @os = os
57
+ @device = device
58
+ @bundleID = bundleID
59
+ end
60
+ end
61
+
62
+
63
+
64
+ # Contains information about a certain application bundle
65
+ class BundleInfo
66
+
67
+ # Bundle ID of the application
68
+ attr_reader :bundleID
69
+
70
+ # Absolute path for the application bundle directory
71
+ attr_reader :bundlePath
72
+
73
+ # Absolute path for the application data directory
74
+ attr_reader :dataPath
75
+
76
+ # Initializes a BundleInfo instance with a given +bundleID+, +bundlePath+, +dataPath+
77
+ def initialize(bundleID, bundlePath, dataPath)
78
+ @bundleID = bundleID
79
+ @bundlePath = bundlePath
80
+ @dataPath = dataPath
81
+ end
82
+
83
+ # Returns +bundleID+
84
+ def inspect
85
+ @bundleID
86
+ end
87
+
88
+ # Same as #inspect
89
+ def to_s
90
+ inspect
91
+ end
92
+ end
93
+
94
+
95
+
96
+ # A class encapsulating logic of searching for an applciation bundle in #xcsim function
97
+ # (i.e. implementation of #xcsim bundle mode)
98
+ class GetBundle
99
+ # Initializes a GetBundle instance with a given device set of OSDevices type
100
+ def initialize(deviceSet)
101
+ @deviceSet = deviceSet
102
+ end
103
+
104
+ # Performs search for the application bundle matching the provided options.
105
+ # See #xcsim description (Bundle Mode section) for more info.
106
+ #
107
+ # Returns a BundleInfo object
108
+ def withOptions(options)
109
+ bundleID = options[:bundleID]
110
+
111
+ if bundleID == nil
112
+ raise ArgumentError
113
+ end
114
+
115
+ os = osNamed(options[:os] || XCSim::defaultOSName)
116
+ device = deviceNamed(os, options[:device] || XCSim::defaultDeviceName)
117
+ bundles = XCSim::parseInstalledBundles(device).values
118
+
119
+ matchingBundles = bundles.select { |bundle| bundle.bundleID.end_with? bundleID }
120
+
121
+ if matchingBundles.count > 1
122
+ raise NonUniqueBundleIDError.new(device, bundleID, matchingBundles.map { |b| b.bundleID })
123
+ elsif matchingBundles.empty?
124
+ raise BundleNotFoundError.new(os, device, bundleID)
125
+ else
126
+ return matchingBundles.first
127
+ end
128
+ end
129
+
130
+ private
131
+ def osNamed(name)
132
+ @deviceSet[OSID.fromString(name)] || (raise OSNotFoundError.new(name))
133
+ end
134
+
135
+ def deviceNamed(os, name)
136
+ os.devices[name] || (raise DeviceNotFoundError.new(os, name))
137
+ end
138
+
139
+ end
140
+ end # module XCSim
141
+
142
+ # eof
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # XCSim module contains utility for parsing iOS Simulator metadata plist files
4
+ # and providing access to the bundle and data directories of the applications
5
+ # installed on iOS Simulator.
6
+ module XCSim
7
+
8
+ # Absolute path of the directory, which stores all of thr iOS Simulator data
9
+ SIMULATORS_ROOT = File.expand_path("~/Library/Developer/CoreSimulator/Devices")
10
+
11
+ # Path for the application bundles directory relative to a concrete iOS Simulator device dir
12
+ DEVICE_APP_BUNDLES_RELATIVE_PATH = "data/Containers/Bundle/Application"
13
+
14
+ # Path for the application data directory relative to a concrete iOS Simulator device dir
15
+ DEVICE_APP_DATA_RELATIVE_PATH = "data/Containers/Data/Application"
16
+
17
+ # Name of the +device_set.plist+ file
18
+ DEVICE_SET_PLIST = "device_set.plist"
19
+
20
+ # Name of the plist file containing a certain application's metadata
21
+ BUNDLE_METADATA_PLIST = ".com.apple.mobile_container_manager.metadata.plist"
22
+
23
+ # A key in application metadata plist file, which corresponds to the bundle ID of the application
24
+ METADATA_ID = "MCMMetadataIdentifier"
25
+
26
+ end
27
+
28
+ # eof
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require_relative 'rbConstants'
4
+
5
+ module XCSim
6
+
7
+ # DeviceID is a pair of device name string and a +GUID+, which identifies
8
+ # the concrete iOS Simulator in the file system. DeviceID provides access
9
+ # to the application bundle and application documents paths.
10
+ class DeviceID
11
+
12
+ # Name of the device (e.g. <tt>'iPhone 5s'</tt>)
13
+ attr_reader :name
14
+
15
+ # Creates a DeviceID instance from a string prefixed by a standard
16
+ # prefix encountered in +device_set.plist+ (+com.apple.CoreSimulator.SimDeviceType.+)
17
+ #
18
+ # Does not perform any additional validation other than checking the prefix.
19
+ def self.fromPrefixedString(string, guid)
20
+ unless string.start_with? @@PREFIX
21
+ return nil
22
+ end
23
+
24
+ name = string.sub(@@PREFIX, "").gsub("-", " ")
25
+ return DeviceID.new(name, guid)
26
+ end
27
+
28
+ # Initializes a DeviceID instance with a given name and +GUID+.
29
+ def initialize(name, guid)
30
+ @name = name
31
+ @guid = guid
32
+ end
33
+
34
+ # Returns an absolute path for application bundles directory
35
+ # corresponding to the given device.
36
+ def appBundlesPath
37
+ "#{SIMULATORS_ROOT}/#{@guid}/#{DEVICE_APP_BUNDLES_RELATIVE_PATH}"
38
+ end
39
+
40
+ # Returns an absolute path for application data directory
41
+ # corresponding to the given device
42
+ def appDataPath
43
+ "#{SIMULATORS_ROOT}/#{@guid}/#{DEVICE_APP_DATA_RELATIVE_PATH}"
44
+ end
45
+
46
+ # Returns device name
47
+ def inspect
48
+ @name
49
+ end
50
+
51
+ # Same as #inspect
52
+ def to_s
53
+ inspect
54
+ end
55
+
56
+ # Returns a string used for indexing device definitions in +device_set.plist+
57
+ # in +com.apple.CoreSimulator.SimDeviceType.iPad-2+ format.
58
+ def key
59
+ "#{@@PREFIX}#{@name.gsub(" ","-")}"
60
+ end
61
+
62
+ # Returns +inspect.hash+
63
+ def hash
64
+ inspect.hash
65
+ end
66
+
67
+ # Checks equality by <tt>@name</tt>
68
+ def eql?(other)
69
+ @name.eql?(other.name)
70
+ end
71
+
72
+ private
73
+ @@PREFIX = "com.apple.CoreSimulator.SimDeviceType."
74
+ end # class DeviceID
75
+
76
+ end # module XCSim
77
+
78
+ # eof
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'cfpropertylist'
4
+
5
+ require_relative 'rbOSID'
6
+ require_relative 'rbDeviceID'
7
+ require_relative 'rbOSDevices'
8
+
9
+ module XCSim
10
+
11
+ # call-seq:
12
+ # parseDeviceSet(absolutePath) => Hash
13
+ #
14
+ # Parses a +device_set.plist+ file located at the given +path+
15
+ #
16
+ # Returns a Hash of <tt>OSID => OSDevices</tt> for iOS Simulator OSes,
17
+ # which have at least one device simulator installed.
18
+ def self.parseDeviceSet(path)
19
+ plist = CFPropertyList::List.new(:file => path)
20
+ plist = CFPropertyList.native_types(plist.value)
21
+ defaultDevices = plist["DefaultDevices"]
22
+
23
+ osIDs = defaultDevices
24
+ .keys
25
+ .map{|s| OSID.fromPrefixedString(s) }
26
+ .select{|id| id != nil}
27
+
28
+ oses = osIDs.map do |id|
29
+ osDevices = defaultDevices[id.key]
30
+ devices = osDevices
31
+ .keys
32
+ .map{ |s| DeviceID.fromPrefixedString(s, osDevices[s])}
33
+ .select{ |device| device != nil }
34
+ .select{ |device| File.directory? device.appBundlesPath }
35
+
36
+ (devices.count > 0) ? OSDevices.new(id, devices) : nil
37
+ end
38
+ .select{ |os| os != nil }
39
+
40
+ osHash = {}
41
+ oses.each{ |os| osHash[os.id] = os }
42
+
43
+ osHash
44
+ end
45
+
46
+ # Returns default device set of OSDevices class (as parsed by #parseDeviceSet with default
47
+ # iOS Simulators path)
48
+ def self.deviceSet
49
+ @@deviceSet
50
+ end
51
+
52
+ # Returns default OS name for use in #xcsim bundle mode when no +:os+ option is provided.
53
+ # Selects the OS with the highest version number as the default.
54
+ def self.defaultOSName
55
+ @@deviceSet.keys.max.to_s
56
+ end
57
+
58
+ # Returns the default device name for use in #xcsim bundle mode when no +:device+
59
+ # option is provided.
60
+ def self.defaultDeviceName
61
+ "iPhone 5s"
62
+ end
63
+
64
+ @@deviceSet = parseDeviceSet("#{XCSim::SIMULATORS_ROOT}/#{XCSim::DEVICE_SET_PLIST}")
65
+ end # module XCSim
66
+
67
+ # eof
@@ -0,0 +1,148 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require_relative 'rbAppBundles'
4
+
5
+ module XCSim
6
+ # An aggregate object containing information on a single iOS Simulator device
7
+ # returned by GetDeviceList#withPattern or GetDeviceList#allDevices
8
+ class DeviceListItem
9
+
10
+ # An OSDevices object corresponding to the device OS version
11
+ attr_reader :os
12
+
13
+ # A DeviceID object corresponding to the device
14
+ attr_reader :device
15
+
16
+ # An array of BundleInfo objects corresponding to the applications installed on the
17
+ # simulator in question
18
+ attr_reader :bundles
19
+
20
+ # Initializes a DeviceListItem instance with a given os, device and bundles array
21
+ def initialize(os, device, bundles)
22
+ @os = os
23
+ @device = device
24
+ @bundles = bundles
25
+ end
26
+
27
+ # Returns a string in <tt>"iPhone 5s (iOS 9.2)"</tt> format
28
+ def fullName
29
+ "#{device.name} (#{os.id})"
30
+ end
31
+
32
+ # Returns device name
33
+ def shortName
34
+ device.name
35
+ end
36
+
37
+ # Same as #fullName
38
+ def inspect
39
+ fullName
40
+ end
41
+
42
+ # Same as #inspect
43
+ def to_s
44
+ inspect
45
+ end
46
+ end
47
+
48
+
49
+ # A class encapsulating logic of searching available OS verions / device models / installed apps
50
+ # in #xcsim function (i.e. implementation of #xcsim list mode)
51
+ class GetDeviceList
52
+
53
+ # Initializes a GetDeviceList instance with a given device set of OSDevices class
54
+ def initialize(deviceSet)
55
+ @deviceSet = deviceSet
56
+ end
57
+
58
+ # Performs search for the iOS simulators matching the provided pattern.
59
+ # See #xcsim description (List Mode section) for more info.
60
+ #
61
+ # Returns an array of DeviceListItem
62
+ def withPattern(pattern)
63
+ if pattern.length == 0
64
+ return allDevices
65
+ else
66
+ pattern = parsePattern(pattern)
67
+ matchingOSes = getMatchingOSList(pattern[:os])
68
+
69
+ devicePairs = matchingOSes
70
+ .map{ |os| os.devices.values.map{ |d| {:os => os, :device => d} } }
71
+ .flatten
72
+
73
+ # try finding a strict match first (disambiguate "iPad Air" from "iPad Air 2")
74
+ # find partial matches otherwise
75
+ matchingDevices =
76
+ strictDeviceMatches(devicePairs, pattern[:device]) ||
77
+ partialDeviceMatches(devicePairs, pattern[:device]) ||
78
+ []
79
+
80
+ return matchingDevices.map do |pair|
81
+ DeviceListItem.new(pair[:os], pair[:device],
82
+ XCSim::parseInstalledBundles(pair[:device]).values)
83
+ end
84
+ .flatten
85
+ end
86
+ end
87
+
88
+ # Returns an array of DeviceListItem corresponding to all installed iOS Simulators
89
+ def allDevices
90
+ @deviceSet.values.map do |os|
91
+ os.devices.values.map do |device|
92
+ DeviceListItem.new(os, device, XCSim::parseInstalledBundles(device).values)
93
+ end
94
+ end
95
+ .flatten
96
+ end
97
+
98
+ private
99
+ def parsePattern(pattern)
100
+ components = pattern.split(",").map{ |s| s.strip }
101
+
102
+ case components.count
103
+ when 2
104
+ # assume first pattern is always for OS,
105
+ # second - for device
106
+ osPattern = components.first
107
+ devicePattern = components.last
108
+
109
+ when 1
110
+ # we don't know whether the pattern is for OS or device,
111
+ # try OS first
112
+ osPattern = components.first
113
+ devicePattern = nil
114
+ matchingOSes = getMatchingOSList(osPattern)
115
+
116
+ # if no OSes match, assume the pattern is for device
117
+ if matchingOSes.empty?
118
+ osPattern = nil
119
+ matchingOSes = nil
120
+ devicePattern = components.first
121
+ end
122
+
123
+ else
124
+ raise ArgumentError
125
+ end
126
+
127
+ { :os => osPattern, :device => devicePattern }
128
+ end
129
+
130
+ def strictDeviceMatches(osDevicePairs, pattern)
131
+ matches = osDevicePairs.select{ |p| p[:device].name == pattern }
132
+ matches.empty? ? nil : matches
133
+ end
134
+
135
+ def partialDeviceMatches(osDevicePairs, pattern)
136
+ matches = osDevicePairs.select{ |p| p[:device].name.include? (pattern || "") }
137
+ matches.empty? ? nil : matches
138
+ end
139
+
140
+ def getMatchingOSList(pattern)
141
+ return @deviceSet.values.select{ |os| os.id.to_s.include? (pattern || "") }
142
+ end
143
+
144
+ end # class GetDeviceList
145
+
146
+ end # module XCSim
147
+
148
+ # eof
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module XCSim
4
+
5
+ # A collection of DeviceID objects related to a given iOS Simulator OS version
6
+ class OSDevices
7
+
8
+ # An OSID of the iOS Simulator OS version related to the OSDevices collection
9
+ attr_reader :id
10
+
11
+ # A hash of <tt>deviceName => DeviceID</tt>
12
+ attr_reader :devices
13
+
14
+ # Initializes an OSDevices instance with a given OSID and a collection of devices
15
+ # +id+:: OSID to associate with the OSDevices object
16
+ # +devices+:: A collection of DeviceID objects. Should support #each method for enumeration.
17
+ def initialize(id, devices)
18
+ @id = id
19
+
20
+ devicesHash = {}
21
+ devices.each{ |d| devicesHash[d.name] = d }
22
+
23
+ @devices = devicesHash
24
+ end
25
+
26
+ # Returns a string in <tt>'iOS 9.0 (5 devices)'</tt> format
27
+ def inspect
28
+ "#{@id.inspect} (#{@devices.count} devices)"
29
+ end
30
+
31
+ # Same as #inspect
32
+ def to_s
33
+ inspect
34
+ end
35
+
36
+ include Comparable
37
+
38
+ # Compares by +id+
39
+ def <=>(other)
40
+ @id <=> other.id
41
+ end
42
+
43
+ end # class OSDevicess
44
+
45
+ end # module XCSim
46
+
47
+ # eof
@@ -0,0 +1,89 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module XCSim
4
+
5
+ # OSID is used to uniquely identify an iOS Simulator operating system
6
+ # (including watchOS and tvOS operating systems) and is basically
7
+ # a pair of OS type string ('iOS', 'watchOS', 'tvOS') and version string
8
+ # ('9.0', '9.1' etc.)
9
+ class OSID
10
+ # Simulated OS type string ('iOS', 'watchOS', 'tvOS')
11
+ attr_reader :type
12
+
13
+ # Simulated OS version string ('7.0', '8.0', '9.0' etc.)
14
+ attr_reader :version
15
+
16
+ # Creates an OSID instance from a string prefixed by a standard
17
+ # prefix encountered in +device_set.plist+ (+com.apple.CoreSimulator.SimRuntime.+)
18
+ #
19
+ # Does not perform any additional validation other than checking the prefix.
20
+ def self.fromPrefixedString(string)
21
+ unless string.start_with? @@PREFIX
22
+ return nil
23
+ end
24
+
25
+ components = string.sub(@@PREFIX, "").split("-")
26
+ type = components.first
27
+ version = components[1..-1].join(".")
28
+
29
+ return OSID.new(type, version)
30
+ end
31
+
32
+ # Creates an OSID instance from a non-prefixed string. Assumes 'iOS 9.2' format
33
+ # where components are separated by whitespace and the first component represents
34
+ # OS type while all others are concatenated and assumed to represent OS version.
35
+ def self.fromString(string)
36
+ components = string.split(" ")
37
+ type = components.first
38
+ version = components[1..-1].join(".")
39
+
40
+ return OSID.new(type, version)
41
+ end
42
+
43
+ # Creates an OSID instance with a given type and version.
44
+ def initialize(type, version)
45
+ @type = type
46
+ @version = version
47
+ end
48
+
49
+ # Returns a string in <tt>'iOS 9.2'</tt> format
50
+ def inspect
51
+ "#{@type} #{@version}"
52
+ end
53
+
54
+ # Same as #inspect
55
+ def to_s
56
+ inspect
57
+ end
58
+
59
+ # Returns a string used for indexing OS definitions in +device_set.plist+
60
+ # in +com.apple.CoreSimulator.SimRuntime.iOS-7-1+ format.
61
+ def key
62
+ "#{@@PREFIX}#{@type}-#{@version.gsub(".","-")}"
63
+ end
64
+
65
+ include Comparable
66
+
67
+ # Uses @version for comparison (allows using <tt>Array#max</tt> on arrays of OSID
68
+ # for selecting max version)
69
+ def <=>(other)
70
+ @version <=> other.version
71
+ end
72
+
73
+ # Returns +inspect.hash+
74
+ def hash
75
+ inspect.hash
76
+ end
77
+
78
+ # Checks attribute-wise equality (compares @type and @version)
79
+ def eql?(other)
80
+ @type.eql?(other.type) && @version.eql?(other.version)
81
+ end
82
+
83
+ private
84
+ @@PREFIX = "com.apple.CoreSimulator.SimRuntime."
85
+ end # class OSID
86
+
87
+ end # module XCSim
88
+
89
+ # eof
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module XCSim
4
+
5
+ # Prepares a more human-readable report from an array of DeviceListItem objects.
6
+ #
7
+ # Depending on the unique OS versions and device models referenced by the +list+ items,
8
+ # may return one of the following results:
9
+ #
10
+ # List of application bundles:: When +list+ contains a single item
11
+ # List of device names:: When +list+ contains multiple devices from a single OS version
12
+ # List of OS version strings:: When +list+ contains a multiple devices from multiple OS version
13
+ def self.reportFromDeviceList(list)
14
+ uniqueOSes = list.map{ |item| item.os.id }.uniq
15
+ uniqueDevices = list.map { |item| item.device.name }.uniq
16
+
17
+ countByOS = {}
18
+ list.each do |item|
19
+ count = countByOS[item.os.id] || 0
20
+ countByOS[item.os.id] = count+1
21
+ end
22
+
23
+ if uniqueOSes.empty? || uniqueDevices.empty?
24
+ raise ArgumentError
25
+
26
+ elsif uniqueOSes.count == 1 && uniqueDevices.count == 1
27
+ list.first.bundles
28
+
29
+ elsif uniqueOSes.count == 1
30
+ list.map{ |item| item.device.name }
31
+
32
+ elsif false == (countByOS.values.include? 1)
33
+ countByOS.map { |id, count| "#{id} (#{count} devices)"}
34
+
35
+ else
36
+ list
37
+ end
38
+ end
39
+
40
+ end # module XCSim
41
+
42
+ #eof
@@ -0,0 +1,121 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require_relative 'rbConstants'
4
+ require_relative 'rbDeviceSet'
5
+ require_relative 'rbList'
6
+
7
+ # call-seq:
8
+ # xcsim(:list, pattern) => Array of DeviceListItem
9
+ # xcsim(:bundle, options) => BundleInfo
10
+ #
11
+ # Main function to call when using xcsim gem. #xcsim works in two modes: list mode and bundle
12
+ # mode. List mode is for getting information about installed iOS Simulators, available OS versions
13
+ # and bundle IDs of the applications installed on these smiulators. Bundle mode focuses on
14
+ # individual applications and provides means of getting absolute paths for the application's data
15
+ # or bundle directories.
16
+ #
17
+ # == List mode
18
+ #
19
+ # List mode is invoked by calling
20
+ #
21
+ # xcsim(:list, pattern)
22
+ #
23
+ # Pattern is expected to be a String containing partial match for iOS version / device model pair
24
+ # in <tt>'iOS 9.2, iPhone 5s'</tt> format. Both of the patterns are optional and coult be omitted,
25
+ # i.e. the following calls are all valid:
26
+ #
27
+ # xcsim(:list, "iOS 9.2, iPhone 5s") # exact match for both iOS version and device
28
+ # xcsim(:list, "iOS 9.2") # match only iOS version
29
+ # xcsim(:list, "iPhone 5s") # match only device
30
+ # xcsim(:list, "iOS") # partial match for iOS version (all iOS simulators)
31
+ # xcsim(:list, "9.2") # partial match for iOS version (including watchOS, tvOS etc.)
32
+ # xcsim(:list, "iPad") # partial match for device model (all iPads)
33
+ # xcsim(:list, "9, iPhone") # partial match for both iOS version and device model
34
+ #
35
+ # In list mode #xcsim function returns an array of DeviceListItem object representing
36
+ # iOS Simulators found, which match the pattern provided. Partial matches are processed
37
+ # in a case-sensitive manner (basically, it finds all devices, whose OS version has the provided
38
+ # OS version pattern as a substring and whose device model contains the provided device model
39
+ # pattern as a substring).
40
+ #
41
+ # Each DeviceListItem will contain info about applications installed on the corresponding
42
+ # Simulator, excluding system apps (Safari et al.)
43
+ #
44
+ # Note that since #xcsim signature is
45
+ #
46
+ # def xcsim(*args)
47
+ #
48
+ # multiple arguments may be provided to the function. In list mode all arguments are concatenated
49
+ # into a single +pattern+ string by using single whitespace as a separator.
50
+ #
51
+ # == Bundle mode
52
+ #
53
+ # Bundle mode is invoked by calling
54
+ #
55
+ # xcsim(:bundle, options)
56
+ #
57
+ # Options is expected to be a Hash with the following keys:
58
+ #
59
+ # +:bundleID+:: Specifies the bundle ID of the application in question. #xcsim will then try
60
+ # to locate the bundle with the ID provided and return the corresponding BundleInfo.
61
+ # This is the only required option.
62
+ #
63
+ # Bundle ID can be partially matched. Actual bundle IDs of the applications found
64
+ # in the iOS Simulator are checked for having the provided bundle ID suffix, so
65
+ # passing <tt>"application"</tt> as the +:bundleID+ option will match bundle ID
66
+ # <tt>"com.yourcompany.application"</tt> for example. If multiple matches are found,
67
+ # the result is ambiguous and #xcsim will raise an error. See Errors section for more
68
+ # info.
69
+ #
70
+ # +:os+:: Specifies, which iOS version to use when searching in <tt>"iOS 9.2"</tt> format.
71
+ # If omitted, #xcsim will default the OS version to the latest one found in the
72
+ # parsed +device_set.plist+
73
+ #
74
+ # +:device+:: Specifies, which device model to use when searching in <tt>"iPad Air 2"</tt>
75
+ # format. If omitted, #xcsim will default the device model to <tt>"iPhone 5s"</tt>
76
+ #
77
+ # Note that the +:os+ and +:device+ options do not perform partial matching to the contrary of
78
+ # what the list mode does.
79
+ #
80
+ # #xcsim will return a BundleInfo instance for bundle matching the options or raise an error.
81
+ #
82
+ # == Errors
83
+ #
84
+ # #xcsim may raise errors of several types when matching its results:
85
+ #
86
+ # ArgumentError:: An ArgumentError is raised if the parameters provided do not make sense
87
+ # as described above. That is, if in list mode multiple comma-separated
88
+ # components are found in the pattern string, or in bundle mode +:bundleID+
89
+ # parameter is not found, an ArgumentError will be raised.
90
+ #
91
+ # NonUniqueBundleIDError:: XCSim::NonUniqueBundleIDError is raised when multiple data or bundle
92
+ # directories are found matching the same bundle ID (should not generally
93
+ # happen since bundle IDs are unique in iOS Simulator) or when partial
94
+ # match of bundle ID in bundle mode finds multiple results.
95
+ #
96
+ # OSNotFoundError:: XCSim::OSNotFoundError is raised in bundle mode when OS version specified
97
+ # by the +:os+ option cannot be found in installed iOS simulators list.
98
+ #
99
+ # DeviceNotFoundError:: XCSim::DeviceNotFoundError is raised in bundle mode when device model
100
+ # specified by the +:device+ option cannot be found in installed iOS
101
+ # simulators list for the selected OS version.
102
+ #
103
+ # BundleNotFoundError:: XCSim::BundleNotFoundError is raised in bundle mode when the bundle ID
104
+ # specified by the +:bundleID+ option does not match any application
105
+ # installed on the selected iOS Simulator.
106
+ #
107
+ def xcsim(*args)
108
+ case args.first
109
+ when :list
110
+ return XCSim::GetDeviceList.new(XCSim.deviceSet).withPattern(args[1..-1].join(" "))
111
+
112
+ when :bundle
113
+ raise ArgumentError if args.count != 2
114
+ return XCSim::GetBundle.new(XCSim.deviceSet).withOptions(args.last)
115
+
116
+ else
117
+ raise ArgumentError
118
+ end
119
+ end
120
+
121
+ # eof
data/lib/xcsim.rb ADDED
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ begin
4
+ require 'cfpropertylist'
5
+ rescue LoadError
6
+ puts "require 'cfpropertylist' failed! Consider running 'gem install CFPropertyList'"
7
+ exit
8
+ end
9
+
10
+ require_relative 'xcsim/rbXCSim'
11
+ require_relative 'xcsim/rbReports'
12
+
13
+ # eof
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xcsim
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Egor Chiglintsev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: CFPropertyList
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.2'
27
+ description: |
28
+ xcsim is a command-line utility to simplify opening iOS Simulator application/data directories. It's as simple as running `xcsim com.yourcompany.application`.
29
+ email: egor.chiglintsev@gmail.com
30
+ executables:
31
+ - xcsim
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - LICENSE
36
+ - README.md
37
+ - bin/xcsim
38
+ - lib/xcsim.rb
39
+ - lib/xcsim/rbAppBundles.rb
40
+ - lib/xcsim/rbBundleInfo.rb
41
+ - lib/xcsim/rbConstants.rb
42
+ - lib/xcsim/rbDeviceID.rb
43
+ - lib/xcsim/rbDeviceSet.rb
44
+ - lib/xcsim/rbList.rb
45
+ - lib/xcsim/rbOSDevices.rb
46
+ - lib/xcsim/rbOSID.rb
47
+ - lib/xcsim/rbReports.rb
48
+ - lib/xcsim/rbXCSim.rb
49
+ homepage: https://github.com/wanderwaltz/xcsim
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.2.2
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: Open iOS Simulator application/data directories by bundle ID
73
+ test_files: []