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.
- data/CHANGELOG.md +5 -0
- data/README.md +183 -66
- data/Rakefile +6 -6
- data/bubble-wrap.gemspec +10 -5
- data/lib/bubble-wrap.rb +4 -39
- data/lib/bubble-wrap/core.rb +7 -0
- data/lib/bubble-wrap/ext.rb +2 -0
- data/lib/bubble-wrap/ext/motion_project_app.rb +26 -0
- data/lib/bubble-wrap/ext/motion_project_config.rb +21 -0
- data/lib/bubble-wrap/http.rb +2 -249
- data/lib/bubble-wrap/loader.rb +23 -0
- data/lib/bubble-wrap/requirement.rb +88 -0
- data/lib/bubble-wrap/requirement/path_manipulation.rb +40 -0
- data/lib/bubble-wrap/version.rb +1 -1
- data/lib_spec/bubble-wrap/requirement/path_manipulation_spec.rb +51 -0
- data/lib_spec/bubble-wrap/requirement_spec.rb +72 -0
- data/lib_spec/bubble-wrap_spec.rb +17 -0
- data/lib_spec/motion_stub.rb +12 -0
- data/{lib/bubble-wrap/module.rb → motion/core.rb} +0 -0
- data/{lib/bubble-wrap → motion/core}/app.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/device.rb +0 -16
- data/{lib/bubble-wrap → motion/core}/device/screen.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/gestures.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/json.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/ns_index_path.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/ns_notification_center.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/ns_user_defaults.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/persistence.rb +0 -0
- data/{lib → motion/core}/pollute.rb +1 -1
- data/motion/core/string.rb +38 -0
- data/{lib/bubble-wrap → motion/core}/time.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/ui_control.rb +0 -0
- data/{lib/bubble-wrap → motion/core}/ui_view_controller.rb +0 -0
- data/motion/http.rb +249 -0
- data/spec/{app_spec.rb → core/app_spec.rb} +2 -2
- data/spec/{device → core/device}/screen_spec.rb +0 -0
- data/spec/{device_spec.rb → core/device_spec.rb} +0 -0
- data/spec/{gestures_spec.rb → core/gestures_spec.rb} +0 -0
- data/spec/{json_spec.rb → core/json_spec.rb} +0 -0
- data/spec/{ns_index_path_spec.rb → core/ns_index_path_spec.rb} +0 -0
- data/spec/{ns_notification_center_spec.rb → core/ns_notification_center_spec.rb} +0 -0
- data/spec/{persistence_spec.rb → core/persistence_spec.rb} +0 -0
- data/spec/core/string_spec.rb +69 -0
- data/spec/{time_spec.rb → core/time_spec.rb} +0 -0
- data/spec/{ui_control_spec.rb → core/ui_control_spec.rb} +0 -0
- data/spec/{module_spec.rb → core_spec.rb} +0 -0
- data/spec/http_spec.rb +300 -280
- 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
|
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
|
File without changes
|
File without changes
|
@@ -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
|
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
|