xcsim 1.0.0

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.
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: []