Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of github.com:mattetti/BubbleWrap

  • Loading branch information...
commit 10830a6da0c2594f551569ea92631d00dbccdcfb 2 parents c8c1a57 + 56558fc
@supermarin supermarin authored
Showing with 773 additions and 295 deletions.
  1. +37 −4 README.md
  2. +11 −5 Rakefile
  3. +10 −5 bubble-wrap.gemspec
  4. +17 −31 lib/bubble-wrap.rb
  5. +6 −0 lib/bubble-wrap/core.rb
  6. +2 −0  lib/bubble-wrap/ext.rb
  7. +26 −0 lib/bubble-wrap/ext/motion_project_app.rb
  8. +21 −0 lib/bubble-wrap/ext/motion_project_config.rb
  9. +1 −249 lib/bubble-wrap/http.rb
  10. +93 −0 lib/bubble-wrap/requirement.rb
  11. +40 −0 lib/bubble-wrap/requirement/path_manipulation.rb
  12. +51 −0 lib_spec/bubble-wrap/requirement/path_manipulation_spec.rb
  13. +72 −0 lib_spec/bubble-wrap/requirement_spec.rb
  14. +17 −0 lib_spec/bubble-wrap_spec.rb
  15. +12 −0 lib_spec/motion_stub.rb
  16. 0  lib/bubble-wrap/module.rb → motion/core.rb
  17. 0  {lib/bubble-wrap → motion/core}/app.rb
  18. 0  {lib/bubble-wrap → motion/core}/device.rb
  19. 0  {lib/bubble-wrap → motion/core}/device/screen.rb
  20. 0  {lib/bubble-wrap → motion/core}/gestures.rb
  21. 0  {lib/bubble-wrap → motion/core}/json.rb
  22. 0  {lib/bubble-wrap → motion/core}/ns_index_path.rb
  23. 0  {lib/bubble-wrap → motion/core}/ns_notification_center.rb
  24. 0  {lib/bubble-wrap → motion/core}/ns_user_defaults.rb
  25. 0  {lib/bubble-wrap → motion/core}/persistence.rb
  26. +1 −1  {lib → motion/core}/pollute.rb
  27. +38 −0 motion/core/string.rb
  28. 0  {lib/bubble-wrap → motion/core}/time.rb
  29. 0  {lib/bubble-wrap → motion/core}/ui_control.rb
  30. 0  {lib/bubble-wrap → motion/core}/ui_view_controller.rb
  31. +249 −0 motion/http.rb
  32. 0  spec/{ → core}/app_spec.rb
  33. 0  spec/{ → core}/device/screen_spec.rb
  34. 0  spec/{ → core}/device_spec.rb
  35. 0  spec/{ → core}/gestures_spec.rb
  36. 0  spec/{ → core}/json_spec.rb
  37. 0  spec/{ → core}/ns_index_path_spec.rb
  38. 0  spec/{ → core}/ns_notification_center_spec.rb
  39. 0  spec/{ → core}/persistence_spec.rb
  40. +69 −0 spec/core/string_spec.rb
  41. 0  spec/{ → core}/time_spec.rb
  42. 0  spec/{ → core}/ui_control_spec.rb
  43. 0  spec/{module_spec.rb → core_spec.rb}
