calabash 1.2.1 → 1.9.9.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +39 -0
  3. data/LICENSE +204 -21
  4. data/README.md +36 -6
  5. data/VERSIONING.md +16 -0
  6. data/bin/calabash +95 -0
  7. data/lib/calabash.rb +185 -1
  8. data/lib/calabash/android.rb +64 -0
  9. data/lib/calabash/android/adb.rb +277 -0
  10. data/lib/calabash/android/application.rb +110 -0
  11. data/lib/calabash/android/build.rb +12 -0
  12. data/lib/calabash/android/build/application.rb +13 -0
  13. data/lib/calabash/android/build/build_error.rb +11 -0
  14. data/lib/calabash/android/build/builder.rb +119 -0
  15. data/lib/calabash/android/build/java_keystore.rb +177 -0
  16. data/lib/calabash/android/build/resigner.rb +56 -0
  17. data/lib/calabash/android/build/test_server.rb +27 -0
  18. data/lib/calabash/android/console_helpers.rb +44 -0
  19. data/lib/calabash/android/cucumber.rb +3 -0
  20. data/lib/calabash/android/device.rb +965 -0
  21. data/lib/calabash/android/environment.rb +470 -0
  22. data/lib/calabash/android/gestures.rb +369 -0
  23. data/lib/calabash/android/interactions.rb +45 -0
  24. data/lib/calabash/android/lib/.irbrc +55 -0
  25. data/lib/calabash/android/lib/AndroidManifest.xml +51 -0
  26. data/lib/calabash/android/lib/TestServer.apk +0 -0
  27. data/lib/calabash/android/lib/calmd5/arm64-v8a/calmd5 +0 -0
  28. data/lib/calabash/android/lib/calmd5/arm64-v8a/calmd5-pie +0 -0
  29. data/lib/calabash/android/lib/calmd5/armeabi-v7a/calmd5 +0 -0
  30. data/lib/calabash/android/lib/calmd5/armeabi-v7a/calmd5-pie +0 -0
  31. data/lib/calabash/android/lib/calmd5/armeabi/calmd5 +0 -0
  32. data/lib/calabash/android/lib/calmd5/armeabi/calmd5-pie +0 -0
  33. data/lib/calabash/android/lib/calmd5/mips/calmd5 +0 -0
  34. data/lib/calabash/android/lib/calmd5/mips/calmd5-pie +0 -0
  35. data/lib/calabash/android/lib/calmd5/mips64/calmd5 +0 -0
  36. data/lib/calabash/android/lib/calmd5/mips64/calmd5-pie +0 -0
  37. data/lib/calabash/android/lib/calmd5/x86/calmd5 +0 -0
  38. data/lib/calabash/android/lib/calmd5/x86/calmd5-pie +0 -0
  39. data/lib/calabash/android/lib/calmd5/x86_64/calmd5 +0 -0
  40. data/lib/calabash/android/lib/calmd5/x86_64/calmd5-pie +0 -0
  41. data/lib/calabash/android/lib/screenshot_taker.jar +0 -0
  42. data/lib/calabash/android/life_cycle.rb +37 -0
  43. data/lib/calabash/android/orientation.rb +30 -0
  44. data/lib/calabash/android/physical_buttons.rb +39 -0
  45. data/lib/calabash/android/screenshot.rb +9 -0
  46. data/lib/calabash/android/scroll.rb +5 -0
  47. data/lib/calabash/android/server.rb +10 -0
  48. data/lib/calabash/android/text.rb +54 -0
  49. data/lib/calabash/application.rb +74 -0
  50. data/lib/calabash/cli.rb +12 -0
  51. data/lib/calabash/cli/build.rb +33 -0
  52. data/lib/calabash/cli/console.rb +90 -0
  53. data/lib/calabash/cli/generate.rb +110 -0
  54. data/lib/calabash/cli/helpers.rb +130 -0
  55. data/lib/calabash/cli/resign.rb +33 -0
  56. data/lib/calabash/cli/run.rb +99 -0
  57. data/lib/calabash/cli/setup_keystore.rb +39 -0
  58. data/lib/calabash/color.rb +32 -0
  59. data/lib/calabash/console_helpers.rb +90 -0
  60. data/lib/calabash/defaults.rb +56 -0
  61. data/lib/calabash/device.rb +401 -0
  62. data/lib/calabash/environment.rb +75 -0
  63. data/lib/calabash/gestures.rb +384 -0
  64. data/lib/calabash/http.rb +8 -0
  65. data/lib/calabash/http/error.rb +15 -0
  66. data/lib/calabash/http/request.rb +42 -0
  67. data/lib/calabash/http/retriable_client.rb +156 -0
  68. data/lib/calabash/interactions.rb +105 -0
  69. data/lib/calabash/ios.rb +37 -0
  70. data/lib/calabash/ios/application.rb +119 -0
  71. data/lib/calabash/ios/conditions.rb +79 -0
  72. data/lib/calabash/ios/console_helpers.rb +72 -0
  73. data/lib/calabash/ios/device.rb +24 -0
  74. data/lib/calabash/ios/device/device_implementation.rb +779 -0
  75. data/lib/calabash/ios/device/gestures_mixin.rb +167 -0
  76. data/lib/calabash/ios/device/keyboard_mixin.rb +133 -0
  77. data/lib/calabash/ios/device/physical_device_mixin.rb +266 -0
  78. data/lib/calabash/ios/device/rotation_mixin.rb +124 -0
  79. data/lib/calabash/ios/device/routes/backdoor_route_mixin.rb +86 -0
  80. data/lib/calabash/ios/device/routes/condition_route_mixin.rb +62 -0
  81. data/lib/calabash/ios/device/routes/error.rb +8 -0
  82. data/lib/calabash/ios/device/routes/handle_route_mixin.rb +102 -0
  83. data/lib/calabash/ios/device/routes/map_route_mixin.rb +38 -0
  84. data/lib/calabash/ios/device/routes/playback_route_mixin.rb +70 -0
  85. data/lib/calabash/ios/device/routes/response_parser.rb +48 -0
  86. data/lib/calabash/ios/device/routes/uia_route_mixin.rb +238 -0
  87. data/lib/calabash/ios/device/runtime_attributes.rb +184 -0
  88. data/lib/calabash/ios/device/status_bar_mixin.rb +17 -0
  89. data/lib/calabash/ios/device/text_mixin.rb +19 -0
  90. data/lib/calabash/ios/device/uia_keyboard_mixin.rb +188 -0
  91. data/lib/calabash/ios/device/uia_mixin.rb +12 -0
  92. data/lib/calabash/ios/environment.rb +41 -0
  93. data/lib/calabash/ios/interactions.rb +10 -0
  94. data/lib/calabash/ios/lib/.irbrc +55 -0
  95. data/lib/calabash/ios/lib/recordings/rotate_left_home_down_ipad.base64 +2 -0
  96. data/lib/calabash/ios/lib/recordings/rotate_left_home_down_iphone.base64 +2 -0
  97. data/lib/calabash/ios/lib/recordings/rotate_left_home_left_ipad.base64 +2 -0
  98. data/lib/calabash/ios/lib/recordings/rotate_left_home_left_iphone.base64 +2 -0
  99. data/lib/calabash/ios/lib/recordings/rotate_left_home_right_ipad.base64 +2 -0
  100. data/lib/calabash/ios/lib/recordings/rotate_left_home_right_iphone.base64 +2 -0
  101. data/lib/calabash/ios/lib/recordings/rotate_left_home_up_ipad.base64 +2 -0
  102. data/lib/calabash/ios/lib/recordings/rotate_left_home_up_iphone.base64 +2 -0
  103. data/lib/calabash/ios/lib/recordings/rotate_right_home_down_ipad.base64 +2 -0
  104. data/lib/calabash/ios/lib/recordings/rotate_right_home_down_iphone.base64 +2 -0
  105. data/lib/calabash/ios/lib/recordings/rotate_right_home_left_ipad.base64 +2 -0
  106. data/lib/calabash/ios/lib/recordings/rotate_right_home_left_iphone.base64 +2 -0
  107. data/lib/calabash/ios/lib/recordings/rotate_right_home_right_ipad.base64 +2 -0
  108. data/lib/calabash/ios/lib/recordings/rotate_right_home_right_iphone.base64 +2 -0
  109. data/lib/calabash/ios/lib/recordings/rotate_right_home_up_ipad.base64 +2 -0
  110. data/lib/calabash/ios/lib/recordings/rotate_right_home_up_iphone.base64 +2 -0
  111. data/lib/calabash/ios/orientation.rb +117 -0
  112. data/lib/calabash/ios/scroll.rb +504 -0
  113. data/lib/calabash/ios/server.rb +73 -0
  114. data/lib/calabash/ios/text.rb +248 -0
  115. data/lib/calabash/ios/uia.rb +24 -0
  116. data/lib/calabash/lib/skeleton/config/cucumber.yml +6 -0
  117. data/lib/calabash/lib/skeleton/features/sample.feature +5 -0
  118. data/lib/calabash/lib/skeleton/features/step_definitions/calabash_steps.rb +29 -0
  119. data/lib/calabash/lib/skeleton/features/support/env.rb +54 -0
  120. data/lib/calabash/lib/skeleton/features/support/hooks.rb +83 -0
  121. data/lib/calabash/life_cycle.rb +111 -0
  122. data/lib/calabash/location.rb +51 -0
  123. data/lib/calabash/logger.rb +87 -0
  124. data/lib/calabash/orientation.rb +84 -0
  125. data/lib/calabash/page.rb +35 -0
  126. data/lib/calabash/patch.rb +14 -0
  127. data/lib/calabash/patch/array.rb +16 -0
  128. data/lib/calabash/patch/run_loop.rb +90 -0
  129. data/lib/calabash/query.rb +160 -0
  130. data/lib/calabash/query_result.rb +85 -0
  131. data/lib/calabash/screenshot.rb +89 -0
  132. data/lib/calabash/server.rb +16 -0
  133. data/lib/calabash/text.rb +76 -0
  134. data/lib/calabash/utility.rb +58 -0
  135. data/lib/calabash/version.rb +3 -1
  136. data/lib/calabash/wait.rb +474 -0
  137. metadata +462 -24
