bubble-wrap 0.3.1 → 0.4.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.
Files changed (48) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/README.md +183 -66
  3. data/Rakefile +6 -6
  4. data/bubble-wrap.gemspec +10 -5
  5. data/lib/bubble-wrap.rb +4 -39
  6. data/lib/bubble-wrap/core.rb +7 -0
  7. data/lib/bubble-wrap/ext.rb +2 -0
  8. data/lib/bubble-wrap/ext/motion_project_app.rb +26 -0
  9. data/lib/bubble-wrap/ext/motion_project_config.rb +21 -0
  10. data/lib/bubble-wrap/http.rb +2 -249
  11. data/lib/bubble-wrap/loader.rb +23 -0
  12. data/lib/bubble-wrap/requirement.rb +88 -0
  13. data/lib/bubble-wrap/requirement/path_manipulation.rb +40 -0
  14. data/lib/bubble-wrap/version.rb +1 -1
  15. data/lib_spec/bubble-wrap/requirement/path_manipulation_spec.rb +51 -0
  16. data/lib_spec/bubble-wrap/requirement_spec.rb +72 -0
  17. data/lib_spec/bubble-wrap_spec.rb +17 -0
  18. data/lib_spec/motion_stub.rb +12 -0
  19. data/{lib/bubble-wrap/module.rb → motion/core.rb} +0 -0
  20. data/{lib/bubble-wrap → motion/core}/app.rb +0 -0
  21. data/{lib/bubble-wrap → motion/core}/device.rb +0 -16
  22. data/{lib/bubble-wrap → motion/core}/device/screen.rb +0 -0
  23. data/{lib/bubble-wrap → motion/core}/gestures.rb +0 -0
  24. data/{lib/bubble-wrap → motion/core}/json.rb +0 -0
  25. data/{lib/bubble-wrap → motion/core}/ns_index_path.rb +0 -0
  26. data/{lib/bubble-wrap → motion/core}/ns_notification_center.rb +0 -0
  27. data/{lib/bubble-wrap → motion/core}/ns_user_defaults.rb +0 -0
  28. data/{lib/bubble-wrap → motion/core}/persistence.rb +0 -0
  29. data/{lib → motion/core}/pollute.rb +1 -1
  30. data/motion/core/string.rb +38 -0
  31. data/{lib/bubble-wrap → motion/core}/time.rb +0 -0
  32. data/{lib/bubble-wrap → motion/core}/ui_control.rb +0 -0
  33. data/{lib/bubble-wrap → motion/core}/ui_view_controller.rb +0 -0
  34. data/motion/http.rb +249 -0
  35. data/spec/{app_spec.rb → core/app_spec.rb} +2 -2
  36. data/spec/{device → core/device}/screen_spec.rb +0 -0
  37. data/spec/{device_spec.rb → core/device_spec.rb} +0 -0
  38. data/spec/{gestures_spec.rb → core/gestures_spec.rb} +0 -0
  39. data/spec/{json_spec.rb → core/json_spec.rb} +0 -0
  40. data/spec/{ns_index_path_spec.rb → core/ns_index_path_spec.rb} +0 -0
  41. data/spec/{ns_notification_center_spec.rb → core/ns_notification_center_spec.rb} +0 -0
  42. data/spec/{persistence_spec.rb → core/persistence_spec.rb} +0 -0
  43. data/spec/core/string_spec.rb +69 -0
  44. data/spec/{time_spec.rb → core/time_spec.rb} +0 -0
  45. data/spec/{ui_control_spec.rb → core/ui_control_spec.rb} +0 -0
  46. data/spec/{module_spec.rb → core_spec.rb} +0 -0
  47. data/spec/http_spec.rb +300 -280
  48. metadata +115 -42
