mr_darcy 0.1.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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +95 -0
  6. data/Guardfile +14 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +267 -0
  9. data/Rakefile +6 -0
  10. data/lib/mr_darcy/context.rb +71 -0
  11. data/lib/mr_darcy/deferred.rb +25 -0
  12. data/lib/mr_darcy/drivers/celluloid.rb +23 -0
  13. data/lib/mr_darcy/drivers/synchronous.rb +15 -0
  14. data/lib/mr_darcy/drivers/thread.rb +22 -0
  15. data/lib/mr_darcy/drivers.rb +13 -0
  16. data/lib/mr_darcy/promise/base.rb +117 -0
  17. data/lib/mr_darcy/promise/celluloid.rb +52 -0
  18. data/lib/mr_darcy/promise/child_promise.rb +83 -0
  19. data/lib/mr_darcy/promise/dsl.rb +29 -0
  20. data/lib/mr_darcy/promise/em.rb +29 -0
  21. data/lib/mr_darcy/promise/state/base.rb +43 -0
  22. data/lib/mr_darcy/promise/state/rejected.rb +11 -0
  23. data/lib/mr_darcy/promise/state/resolved.rb +11 -0
  24. data/lib/mr_darcy/promise/state/unresolved.rb +19 -0
  25. data/lib/mr_darcy/promise/state.rb +25 -0
  26. data/lib/mr_darcy/promise/synchronous.rb +27 -0
  27. data/lib/mr_darcy/promise/thread.rb +64 -0
  28. data/lib/mr_darcy/promise.rb +31 -0
  29. data/lib/mr_darcy/role.rb +45 -0
  30. data/lib/mr_darcy/version.rb +3 -0
  31. data/lib/mr_darcy.rb +26 -0
  32. data/mr_darcy.gemspec +29 -0
  33. data/spec/acceptance/dci_bank_transfer_spec.rb +62 -0
  34. data/spec/acceptance/em_http_request_spec.rb +22 -0
  35. data/spec/acceptance/open-uri_http_request_spec.rb +21 -0
  36. data/spec/acceptance/simple_promise_spec.rb +25 -0
  37. data/spec/acceptance/simple_promise_with_chained_fail.rb +25 -0
  38. data/spec/acceptance/simple_promise_with_fail_spec.rb +25 -0
  39. data/spec/acceptance/simple_promise_with_then_spec.rb +25 -0
  40. data/spec/lib/mr_darcy/context_spec.rb +22 -0
  41. data/spec/lib/mr_darcy/promise/base_spec.rb +197 -0
  42. data/spec/lib/mr_darcy/promise/child_promise_spec.rb +169 -0
  43. data/spec/lib/mr_darcy/promise/dsl_spec.rb +43 -0
  44. data/spec/lib/mr_darcy/promise/state/base_spec.rb +24 -0
  45. data/spec/lib/mr_darcy/promise/state/rejected_spec.rb +12 -0
  46. data/spec/lib/mr_darcy/promise/state/resolved_spec.rb +12 -0
  47. data/spec/lib/mr_darcy/promise/state/unresolved_spec.rb +22 -0
  48. data/spec/lib/mr_darcy/promise/state_spec.rb +30 -0
  49. data/spec/lib/mr_darcy/promise/synchronous_spec.rb +21 -0
  50. data/spec/lib/mr_darcy/promise_spec.rb +72 -0
  51. data/spec/lib/mr_darcy/role_spec.rb +89 -0
  52. data/spec/lib/mr_darcy_spec.rb +19 -0
  53. data/spec/spec_helper.rb +10 -0
  54. data/spec/support/context_helpers.rb +19 -0
  55. data/spec/support/shared_examples_for_promise.rb +47 -0
  56. data/spec/support/shared_examples_for_thennable.rb +10 -0
  57. metadata +279 -0
