calabash 1.2.1 → 1.9.9.pre1

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.
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