@@ -0,0 +1,72 @@
1
+ require File.expand_path('../../motion_stub', __FILE__)
2
+ require 'bubble-wrap'
3
+
4
+ describe BubbleWrap::Requirement do
5
+
6
+ subject{ BubbleWrap::Requirement }
7
+
8
+ describe '.scan' do
9
+ before do
10
+ subject.paths = {}
11
+ end
12
+
13
+ let(:root_path) { File.expand_path('../../../', __FILE__) }
14
+
15
+ it 'asking for a not-yet-found file raises an exception' do
16
+ proc do
17
+ subject.find('foo')
18
+ end.should raise_error
19
+ end
20
+
21
+ it 'finds the specified file' do
22
+ subject.scan(root_path, 'motion/core.rb')
23
+ subject.paths.keys.first.should == 'motion/core.rb'
24
+ end
25
+
26
+ it 'finds multiple files according to spec' do
27
+ subject.scan(root_path, 'motion/**/*.rb')
28
+ subject.files.size.should > 1
29
+ end
30
+
31
+ it 'never depends on itself' do
32
+ subject.scan(root_path, 'motion/core.rb') do
33
+ file('motion/core.rb').depends_on 'motion/core.rb'
34
+ end
35
+ subject.file('motion/core.rb').file_dependencies.should == []
36
+ end
37
+
38
+ it 'can depend on another file' do
39
+ subject.scan(root_path, 'motion/*.rb') do
40
+ file('motion/http.rb').depends_on('motion/core.rb')
41
+ end
42
+ subject.file('motion/http.rb').file_dependencies.should be_one
43
+ end
44
+
45
+ it 'can use a framework' do
46
+ subject.scan(root_path, 'motion/core.rb') do
47
+ file('motion/core.rb').uses_framework('FakeFramework')
48
+ end
49
+ subject.file('motion/core.rb').frameworks.should include('FakeFramework')
50
+ end
51
+
52
+ it "figures out the root of the project" do
53
+ subject.scan(File.join(root_path, 'lib/bubble-wrap.rb'), 'motion/core.rb')
54
+ subject.paths.values.first.root.should == root_path
55
+ end
56
+
57
+ describe '.frameworks' do
58
+ it 'includes UIKit by default' do
59
+ subject.frameworks.should include('UIKit')
60
+ end
61
+
62
+ it 'includes Foundation by default' do
63
+ subject.frameworks.should include('Foundation')
64
+ end
65
+
66
+ it 'includes CoreGraphics by default' do
67
+ subject.frameworks.should include('CoreGraphics')
68
+ end
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path('../motion_stub.rb', __FILE__)
2
+ require 'bubble-wrap'
3
+
4
+ describe BubbleWrap do
5
+ describe '.root' do
6
+ it 'returns an absolute path' do
7
+ BubbleWrap.root[0].should == '/'
8
+ end
9
+ end
10
+
11
+ describe '.require' do
12
+ it 'delegates to Requirement.scan' do
13
+ BW::Requirement.should_receive(:scan)
14
+ BW.require('foo')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ # Create a fake Motion class hierarchy for testing.
2
+ module Motion
3
+ module Project
4
+ class App
5
+ def self.setup
6
+ end
7
+ end
8
+
9
+ class Config
10
+ end
11
+ end
12
+ end
File without changes
File without changes
@@ -30,22 +30,6 @@ module BubbleWrap
30
30
  @simulator_state ||= !(UIDevice.currentDevice.model =~ /simulator/i).nil?
31
31
  end
32
32
 
33
- # Verifies that the device running has a front facing camera.
34
- # @return [TrueClass, FalseClass] true will be returned if the device has a front facing camera, false otherwise.
35
- def front_camera?(picker=UIImagePickerController)
36
- picker.isCameraDeviceAvailable(UIImagePickerControllerCameraDeviceFront)
37
- end
38
-
39
- # Verifies that the device running has a rear facing camera.
40
- # @return [TrueClass, FalseClass] true will be returned if the device has a rear facing camera, false otherwise.
41
- def rear_camera?(picker=UIImagePickerController)
42
- picker.isCameraDeviceAvailable(UIImagePickerControllerCameraDeviceRear)
43
- end
44
-
45
- def simulator?
46
- @simulator_state ||= !(UIDevice.currentDevice.model =~ /simulator/i).nil?
47
- end
48
-
49
33
  # Shameless shorthand for accessing BubbleWrap::Screen
