deferrable_gratification 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.MIT ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Rapportive Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,64 @@
1
+ # Deferrable Gratification #
2
+
3
+ ## Purpose ##
4
+
5
+ Deferrable Gratification (DG) makes evented code less error-prone and easier to
6
+ compose, and thus easier to create higher-level abstractions around. It also
7
+ enhances the API offered by Ruby Deferrables to make them more pleasant to work
8
+ with.
9
+
10
+ ## Documentation ##
11
+
12
+ * [API](http://samstokes.github.com/deferrable_gratification/doc/frames.html)
13
+ * [Behaviour specs](http://samstokes.github.com/deferrable_gratification/doc/spec/index.html)
14
+ (generated from RSpec code examples)
15
+
16
+ ## Components ##
17
+
18
+ It currently consists of the following components:
19
+
20
+ * [`DG::Fluent`](#fluent): fluent (aka chainable) syntax for registering
21
+ multiple callbacks and errbacks to the same Deferrable.
22
+
23
+ * [`DG::Bothback`](#bothback): a `#bothback` method for registering code to
24
+ run on either success or failure.
25
+
26
+ * [`DG::Combinators`](#combinators): a combinator library for building up
27
+ complex asynchronous operations out of simpler ones.
28
+
29
+
30
+ <h3 id="fluent"><tt>DG::Fluent</tt></h3>
31
+
32
+ Use JQuery-style fluent syntax for registering several callbacks and
33
+ errbacks on the same Deferrable. e.g.
34
+
35
+ DeferrableMonkeyShaver.new(monkey).
36
+ callback { puts "Monkey is shaved" }.
37
+ callback { monkey.entertain! }.
38
+ errback {|e| puts "Unable to shave monkey! #{e}" }.
39
+ errback {|_| monkey.terminate! }.
40
+ shave
41
+
42
+ <h3 id="bothback"><tt>DG::Bothback</tt></h3>
43
+
44
+ Register code to run on either success or failure: shorthand for calling both
45
+ `#callback` and `#errback` with the same code block.
46
+
47
+ <h3 id="combinators"><tt>DG::Combinators</tt></h3>
48
+
49
+ Allows building up higher-level asynchronous abstractions by composing simpler
50
+ asynchronous operations, without having to manually wire callbacks together
51
+ and remember to propagate errors correctly.
52
+
53
+ Motivating example: assume we have an asynchronous database API `DB.query`
54
+ which returns a Deferrable to communicate when the query finishes. (See
55
+ the [API docs for `DG::Combinators`](DeferrableGratification/Combinators.html)
56
+ for more detail.)
57
+
58
+ def product_names_for_username(username)
59
+ DB.query('SELECT id FROM users WHERE username = ?', username).bind! do |user_id|
60
+ DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
61
+ end.map do |product_names|
62
+ product_names.join(', ')
63
+ end
64
+ end
@@ -0,0 +1,59 @@
1
+ require File.join(File.dirname(__FILE__), *%w[deferrable_gratification bothback])
2
+ require File.join(File.dirname(__FILE__), *%w[deferrable_gratification combinators])
3
+ require File.join(File.dirname(__FILE__), *%w[deferrable_gratification default_deferrable])
4
+ require File.join(File.dirname(__FILE__), *%w[deferrable_gratification fluent])
5
+ require File.join(File.dirname(__FILE__), *%w[deferrable_gratification primitives])
6
+
7
+ # Deferrable Gratification ({DG}) makes evented code less error-prone and
8
+ # easier to compose, and thus easier to create higher-level abstractions
9
+ # around. It also enhances the API offered by Ruby Deferrables to make them
10
+ # more pleasant to work with.
11
+ #
12
+ # @see Fluent
13
+ # @see Bothback
14
+ # @see Combinators
15
+ module DeferrableGratification
16
+ # Allow DG.lift, DG.chain etc
17
+ extend Combinators::ClassMethods
18
+
19
+ # Allow DG.const, DG.failure etc
20
+ extend Primitives
21
+
22
+ # Bestow DG goodness upon an existing module or class.
23
+ #
24
+ # N.B. calling this on a module won't enhance any classes that have already
25
+ # included that module.
26
+ def self.enhance!(module_or_class)
27
+ module_or_class.send :include, Combinators
28
+ module_or_class.send :include, Fluent
29
+ module_or_class.send :include, Bothback
30
+ end
31
+
32
+ # Enhance EventMachine::Deferrable itself so that any class including it
33
+ # gets DG goodness. This should mean that all Deferrables in the current
34
+ # process will get enhanced.
35
+ #
36
+ # N.B. this will not enhance any classes that have *already* included
37
+ # Deferrable before this method was called, so you should call this before
38
+ # loading any other Deferrable libraries, and before defining any of your
39
+ # own Deferrable classes. (If that isn't possible, you can always call
40
+ # {enhance!} on them after definition.)
41
+ def self.enhance_all_deferrables!
42
+ require 'eventmachine'
43
+ require 'em/deferrable'
44
+
45
+ enhance! EventMachine::Deferrable
46
+
47
+ # Also have to do that to EM::DefaultDeferrable because it included
48
+ # Deferrable before we enhanced it.
49
+ enhance! EventMachine::DefaultDeferrable
50
+ end
51
+
52
+ # Make sure the combinator implementations themselves are enhanced. Have to
53
+ # do this here, rather than just including the modules in DefaultDeferrable,
54
+ # to avoid a nasty circular dependencies with default_deferrable.rb.
55
+ enhance! DefaultDeferrable
56
+ end
57
+
58
+ # Shorthand
59
+ DG = DeferrableGratification
@@ -0,0 +1,18 @@
1
+ module DeferrableGratification
2
+ # Allows registering a 'bothback' that will be fired on either success or
3
+ # failure, analogous to the +ensure+ clause of a +begin+/+rescue+ block.
4
+ #
5
+ # Include this into a class that has already included +Deferrable+.
6
+ module Bothback
7
+ # Register +block+ to be called on either success or failure.
8
+ # This is just a shorthand for registering the same +block+ as both a
9
+ # callback and an errback.
10
+ #
11
+ # @return [Deferrable, Bothback] +self+
12
+ def bothback(&block)
13
+ callback(&block)
14
+ errback(&block)
15
+ self
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,172 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), *%w[combinators *.rb])) do |file|
2
+ require file.sub(/\.rb$/, '')
3
+ end
4
+
5
+ module DeferrableGratification
6
+ # Combinators for building up higher-level asynchronous abstractions by
7
+ # composing simpler asynchronous operations, without having to manually wire
8
+ # callbacks together and remember to propagate errors correctly.
9
+ #
10
+ # @example Perform a sequence of database queries and transform the result.
11
+ # # With DG::Combinators:
12
+ # def product_names_for_username(username)
13
+ # DB.query('SELECT id FROM users WHERE username = ?', username).bind! do |user_id|
14
+ # DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
15
+ # end.map do |product_names|
16
+ # product_names.join(', ')
17
+ # end
18
+ # end
19
+ #
20
+ # status = product_names_for_username('bob')
21
+ #
22
+ # status.callback {|product_names| ... }
23
+ # # If both queries complete successfully, the callback receives the string
24
+ # # "Car, Spoon, Coffee". The caller doesn't have to know that two separate
25
+ # # queries were made, or that the query result needed transforming into the
26
+ # # desired format: he just gets the event he cares about.
27
+ #
28
+ # status.errback {|error| puts "Oh no! #{error}" }
29
+ # # If either query went wrong, the errback receives the error that occurred.
30
+ #
31
+ #
32
+ # # Without DG::Combinators:
33
+ # def product_names_for_username(username)
34
+ # product_names_status = EM::DefaultDeferrable.new
35
+ # query1_status = DB.query('SELECT id FROM users WHERE username = ?', username)
36
+ # query1_status.callback do |user_id|
37
+ # query2_status = DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
38
+ # query2_status.callback do |product_names|
39
+ # product_names = product_names.join(', ')
40
+ # # N.B. don't forget to report success to the caller!
41
+ # product_names_status.succeed(product_names)
42
+ # end
43
+ # query2_status.errback do |error|
44
+ # # N.B. don't forget to tell the caller we failed!
45
+ # product_names_status.fail(error)
46
+ # end
47
+ # end
48
+ # query1_status.errback do |error|
49
+ # # N.B. don't forget to tell the caller we failed!
50
+ # product_names_status.fail(error)
51
+ # end
52
+ # # oh yes, and don't forget to return this!
53
+ # product_names_status
54
+ # end
55
+
56
+ module Combinators
57
+ # Alias for {#bind!}.
58
+ #
59
+ # Note that this takes a +Proc+ (e.g. a lambda) while {#bind!} takes a
60
+ # block.
61
+ #
62
+ # @param [Proc] prok proc to call with the successful result of +self+.
63
+ # Assumed to return a Deferrable representing the status of its own
64
+ # operation.
65
+ #
66
+ # @return [Deferrable] status of the compound operation of passing the
67
+ # result of +self+ into the proc.
68
+ #
69
+ # @example Perform a database query that depends on the result of a previous query.
70
+ # DB.query('first query') >> lambda {|result| DB.query("query with #{result}") }
71
+ def >>(prok)
72
+ Bind.setup!(self, &prok)
73
+ end
74
+
75
+ # Register callbacks so that when this Deferrable succeeds, its result
76
+ # will be passed to the block, which is assumed to return another
77
+ # Deferrable representing the status of a second operation.
78
+ #
79
+ # If this operation fails, the block will not be run. If either operation
80
+ # fails, the compound Deferrable returned will fire its errbacks, meaning
81
+ # callers don't have to know about the inner operations and can just
82
+ # subscribe to the result of {#bind!}.
83
+ #
84
+ #
85
+ # If you find yourself writing lots of nested {#bind!} calls, you can
86
+ # equivalently rewrite them as a chain and remove the nesting: e.g.
87
+ #
88
+ # a.bind! do |x|
89
+ # b(x).bind! do |y|
90
+ # c(y).bind! do |z|
91
+ # d(z)
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ # has the same behaviour as
97
+ #
98
+ # a.bind! do |x|
99
+ # b(x)
100
+ # end.bind! do |y|
101
+ # c(y)
102
+ # end.bind! do |z|
103
+ # d(y)
104
+ # end
105
+ #
106
+ # As well as being more readable due to avoiding left margin inflation,
107
+ # this prevents introducing bugs due to inadvertent local variable capture
108
+ # by the nested blocks.
109
+ #
110
+ #
111
+ # @see #>>
112
+ #
113
+ # @param &block block to call with the successful result of +self+.
114
+ # Assumed to return a Deferrable representing the status of its own
115
+ # operation.
116
+ #
117
+ # @return [Deferrable] status of the compound operation of passing the
118
+ # result of +self+ into the block.
119
+ #
120
+ # @example Perform a web request based on the result of a database query.
121
+ # DB.query('url').bind! {|url| HTTP.get(url) }.
122
+ # callback {|response| puts "Got response!" }
123
+ def bind!(&block)
124
+ Bind.setup!(self, &block)
125
+ end
126
+
127
+ # Transform the result of this Deferrable by invoking +block+, returning
128
+ # a Deferrable which succeeds with the transformed result.
129
+ #
130
+ # If this operation fails, the operation will not be run, and the returned
131
+ # Deferrable will also fail.
132
+ #
133
+ # @param &block block that transforms the expected result of this
134
+ # operation in some way.
135
+ #
136
+ # @return [Deferrable] Deferrable that will succeed if this operation did,
137
+ # after transforming its result.
138
+ #
139
+ # @example Retrieve a web page and call back with its title.
140
+ # HTTP.request(url).map {|page| Hpricot(page).at(:title).inner_html }
141
+ def map(&block)
142
+ bind!(&block)
143
+ end
144
+
145
+
146
+ # Boilerplate hook to extend {ClassMethods}.
147
+ def self.included(base)
148
+ base.send :extend, ClassMethods
149
+ end
150
+
151
+ # Combinators which don't make sense as methods on +Deferrable+.
152
+ #
153
+ # {DeferrableGratification} extends this module, and thus the methods
154
+ # here are accessible via the {DG} alias.
155
+ module ClassMethods
156
+ # Execute a sequence of asynchronous operations that may each depend on
157
+ # the result of the previous operation.
158
+ #
159
+ # @see #bind! more detail on the semantics.
160
+ #
161
+ # @param [*Proc] *actions procs that will perform an operation and
162
+ # return a Deferrable.
163
+ #
164
+ # @return [Deferrable] Deferrable that will succeed if all of the
165
+ # chained operations succeeded, and callback with the result of the
166
+ # last operation.
167
+ def chain(*actions)
168
+ actions.inject(DG.const(nil), &:>>)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,94 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. default_deferrable])
2
+
3
+ module DeferrableGratification
4
+ module Combinators
5
+ # Combinator that passes the result of one deferred operation to a block
6
+ # that uses that result to begin another deferred operation. The compound
7
+ # operation itself succeeds if the second operation does.
8
+ #
9
+ # You probably want to call {#bind!} rather than using this class directly.
10
+ #
11
+ # If we define +Deferrable err (IO a)+ to be the type of a Deferrable that
12
+ # may perform a side effect and then either succeed with a value of type a
13
+ # or fail with an error of type err, then we expect the block to have type
14
+ #
15
+ # a -> Deferrable err (IO b)
16
+ #
17
+ # and if so this combinator (actually {Bind.setup!}) is a specialisation
18
+ # of the monadic bind operator +>>=+:
19
+ #
20
+ # # example: database query that depends on the result of another
21
+ # Bind.setup!(DB.query('select foo from bar')) do |result|
22
+ # DB.query("select baz from quuz where name = '#{result}'")
23
+ # end
24
+ #
25
+ # # type signatures, in pseudo-Haskell
26
+ # (>>=) :: Deferrable err a ->
27
+ # (a -> Deferrable err b) -> Deferrable err b
28
+ # Bind :: Deferrable err a ->
29
+ # (a -> Deferrable err (IO b)) -> Deferrable err (IO b)
30
+ #
31
+ # However, because Ruby doesn't actually type-check blocks, we can't
32
+ # enforce that the block really does return a second Deferrable. This
33
+ # therefore also supports (reasonably) arbitrary blocks. However, it's
34
+ # probably clearer (though equivalent) to use {#map} for this case.
35
+ class Bind < DefaultDeferrable
36
+ # Prepare to bind +block+ to +first+, and create the Deferrable that
37
+ # will represent the bind.
38
+ #
39
+ # Does not actually set up any callbacks or errbacks: call {#setup!} for
40
+ # that.
41
+ #
42
+ # @param [Deferrable] first operation to bind to.
43
+ # @param &block block to run on success; should return a Deferrable.
44
+ #
45
+ # @raise [ArgumentError] if called without a block.
46
+ def initialize(first, &block)
47
+ @first = first
48
+
49
+ raise ArgumentError, 'must pass a block' unless block
50
+ @proc = block
51
+ end
52
+
53
+ # Register a callback on the first Deferrable to run the bound block on
54
+ # success, and an errback to fail this {Bind} on failure.
55
+ def setup!
56
+ @first.callback {|*args| run_bound_proc(*args) }
57
+ @first.errback {|*args| self.fail(*args) }
58
+ end
59
+
60
+ # Create a {Bind} and register the callbacks.
61
+ #
62
+ # @param (see #initialize)
63
+ #
64
+ # @return [Bind] Deferrable representing the compound operation.
65
+ #
66
+ # @raise (see #initialize)
67
+ def self.setup!(first, &block)
68
+ new(first, &block).tap(&:setup!)
69
+ end
70
+
71
+ private
72
+ def run_bound_proc(*args)
73
+ begin
74
+ second = @proc.call(*args)
75
+ rescue => error
76
+ self.fail(error)
77
+ else
78
+ # We expect the block to return a Deferrable, on which we can set
79
+ # callbacks. However, as referred to in the class comment above, we
80
+ # can't assume that it will, and need to behave sensibly if it
81
+ # doesn't.
82
+ if second.respond_to?(:callback) && second.respond_to?(:errback)
83
+ second.callback {|*args2| self.succeed(*args2) }
84
+ second.errback {|*error| self.fail(*error) }
85
+ else
86
+ # Not a Deferrable, so we need to "behave sensibly" as alluded to
87
+ # above. Just behaving like #map is sensible enough.
88
+ self.succeed(second)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,17 @@
1
+ require 'eventmachine'
2
+ require 'em/deferrable'
3
+
4
+ module DeferrableGratification
5
+ # Barebones Deferrable that includes the {DeferrableGratification}
6
+ # extensions:
7
+ #
8
+ # @see Fluent
9
+ # @see Bothback
10
+ # @see Combinators
11
+ class DefaultDeferrable
12
+ include EventMachine::Deferrable
13
+
14
+ # want to include Combinators here, but that would introduce a circular
15
+ # dependency, so have to do that in the top-level .rb file.
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ module DeferrableGratification
2
+ # Allows JQuery-style fluent syntax for registering several callbacks and
3
+ # errbacks on the same Deferrable. e.g.
4
+ #
5
+ # DeferrableMonkeyShaver.new(monkey).
6
+ # callback { puts "Monkey is shaved" }.
7
+ # callback { monkey.entertain! }.
8
+ # errback {|e| puts "Unable to shave monkey! #{e}" }.
9
+ # errback {|_| monkey.terminate! }.
10
+ # shave
11
+ #
12
+ # Include this into a class that has already included +Deferrable+.
13
+ module Fluent
14
+ # Register +block+ to be called on success.
15
+ #
16
+ # @return [Deferrable, Fluent] +self+
17
+ #
18
+ # @see EventMachine::Deferrable#callback
19
+ def callback(&block)
20
+ super(&block)
21
+ self
22
+ end
23
+
24
+ # Register +block+ to be called on failure.
25
+ #
26
+ # @return [Deferrable, Fluent] +self+
27
+ #
28
+ # @see EventMachine::Deferrable#errback
29
+ def errback(&block)
30
+ super(&block)
31
+ self
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), *%w[primitives *.rb])) do |file|
2
+ require file.sub(/\.rb$/, '')
3
+ end
4
+
5
+ module DeferrableGratification
6
+ # Trivial operations which return Deferrables.
7
+ #
8
+ # Used internally by the library, and may be useful in cases where you need
9
+ # to return a Deferrable to keep API compatibility but the result is already
10
+ # available.
11
+ #
12
+ # {DeferrableGratification} extends this module, and thus the methods here
13
+ # are accessible via the {DG} alias.
14
+ module Primitives
15
+ # Return a Deferrable which immediately succeeds with a constant value.
16
+ def const(value)
17
+ blank.tap {|d| d.succeed(value) }
18
+ end
19
+
20
+ # Return a Deferrable which immediately fails with an exception.
21
+ def failure(class_or_message, message_or_nil = nil)
22
+ blank.tap do |d|
23
+ d.fail(
24
+ case class_or_message
25
+ when Class
26
+ class_or_message.new(message_or_nil)
27
+ else
28
+ RuntimeError.new(class_or_message.to_s)
29
+ end)
30
+ end
31
+ end
32
+
33
+ # Return a completely uninteresting Deferrable.
34
+ def blank
35
+ DeferrableGratification::DefaultDeferrable.new
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deferrable_gratification
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Sam Stokes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-15 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: eventmachine
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rake
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: yard
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: bluecloth
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :development
76
+ version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: rspec
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 2
88
+ - 3
89
+ - 0
90
+ version: 2.3.0
91
+ type: :development
92
+ version_requirements: *id005
93
+ description: |
94
+ Deferrable Gratification (DG) makes evented code less error-prone and easier to compose, and thus easier to create higher-level abstractions around. It also enhances the API offered by Ruby Deferrables to make them more pleasant to work with.
95
+
96
+ Currently consists of the following components:
97
+
98
+ * fluent (aka chainable) syntax for registering multiple callbacks and errbacks to the same Deferrable.
99
+
100
+ * a #bothback method for registering code to run on either success or failure.
101
+
102
+ * a combinator library for building up complex asynchronous operations out of simpler ones.
103
+
104
+ email:
105
+ - sam@rapportive.com
106
+ executables: []
107
+
108
+ extensions: []
109
+
110
+ extra_rdoc_files: []
111
+
112
+ files:
113
+ - lib/deferrable_gratification.rb
114
+ - lib/deferrable_gratification/default_deferrable.rb
115
+ - lib/deferrable_gratification/combinators/bind.rb
116
+ - lib/deferrable_gratification/combinators.rb
117
+ - lib/deferrable_gratification/bothback.rb
118
+ - lib/deferrable_gratification/fluent.rb
119
+ - lib/deferrable_gratification/primitives.rb
120
+ - LICENSE.MIT
121
+ - README.markdown
122
+ has_rdoc: true
123
+ homepage: http://github.com/samstokes/deferrable_gratification
124
+ licenses:
125
+ - MIT
126
+ post_install_message:
127
+ rdoc_options: []
128
+
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ hash: 57
137
+ segments:
138
+ - 1
139
+ - 8
140
+ - 7
141
+ version: 1.8.7
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 3
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ requirements: []
152
+
153
+ rubyforge_project:
154
+ rubygems_version: 1.3.7
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: Makes evented programming easier with composition and abstraction.
158
+ test_files: []
159
+