@@ -0,0 +1,117 @@
1
+ module MrDarcy
2
+ module Promise
3
+ class Base
4
+
5
+ def initialize block
6
+ state
7
+ schedule_promise do
8
+ evaluate_promise &block
9
+ end
10
+ end
11
+
12
+ def then &block
13
+ @child_promise ||= generate_child_promise
14
+ child_promise.resolve_block = block
15
+ resolve_child_promise if resolved?
16
+ reject_child_promise if rejected?
17
+ child_promise.promise
18
+ end
19
+
20
+ def fail &block
21
+ @child_promise ||= generate_child_promise
22
+ child_promise.reject_block = block
23
+ resolve_child_promise if resolved?
24
+ reject_child_promise if rejected?
25
+ child_promise.promise
26
+ end
27
+
28
+ def result
29
+ Kernel::raise "Subclasses must implement me"
30
+ end
31
+
32
+ def final
33
+ Kernel::raise "Subclasses must implement me"
34
+ end
35
+
36
+ def raise
37
+ r = result
38
+ Kernel::raise r if rejected?
39
+ end
40
+
41
+ %w| resolved? unresolved? rejected? |.map(&:to_sym).each do |method|
42
+ define_method method do |*args|
43
+ state_machine.public_send(method, *args)
44
+ end
45
+ end
46
+
47
+ def resolve value
48
+ set_value_to value
49
+ state_machine_resolve
50
+ resolve_child_promise
51
+ end
52
+
53
+ def reject exception
54
+ set_value_to exception
55
+ state_machine_reject
56
+ reject_child_promise
57
+ end
58
+
59
+ private
60
+
61
+ attr_accessor :value, :child_promise, :state
62
+
63
+ def state
64
+ @state ||= :unresolved
65
+ end
66
+
67
+ def set_value_to value
68
+ @value = value
69
+ end
70
+
71
+ def state_machine_resolve
72
+ state_machine.resolve
73
+ end
74
+
75
+ def state_machine_reject
76
+ state_machine.reject
77
+ end
78
+
79
+ def state_machine
80
+ State.state(self)
81
+ end
82
+
83
+ def has_child_promise?
84
+ !!child_promise
85
+ end
86
+
87
+ def resolve_child_promise
88
+ schedule_promise do
89
+ child_promise.parent_resolved(value) if has_child_promise?
90
+ end
91
+ end
92
+
93
+ def reject_child_promise
94
+ schedule_promise do
95
+ child_promise.parent_rejected(value) if has_child_promise?
96
+ end
97
+ end
98
+
99
+ def schedule_promise
100
+ Kernel::raise "Subclasses must implement me"
101
+ end
102
+
103
+ def evaluate_promise &block
104
+ begin
105
+ block.call DSL.new(self)
106
+ rescue Exception => e
107
+ reject e
108
+ end
109
+ end
110
+
111
+ def generate_child_promise
112
+ Kernel::raise "Subclasses must implement me"
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,52 @@
1
+ require 'celluloid/autostart'
2
+
3
+ module MrDarcy
4
+ module Promise
5
+ class Celluloid < Base
6
+
7
+ def initialize *args
8
+ @waiting = false
9
+ super
10
+ end
11
+
12
+ def resolve value
13
+ super
14
+ condition.signal if @waiting
15
+ end
16
+
17
+ def reject exception
18
+ super
19
+ condition.signal if @waiting
20
+ end
21
+
22
+ def result
23
+ wait_until_not_unresolved
24
+ value
25
+ end
26
+
27
+ def final
28
+ wait_until_not_unresolved
29
+ self
30
+ end
31
+
32
+ private
33
+
34
+ def schedule_promise &block
35
+ ::Celluloid::Future.new &block
36
+ end
37
+
38
+ def condition
39
+ @condition ||= ::Celluloid::Condition.new
40
+ end
41
+
42
+ def wait_until_not_unresolved
43
+ @waiting = true
44
+ condition.wait if unresolved?
45
+ end
46
+
47
+ def generate_child_promise
48
+ ChildPromise.new driver: :thread
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,83 @@
1
+ module MrDarcy
2
+ module Promise
3
+ class ChildPromise < MrDarcy::Deferred
4
+
5
+ attr_accessor :resolve_block, :reject_block
6
+
7
+ def initialize opts={}
8
+ @driver = opts[:driver] if opts.has_key? :driver
9
+ super
10
+ end
11
+
12
+ def parent_resolved value
13
+ begin
14
+ return resolve_with value unless handles_resolve?
15
+ new_value = result_for :resolve, value
16
+ if thenable? new_value
17
+ defer_resolution_via new_value
18
+ else
19
+ resolve_with new_value
20
+ end
21
+ rescue Exception => e
22
+ reject_with e
23
+ end
24
+ end
25
+
26
+ def parent_rejected value
27
+ begin
28
+ return reject_with value unless handles_reject?
29
+ new_value = result_for :reject, value
30
+ if thenable? new_value
31
+ defer_resolution_via new_value
32
+ else
33
+ resolve_with new_value
34
+ end
35
+ rescue Exception => e
36
+ reject_with e
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def result_for which, value
43
+ block = public_send("#{which}_block")
44
+ if block
45
+ block.call value
46
+ else
47
+ value
48
+ end
49
+ end
50
+
51
+ def handles_reject?
52
+ !!reject_block
53
+ end
54
+
55
+ def handles_resolve?
56
+ !!resolve_block
57
+ end
58
+
59
+ def thenable? object
60
+ object.respond_to?(:then) && object.respond_to?(:fail)
61
+ end
62
+
63
+ def defer_resolution_via child_promise
64
+ child_promise.then do |value|
65
+ resolve_with value
66
+ value
67
+ end
68
+ child_promise.fail do |exception|
69
+ reject_with exception
70
+ exception
71
+ end
72
+ end
73
+
74
+ def resolve_with value
75
+ promise.resolve value
76
+ end
77
+
78
+ def reject_with exception
79
+ promise.reject exception
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,29 @@
1
+ module MrDarcy
2
+ module Promise
3
+ class DSL
4
+
5
+ def initialize promise
6
+ @promise = promise
7
+ end
8
+
9
+ def resolve(value)
10
+ promise.resolve value
11
+ end
12
+
13
+ def reject(exception)
14
+ promise.reject exception
15
+ end
16
+
17
+ %w| unresolved? resolved? rejected? then fail result final |.map(&:to_sym).each do |method|
18
+ define_method method do |*args|
19
+ promise.public_send method, *args
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_accessor :promise
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require 'eventmachine'
2
+
3
+ module MrDarcy
4
+ module Promise
5
+ class EM < Thread
6
+
7
+ def initialize *args
8
+ unless EventMachine.reactor_running?
9
+ ::Thread.new { EventMachine.run }
10
+ ::Thread.pass until EventMachine.reactor_running?
11
+ end
12
+ super
13
+ end
14
+
15
+ private
16
+
17
+ def schedule_promise
18
+ EventMachine.schedule proc do
19
+ yield
20
+ end
21
+ end
22
+
23
+ def generate_child_promise
24
+ ChildPromise.new driver: :em
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,43 @@
1
+ module MrDarcy
2
+ module Promise
3
+ module State
4
+ class Base
5
+ attr_accessor :stateful
6
+
7
+ def initialize stateful
8
+ self.stateful = stateful
9
+ end
10
+
11
+ def unresolved?
12
+ false
13
+ end
14
+
15
+ def resolved?
16
+ false
17
+ end
18
+
19
+ def rejected?
20
+ false
21
+ end
22
+
23
+ def resolve
24
+ raise "Can't resolve from #{get_state} state"
25
+ end
26
+
27
+ def reject
28
+ raise "Cant reject from #{get_state} state"
29
+ end
30
+
31
+ private
32
+
33
+ def get_state
34
+ stateful.send :state
35
+ end
36
+
37
+ def set_state state
38
+ stateful.send :state=, state
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ module MrDarcy
2
+ module Promise
3
+ module State
4
+ class Rejected < Base
5
+ def rejected?
6
+ true
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module MrDarcy
2
+ module Promise
3
+ module State
4
+ class Resolved < Base
5
+ def resolved?
6
+ true
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ module MrDarcy
2
+ module Promise
3
+ module State
4
+ class Unresolved < Base
5
+ def unresolved?
6
+ true
7
+ end
8
+
9
+ def resolve
10
+ set_state :resolved
11
+ end
12
+
13
+ def reject
14
+ set_state :rejected
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module MrDarcy
2
+ module Promise
3
+ module State
4
+ autoload :Base, File.expand_path('../state/base', __FILE__)
5
+ autoload :Unresolved, File.expand_path('../state/unresolved', __FILE__)
6
+ autoload :Resolved, File.expand_path('../state/resolved', __FILE__)
7
+ autoload :Rejected, File.expand_path('../state/rejected', __FILE__)
8
+
9
+ module_function
10
+
11
+ def state stateful
12
+ case stateful.send :state
13
+ when :unresolved
14
+ Unresolved.new stateful
15
+ when :resolved
16
+ Resolved.new stateful
17
+ when :rejected
18
+ Rejected.new stateful
19
+ else
20
+ raise "Unknown state #{stateful.state}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # This class implements a synchronous interface to promise execution.
2
+ # It's not much use, except for unit testing.
3
+
4
+ module MrDarcy
5
+ module Promise
6
+ class Synchronous < Base
7
+
8
+ def result
9
+ value
10
+ end
11
+
12
+ def final
13
+ self
14
+ end
15
+
16
+ private
17
+
18
+ def schedule_promise
19
+ yield
20
+ end
21
+
22
+ def generate_child_promise
23
+ ChildPromise.new driver: :synchronous
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ module MrDarcy
2
+ module Promise
3
+ class Thread < Base
4
+
5
+ def initialize *args
6
+ @wait_lock = Mutex.new
7
+ @wait_cond = ConditionVariable.new
8
+ @wait_lock.lock
9
+ super
10
+ end
11
+
12
+ def result
13
+ wait_if_unresolved
14
+ value
15
+ end
16
+
17
+ def final
18
+ wait_if_unresolved
19
+ self
20
+ end
21
+
22
+ def resolve value
23
+ super
24
+ @wait_cond.signal
25
+ end
26
+
27
+ def reject value
28
+ super
29
+ @wait_cond.signal
30
+ end
31
+
32
+ private
33
+
34
+ def schedule_promise &block
35
+ ::Thread.new &block
36
+ end
37
+
38
+ def wait_if_unresolved
39
+ @wait_cond.wait @wait_lock if unresolved?
40
+ end
41
+
42
+ def semaphore
43
+ @semaphore ||= Mutex.new
44
+ end
45
+
46
+ def generate_child_promise
47
+ ChildPromise.new driver: :thread
48
+ end
49
+
50
+ def set_value_to value
51
+ semaphore.synchronize { super }
52
+ end
53
+
54
+ def state_machine_resolve
55
+ semaphore.synchronize { super }
56
+ end
57
+
58
+ def state_machine_reject
59
+ semaphore.synchronize { super }
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ module MrDarcy
2
+ module Promise
3
+ autoload :State, File.expand_path('../promise/state', __FILE__)
4
+ autoload :Base, File.expand_path('../promise/base', __FILE__)
5
+ autoload :DSL, File.expand_path('../promise/dsl', __FILE__)
6
+ autoload :ChildPromise, File.expand_path('../promise/child_promise', __FILE__)
7
+
8
+ autoload :Synchronous, File.expand_path('../promise/synchronous', __FILE__)
9
+ autoload :Thread, File.expand_path('../promise/thread', __FILE__)
10
+ autoload :Celluloid, File.expand_path('../promise/celluloid', __FILE__)
11
+ autoload :EM, File.expand_path('../promise/em', __FILE__)
12
+
13
+ module_function
14
+
15
+ def new driver: ::MrDarcy.driver, &block
16
+ case driver
17
+ when :thread, :Thread
18
+ ::MrDarcy::Promise::Thread.new block
19
+ when :synchronous, :Synchronous
20
+ ::MrDarcy::Promise::Synchronous.new block
21
+ when :celluloid, :Celluloid
22
+ ::MrDarcy::Promise::Celluloid.new block
23
+ when :em, :EM, :event_machine, :eventmachine, :EventMachine
24
+ ::MrDarcy::Promise::EM.new block
25
+ else
26
+ raise "Unknown driver #{driver}"
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ module MrDarcy
2
+ class Role
3
+ attr_accessor :name, :options
4
+
5
+ def initialize name, opts={}, &block
6
+ self.name = name
7
+ self.options = opts
8
+ @module = Module.new(&block)
9
+ end
10
+
11
+ def pollute player
12
+ guard_against_false_players player
13
+
14
+ @module.instance_methods.each do |method_name|
15
+ implementation = @module.instance_method method_name
16
+ player.define_singleton_method method_name, implementation
17
+ end
18
+ end
19
+
20
+ def clean player
21
+ @module.instance_methods.each do |method_name|
22
+ player.singleton_class.send :remove_method, method_name if player.respond_to? method_name
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def guard_against_false_players player
29
+ options.each do |test_type, values|
30
+ case test_type
31
+ when :must_respond_to
32
+ Array(values).each do |method_name|
33
+ raise ArgumentError, "player must implement #{method_name}" unless player.respond_to? method_name
34
+ end
35
+ when :must_not_respond_to
36
+ Array(values).each do |method_name|
37
+ raise ArgumentError, "player must not implement #{method_name}" if player.respond_to? method_name
38
+ end
39
+ else
40
+ raise ArgumentError, "unknown restriction #{test_type}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module MrDarcy
2
+ VERSION = "0.1.0"
3
+ end
data/lib/mr_darcy.rb ADDED
@@ -0,0 +1,26 @@
1
+ require "mr_darcy/version"
2
+ require "mr_darcy/role"
3
+ require "mr_darcy/context"
4
+ require "mr_darcy/deferred"
5
+ require "mr_darcy/promise"
6
+
7
+ module MrDarcy
8
+
9
+ module_function
10
+
11
+ def driver=(driver)
12
+ @driver=driver
13
+ end
14
+
15
+ def driver
16
+ @driver ||= :Thread
17
+ end
18
+
19
+ def all_drivers
20
+ %w| synchronous thread celluloid em |.map(&:to_sym)
21
+ end
22
+
23
+ def promise driver: driver, &block
24
+ MrDarcy::Promise.new driver: driver, &block
25
+ end
26
+ end
data/mr_darcy.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mr_darcy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mr_darcy"
8
+ spec.version = MrDarcy::VERSION
9
+ spec.authors = ["James Harton"]
10
+ spec.email = ["james@resistor.io"]
11
+ spec.summary = %q{A mashup of async Promises and DCI in Ruby.}
12
+ spec.description = <<-EOF
13
+ MrDarcy takes async promises from the javascript word, DCI from Jim
14
+ Gay's brain and sprinkles some ruby on top for great justice!
15
+ EOF
16
+ spec.homepage = "https://github.com/jamesotron/MrDarcy"
17
+ spec.license = "MIT"
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.5"
25
+ %w| rake rspec guard guard-rspec guard-bundler terminal-notifier-guard
26
+ pry eventmachine em-http-request celluloid |.each do |gem|
27
+ spec.add_development_dependency gem
28
+ end
29
+ end