50
34
  def screen
51
35
  BubbleWrap::Device::Screen
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -2,5 +2,5 @@
2
2
  [NSIndexPath, NSIndexPathWrap],
3
3
  [UIControl, UIControlWrap]
4
4
  ].each do |base, wrapper|
5
- base.send(:include, wrapper)
5
+ base.send(:include, wrapper)
6
6
  end
@@ -0,0 +1,38 @@
1
+ module BubbleWrap
2
+ # This module contains simplified version of the `camelize` and
3
+ # `underscore` methods from ActiveSupport, since these are such
4
+ # common operations when dealing with the Cocoa API.
5
+ module String
6
+
7
+ # Convert 'snake_case' into 'CamelCase'
8
+ def camelize(uppercase_first_letter = true)
9
+ string = self.dup
10
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
11
+ new_word = $2.downcase
12
+ new_word[0] = new_word[0].upcase
13
+ new_word = "/#{new_word}" if $1 == '/'
14
+ new_word
15
+ end
16
+ if uppercase_first_letter
17
+ string[0] = string[0].upcase
18
+ else
19
+ string[0] = string[0].downcase
20
+ end
21
+ string.gsub!('/', '::')
22
+ string
23
+ end
24
+
25
+ # Convert 'CamelCase' into 'snake_case'
26
+ def underscore
27
+ word = self.dup
28
+ word.gsub!(/::/, '/')
29
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
30
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
31
+ word.tr!("-", "_")
32
+ word.downcase!
33
+ word
34
+ end
35
+ end
36
+ end
37
+
38
+ String.send(:include, BubbleWrap::String)
File without changes
File without changes
data/motion/http.rb ADDED
@@ -0,0 +1,249 @@
1
+ module BubbleWrap
2
+
3
+ SETTINGS = {}
4
+
5
+ # The HTTP module provides a simple interface to make HTTP requests.
6
+ #
7
+ # TODO: preflight support, easier/better cookie support, better error handling
8
+ module HTTP
9
+
10
+ # Make a GET request and process the response asynchronously via a block.
11
+ #
12
+ # @examples
13
+ # # Simple GET request printing the body
14
+ # BubbleWrap::HTTP.get("https://api.github.com/users/mattetti") do |response|
15
+ # p response.body.to_str
16
+ # end
17
+ #
18
+ # # GET request with basic auth credentials
19
+ # BubbleWrap::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response|
20
+ # p response.body.to_str # prints the response's body
21
+ # end
22
+ #
23
+ def self.get(url, options={}, &block)
24
+ delegator = block_given? ? block : options.delete(:action)
25
+ HTTP::Query.new( url, :get, options.merge({:action => delegator}) )
26
+ end
27
+
28
+ # Make a POST request
29
+ def self.post(url, options={}, &block)
30
+ delegator = block_given? ? block : options.delete(:action)
31
+ HTTP::Query.new( url, :post, options.merge({:action => delegator}) )
32
+ end
33
+
34
+ # Make a PUT request
35
+ def self.put(url, options={}, &block)
36
+ delegator = block_given? ? block : options.delete(:action)
37
+ HTTP::Query.new( url, :put, options.merge({:action => delegator}) )
38
+ end
39
+
40
+ # Make a DELETE request
41
+ def self.delete(url, options={}, &block)
42
+ delegator = block_given? ? block : options.delete(:action)
43
+ HTTP::Query.new( url, :delete, options.merge({:action => delegator}) )
44
+ end
45
+
46
+ # Make a HEAD request
47
+ def self.head(url, options={}, &block)
48
+ delegator = block_given? ? block : options.delete(:action)
49
+ HTTP::Query.new( url, :head, options.merge({:action => delegator}) )
50
+ end
51
+
52
+ # Make a PATCH request
53
+ def self.patch(url, options={}, &block)
54
+ delegator = block_given? ? block : options.delete(:action)
55
+ HTTP::Query.new( url, :patch, options.merge({:action => delegator}) )
56
+ end
57
+
58
+ # Response class wrapping the results of a Query's response
59
+ class Response
60
+ attr_reader :body
61
+ attr_reader :headers
62
+ attr_accessor :status_code, :error_message
63
+ attr_reader :url
64
+
65
+ def initialize(values={})
66
+ self.update(values)
67
+ end
68
+
69
+ def update(values)
70
+ values.each do |k,v|
71
+ self.instance_variable_set("@#{k}", v)
72
+ end
73
+ end
74
+
75
+ def ok?
76
+ status_code.to_s =~ /20\d/ ? true : false
77
+ end
78
+
79
+ end
80
+
81
+ # Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
82
+ class Query
83
+ attr_accessor :request
84
+ attr_accessor :connection
85
+ attr_accessor :credentials # username & password has a hash
86
+ attr_accessor :proxy_credential # credential supplied to proxy servers
87
+ attr_accessor :post_data
88
+ attr_reader :method
89
+
90
+ attr_reader :response
91
+ attr_reader :status_code
92
+ attr_reader :response_headers
93
+ attr_reader :response_size
94
+ attr_reader :options
95
+
96
+ # ==== Parameters
97
+ # url<String>:: url of the resource to download
98
+ # http_method<Symbol>:: Value representing the HTTP method to use
99
+ # options<Hash>:: optional options used for the query
100
+ #
101
+ # ==== Options
102
+ # :payload<String> - data to pass to a POST, PUT, DELETE query.
103
+ # :delegator - Proc, class or object to call when the file is downloaded.
104
+ # a proc will receive a Response object while the passed object
105
+ # will receive the handle_query_response method
106
+ # :headers<Hash> - headers send with the request
107
+ # Anything else will be available via the options attribute reader.
108
+ #
109
+ def initialize(url, http_method = :get, options={})
110
+ @method = http_method.upcase.to_s
111
+ @delegator = options.delete(:action) || self
112
+ @payload = options.delete(:payload)
113
+ @credentials = options.delete(:credentials) || {}
114
+ @credentials = {:username => '', :password => ''}.merge(@credentials)
115
+ @timeout = options.delete(:timeout) || 30.0
116
+ headers = options.delete(:headers)
117
+ if headers
118
+ @headers = {}
119
+ headers.each{|k,v| @headers[k] = v.gsub("\n", '\\n') } # escaping LFs
120
+ end
121
+ @cachePolicy = options.delete(:cache_policy) || NSURLRequestUseProtocolCachePolicy
122
+ @options = options
123
+ @response = HTTP::Response.new
124
+ initiate_request(url)
125
+ connection.start
126
+ UIApplication.sharedApplication.networkActivityIndicatorVisible = true
127
+ connection
128
+ end
129
+
130
+ def generate_get_params(payload, prefix=nil)
131
+ list = []
132
+ payload.each do |k,v|
133
+ if v.is_a?(Hash)
134
+ new_prefix = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
135
+ param = generate_get_params(v, new_prefix)
136
+ else
137
+ param = prefix ? "#{prefix}[#{k}]=#{v}" : "#{k}=#{v}"
138
+ end
139
+ list << param
140
+ end
141
+ return list.flatten
142
+ end
143
+
144
+ def initiate_request(url_string)
145
+ # http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/nsrunloop_Class/Reference/Reference.html#//apple_ref/doc/constant_group/Run_Loop_Modes
146
+ # NSConnectionReplyMode
147
+
148
+ unless @payload.nil?
149
+ if @payload.is_a?(Hash)
150
+ params = generate_get_params(@payload)
151
+ @payload = params.join("&")
152
+ end
153
+ url_string = "#{url_string}?#{@payload}" if @method == "GET"
154
+ end
155
+
156
+ p "BubbleWrap::HTTP building a NSRequest for #{url_string}" if SETTINGS[:debug]
157
+ @url = NSURL.URLWithString(url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding)
158
+ @request = NSMutableURLRequest.requestWithURL(@url,
159
+ cachePolicy:@cachePolicy,
160
+ timeoutInterval:@timeout)
161
+ @request.setHTTPMethod @method
162
+ @request.setAllHTTPHeaderFields(@headers) if @headers
163
+
164
+ # @payload needs to be converted to data
165
+ unless @method == "GET" || @payload.nil?
166
+ @payload = @payload.to_s.dataUsingEncoding(NSUTF8StringEncoding)
167
+ @request.setHTTPBody @payload
168
+ end
169
+
170
+ # NSHTTPCookieStorage.sharedHTTPCookieStorage
171
+
172
+ @connection = create_connection(request, self)
173
+ @request.instance_variable_set("@done_loading", false)
174
+ def @request.done_loading; @done_loading; end
175
+ def @request.done_loading!; @done_loading = true; end
176
+ end
177
+
178
+ def connection(connection, didReceiveResponse:response)
179
+ @status_code = response.statusCode
180
+ @response_headers = response.allHeaderFields
181
+ @response_size = response.expectedContentLength.to_f
182
+ end
183
+
184
+ # This delegate method get called every time a chunk of data is being received
185
+ def connection(connection, didReceiveData:received_data)
186
+ @received_data ||= NSMutableData.new
187
+ @received_data.appendData(received_data)
188
+ end
189
+
190
+ def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
191
+ p "HTTP redirected #{request.description}" if SETTINGS[:debug]
192
+ new_request = request.mutableCopy
193
+ # new_request.setValue(@credentials.inspect, forHTTPHeaderField:'Authorization') # disabled while we figure this one out
194
+ new_request.setAllHTTPHeaderFields(@headers) if @headers
195
+ @connection.cancel
196
+ @connection = create_connection(new_request, self)
197
+ new_request
198
+ end
199
+
200
+ def connection(connection, didFailWithError: error)
201
+ UIApplication.sharedApplication.networkActivityIndicatorVisible = false
202
+ @request.done_loading!
203
+ NSLog"HTTP Connection failed #{error.localizedDescription}" if SETTINGS[:debug]
204
+ @response.error_message = error.localizedDescription
205
+ call_delegator_with_response
206
+ end
207
+
208
+
209
+ # The transfer is done and everything went well
210
+ def connectionDidFinishLoading(connection)
211
+ UIApplication.sharedApplication.networkActivityIndicatorVisible = false
212
+ @request.done_loading!
213
+ # copy the data in a local var that we will attach to the response object
214
+ response_body = NSData.dataWithData(@received_data) if @received_data
215
+ @response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url)
216
+
217
+ call_delegator_with_response
218
+ end
219
+
220
+ def connection(connection, didReceiveAuthenticationChallenge:challenge)
221
+
222
+ if (challenge.previousFailureCount == 0)
223
+ # by default we are keeping the credential for the entire session
224
+ # Eventually, it would be good to let the user pick one of the 3 possible credential persistence options:
225
+ # NSURLCredentialPersistenceNone,
226
+ # NSURLCredentialPersistenceForSession,
227
+ # NSURLCredentialPersistencePermanent
228
+ p "auth challenged, answered with credentials: #{credentials.inspect}" if SETTINGS[:debug]
229
+ new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:NSURLCredentialPersistenceForSession)
230
+ challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
231
+ else
232
+ challenge.sender.cancelAuthenticationChallenge(challenge)
233
+ p 'Auth Failed :('
234
+ end
235
+ end
236
+
237
+ def call_delegator_with_response
238
+ if @delegator.respond_to?(:call)
239
+ @delegator.call( @response, self )
240
+ end
241
+ end
242
+
243
+ # This is a temporary method used for mocking.
244
+ def create_connection(request, delegate)
245
+ NSURLConnection.connectionWithRequest(request, delegate:delegate)
246
+ end
247
+ end
248
+ end
249
+ end