@@ -0,0 +1,111 @@
1
+ module Calabash
2
+ module LifeCycle
3
+ def start_app(path_or_application = nil, **opt)
4
+ path_or_application ||= Application.default
5
+
6
+ unless path_or_application
7
+ raise 'No application given, and Application.default is not set'
8
+ end
9
+
10
+ Device.default.start_app(path_or_application, opt.dup)
11
+ end
12
+
13
+ def stop_app
14
+ Device.default.stop_app
15
+ end
16
+
17
+ # Installs the given application. If the application is already installed,
18
+ # the application will be uninstalled, and installed afterwards. If no
19
+ # application is given, it will install `Application.default`.
20
+ #
21
+ # If the given application is an instance of
22
+ # `Calabash::Android::Application`, the same procedure is executed for the
23
+ # test-server of the application, if it is set.
24
+ #
25
+ # @param [String, Calabash::Application] path_or_application A path to the
26
+ # application, or an instance of `Calabash::Application`. Defaults to
27
+ # `Application.default`
28
+ def install_app(path_or_application = nil)
29
+ path_or_application ||= Application.default
30
+
31
+ unless path_or_application
32
+ raise 'No application given, and Application.default is not set'
33
+ end
34
+
35
+ Device.default.install_app(path_or_application)
36
+ end
37
+
38
+ # Installs the given application *if it is not already installed*. If no
39
+ # application is given, it will ensure `Application.default` is installed.
40
+ # If the application has changed, it will be installed using the same
41
+ # approach as #{Calabash::LifeCycle#install_app}.
42
+ #
43
+ # If the given application is an instance of
44
+ # `Calabash::Android::Application`, the same procedure is executed for the
45
+ # test-server of the application, if it is set.
46
+ #
47
+ # @param [String, Calabash::Application] path_or_application A path to the
48
+ # application, or an instance of `Calabash::Application`. Defaults to
49
+ # `Application.default`
50
+ def ensure_app_installed(path_or_application = nil)
51
+ path_or_application ||= Application.default
52
+
53
+ unless path_or_application
54
+ raise 'No application given, and Application.default is not set'
55
+ end
56
+
57
+ Device.default.ensure_app_installed(path_or_application)
58
+ end
59
+
60
+ # Uninstalls the given application. Does nothing if the application is
61
+ # already uninstalled. If no application is given, it will uninstall
62
+ # `Application.default`.
63
+ #
64
+ # @param [String, Calabash::Application] path_or_application A path to the
65
+ # application, or an instance of `Calabash::Application`. Defaults to
66
+ # `Application.default`
67
+ def uninstall_app(path_or_application = nil)
68
+ path_or_application ||= Application.default
69
+
70
+ unless path_or_application
71
+ raise 'No application given, and Application.default is not set'
72
+ end
73
+
74
+ Device.default.uninstall_app(path_or_application)
75
+ end
76
+
77
+ # Clears the contents of the given application. This is roughly equivalent to
78
+ # reinstalling the application. If no application is given, it will clear
79
+ # `Application.default`.
80
+ #
81
+ # @param [String, Calabash::Application] path_or_application A path to the
82
+ # application, or an instance of `Calabash::Application`. Defaults to
83
+ # `Application.default`
84
+ def clear_app_data(path_or_application = nil)
85
+ path_or_application ||= Application.default
86
+
87
+ unless path_or_application
88
+ raise 'No application given, and Application.default is not set'
89
+ end
90
+
91
+ Device.default.clear_app_data(path_or_application)
92
+ end
93
+
94
+ # Sends the current app to the background and resumes it after
95
+ # `for_seconds`. This should not exceed 60 seconds for iOS.
96
+ #
97
+ # On Android you can control the app lifecycle more granularly using
98
+ # #{Calabash::Android::Interactions#go_home} and
99
+ # #{Calabash::Android::LifeCycle#resume_app}.
100
+ def send_current_app_to_background(for_seconds = 10)
101
+ _send_current_app_to_background(for_seconds)
102
+
103
+ true
104
+ end
105
+
106
+ # !@visibility private
107
+ def _send_current_app_to_background(for_seconds)
108
+ abstract_method!
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,51 @@
1
+ require 'geocoder'
2
+
3
+ module Calabash
4
+ module Location
5
+ # Simulates gps location of the device/simulator.
6
+ # @note Seems UIAutomation is broken here on physical devices on iOS 7.1
7
+ #
8
+ # @example
9
+ # set_location({latitude: 48.8567, longitude: 2.3508})
10
+ #
11
+ # @example
12
+ # set_location(coordinates_for_place('The little mermaid, Copenhagen'))
13
+ #
14
+ # @param {Hash} options specifies which location to simulate
15
+ # @option options {Numeric} :latitude latitude of a gps coordinate (same
16
+ # coordinate system as Google maps)
17
+ # @option options {Numeric} :longitude longitude of a gps coordinate (same
18
+ # coordinate system as Google maps)
19
+ def set_location(location)
20
+ unless location.is_a?(Hash)
21
+ raise ArgumentError, "Expected location to be a Hash, not '#{location.class}'"
22
+ end
23
+
24
+ unless location[:latitude] || location[:longitude]
25
+ raise ArgumentError, "You must supply :latitude and :longitude"
26
+ end
27
+
28
+ Device.default.set_location(location)
29
+ end
30
+
31
+ # Get the latitude and longitude for a certain place, resolved via Google
32
+ # maps api. This hash can be used in `set_location`.
33
+ #
34
+ # @example
35
+ # coordinates_for_place('The little mermaid, Copenhagen')
36
+ # # => {:latitude => 55.6760968, :longitude => 12.5683371}
37
+ #
38
+ # @return [Hash] Latitude and longitude for the given place
39
+ # @raise [RuntimeError] If the place cannot be found
40
+ def coordinates_for_place(place)
41
+ result = Geocoder.search(place)
42
+
43
+ if result.empty?
44
+ raise "No result found for '#{place}'"
45
+ end
46
+
47
+ {latitude: result.first.latitude,
48
+ longitude: result.first.longitude}
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,87 @@
1
+ module Calabash
2
+
3
+ # A logger for Calabash.
4
+ class Logger
5
+ attr_accessor :default_log_level
6
+
7
+ def initialize(output=nil)
8
+ @output = output || STDOUT.dup
9
+ @default_log_level = :debug
10
+ end
11
+
12
+ # Log a message
13
+ #
14
+ # @param [String] message to log
15
+ # @param [Symbol] log_level
16
+ # Can be one of `{:info | :debug | :warn | :error }`
17
+ def log(message, log_level = default_log_level)
18
+ @output.write("#{message}\n") if should_log?(log_level)
19
+ end
20
+
21
+ private
22
+
23
+ # @!visibility private
24
+ def should_log?(log_level)
25
+ Logger.log_levels.include?(log_level)
26
+ end
27
+
28
+ public
29
+
30
+ if Environment::DEBUG
31
+ @@log_levels = [:info, :warn, :error, :debug]
32
+ else
33
+ @@log_levels = [:info, :warn, :error]
34
+ end
35
+
36
+ # Log a message to the default output
37
+ #
38
+ # @param [String] message to log
39
+ # @param [Symbol] log_level (:debug, :info, :warning, :error)
40
+ # Can be one of `{:info | :debug | :warn | :error }`
41
+ def self.log(message, log_level = :info)
42
+ Logger.new.log(message, log_level)
43
+ end
44
+
45
+ # Log a message to the default output with log level :info
46
+ #
47
+ # @param [String] message to log
48
+ def self.info(message)
49
+ log(message, :info)
50
+ end
51
+
52
+ # Log a message to the default output with log level :debug
53
+ #
54
+ # @param [String] message to log
55
+ def self.debug(message)
56
+ log(message, :debug)
57
+ end
58
+
59
+ # Log a message to the default output with log level :warn
60
+ #
61
+ # @param [String] message to log
62
+ def self.warn(message)
63
+ log(message, :warn)
64
+ end
65
+
66
+ # Log a message to the default output with log level :error
67
+ #
68
+ # @param [String] message to log
69
+ def self.error(message)
70
+ log(message, :error)
71
+ end
72
+
73
+ # The log levels of the logger
74
+ #
75
+ # @return [Array] log levels
76
+ def self.log_levels
77
+ @@log_levels
78
+ end
79
+
80
+ # Set the log levels of the logger
81
+ #
82
+ # @param [Array] log_levels
83
+ def self.log_levels=(log_levels)
84
+ @@log_levels = log_levels
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,84 @@
1
+ module Calabash
2
+
3
+ # Methods for querying an app's orientation and for rotating the app into
4
+ # different orientations.
5
+ module Orientation
6
+ # Is the device in the portrait orientation?
7
+ #
8
+ # @return [Boolean] Returns true if the device is in the 'up' or 'down'
9
+ # orientation.
10
+ def portrait?
11
+ _portrait?
12
+ end
13
+
14
+ # Is the device in the landscape orientation?
15
+ #
16
+ # @return [Boolean] Returns true if the device is in the 'left' or 'right'
17
+ # orientation.
18
+ def landscape?
19
+ _landscape?
20
+ end
21
+
22
+ # Set the device orientation to portrait.
23
+ #
24
+ # In portrait orientation, the display of the device has a bigger width
25
+ # than height.
26
+ #
27
+ # On iOS, the presenting view controller must respond to rotation events.
28
+ # If the presenting view controller does not respond to rotation events,
29
+ # then no rotation will be performed.
30
+ def set_orientation_portrait
31
+ _set_orientation_portrait
32
+ end
33
+
34
+ # Set the device orientation to landscape.
35
+ #
36
+ # In landscape orientation, the display of the device has a bigger height
37
+ # than width.
38
+ #
39
+ # On iOS, the presenting view controller must respond to rotation events.
40
+ # If the presenting view controller does not respond to rotation events,
41
+ # then no rotation will be performed.
42
+ def set_orientation_landscape
43
+ _set_orientation_landscape
44
+ end
45
+
46
+ # Changes the orientation of the device.
47
+ #
48
+ # If the orientation is currently landscape, it will be set to portrait.
49
+ # If the orientation is currently portrait, it will be set to landscape.
50
+ #
51
+ # On iOS, the presenting view controller must respond to rotation events.
52
+ # If the presenting view controller does not respond to rotation events,
53
+ # then no rotation will be performed.
54
+ def change_orientation
55
+ if portrait?
56
+ set_orientation_landscape
57
+ elsif landscape?
58
+ set_orientation_portrait
59
+ else
60
+ raise 'Could not detect current orientation'
61
+ end
62
+ end
63
+
64
+ # @!visibility private
65
+ def _portrait?
66
+ abstract_method!
67
+ end
68
+
69
+ # @!visibility private
70
+ def _landscape?
71
+ abstract_method!
72
+ end
73
+
74
+ # @!visibility private
75
+ def _set_orientation_portrait
76
+ abstract_method!
77
+ end
78
+
79
+ # @!visibility private
80
+ def _set_orientation_landscape
81
+ abstract_method!
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,35 @@
1
+ module Calabash
2
+ class Page
3
+ # For auto-completion
4
+ include Calabash
5
+
6
+ def self.inherited(subclass)
7
+ # Define the page into global scope
8
+ name = subclass.to_s.split('::').last
9
+
10
+ unless Object.const_defined?(name.to_sym)
11
+ # We need a unique type for this constant
12
+ clz = Class.new(StubPage)
13
+ Object.const_set(name.to_sym, clz)
14
+ end
15
+ end
16
+
17
+ private_class_method :new
18
+
19
+ def initialize(world)
20
+ @world = world
21
+ end
22
+
23
+ def trait
24
+ raise 'Implement your own trait'
25
+ end
26
+
27
+ def await(timeout = nil)
28
+ wait_for_view(trait, timeout: timeout)
29
+ end
30
+
31
+ class StubPage
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ module Calabash
2
+ # @!visibility private
3
+ module Patch
4
+ require 'calabash/patch/array'
5
+
6
+ def self.apply_patches!
7
+ modules = Patch.constants(false)
8
+
9
+ modules.each do |constant|
10
+ Calabash.const_get(constant).send(:include, const_get(constant))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Calabash
2
+ # @!visibility private
3
+ module Patch
4
+
5
+ # @!visibility private
6
+ module Array
7
+ def to_pct
8
+ if length != 2
9
+ raise RangeError, "Cannot convert #{self} to {:x, :y} hash"
10
+ end
11
+
12
+ {x: self.[](0), y: self.[](1)}
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,90 @@
1
+ require 'run_loop'
2
+
3
+ if RunLoop.const_defined?(:Ipa)
4
+ "\033[34mRunLoop::Ipa patch can be removed.\033[0m"
5
+ else
6
+ "\033[34mApplying RunLoop::Ipa patch.\033[0m"
7
+
8
+ # @!visibility private
9
+ module RunLoop
10
+ # A model of the an .ipa - a application binary for iOS devices.
11
+ class Ipa
12
+
13
+
14
+ # The path to this .ipa.
15
+ # @!attribute [r] path
16
+ # @return [String] A path to this .ipa.
17
+ attr_reader :path
18
+
19
+ # The bundle identifier of this ipa.
20
+ # @!attribute [r] bundle_identifier
21
+ # @return [String] The bundle identifier of this ipa; obtained by inspecting
22
+ # the app's Info.plist.
23
+ attr_reader :bundle_identifier
24
+
25
+ # Create a new ipa instance.
26
+ # @param [String] path_to_ipa The path the .ipa file.
27
+ # @return [Calabash::Ipa] A new ipa instance.
28
+ # @raise [RuntimeError] If the file does not exist.
29
+ # @raise [RuntimeError] If the file does not end in .ipa.
30
+ def initialize(path_to_ipa)
31
+ unless File.exist? path_to_ipa
32
+ raise "Expected an ipa at '#{path_to_ipa}'"
33
+ end
34
+
35
+ unless path_to_ipa.end_with?('.ipa')
36
+ raise "Expected '#{path_to_ipa}' to be an ipa"
37
+ end
38
+ @path = path_to_ipa
39
+ end
40
+
41
+ # @!visibility private
42
+ def to_s
43
+ "#<Ipa: #{bundle_identifier}: '#{path}'>"
44
+ end
45
+
46
+ # The bundle identifier of this ipa.
47
+ # @return [String] A string representation of this ipa's CFBundleIdentifier
48
+ # @raise [RuntimeError] If ipa does not expand into a Payload/<app name>.app
49
+ # directory.
50
+ # @raise [RuntimeError] If an Info.plist does exist in the .app.
51
+ def bundle_identifier
52
+ unless File.exist?(bundle_dir)
53
+ raise "Expected a '#{File.basename(path).split('.').first}.app'\nat path '#{payload_dir}'"
54
+ end
55
+
56
+ @bundle_identifier ||= lambda {
57
+ info_plist_path = File.join(bundle_dir, 'Info.plist')
58
+ unless File.exist? info_plist_path
59
+ raise "Expected an 'Info.plist' at '#{bundle_dir}'"
60
+ end
61
+ pbuddy = RunLoop::PlistBuddy.new
62
+ pbuddy.plist_read('CFBundleIdentifier', info_plist_path)
63
+ }.call
64
+ end
65
+
66
+ private
67
+
68
+ def tmpdir
69
+ @tmpdir ||= Dir.mktmpdir
70
+ end
71
+
72
+ def payload_dir
73
+ @payload_dir ||= lambda {
74
+ FileUtils.cp(path, tmpdir)
75
+ zip_path = File.join(tmpdir, File.basename(path))
76
+ Dir.chdir(tmpdir) do
77
+ system('unzip', *['-q', zip_path])
78
+ end
79
+ File.join(tmpdir, 'Payload')
80
+ }.call
81
+ end
82
+
83
+ def bundle_dir
84
+ @bundle_dir ||= lambda {
85
+ Dir.glob(File.join(payload_dir, '*')).detect {|f| File.directory?(f) && f.end_with?('.app')}
86
+ }.call
87
+ end
88
+ end
89
+ end
90
+ end