bubble-wrap 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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