elevate 0.3

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.
@@ -0,0 +1,100 @@
1
+ module Elevate
2
+ module HTTP
3
+ class HTTPClient
4
+ def initialize(base_url)
5
+ @base_url = NSURL.URLWithString(base_url)
6
+ @credentials = nil
7
+ end
8
+
9
+ def get(path, query={}, &block)
10
+ issue(:GET, path, nil, query: query, &block)
11
+ end
12
+
13
+ def post(path, body, &block)
14
+ issue(:post, path, body, &block)
15
+ end
16
+
17
+ def put(path, body, &block)
18
+ issue(:put, path, body, &block)
19
+ end
20
+
21
+ def delete(path, &block)
22
+ issue(:delete, path, nil, &block)
23
+ end
24
+
25
+ def set_credentials(username, password)
26
+ @credentials = { username: username, password: password }
27
+ end
28
+
29
+ private
30
+
31
+ def issue(method, path, body, options={}, &block)
32
+ url = url_for(path)
33
+
34
+ options[:headers] ||= {}
35
+ options[:headers]["Accept"] = "application/json"
36
+
37
+ if @credentials
38
+ options[:credentials] = @credentials
39
+ end
40
+
41
+ if body
42
+ options[:body] = NSJSONSerialization.dataWithJSONObject(body, options:0, error:nil)
43
+ options[:headers]["Content-Type"] = "application/json"
44
+ end
45
+
46
+ request = HTTPRequest.new(method, url, options)
47
+
48
+ coordinator = IOCoordinator.for_thread
49
+
50
+ coordinator.signal_blocked(request) if coordinator
51
+ response = JSONHTTPResponse.new(request.response)
52
+ coordinator.signal_unblocked(request) if coordinator
53
+
54
+ if response.error == nil && block_given?
55
+ result = yield response.body
56
+ puts result.inspect
57
+
58
+ result
59
+ else
60
+ response
61
+ end
62
+ end
63
+
64
+ def url_for(path)
65
+ path = CFURLCreateStringByAddingPercentEscapes(nil, path.to_s, "[]", ";=&,", KCFStringEncodingUTF8)
66
+
67
+ NSURL.URLWithString(path, relativeToURL:@base_url).absoluteString
68
+ end
69
+ end
70
+
71
+ class JSONHTTPResponse
72
+ def initialize(response)
73
+ @response = response
74
+ @body = decode(response.body)
75
+ end
76
+
77
+ def decode(data)
78
+ return nil if data.nil?
79
+
80
+ NSJSONSerialization.JSONObjectWithData(data, options:0, error:nil)
81
+ end
82
+
83
+ attr_reader :body
84
+
85
+ # TODO: delegate
86
+ def error
87
+ @response.error
88
+ end
89
+
90
+ def headers
91
+ @response.headers
92
+ end
93
+
94
+ def status_code
95
+ @response.status_code
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,95 @@
1
+ module Elevate
2
+ module HTTP
3
+ # TODO: redirects
4
+ class HTTPRequest
5
+ METHODS = [:get, :post, :put, :delete, :patch, :head, :options].freeze
6
+ QUEUE = NSOperationQueue.alloc.init
7
+
8
+ def initialize(method, url, options={})
9
+ raise ArgumentError, "invalid HTTP method" unless METHODS.include? method.downcase
10
+ raise ArgumentError, "invalid URL" unless url.start_with? "http"
11
+ raise ArgumentError, "invalid body type; must be NSData" if options[:body] && ! options[:body].is_a?(NSData)
12
+
13
+ unless options.fetch(:query, {}).empty?
14
+ url += "?" + URI.encode_query(options[:query])
15
+ end
16
+
17
+ @request = NSMutableURLRequest.alloc.init
18
+ @request.CachePolicy = NSURLRequestReloadIgnoringLocalCacheData
19
+ @request.HTTPBody = options[:body]
20
+ @request.HTTPMethod = method
21
+ @request.URL = NSURL.URLWithString(url)
22
+
23
+ headers = options.fetch(:headers, {})
24
+
25
+ if credentials = options[:credentials]
26
+ headers["Authorization"] = get_authorization_header(credentials)
27
+ end
28
+
29
+ headers.each do |key, value|
30
+ @request.setValue(value.to_s, forHTTPHeaderField:key.to_s)
31
+ end
32
+
33
+ @response = HTTPResponse.new
34
+
35
+ @connection = nil
36
+ @promise = Promise.new
37
+ end
38
+
39
+ def cancel
40
+ return unless started?
41
+
42
+ @connection.cancel()
43
+ @promise.set(nil)
44
+ end
45
+
46
+ def response
47
+ unless started?
48
+ start()
49
+ end
50
+
51
+ @promise.get()
52
+ end
53
+
54
+ def start
55
+ @connection = NSURLConnection.alloc.initWithRequest(@request, delegate:self, startImmediately:false)
56
+ @connection.setDelegateQueue(QUEUE)
57
+ @connection.start()
58
+ end
59
+
60
+ def started?
61
+ @connection != nil
62
+ end
63
+
64
+ private
65
+
66
+ def connection(connection, didReceiveResponse: response)
67
+ @response.headers = response.allHeaderFields
68
+ @response.status_code = response.statusCode
69
+ end
70
+
71
+ def connection(connection, didReceiveData: data)
72
+ @response.append_data(data)
73
+ end
74
+
75
+ def connection(connection, didFailWithError: error)
76
+ puts "ERROR: #{error.localizedDescription}"
77
+
78
+ @response.error = error
79
+ @response.freeze
80
+
81
+ @promise.set(@response)
82
+ end
83
+
84
+ def connectionDidFinishLoading(connection)
85
+ @response.freeze
86
+
87
+ @promise.set(@response)
88
+ end
89
+
90
+ def get_authorization_header(credentials)
91
+ "Basic " + Base64.encode("#{credentials[:username]}:#{credentials[:password]}")
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ module Elevate
2
+ module HTTP
3
+ class HTTPResponse
4
+ def initialize
5
+ @body = nil
6
+ @headers = nil
7
+ @status_code = nil
8
+ @error = nil
9
+ end
10
+
11
+ def append_data(data)
12
+ @body ||= NSMutableData.alloc.init
13
+ @body.appendData(data)
14
+ end
15
+
16
+ attr_reader :body
17
+ attr_accessor :headers
18
+ attr_accessor :status_code
19
+ attr_accessor :error
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Elevate
2
+ module HTTP
3
+ module URI
4
+ def self.encode_query(hash)
5
+ return "" if hash.nil? || hash.empty?
6
+
7
+ hash.map do |key, value|
8
+ "#{URI.escape_query_component(key.to_s)}=#{URI.escape_query_component(value.to_s)}"
9
+ end.join("&")
10
+ end
11
+
12
+ def self.escape_query_component(component)
13
+ component.gsub(/([^ a-zA-Z0-9_.-]+)/) do
14
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
15
+ end.tr(' ', '+')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ module Elevate
2
+ class IOCoordinator
3
+ def self.for_thread
4
+ Thread.current[:io_coordinator]
5
+ end
6
+
7
+ def initialize
8
+ @lock = NSLock.alloc.init
9
+ @blocking_operation = nil
10
+ @cancelled = false
11
+ end
12
+
13
+ def cancel
14
+ blocking_operation = nil
15
+
16
+ @lock.lock()
17
+ @cancelled = true
18
+ blocking_operation = @blocking_operation
19
+ @lock.unlock()
20
+
21
+ if blocking_operation
22
+ blocking_operation.cancel()
23
+ end
24
+ end
25
+
26
+ def cancelled?
27
+ cancelled = nil
28
+
29
+ @lock.lock()
30
+ cancelled = @cancelled
31
+ @lock.unlock()
32
+
33
+ cancelled
34
+ end
35
+
36
+ def install
37
+ Thread.current[:io_coordinator] = self
38
+ end
39
+
40
+ def signal_blocked(operation)
41
+ check_for_cancellation
42
+
43
+ @lock.lock()
44
+ @blocking_operation = operation
45
+ @lock.unlock()
46
+ end
47
+
48
+ def signal_unblocked(operation)
49
+ @lock.lock()
50
+ @blocking_operation = nil
51
+ @lock.unlock()
52
+
53
+ check_for_cancellation
54
+ end
55
+
56
+ def uninstall
57
+ Thread.current[:io_coordinator] = nil
58
+ end
59
+
60
+ private
61
+
62
+ def check_for_cancellation
63
+ raise CancelledError if cancelled?
64
+ end
65
+ end
66
+
67
+ class CancelledError < StandardError
68
+ end
69
+ end
@@ -0,0 +1,74 @@
1
+ module Elevate
2
+ class ElevateOperation < NSOperation
3
+ def initWithTarget(target)
4
+ if init()
5
+ @target = target
6
+ @coordinator = IOCoordinator.new
7
+ @dispatcher = Dispatcher.new
8
+
9
+ setCompletionBlock(lambda do
10
+ @target = nil
11
+
12
+ @dispatcher.invoke_finished_callback() unless isCancelled()
13
+ @dispatcher.dispose()
14
+ end)
15
+ end
16
+
17
+ self
18
+ end
19
+
20
+ def cancel
21
+ @coordinator.cancel()
22
+
23
+ super
24
+ end
25
+
26
+ def dealloc
27
+ #puts 'dealloc!'
28
+
29
+ super
30
+ end
31
+
32
+ def inspect
33
+ details = []
34
+ details << "<canceled>" if @coordinator.cancelled?
35
+ details << "@target=#{@target.class.name}"
36
+
37
+ "#<#{self.class.name}: #{details.join(" ")}>"
38
+ end
39
+
40
+ def log(line)
41
+ puts line unless RUBYMOTION_ENV == "test"
42
+ end
43
+
44
+ def main
45
+ log " START: #{inspect}"
46
+
47
+ @coordinator.install()
48
+
49
+ begin
50
+ unless @coordinator.cancelled?
51
+ @result = @target.execute
52
+ end
53
+
54
+ rescue => e
55
+ @exception = e
56
+ end
57
+
58
+ @coordinator.uninstall()
59
+
60
+ log "FINISH: #{inspect}"
61
+ end
62
+
63
+ attr_reader :exception
64
+ attr_reader :result
65
+
66
+ def on_started=(callback)
67
+ @dispatcher.on_started = callback
68
+ end
69
+
70
+ def on_finished=(callback)
71
+ @dispatcher.on_finished = callback
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,27 @@
1
+ module Elevate
2
+ class Promise
3
+ OUTSTANDING = 0
4
+ FULFILLED = 1
5
+
6
+ def initialize
7
+ @lock = NSConditionLock.alloc.initWithCondition(OUTSTANDING)
8
+ @result = nil
9
+ end
10
+
11
+ def get
12
+ result = nil
13
+
14
+ @lock.lockWhenCondition(FULFILLED)
15
+ result = @result
16
+ @lock.unlockWithCondition(FULFILLED)
17
+
18
+ result
19
+ end
20
+
21
+ def set(result)
22
+ @lock.lockWhenCondition(OUTSTANDING)
23
+ @result = result
24
+ @lock.unlockWithCondition(FULFILLED)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module Elevate
2
+ VERSION = "0.3"
3
+ end
@@ -0,0 +1,23 @@
1
+ module Bacon
2
+ class Context
3
+ include ::Elevate
4
+ end
5
+ end
6
+
7
+ describe Elevate do
8
+ describe "#async" do
9
+ it "runs the specified interactor asynchronously" do
10
+
11
+ async Target.new() do
12
+ on_completed do |operation|
13
+ @called = true
14
+ resume
15
+ end
16
+ end
17
+
18
+ wait_max 1.0 do
19
+ @called.should.be.true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ describe Elevate::Callback do
2
+ describe "#call" do
3
+ it "invokes the block using instance_*" do
4
+ callback = Elevate::Callback.new(self, 42, lambda { |v| @value = v })
5
+ callback.call
6
+
7
+ @value.should == 42
8
+ end
9
+
10
+ end
11
+ end
12
+