deferrable_gratification 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.
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
+