View
41 README.md
@@ -1,6 +1,8 @@
# BubbleWrap for RubyMotion
-A collection of helpers and wrappers used to wrap CocoaTouch code and provide more Ruby like APIs.
+A collection of (tested) helpers and wrappers used to wrap CocoaTouch code and provide more Ruby like APIs.
+
+[BubbleWrap website](http://bubblewrap.io)
## Installation
@@ -10,10 +12,15 @@ gem install bubble-wrap
## Setup
-1. Edit the Rakefile of your RubyMotion project and add the following require line.
+1. Edit the `Rakefile` of your RubyMotion project and add the following require line.
```ruby
require 'bubble-wrap'
```
+
+BubbleWrap is split into multiple submodules so that you can easily choose which parts
+are included at compile-time. You enable them by adding additional requires under the
+line above in your `Rakefile`.
+
Note: **DON'T** use `app.files =` in your Rakefile to set up your files once you've required BubbleWrap.
Make sure to append onto the array or use `+=`.
@@ -28,13 +35,19 @@ class AppDelegate
end
```
-For a more complete list of helper/wrapper descriptions and more details, see the [wiki](https://github.com/mattetti/BubbleWrap/wiki).
+Note: You can also vendor this repository but the recommended way is to
+use the versioned gem.
## HTTP
`BubbleWrap::HTTP` wraps `NSURLRequest`, `NSURLConnection` and friends to provide Ruby developers with a more familiar and easier to use API.
The API uses async calls and blocks to stay as simple as possible.
+To enable it add the following require line to your `Rakefile`:
+```ruby
+require 'bubble-wrap/http'
+```
+
Usage example:
```ruby
@@ -67,6 +80,14 @@ end
`BubbleWrap::JSON` wraps `NSJSONSerialization` available in iOS5 and offers the same API as Ruby's JSON std lib.
+```ruby
+BW::JSON.generate({'foo => 1, 'bar' => [1,2,3], 'baz => 'awesome'})
+=> "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}"
+BW::JSON.parse "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}"
+=> {"foo"=>1, "bar"=>[1, 2, 3], "baz"=>"awesome"}
+```
+
+
## Device
A collection of useful methods about the current device:
@@ -100,11 +121,18 @@ A module with useful methods related to the running application
# creates and shows an alert message.
> App.run_after(0.5) { p "It's #{Time.now}" }
# Runs the block after 0.5 seconds.
+> App::Persistence['channels'] # application specific persistence storage
+# ['NBC', 'ABC', 'Fox', 'CBS', 'PBS']
+> App::Persistence['channels'] = ['TF1', 'France 2', 'France 3']
+# ['TF1', 'France 2', 'France 3']
```
+
+
## NSUserDefaults
-Helper methods added to the class repsonsible for user preferences.
+Helper methods added to the class repsonsible for user preferences used
+by the `App::Persistence` module shown above.
## NSIndexPath
@@ -161,3 +189,8 @@ def reload
notification_center.post ReloadNotification
end
```
+
+Do you have a suggestion for a specific wrapper? Feel free to open an
+issue/ticket and tell us about what you are after. If you have a
+wrapper/helper you are using and are thinking that others might enjoy,
+please send a pull request (with tests if possible).
View
16 Rakefile
@@ -1,6 +1,12 @@
require "bundler/gem_tasks"
$:.unshift("/Library/RubyMotion/lib")
require 'motion/project'
+require File.expand_path '../lib/bubble-wrap', __FILE__
+require File.expand_path '../lib/bubble-wrap/http', __FILE__
+
+task :rspec do
+ sh "rspec lib_spec/"
+end
Motion::Project::App.setup do |app|
app.name = 'testSuite'
@@ -11,9 +17,9 @@ Motion::Project::App.setup do |app|
app.delegate_class = 'TestSuiteDelegate'
end
- app.files += Dir.glob('./lib/bubble-wrap/**/*.rb')
- wrapper_files = app.files.dup
- pollution_file = Dir.glob('./lib/pollute.rb')[0]
- app.files << pollution_file
- app.files_dependencies pollution_file => wrapper_files
+ # app.files += Dir.glob('./lib/bubble-wrap/**/*.rb')
+ # wrapper_files = app.files.dup
+ # pollution_file = Dir.glob('./lib/pollute.rb')[0]
+ # app.files << pollution_file
+ # app.files_dependencies pollution_file => wrapper_files
end
View
15 bubble-wrap.gemspec
@@ -2,15 +2,20 @@
require File.expand_path('../lib/bubble-wrap/version', __FILE__)
Gem::Specification.new do |gem|
- gem.authors = ["Matt Aimonetti", "Francis Chong"]
- gem.email = ["mattaimonetti@gmail.com", "francis@ignition.hk"]
+ gem.authors = ["Matt Aimonetti", "Francis Chong", "James Harton"]
+ gem.email = ["mattaimonetti@gmail.com", "francis@ignition.hk", "james@sociable.co.nz"]
gem.description = "RubyMotion wrappers and helpers (Ruby for iOS) - Making Cocoa APIs more Ruby like, one API at a time. Fork away and send your pull request."
gem.summary = "RubyMotion wrappers and helpers (Ruby for iOS) - Making Cocoa APIs more Ruby like, one API at a time. Fork away and send your pull request."
- gem.homepage = "https://github.com/mattetti/BubbleWrap"
+ gem.homepage = "http://bubblewrap.io/"
gem.files = `git ls-files`.split($\)
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
+ gem.test_files = gem.files.grep(%r{^(test|spec|lib_spec|features)/})
gem.name = "bubble-wrap"
gem.require_paths = ["lib"]
gem.version = BubbleWrap::VERSION
-end
+
+ gem.extra_rdoc_files = gem.files.grep(%r{motion})
+
+ gem.add_development_dependency 'rspec'
+ gem.add_development_dependency 'rake'
+end
View
48 lib/bubble-wrap.rb
@@ -1,39 +1,25 @@
-require "bubble-wrap/version"
-
unless defined?(Motion::Project::Config)
raise "This file must be required within a RubyMotion project Rakefile."
end
-module Motion
- module Project
- class Config
- # HACK NEEDED since RubyMotion doesn't support full path
- # dependencies.
- def files_dependencies(deps_hash)
- res_path = lambda do |x|
- path = /^\.?\//.match(x) ? x : File.join('.', x)
- unless @files.include?(path)
- App.fail "Can't resolve dependency `#{x}' because #{path} is not in #{@files.inspect}"
- end
- path
- end
- deps_hash.each do |path, deps|
- deps = [deps] unless deps.is_a?(Array)
- @dependencies[res_path.call(path)] = deps.map(&res_path)
- end
- end
- end
- end
-end
+require "bubble-wrap/version" unless defined?(BubbleWrap::VERSION)
+require "bubble-wrap/ext"
+require "bubble-wrap/requirement"
+module BubbleWrap
-Motion::Project::App.setup do |app|
- wrapper_files = []
- Dir.glob(File.join(File.dirname(__FILE__), 'bubble-wrap/**/*.rb')).each do |file|
- app.files << file
- wrapper_files << file
+ module_function
+
+ def root
+ File.expand_path('../../', __FILE__)
end
- pollution_file = File.expand_path(File.join(File.dirname(__FILE__), 'pollute.rb'))
- app.files.unshift pollution_file
- app.files_dependencies pollution_file => wrapper_files
+
+ def require(file_spec, &block)
+ Requirement.scan(caller.first, file_spec, &block)
+ end
+
end
+
+BW = BubbleWrap
+
+require "bubble-wrap/core"
View
6 lib/bubble-wrap/core.rb
@@ -0,0 +1,6 @@
+BubbleWrap.require('motion/core.rb')
+BubbleWrap.require('motion/core/**/*.rb') do
+ file('motion/core/device/screen.rb').depends_on 'motion/core/device.rb'
+ file('motion/core/pollute.rb').depends_on 'motion/core/ns_index_path.rb'
+ file('motion/core/pollute.rb').depends_on 'motion/core/ui_control.rb'
+end
View
2  lib/bubble-wrap/ext.rb
@@ -0,0 +1,2 @@
+require File.expand_path('../ext/motion_project_config', __FILE__)
+require File.expand_path('../ext/motion_project_app', __FILE__)
View
26 lib/bubble-wrap/ext/motion_project_app.rb
@@ -0,0 +1,26 @@
+module BubbleWrap
+ module Ext
+ module BuildTask
+
+ def self.extended(base)
+ base.instance_eval do
+ def setup_with_bubblewrap(&block)
+ bw_config = proc do |app|
+ app.files = ::BubbleWrap::Requirement.files + Dir.glob('app/**/*.rb')
+ app.files_dependencies ::BubbleWrap::Requirement.files_dependencies
+ app.frameworks = ::BubbleWrap::Requirement.frameworks
+ block.call(app)
+ end
+ configs.each_value &bw_config
+ config.validate
+ end
+ alias :setup_without_bubblewrap :setup
+ alias :setup :setup_with_bubblewrap
+ end
+ end
+
+ end
+ end
+end
+
+Motion::Project::App.extend(BubbleWrap::Ext::BuildTask)
View
21 lib/bubble-wrap/ext/motion_project_config.rb
@@ -0,0 +1,21 @@
+module Motion
+ module Project
+ class Config
+ # HACK NEEDED since RubyMotion doesn't support full path
+ # dependencies.
+ def files_dependencies(deps_hash)
+ res_path = lambda do |x|
+ path = /^\.?\//.match(x) ? x : File.join('.', x)
+ unless @files.include?(path)
+ App.fail "Can't resolve dependency `#{x}' because #{path} is not in #{@files.inspect}"
+ end
+ path
+ end
+ deps_hash.each do |path, deps|
+ deps = [deps] unless deps.is_a?(Array)
+ @dependencies[res_path.call(path)] = deps.map(&res_path)
+ end
+ end
+ end
+ end
+end
View
250 lib/bubble-wrap/http.rb
@@ -1,249 +1 @@
-module BubbleWrap
-
- SETTINGS = {}
-
- # The HTTP module provides a simple interface to make HTTP requests.
- #
- # TODO: preflight support, easier/better cookie support, better error handling
- module HTTP
-
- # Make a GET request and process the response asynchronously via a block.
- #
- # @examples
- # # Simple GET request printing the body
- # BubbleWrap::HTTP.get("https://api.github.com/users/mattetti") do |response|
- # p response.body.to_str
- # end
- #
- # # GET request with basic auth credentials
- # BubbleWrap::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response|
- # p response.body.to_str # prints the response's body
- # end
- #
- def self.get(url, options={}, &block)
- delegator = block_given? ? block : options.delete(:action)
- HTTP::Query.new( url, :get, options.merge({:action => delegator}) )
- end
-
- # Make a POST request
- def self.post(url, options={}, &block)
- delegator = block_given? ? block : options.delete(:action)
- HTTP::Query.new( url, :post, options.merge({:action => delegator}) )
- end
-
- # Make a PUT request
- def self.put(url, options={}, &block)
- delegator = block_given? ? block : options.delete(:action)
- HTTP::Query.new( url, :put, options.merge({:action => delegator}) )
- end
-
- # Make a DELETE request
- def self.delete(url, options={}, &block)
- delegator = block_given? ? block : options.delete(:action)
- HTTP::Query.new( url, :delete, options.merge({:action => delegator}) )
- end
-
- # Make a HEAD request
- def self.head(url, options={}, &block)
- delegator = block_given? ? block : options.delete(:action)
- HTTP::Query.new( url, :head, options.merge({:action => delegator}) )
- end
-
- # Make a PATCH request
- def self.patch(url, options={}, &block)
- delegator = block_given? ? block : options.delete(:action)
- HTTP::Query.new( url, :patch, options.merge({:action => delegator}) )
- end
-
- # Response class wrapping the results of a Query's response
- class Response
- attr_reader :body
- attr_reader :headers
- attr_accessor :status_code, :error_message
- attr_reader :url
-
- def initialize(values={})
- self.update(values)
- end
-
- def update(values)
- values.each do |k,v|
- self.instance_variable_set("@#{k}", v)
- end
- end
-
- def ok?
- status_code.to_s =~ /20\d/ ? true : false
- end
-
- end
-
- # Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
- class Query
- attr_accessor :request
- attr_accessor :connection
- attr_accessor :credentials # username & password has a hash
- attr_accessor :proxy_credential # credential supplied to proxy servers
- attr_accessor :post_data
- attr_reader :method
-
- attr_reader :response
- attr_reader :status_code
- attr_reader :response_headers
- attr_reader :response_size
- attr_reader :options
-
- # ==== Parameters
- # url<String>:: url of the resource to download
- # http_method<Symbol>:: Value representing the HTTP method to use
- # options<Hash>:: optional options used for the query
- #
- # ==== Options
- # :payload<String> - data to pass to a POST, PUT, DELETE query.
- # :delegator - Proc, class or object to call when the file is downloaded.
- # a proc will receive a Response object while the passed object
- # will receive the handle_query_response method
- # :headers<Hash> - headers send with the request
- # Anything else will be available via the options attribute reader.
- #
- def initialize(url, http_method = :get, options={})
- @method = http_method.upcase.to_s
- @delegator = options.delete(:action) || self
- @payload = options.delete(:payload)
- @credentials = options.delete(:credentials) || {}
- @credentials = {:username => '', :password => ''}.merge(@credentials)
- @timeout = options.delete(:timeout) || 30.0
- headers = options.delete(:headers)
- if headers
- @headers = {}
- headers.each{|k,v| @headers[k] = v.gsub("\n", '\\n') } # escaping LFs
- end
- @cachePolicy = options.delete(:cache_policy) || NSURLRequestUseProtocolCachePolicy
- @options = options
- @response = HTTP::Response.new
- initiate_request(url)
- connection.start
- UIApplication.sharedApplication.networkActivityIndicatorVisible = true
- connection
- end
-
- def generate_get_params(payload, prefix=nil)
- list = []
- payload.each do |k,v|
- if v.is_a?(Hash)
- new_prefix = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
- param = generate_get_params(v, new_prefix)
- else
- param = prefix ? "#{prefix}[#{k}]=#{v}" : "#{k}=#{v}"
- end
- list << param
- end
- return list.flatten
- end
-
- def initiate_request(url_string)
- # http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/nsrunloop_Class/Reference/Reference.html#//apple_ref/doc/constant_group/Run_Loop_Modes
- # NSConnectionReplyMode
-
- unless @payload.nil?
- if @payload.is_a?(Hash)
- params = generate_get_params(@payload)
- @payload = params.join("&")
- end
- url_string = "#{url_string}?#{@payload}" if @method == "GET"
- end
-
- p "BubbleWrap::HTTP building a NSRequest for #{url_string}" if SETTINGS[:debug]
- @url = NSURL.URLWithString(url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding)
- @request = NSMutableURLRequest.requestWithURL(@url,
- cachePolicy:@cachePolicy,
- timeoutInterval:@timeout)
- @request.setHTTPMethod @method
- @request.setAllHTTPHeaderFields(@headers) if @headers
-
- # @payload needs to be converted to data
- unless @method == "GET" || @payload.nil?
- @payload = @payload.to_s.dataUsingEncoding(NSUTF8StringEncoding)
- @request.setHTTPBody @payload
- end
-
- # NSHTTPCookieStorage.sharedHTTPCookieStorage
-
- @connection = create_connection(request, self)
- @request.instance_variable_set("@done_loading", false)
- def @request.done_loading; @done_loading; end
- def @request.done_loading!; @done_loading = true; end
- end
-
- def connection(connection, didReceiveResponse:response)
- @status_code = response.statusCode
- @response_headers = response.allHeaderFields
- @response_size = response.expectedContentLength.to_f
- end
-
- # This delegate method get called every time a chunk of data is being received
- def connection(connection, didReceiveData:received_data)
- @received_data ||= NSMutableData.new
- @received_data.appendData(received_data)
- end
-
- def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
- p "HTTP redirected #{request.description}" if SETTINGS[:debug]
- new_request = request.mutableCopy
- # new_request.setValue(@credentials.inspect, forHTTPHeaderField:'Authorization') # disabled while we figure this one out
- new_request.setAllHTTPHeaderFields(@headers) if @headers
- @connection.cancel
- @connection = create_connection(new_request, self)
- new_request
- end
-
- def connection(connection, didFailWithError: error)
- UIApplication.sharedApplication.networkActivityIndicatorVisible = false
- @request.done_loading!
- NSLog"HTTP Connection failed #{error.localizedDescription}" if SETTINGS[:debug]
- @response.error_message = error.localizedDescription
- call_delegator_with_response
- end
-
-
- # The transfer is done and everything went well
- def connectionDidFinishLoading(connection)
- UIApplication.sharedApplication.networkActivityIndicatorVisible = false
- @request.done_loading!
- # copy the data in a local var that we will attach to the response object
- response_body = NSData.dataWithData(@received_data) if @received_data
- @response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url)
-
- call_delegator_with_response
- end
-
- def connection(connection, didReceiveAuthenticationChallenge:challenge)
-
- if (challenge.previousFailureCount == 0)
- # by default we are keeping the credential for the entire session
- # Eventually, it would be good to let the user pick one of the 3 possible credential persistence options:
- # NSURLCredentialPersistenceNone,
- # NSURLCredentialPersistenceForSession,
- # NSURLCredentialPersistencePermanent
- p "auth challenged, answered with credentials: #{credentials.inspect}" if SETTINGS[:debug]
- new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:NSURLCredentialPersistenceForSession)
- challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
- else
- challenge.sender.cancelAuthenticationChallenge(challenge)
- p 'Auth Failed :('
- end
- end
-
- def call_delegator_with_response
- if @delegator.respond_to?(:call)
- @delegator.call( @response, self )
- end
- end
-
- # This is a temporary method used for mocking.
- def create_connection(request, delegate)
- NSURLConnection.connectionWithRequest(request, delegate:delegate)
- end
- end
- end
-end
+BubbleWrap.require('motion/http.rb')
View
93 lib/bubble-wrap/requirement.rb
@@ -0,0 +1,93 @@
+require File.expand_path "../requirement/path_manipulation", __FILE__
+
+module BubbleWrap
+ class Requirement
+ extend PathManipulation
+ include PathManipulation
+
+ attr_accessor :file, :root
+ attr_writer :file_dependencies
+
+ def initialize(file,root)
+ self.file = file
+ self.root = root
+ end
+
+ def relative
+ convert_to_relative(file, root)
+ end
+
+ def depends_on(file_or_paths)
+ paths = file_or_paths.respond_to?(:each) ? file_or_paths : [ file_or_paths ]
+ self.file_dependencies += paths.map do |f|
+ f = self.class.file(f) unless f.is_a? Requirement
+ f unless f.file == file
+ end.compact
+ self.file_dependencies.uniq!(&:to_s)
+ end
+
+ def uses_framework(framework_name)
+ self.frameworks << framework_name
+ end
+
+ def dependencies
+ return {} if file_dependencies.empty?
+ { file => file_dependencies.map(&:to_s) }
+ end
+
+ def to_s
+ file
+ end
+
+ def file_dependencies
+ @file_dependencies ||= []
+ end
+
+ def frameworks
+ @frameworks ||= []
+ end
+
+ class << self
+
+ attr_accessor :paths, :bw_file
+
+ def scan(caller_location, file_spec, &block)
+ root = convert_caller_to_root_path caller_location
+ self.paths ||= {}
+ Dir.glob(File.expand_path(file_spec, root)).each do |file|
+ p = new(file,root)
+ p.depends_on bw_file
+ self.paths[p.relative] = p
+ end
+ self.class_eval(&block) if block
+ end
+
+ def file(relative)
+ paths.fetch(relative)
+ end
+
+ def files
+ paths.values.map(&:to_s)
+ end
+
+ def files_dependencies
+ deps = {}
+ paths.each_value do |file|
+ deps.merge! file.dependencies
+ end
+ deps
+ end
+
+ def frameworks
+ frameworks = ['UIKit', 'Foundation', 'CoreGraphics'] +
+ paths.values.map(&:frameworks)
+ frameworks.flatten.compact.sort.uniq
+ end
+
+ def bw_file
+ @bw_file ||= new(File.join(::BubbleWrap.root,'motion/core.rb'), ::BubbleWrap.root)
+ end
+
+ end
+ end
+end
View
40 lib/bubble-wrap/requirement/path_manipulation.rb
@@ -0,0 +1,40 @@
+module BubbleWrap
+ class Requirement
+ module PathManipulation
+
+ def convert_caller_to_root_path(path)
+ path = convert_caller_to_path path
+ path = convert_to_absolute_path path
+ strip_up_to_last_lib path
+ end
+
+ def convert_caller_to_path(string)
+ chunks = string.split(':')
+ return chunks[0..-3].join(':') if chunks.size >= 3
+ string
+ end
+
+ def convert_to_absolute_path(path)
+ File.expand_path(path)
+ end
+
+ def strip_up_to_last_lib(path)
+ path = path.split('lib')
+ path = if path.size > 1
+ path[0..-2].join('lib')
+ else
+ path[0]
+ end
+ path = path[0..-2] if path[-1] == '/'
+ path
+ end
+
+ def convert_to_relative(path,root)
+ path = path.gsub(root,'')
+ path = path[1..-1] if path[0] == '/'
+ path
+ end
+
+ end
+ end
+end
View
51 lib_spec/bubble-wrap/requirement/path_manipulation_spec.rb
@@ -0,0 +1,51 @@
+require File.expand_path('../../../../lib/bubble-wrap/requirement/path_manipulation', __FILE__)
+
+describe BubbleWrap::Requirement::PathManipulation do
+
+ subject do
+ o = Object.new
+ o.extend BubbleWrap::Requirement::PathManipulation
+ o
+ end
+
+ describe '#convert_caller_to_path' do
+ it 'strips off from the second-to-last colon' do
+ subject.convert_caller_to_path("/fake/:path:91:in `fake_method'").
+ should == '/fake/:path'
+ end
+
+ it 'leaves plain old paths unmolested' do
+ subject.convert_caller_to_path("/fake/path").
+ should == '/fake/path'
+ end
+ end
+
+ describe '#convert_to_absolute_path' do
+ it 'converts relative paths to absolute paths' do
+ subject.convert_to_absolute_path('foo')[0].should == '/'
+ end
+
+ it "doesn't modify absolute paths" do
+ subject.convert_to_absolute_path('/foo').should == '/foo'
+ end
+ end
+
+ describe '#strip_up_to_last_lib' do
+ it 'strips off from the last lib' do
+ subject.strip_up_to_last_lib('/fake/lib/dir/lib/foo').
+ should == '/fake/lib/dir'
+ end
+
+ it "doesn't modify the path otherwise" do
+ subject.strip_up_to_last_lib('/fake/path').
+ should == '/fake/path'
+ end
+ end
+
+ describe "#convert_to_relative" do
+ it 'strips off the root portion' do
+ subject.convert_to_relative('/foo/bar/baz', '/foo').
+ should == 'bar/baz'
+ end
+ end
+end
View
72 lib_spec/bubble-wrap/requirement_spec.rb
@@ -0,0 +1,72 @@
+require File.expand_path('../../motion_stub', __FILE__)
+require 'bubble-wrap'
+
+describe BubbleWrap::Requirement do
+
+ subject{ BubbleWrap::Requirement }
+
+ describe '.scan' do
+ before do
+ subject.paths = {}
+ end
+
+ let(:root_path) { File.expand_path('../../../', __FILE__) }
+
+ it 'asking for a not-yet-found file raises an exception' do
+ proc do
+ subject.find('foo')
+ end.should raise_error
+ end
+
+ it 'finds the specified file' do
+ subject.scan(root_path, 'motion/core.rb')
+ subject.paths.keys.first.should == 'motion/core.rb'
+ end
+
+ it 'finds multiple files according to spec' do
+ subject.scan(root_path, 'motion/**/*.rb')
+ subject.files.size.should > 1
+ end
+
+ it 'never depends on itself' do
+ subject.scan(root_path, 'motion/core.rb') do
+ file('motion/core.rb').depends_on 'motion/core.rb'
+ end
+ subject.file('motion/core.rb').file_dependencies.should == []
+ end
+
+ it 'can depend on another file' do
+ subject.scan(root_path, 'motion/*.rb') do
+ file('motion/http.rb').depends_on('motion/core.rb')
+ end
+ subject.file('motion/http.rb').file_dependencies.should be_one
+ end
+
+ it 'can use a framework' do
+ subject.scan(root_path, 'motion/core.rb') do
+ file('motion/core.rb').uses_framework('FakeFramework')
+ end
+ subject.file('motion/core.rb').frameworks.should include('FakeFramework')
+ end
+
+ it "figures out the root of the project" do
+ subject.scan(File.join(root_path, 'lib/bubble-wrap.rb'), 'motion/core.rb')
+ subject.paths.values.first.root.should == root_path
+ end
+
+ describe '.frameworks' do
+ it 'includes UIKit by default' do
+ subject.frameworks.should include('UIKit')
+ end
+
+ it 'includes Foundation by default' do
+ subject.frameworks.should include('Foundation')
+ end
+
+ it 'includes CoreGraphics by default' do
+ subject.frameworks.should include('CoreGraphics')
+ end
+ end
+ end
+
+end
View
17 lib_spec/bubble-wrap_spec.rb
@@ -0,0 +1,17 @@
+require File.expand_path('../motion_stub.rb', __FILE__)
+require 'bubble-wrap'
+
+describe BubbleWrap do
+ describe '.root' do
+ it 'returns an absolute path' do
+ BubbleWrap.root[0].should == '/'
+ end
+ end
+
+ describe '.require' do
+ it 'delegates to Requirement.scan' do
+ BW::Requirement.should_receive(:scan)
+ BW.require('foo')
+ end
+ end
+end
View
12 lib_spec/motion_stub.rb
@@ -0,0 +1,12 @@
+# Create a fake Motion class heirarcy for testing.
+module Motion
+ module Project
+ class App
+ def self.setup
+ end
+ end
+
+ class Config
+ end
+ end
+end
View
0  lib/bubble-wrap/module.rb → motion/core.rb
File renamed without changes
View
0  lib/bubble-wrap/app.rb → motion/core/app.rb
File renamed without changes
View
0  lib/bubble-wrap/device.rb → motion/core/device.rb
File renamed without changes
View
0  lib/bubble-wrap/device/screen.rb → motion/core/device/screen.rb
File renamed without changes
View
0  lib/bubble-wrap/gestures.rb → motion/core/gestures.rb
File renamed without changes
View
0  lib/bubble-wrap/json.rb → motion/core/json.rb
File renamed without changes
View
0  lib/bubble-wrap/ns_index_path.rb → motion/core/ns_index_path.rb
File renamed without changes
View
0  lib/bubble-wrap/ns_notification_center.rb → motion/core/ns_notification_center.rb
File renamed without changes
View
0  lib/bubble-wrap/ns_user_defaults.rb → motion/core/ns_user_defaults.rb
File renamed without changes
View
0  lib/bubble-wrap/persistence.rb → motion/core/persistence.rb
File renamed without changes
View
2  lib/pollute.rb → motion/core/pollute.rb
@@ -2,5 +2,5 @@
[NSIndexPath, NSIndexPathWrap],
[UIControl, UIControlWrap]
].each do |base, wrapper|
- base.send(:include, wrapper)
+ base.send(:include, wrapper)
end
View
38 motion/core/string.rb
@@ -0,0 +1,38 @@
+module BubbleWrap
+ # This module contains simplified version of the `camelize` and
+ # `underscore` methods from ActiveSupport, since these are such
+ # common operations when dealing with the Cocoa API.
+ module String
+
+ # Convert 'snake_case' into 'CamelCase'
+ def camelize(uppercase_first_letter = true)
+ string = self.dup
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
+ new_word = $2.downcase
+ new_word[0] = new_word[0].upcase
+ new_word = "/#{new_word}" if $1 == '/'
+ new_word
+ end
+ if uppercase_first_letter
+ string[0] = string[0].upcase
+ else
+ string[0] = string[0].downcase
+ end
+ string.gsub!('/', '::')
+ string
+ end
+
+ # Convert 'CamelCase' into 'snake_case'
+ def underscore
+ word = self.dup
+ word.gsub!(/::/, '/')
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
+ word.tr!("-", "_")
+ word.downcase!
+ word
+ end
+ end
+end
+
+String.send(:include, BubbleWrap::String)
View
0  lib/bubble-wrap/time.rb → motion/core/time.rb
File renamed without changes
View
0  lib/bubble-wrap/ui_control.rb → motion/core/ui_control.rb
File renamed without changes
View
0  lib/bubble-wrap/ui_view_controller.rb → motion/core/ui_view_controller.rb
File renamed without changes
View
249 motion/http.rb
@@ -0,0 +1,249 @@
+module BubbleWrap
+
+ SETTINGS = {}
+
+ # The HTTP module provides a simple interface to make HTTP requests.
+ #
+ # TODO: preflight support, easier/better cookie support, better error handling
+ module HTTP
+
+ # Make a GET request and process the response asynchronously via a block.
+ #
+ # @examples
+ # # Simple GET request printing the body
+ # BubbleWrap::HTTP.get("https://api.github.com/users/mattetti") do |response|
+ # p response.body.to_str
+ # end
+ #
+ # # GET request with basic auth credentials
+ # BubbleWrap::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response|
+ # p response.body.to_str # prints the response's body
+ # end
+ #
+ def self.get(url, options={}, &block)
+ delegator = block_given? ? block : options.delete(:action)
+ HTTP::Query.new( url, :get, options.merge({:action => delegator}) )
+ end
+
+ # Make a POST request
+ def self.post(url, options={}, &block)
+ delegator = block_given? ? block : options.delete(:action)
+ HTTP::Query.new( url, :post, options.merge({:action => delegator}) )
+ end
+
+ # Make a PUT request
+ def self.put(url, options={}, &block)
+ delegator = block_given? ? block : options.delete(:action)
+ HTTP::Query.new( url, :put, options.merge({:action => delegator}) )
+ end
+
+ # Make a DELETE request
+ def self.delete(url, options={}, &block)
+ delegator = block_given? ? block : options.delete(:action)
+ HTTP::Query.new( url, :delete, options.merge({:action => delegator}) )
+ end
+
+ # Make a HEAD request
+ def self.head(url, options={}, &block)
+ delegator = block_given? ? block : options.delete(:action)
+ HTTP::Query.new( url, :head, options.merge({:action => delegator}) )
+ end
+
+ # Make a PATCH request
+ def self.patch(url, options={}, &block)
+ delegator = block_given? ? block : options.delete(:action)
+ HTTP::Query.new( url, :patch, options.merge({:action => delegator}) )
+ end
+
+ # Response class wrapping the results of a Query's response
+ class Response
+ attr_reader :body
+ attr_reader :headers
+ attr_accessor :status_code, :error_message
+ attr_reader :url
+
+ def initialize(values={})
+ self.update(values)
+ end
+
+ def update(values)
+ values.each do |k,v|
+ self.instance_variable_set("@#{k}", v)
+ end
+ end
+
+ def ok?
+ status_code.to_s =~ /20\d/ ? true : false
+ end
+
+ end
+
+ # Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
+ class Query
+ attr_accessor :request
+ attr_accessor :connection
+ attr_accessor :credentials # username & password has a hash
+ attr_accessor :proxy_credential # credential supplied to proxy servers
+ attr_accessor :post_data
+ attr_reader :method
+
+ attr_reader :response
+ attr_reader :status_code
+ attr_reader :response_headers
+ attr_reader :response_size
+ attr_reader :options
+
+ # ==== Parameters
+ # url<String>:: url of the resource to download
+ # http_method<Symbol>:: Value representing the HTTP method to use
+ # options<Hash>:: optional options used for the query
+ #
+ # ==== Options
+ # :payload<String> - data to pass to a POST, PUT, DELETE query.
+ # :delegator - Proc, class or object to call when the file is downloaded.
+ # a proc will receive a Response object while the passed object
+ # will receive the handle_query_response method
+ # :headers<Hash> - headers send with the request
+ # Anything else will be available via the options attribute reader.
+ #
+ def initialize(url, http_method = :get, options={})
+ @method = http_method.upcase.to_s
+ @delegator = options.delete(:action) || self
+ @payload = options.delete(:payload)
+ @credentials = options.delete(:credentials) || {}
+ @credentials = {:username => '', :password => ''}.merge(@credentials)
+ @timeout = options.delete(:timeout) || 30.0
+ headers = options.delete(:headers)
+ if headers
+ @headers = {}
+ headers.each{|k,v| @headers[k] = v.gsub("\n", '\\n') } # escaping LFs
+ end
+ @cachePolicy = options.delete(:cache_policy) || NSURLRequestUseProtocolCachePolicy
+ @options = options
+ @response = HTTP::Response.new
+ initiate_request(url)
+ connection.start
+ UIApplication.sharedApplication.networkActivityIndicatorVisible = true
+ connection
+ end
+
+ def generate_get_params(payload, prefix=nil)
+ list = []
+ payload.each do |k,v|
+ if v.is_a?(Hash)
+ new_prefix = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
+ param = generate_get_params(v, new_prefix)
+ else
+ param = prefix ? "#{prefix}[#{k}]=#{v}" : "#{k}=#{v}"
+ end
+ list << param
+ end
+ return list.flatten
+ end
+
+ def initiate_request(url_string)
+ # http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/nsrunloop_Class/Reference/Reference.html#//apple_ref/doc/constant_group/Run_Loop_Modes
+ # NSConnectionReplyMode
+
+ unless @payload.nil?
+ if @payload.is_a?(Hash)
+ params = generate_get_params(@payload)
+ @payload = params.join("&")
+ end
+ url_string = "#{url_string}?#{@payload}" if @method == "GET"
+ end
+
+ p "BubbleWrap::HTTP building a NSRequest for #{url_string}" if SETTINGS[:debug]
+ @url = NSURL.URLWithString(url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding)
+ @request = NSMutableURLRequest.requestWithURL(@url,
+ cachePolicy:@cachePolicy,
+ timeoutInterval:@timeout)
+ @request.setHTTPMethod @method
+ @request.setAllHTTPHeaderFields(@headers) if @headers
+
+ # @payload needs to be converted to data
+ unless @method == "GET" || @payload.nil?
+ @payload = @payload.to_s.dataUsingEncoding(NSUTF8StringEncoding)
+ @request.setHTTPBody @payload
+ end
+
+ # NSHTTPCookieStorage.sharedHTTPCookieStorage
+
+ @connection = create_connection(request, self)
+ @request.instance_variable_set("@done_loading", false)
+ def @request.done_loading; @done_loading; end
+ def @request.done_loading!; @done_loading = true; end
+ end
+
+ def connection(connection, didReceiveResponse:response)
+ @status_code = response.statusCode
+ @response_headers = response.allHeaderFields
+ @response_size = response.expectedContentLength.to_f
+ end
+
+ # This delegate method get called every time a chunk of data is being received
+ def connection(connection, didReceiveData:received_data)
+ @received_data ||= NSMutableData.new
+ @received_data.appendData(received_data)
+ end
+
+ def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
+ p "HTTP redirected #{request.description}" if SETTINGS[:debug]
+ new_request = request.mutableCopy
+ # new_request.setValue(@credentials.inspect, forHTTPHeaderField:'Authorization') # disabled while we figure this one out
+ new_request.setAllHTTPHeaderFields(@headers) if @headers
+ @connection.cancel
+ @connection = create_connection(new_request, self)
+ new_request
+ end
+
+ def connection(connection, didFailWithError: error)
+ UIApplication.sharedApplication.networkActivityIndicatorVisible = false
+ @request.done_loading!
+ NSLog"HTTP Connection failed #{error.localizedDescription}" if SETTINGS[:debug]
+ @response.error_message = error.localizedDescription
+ call_delegator_with_response
+ end
+
+
+ # The transfer is done and everything went well
+ def connectionDidFinishLoading(connection)
+ UIApplication.sharedApplication.networkActivityIndicatorVisible = false
+ @request.done_loading!
+ # copy the data in a local var that we will attach to the response object
+ response_body = NSData.dataWithData(@received_data) if @received_data
+ @response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url)
+
+ call_delegator_with_response
+ end
+
+ def connection(connection, didReceiveAuthenticationChallenge:challenge)
+
+ if (challenge.previousFailureCount == 0)
+ # by default we are keeping the credential for the entire session
+ # Eventually, it would be good to let the user pick one of the 3 possible credential persistence options:
+ # NSURLCredentialPersistenceNone,
+ # NSURLCredentialPersistenceForSession,
+ # NSURLCredentialPersistencePermanent
+ p "auth challenged, answered with credentials: #{credentials.inspect}" if SETTINGS[:debug]
+ new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:NSURLCredentialPersistenceForSession)
+ challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
+ else
+ challenge.sender.cancelAuthenticationChallenge(challenge)
+ p 'Auth Failed :('
+ end
+ end
+
+ def call_delegator_with_response
+ if @delegator.respond_to?(:call)
+ @delegator.call( @response, self )
+ end
+ end
+
+ # This is a temporary method used for mocking.
+ def create_connection(request, delegate)
+ NSURLConnection.connectionWithRequest(request, delegate:delegate)
+ end
+ end
+ end
+end
View
0  spec/app_spec.rb → spec/core/app_spec.rb
File renamed without changes
View
0  spec/device/screen_spec.rb → spec/core/device/screen_spec.rb
File renamed without changes
View
0  spec/device_spec.rb → spec/core/device_spec.rb
File renamed without changes
View
0  spec/gestures_spec.rb → spec/core/gestures_spec.rb
File renamed without changes
View
0  spec/json_spec.rb → spec/core/json_spec.rb
File renamed without changes
View
0  spec/ns_index_path_spec.rb → spec/core/ns_index_path_spec.rb
File renamed without changes
View
0  spec/ns_notification_center_spec.rb → spec/core/ns_notification_center_spec.rb
File renamed without changes
View
0  spec/persistence_spec.rb → spec/core/persistence_spec.rb
File renamed without changes
View
69 spec/core/string_spec.rb
@@ -0,0 +1,69 @@
+describe BubbleWrap::String do
+
+ describe ::String do
+ it 'should include BubbleWrap::String' do
+ ::String.ancestors.member?(BubbleWrap::String).should == true
+ end
+ end
+
+ describe 'CamelCase input' do
+ describe '.camelize(true)' do
+ it "doesn't change the value" do
+ 'CamelCase'.camelize(true).should == 'CamelCase'
+ end
+ end
+
+ describe '.camelize(false)' do
+ it 'lower cases the first character' do
+ 'CamelCase'.camelize(false).should == 'camelCase'
+ end
+ end
+
+ describe '.underscore' do
+ it 'converts it to underscores' do
+ 'CamelCase'.underscore.should == 'camel_case'
+ end
+ end
+ end
+
+ describe 'camelCase input' do
+ describe '.camelize(true)' do
+ it "upper cases the first character" do
+ 'camelCase'.camelize(true).should == 'CamelCase'
+ end
+ end
+
+ describe '.camelize(false)' do
+ it "doesn't change the value" do
+ 'camelCase'.camelize(false).should == 'camelCase'
+ end
+ end
+
+ describe '.underscore' do
+ it 'converts it to underscores' do
+ 'camelCase'.underscore.should == 'camel_case'
+ end
+ end
+ end
+
+ describe 'snake_case input' do
+ describe '.camelize(true)' do
+ it 'converts to CamelCase' do
+ 'snake_case'.camelize(true).should == 'SnakeCase'
+ end
+ end
+
+ describe '.camelize(false)' do
+ it 'converts to camelCase' do
+ 'snake_case'.camelize(false).should == 'snakeCase'
+ end
+ end
+
+ describe '.underscore' do
+ it "doesn't change the value" do
+ 'snake_case'.underscore.should == 'snake_case'
+ end
+ end
+ end
+
+end
View
0  spec/time_spec.rb → spec/core/time_spec.rb
File renamed without changes
View
0  spec/ui_control_spec.rb → spec/core/ui_control_spec.rb
File renamed without changes
View
0  spec/module_spec.rb → spec/core_spec.rb
File renamed without changes
Please sign in to comment.
Something went wrong with that request. Please try again.