eventually 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +3 -2
- data/eventually.gemspec +2 -0
- data/lib/eventually.rb +77 -37
- data/lib/eventually/callable.rb +34 -0
- data/lib/eventually/event.rb +47 -0
- data/lib/eventually/validation/arity.rb +22 -0
- data/lib/eventually/validation/max_listeners.rb +17 -0
- data/lib/eventually/version.rb +1 -1
- data/spec/lib/callable_spec.rb +70 -0
- data/spec/lib/event_spec.rb +115 -0
- data/spec/lib/eventually_spec.rb +149 -85
- data/spec/lib/validation/arity_spec.rb +73 -0
- data/spec/lib/validation/max_listeners_spec.rb +57 -0
- data/spec/spec_helper.rb +3 -1
- metadata +39 -4
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Eventually
|
2
2
|
|
3
|
-
`Eventually` is a module that facilitates evented callback management *similar* to the [EventEmitter API](http://nodejs.org/docs/v0.4.7/api/events.html) in NodeJS. Support for Ruby's various
|
3
|
+
`Eventually` is a module that facilitates evented callback management *similar* to the [EventEmitter API](http://nodejs.org/docs/v0.4.7/api/events.html) in NodeJS. Support for Ruby's various callback styles is heavily baked in, so using blocks, lambdas, procs, or even detached methods works out of the box, batteries included. Simply include `Eventually` in the class you will be emitting events from, register some listeners and fire away.
|
4
4
|
|
5
5
|
```ruby
|
6
6
|
class Car
|
@@ -36,7 +36,7 @@ The previous snippet acts mostly as documentation.
|
|
36
36
|
|
37
37
|
## Callback arity validation
|
38
38
|
|
39
|
-
However, sometimes you want to be sure that a given registered callback will conform to your event interface, so specify an **arity validation**. Let's add another event to our definition and ensure that callbacks registering for that event must have an arity of 1.
|
39
|
+
However, sometimes you want to be sure that a given registered callback will conform to your event interface, so specify an **arity validation**. Let's add another event to our definition and ensure that callbacks registering for that event must have an arity of 1. This will also raise an error if you attempt to emit an event with the wrong argument arity.
|
40
40
|
|
41
41
|
```ruby
|
42
42
|
class Car
|
@@ -74,6 +74,7 @@ car = Car.new
|
|
74
74
|
car.on(:turning) do
|
75
75
|
puts 'the car is turning'
|
76
76
|
end
|
77
|
+
```
|
77
78
|
|
78
79
|
*See the **examples/scrict.rb** file for more on this.*
|
79
80
|
|
data/eventually.gemspec
CHANGED
data/lib/eventually.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'eventually/version'
|
2
|
+
require 'eventually/event'
|
3
|
+
require 'eventually/callable'
|
4
|
+
require 'eventually/validation/arity'
|
5
|
+
require 'eventually/validation/max_listeners'
|
2
6
|
|
3
7
|
# Eventually is a module that facilitates evented callback
|
4
8
|
# management similar to the EventEmitter API in NodeJS.
|
@@ -11,10 +15,13 @@ require "eventually/version"
|
|
11
15
|
module Eventually
|
12
16
|
def self.included(base)
|
13
17
|
base.extend(ClassMethods)
|
18
|
+
base.emits(:listener_added, :arity => 1)
|
14
19
|
end
|
15
20
|
|
16
21
|
module ClassMethods
|
17
|
-
NO_CHECK_ARITY = -
|
22
|
+
NO_CHECK_ARITY = -2
|
23
|
+
DEFAULT_MAX_LISTENERS = 10
|
24
|
+
|
18
25
|
attr_accessor :emittable_events
|
19
26
|
|
20
27
|
# Define an event or list of events
|
@@ -47,6 +54,19 @@ module Eventually
|
|
47
54
|
emittable.key?(evt.to_sym)
|
48
55
|
end
|
49
56
|
|
57
|
+
# Return the maximum number of listeners before
|
58
|
+
# we'll start printing memory warnings.
|
59
|
+
# Default max is 10
|
60
|
+
def max_listeners
|
61
|
+
@max_listeners ||= DEFAULT_MAX_LISTENERS
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set the maximum listener number. Setting this max
|
65
|
+
# to 0 indicates unlimited listeners allowed.
|
66
|
+
def max_listeners= max
|
67
|
+
@max_listeners = max
|
68
|
+
end
|
69
|
+
|
50
70
|
# Puts instances into strict mode. This does two things:
|
51
71
|
# - Raise an error if registering a callback for an event
|
52
72
|
# that has not already been pre-defined (e.g. with #emits)
|
@@ -62,7 +82,7 @@ module Eventually
|
|
62
82
|
@strict = false
|
63
83
|
end
|
64
84
|
|
65
|
-
#
|
85
|
+
# Report on strict mode
|
66
86
|
def strict?
|
67
87
|
@strict || false
|
68
88
|
end
|
@@ -86,9 +106,10 @@ module Eventually
|
|
86
106
|
|
87
107
|
# Shorthand predicate to determine if a given event is
|
88
108
|
# "emittable" or "registerable"
|
89
|
-
def
|
109
|
+
def emittable?(event)
|
90
110
|
!strict? || emits?(event)
|
91
111
|
end
|
112
|
+
alias :registerable? :emittable?
|
92
113
|
|
93
114
|
private
|
94
115
|
|
@@ -102,24 +123,26 @@ module Eventually
|
|
102
123
|
#
|
103
124
|
# Usage: see Eventually#emit or examples directory
|
104
125
|
#
|
105
|
-
def on(
|
106
|
-
|
107
|
-
|
108
|
-
cbk = nil
|
109
|
-
if callable.respond_to?(:call)
|
110
|
-
cbk = callable
|
111
|
-
elsif block_given? && !blk.nil?
|
112
|
-
cbk = blk
|
113
|
-
else
|
114
|
-
raise 'Cannot register callback. Neither callable nor block was given'
|
115
|
-
end
|
126
|
+
def on(evt_name, callable=nil, &blk)
|
127
|
+
evt_name = evt_name.to_sym unless evt_name.is_a?(Symbol)
|
116
128
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
129
|
+
cb = Eventually::Callable.new(callable, blk)
|
130
|
+
Eventually::Validation::Arity.new(evt_name, self, cb.arity).raise_unless_valid!
|
131
|
+
|
132
|
+
event = get_event(evt_name)
|
133
|
+
event.add_callable(cb)
|
134
|
+
emit(:listener_added, cb)
|
121
135
|
|
122
|
-
(
|
136
|
+
Eventually::Validation::MaxListeners.new(self).warn_unless_valid!
|
137
|
+
[event, cb]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Event registration method which will remove the given
|
141
|
+
# callback after it is invoked. See Eventually#on for registration details.
|
142
|
+
def once(event, callable=nil, &blk)
|
143
|
+
event, cb = on(event, callable, &blk)
|
144
|
+
cb.availability = :once
|
145
|
+
[event, cb]
|
123
146
|
end
|
124
147
|
|
125
148
|
# Emit the payload arguments back to the registered listeners
|
@@ -141,29 +164,46 @@ module Eventually
|
|
141
164
|
# end
|
142
165
|
# car.stop # this will indirectly invoke the above callback
|
143
166
|
#
|
144
|
-
def emit(
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
167
|
+
def emit(evt_name, *payload)
|
168
|
+
evt_name = evt_name.to_sym unless evt_name.is_a?(Symbol)
|
169
|
+
event = get_event(evt_name)
|
170
|
+
Eventually::Validation::Arity.new(evt_name, self, payload.length).raise_unless_valid!
|
171
|
+
event.emit(*payload)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Report the number of listeners across all registered events
|
175
|
+
def num_listeners
|
176
|
+
_events.values.inject(0){|acc, evt| acc + evt.callables.size}
|
177
|
+
end
|
178
|
+
|
179
|
+
# Report the number of registered listeners for the given event
|
180
|
+
def listeners(event)
|
181
|
+
get_event(event).callables.map(&:callable)
|
154
182
|
end
|
155
183
|
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
184
|
+
# Remove the given listener callback from the given event callback list
|
185
|
+
def remove_listener(event, cbk)
|
186
|
+
get_event(event).remove_callable(cbk)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Remove all listener callbacks for the given event
|
190
|
+
def remove_all_listeners(event)
|
191
|
+
get_event(event).remove_all_callables
|
161
192
|
end
|
162
193
|
|
163
194
|
private
|
164
195
|
|
165
|
-
def
|
166
|
-
@
|
196
|
+
def _events
|
197
|
+
@_events ||= {}
|
198
|
+
end
|
199
|
+
|
200
|
+
def get_event(event)
|
201
|
+
evt = _events[event]
|
202
|
+
unless evt
|
203
|
+
evt = Eventually::Event.new(event, self.class.registerable?(event))
|
204
|
+
_events[event] = evt
|
205
|
+
end
|
206
|
+
evt
|
167
207
|
end
|
168
208
|
|
169
209
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'eventually/validation/arity'
|
3
|
+
|
4
|
+
module Eventually
|
5
|
+
class Callable
|
6
|
+
extend Forwardable
|
7
|
+
attr_reader :callable
|
8
|
+
attr_accessor :availability
|
9
|
+
|
10
|
+
delegate [:arity, :call, :to_proc] => :@callable
|
11
|
+
|
12
|
+
def initialize(callable, block, availability=:continuous)
|
13
|
+
@callable = pick_callable(callable, block)
|
14
|
+
@availability = availability
|
15
|
+
end
|
16
|
+
|
17
|
+
def pick_callable(c, b)
|
18
|
+
cb = nil
|
19
|
+
if c.respond_to?(:call)
|
20
|
+
cb = c
|
21
|
+
elsif !b.nil?
|
22
|
+
cb = b
|
23
|
+
else
|
24
|
+
raise 'Cannot register callable. Neither callable nor block was given.'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def continuous?
|
29
|
+
@availability == :continuous
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'eventually'
|
2
|
+
require 'eventually/callable'
|
3
|
+
|
4
|
+
module Eventually
|
5
|
+
class Event
|
6
|
+
attr_reader :name, :callables
|
7
|
+
|
8
|
+
def initialize(name, emittable=true)
|
9
|
+
@name = name.to_sym
|
10
|
+
@emittable = !!emittable
|
11
|
+
@callables = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_callable(callable)
|
15
|
+
raise "Event type :#{name} will not be emitted." unless emittable?
|
16
|
+
@callables << callable if callable_valid?(callable)
|
17
|
+
end
|
18
|
+
|
19
|
+
def remove_callable(callable_to_remove)
|
20
|
+
if callable_valid?(callable_to_remove)
|
21
|
+
delete_handler = proc{|callable| callable == callable_to_remove }
|
22
|
+
else
|
23
|
+
delete_handler = proc{|callable| callable.callable == callable_to_remove }
|
24
|
+
end
|
25
|
+
@callables.delete_if(&delete_handler)
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_all_callables
|
29
|
+
@callables = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def emit(*payload)
|
33
|
+
raise "Event type :#{name} cannot be emitted." unless emittable?
|
34
|
+
@callables.each {|callable| callable.call(*payload) }
|
35
|
+
@callables.delete_if {|callable| !callable.continuous? }
|
36
|
+
end
|
37
|
+
|
38
|
+
def callable_valid?(callable)
|
39
|
+
callable.is_a?(Eventually::Callable)
|
40
|
+
end
|
41
|
+
|
42
|
+
def emittable?
|
43
|
+
@emittable
|
44
|
+
end
|
45
|
+
alias :registerable? :emittable?
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'eventually/callable'
|
2
|
+
module Eventually
|
3
|
+
module Validation
|
4
|
+
class Arity
|
5
|
+
attr_reader :expected, :received
|
6
|
+
def initialize(event, emitter, arity)
|
7
|
+
@event = event
|
8
|
+
@emitter = emitter
|
9
|
+
@arity = @received = arity
|
10
|
+
@expected = @emitter.class.arity_for_event(@event)
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
!@emitter.class.validates_arity?(@event) || @received == @expected
|
15
|
+
end
|
16
|
+
|
17
|
+
def raise_unless_valid!
|
18
|
+
raise "Arity validation failed for event :#{@event} (expected #{@expected}, received #{@received})" unless valid?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Eventually
|
2
|
+
module Validation
|
3
|
+
class MaxListeners
|
4
|
+
def initialize(emitter)
|
5
|
+
@emitter = emitter
|
6
|
+
end
|
7
|
+
|
8
|
+
def valid?
|
9
|
+
@emitter.class.max_listeners == 0 || @emitter.num_listeners <= @emitter.class.max_listeners
|
10
|
+
end
|
11
|
+
|
12
|
+
def warn_unless_valid!
|
13
|
+
puts "Warning: #{@emitter.class.name} has more than #{@emitter.class.max_listeners} registered listeners." unless valid?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/eventually/version.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'eventually/callable'
|
3
|
+
|
4
|
+
describe Eventually::Callable do
|
5
|
+
let(:cb_lambda) { lambda{ :lambda } }
|
6
|
+
let(:cb_proc) { proc{ :proc } }
|
7
|
+
let(:cb_block) { proc{ :block } }
|
8
|
+
let(:cb_method) { def callable_meth; :meth end; method(:callable_meth) }
|
9
|
+
let(:availability) { :continuous }
|
10
|
+
|
11
|
+
subject { Eventually::Callable.new(cb_lambda, cb_block, availability) }
|
12
|
+
its(:callable) { should eq cb_lambda }
|
13
|
+
its(:availability) { should eq availability }
|
14
|
+
|
15
|
+
context 'when callable arg is' do
|
16
|
+
context 'a lambda' do
|
17
|
+
subject { Eventually::Callable.new(cb_lambda, nil) }
|
18
|
+
its(:callable) { should eq cb_lambda }
|
19
|
+
end
|
20
|
+
context 'a proc' do
|
21
|
+
subject { Eventually::Callable.new(cb_proc, nil) }
|
22
|
+
its(:callable) { should eq cb_proc }
|
23
|
+
end
|
24
|
+
context 'a detached method' do
|
25
|
+
subject { Eventually::Callable.new(cb_method, nil) }
|
26
|
+
its(:callable) { should eq cb_method }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when callable arg is not valid' do
|
31
|
+
context 'and block arg is valid' do
|
32
|
+
subject { Eventually::Callable.new(nil, cb_block) }
|
33
|
+
its(:callable) { should eq cb_block }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'and block arg is valid' do
|
37
|
+
it 'raises an error that provided callable(s) were invalid' do
|
38
|
+
expect {
|
39
|
+
Eventually::Callable.new(nil, nil)
|
40
|
+
}.should raise_error(/Cannot register callable\. Neither callable nor block was given\./)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#continuous?' do
|
46
|
+
context 'when availability is :continuous' do
|
47
|
+
subject { Eventually::Callable.new(cb_lambda, nil, :continuous) }
|
48
|
+
its(:availability) { should eq :continuous }
|
49
|
+
its(:continuous?) { should be_true }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when availability is :continuous' do
|
53
|
+
subject { Eventually::Callable.new(cb_lambda, nil, :once) }
|
54
|
+
its(:availability) { should eq :once }
|
55
|
+
its(:continuous?) { should be_false }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'delegation' do
|
60
|
+
it 'delegates :arity to callable' do
|
61
|
+
subject.arity.should eq subject.callable.arity
|
62
|
+
end
|
63
|
+
it 'delegates :call to callable' do
|
64
|
+
subject.call.should eq subject.callable.call
|
65
|
+
end
|
66
|
+
it 'delegates :to_proc to callable' do
|
67
|
+
subject.to_proc.should eq subject.callable.to_proc
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'eventually/event'
|
3
|
+
|
4
|
+
describe Eventually::Event do
|
5
|
+
let(:event_name) { :started }
|
6
|
+
let(:callable) { Eventually::Callable.new(lambda{}, nil) }
|
7
|
+
|
8
|
+
subject { Eventually::Event.new(event_name) }
|
9
|
+
its(:name) { should eq event_name }
|
10
|
+
its(:emittable?) { should be_true }
|
11
|
+
its(:callables) { should be_instance_of(Array) }
|
12
|
+
its(:callables) { should be_empty }
|
13
|
+
|
14
|
+
describe '#add_callable' do
|
15
|
+
it 'adds a callable object to the callables array' do
|
16
|
+
subject.add_callable(callable)
|
17
|
+
subject.callables.should eq [callable]
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'skips adding invalid callable objects to callables array' do
|
21
|
+
subject.add_callable(nil)
|
22
|
+
subject.add_callable(1)
|
23
|
+
subject.add_callable("hello, world")
|
24
|
+
subject.callables.should be_empty
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when event is not emittable' do
|
28
|
+
it 'raises an error' do
|
29
|
+
evt = Eventually::Event.new(event_name, false)
|
30
|
+
expect { evt.add_callable(callable) }.to raise_error(/Event type :#{event_name} will not be emitted\./)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#remove_callable' do
|
36
|
+
it 'removes raw callable' do
|
37
|
+
evt = Eventually::Event.new(event_name)
|
38
|
+
raw_callable = lambda{}
|
39
|
+
callable = Eventually::Callable.new(raw_callable, nil)
|
40
|
+
evt.add_callable(callable)
|
41
|
+
evt.callables.should have(1).item
|
42
|
+
evt.remove_callable(raw_callable)
|
43
|
+
evt.callables.should be_empty
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'removes wrapped raw callable' do
|
47
|
+
evt = Eventually::Event.new(event_name)
|
48
|
+
callable = Eventually::Callable.new(lambda{}, nil)
|
49
|
+
evt.add_callable(callable)
|
50
|
+
evt.callables.should have(1).item
|
51
|
+
evt.remove_callable(callable)
|
52
|
+
evt.callables.should be_empty
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#remove_all_callables' do
|
57
|
+
it 'clears out all registered callables' do
|
58
|
+
evt = Eventually::Event.new(event_name)
|
59
|
+
evt.add_callable(Eventually::Callable.new(lambda{}, nil))
|
60
|
+
evt.add_callable(Eventually::Callable.new(lambda{}, nil))
|
61
|
+
evt.add_callable(Eventually::Callable.new(lambda{}, nil))
|
62
|
+
evt.add_callable(Eventually::Callable.new(lambda{}, nil))
|
63
|
+
evt.add_callable(Eventually::Callable.new(lambda{}, nil))
|
64
|
+
evt.callables.should have(5).items
|
65
|
+
evt.remove_all_callables
|
66
|
+
evt.callables.should be_empty
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#emit' do
|
71
|
+
context 'when not emittable' do
|
72
|
+
it 'raises an error' do
|
73
|
+
evt = Eventually::Event.new(event_name, false)
|
74
|
+
expect { evt.emit(:data) }.to raise_error(/Event type :#{event_name} cannot be emitted\./)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'calls each listener callback' do
|
79
|
+
evt = Eventually::Event.new(event_name)
|
80
|
+
cbs = [
|
81
|
+
Eventually::Callable.new(lambda{}, nil),
|
82
|
+
Eventually::Callable.new(lambda{}, nil),
|
83
|
+
Eventually::Callable.new(lambda{}, nil),
|
84
|
+
Eventually::Callable.new(lambda{}, nil),
|
85
|
+
Eventually::Callable.new(lambda{}, nil)
|
86
|
+
]
|
87
|
+
cbs.each do |cb|
|
88
|
+
evt.add_callable(cb)
|
89
|
+
cb.should_receive(:call).once.with(:data)
|
90
|
+
end
|
91
|
+
evt.emit(:data)
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when some callables are one-time callable' do
|
95
|
+
it 'removes the one-time callable callables from the callables list' do
|
96
|
+
evt = Eventually::Event.new(event_name)
|
97
|
+
cbs = [
|
98
|
+
Eventually::Callable.new(lambda{}, nil),
|
99
|
+
Eventually::Callable.new(lambda{}, nil, :once),
|
100
|
+
Eventually::Callable.new(lambda{}, nil, :once),
|
101
|
+
Eventually::Callable.new(lambda{}, nil, :once),
|
102
|
+
Eventually::Callable.new(lambda{}, nil)
|
103
|
+
]
|
104
|
+
cbs.each do |cb|
|
105
|
+
evt.add_callable(cb)
|
106
|
+
cb.should_receive(:call).once.with(:data)
|
107
|
+
end
|
108
|
+
evt.callables.should have(5).items
|
109
|
+
evt.emit(:data)
|
110
|
+
evt.callables.should have(2).items
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/spec/lib/eventually_spec.rb
CHANGED
@@ -8,12 +8,27 @@ describe Eventually do
|
|
8
8
|
before(:each) do
|
9
9
|
Emitter.disable_strict!
|
10
10
|
Emitter.emits_none
|
11
|
+
Emitter.emits(:listener_added, :arity => 1)
|
12
|
+
Emitter.max_listeners = 10
|
11
13
|
end
|
12
14
|
|
13
15
|
let(:emitter) { Emitter.new }
|
14
16
|
let(:defined_events) { [:one, :two, :three] }
|
15
17
|
|
16
18
|
context 'external api' do
|
19
|
+
describe '.max_listeners' do
|
20
|
+
it 'returns the number of listeners allowed before memory warnings are printed' do
|
21
|
+
Emitter.max_listeners.should eq 10
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.max_listeners=' do
|
26
|
+
it 'sets the max number of listeners to allow before printing memory warnings' do
|
27
|
+
Emitter.max_listeners = 20
|
28
|
+
Emitter.max_listeners.should eq 20
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
17
32
|
describe '.emits_none' do
|
18
33
|
it 'clears out emitter definitions' do
|
19
34
|
Emitter.emits(:jigger)
|
@@ -36,7 +51,7 @@ describe Eventually do
|
|
36
51
|
|
37
52
|
it 'provides a list of pre-defined emittable events' do
|
38
53
|
Emitter.emits(*defined_events)
|
39
|
-
Emitter.emits.should eq defined_events
|
54
|
+
Emitter.emits.should eq [:listener_added, defined_events].flatten
|
40
55
|
end
|
41
56
|
|
42
57
|
describe '.enable_strict!' do
|
@@ -97,51 +112,32 @@ describe Eventually do
|
|
97
112
|
Emitter.emits(:jigger, arity: 5)
|
98
113
|
Emitter.arity_for_event(:jigger).should eq 5
|
99
114
|
Emitter.emits(:pingpong)
|
100
|
-
Emitter.arity_for_event(:pingpong).should eq -
|
115
|
+
Emitter.arity_for_event(:pingpong).should eq -2
|
101
116
|
Emitter.arity_for_event(:nonevent).should eq nil
|
102
117
|
end
|
103
118
|
end
|
104
119
|
end
|
105
120
|
end
|
106
121
|
|
107
|
-
it 'allows event registration with lambda' do
|
108
|
-
emitter.on(:start, lambda{ puts 'hi' })
|
109
|
-
end
|
110
|
-
|
111
|
-
it 'allows event registration with proc' do
|
112
|
-
emitter.on(:start, proc{ puts 'hi' })
|
113
|
-
end
|
114
|
-
|
115
|
-
it 'allows event registration with block' do
|
116
|
-
emitter.on(:start) do
|
117
|
-
puts 'hi'
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
it 'allows event registration with detached method' do
|
122
|
-
def event_handler; end
|
123
|
-
emitter.on(:start, method(:event_handler))
|
124
|
-
end
|
125
|
-
|
126
122
|
it 'allows multiple registrations for a given event' do
|
127
123
|
emitter.on(:start) { puts 'hello' }
|
128
124
|
emitter.on(:start) { puts 'world' }
|
129
125
|
end
|
130
126
|
|
131
|
-
describe '.
|
127
|
+
describe '.emittable?' do
|
132
128
|
context 'when strict mode enabled' do
|
133
129
|
before { Emitter.enable_strict! }
|
134
130
|
context 'when given event is registered' do
|
135
131
|
it 'returns true' do
|
136
132
|
Emitter.emits(:known)
|
137
133
|
Emitter.emits?(:known).should be_true
|
138
|
-
Emitter.
|
134
|
+
Emitter.emittable?(:known).should be_true
|
139
135
|
end
|
140
136
|
end
|
141
137
|
context 'when given event is not registered' do
|
142
138
|
it 'returns false' do
|
143
139
|
Emitter.emits?(:unknown).should be_false
|
144
|
-
Emitter.
|
140
|
+
Emitter.emittable?(:unknown).should be_false
|
145
141
|
end
|
146
142
|
end
|
147
143
|
end
|
@@ -152,20 +148,64 @@ describe Eventually do
|
|
152
148
|
it 'returns true' do
|
153
149
|
Emitter.emits(:known)
|
154
150
|
Emitter.emits?(:known).should be_true
|
155
|
-
Emitter.
|
151
|
+
Emitter.emittable?(:known).should be_true
|
156
152
|
end
|
157
153
|
end
|
158
154
|
context 'when given event is not registered' do
|
159
155
|
it 'returns true' do
|
160
156
|
Emitter.emits?(:unknown).should be_false
|
161
|
-
Emitter.
|
157
|
+
Emitter.emittable?(:unknown).should be_true
|
162
158
|
end
|
163
159
|
end
|
164
160
|
end
|
165
161
|
end
|
166
162
|
end
|
167
163
|
|
168
|
-
|
164
|
+
describe '#on' do
|
165
|
+
it 'raises an error when a given callback is invalid' do
|
166
|
+
expect { emitter.on(:start, nil, &nil) }.should raise_error(/Cannot register callable/)
|
167
|
+
expect { emitter.on(:start, 10_000) }.should raise_error(/Cannot register callable/)
|
168
|
+
expect { emitter.on(:start, "callback") }.should raise_error(/Cannot register callable/)
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when arity validation is enabled' do
|
172
|
+
before { Emitter.emits(:hello_world, arity: 2) }
|
173
|
+
it 'accepts a callback with matching arity' do
|
174
|
+
expect {
|
175
|
+
emitter.on(:hello_world) do |param1, param2|
|
176
|
+
# callback will not be invoked
|
177
|
+
end
|
178
|
+
}.should_not raise_error
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'rejects a callback if the given arity is not exact' do
|
182
|
+
expect {
|
183
|
+
emitter.on(:hello_world) do |param1, param2, param3|
|
184
|
+
# callback will not be invoked
|
185
|
+
end
|
186
|
+
}.should raise_error(/Arity validation failed for event :hello_world \(expected 2, received 3\)/)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'when detecting potential memory leaks' do
|
191
|
+
it 'writes a warning to stdout when > 10 listeners are added' do
|
192
|
+
$stdout.should_receive(:puts).once.with('Warning: Emitter has more than 10 registered listeners.')
|
193
|
+
(emitter.class.max_listeners+1).times do
|
194
|
+
emitter.on(:some_event, lambda{})
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'will not print a warning if max_listeners is set to 0' do
|
199
|
+
emitter.class.max_listeners = 0
|
200
|
+
$stdout.should_not_receive(:puts).with('Warning: Emitter has more than 10 registered listeners.')
|
201
|
+
1000.times do
|
202
|
+
emitter.on(:some_event, lambda{})
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe '#emit' do
|
169
209
|
let(:emitter) { Emitter.new }
|
170
210
|
let(:watcher) { Watcher.new }
|
171
211
|
|
@@ -186,7 +226,6 @@ describe Eventually do
|
|
186
226
|
def proc_callback
|
187
227
|
proc{|payload| @value += payload }
|
188
228
|
end
|
189
|
-
|
190
229
|
def update_method(payload)
|
191
230
|
@value += payload
|
192
231
|
end
|
@@ -216,14 +255,18 @@ describe Eventually do
|
|
216
255
|
it_behaves_like 'emitting an event', :method
|
217
256
|
it_behaves_like 'emitting an event', :proc
|
218
257
|
|
219
|
-
it 'emits
|
220
|
-
|
258
|
+
it 'emits an event on the emitter when a new listener is added' do
|
259
|
+
callback_to_add = lambda{ puts 'hi' }
|
260
|
+
listener_id = nil
|
261
|
+
emitter.on(:listener_added) do |listener|
|
262
|
+
listener_id = listener.callable.object_id
|
263
|
+
end
|
264
|
+
emitter.on(:some_event, callback_to_add)
|
265
|
+
listener_id.should eq callback_to_add.object_id
|
221
266
|
end
|
222
267
|
|
223
|
-
it '
|
224
|
-
expect { emitter.
|
225
|
-
expect { emitter.on(:start, 10_000) }.should raise_error(/Cannot register callback/)
|
226
|
-
expect { emitter.on(:start, "callback") }.should raise_error(/Cannot register callback/)
|
268
|
+
it 'emits nothing when no event callbacks are given' do
|
269
|
+
expect { emitter.__send__(:emit, :hullabaloo) }.should_not raise_error
|
227
270
|
end
|
228
271
|
|
229
272
|
it 'invokes registered callbacks in a FIFO manner' do
|
@@ -244,33 +287,18 @@ describe Eventually do
|
|
244
287
|
end
|
245
288
|
|
246
289
|
context 'when arity validation is enabled' do
|
247
|
-
|
248
|
-
|
249
|
-
expect {
|
250
|
-
emitter.on(:hello_world) do |param1, param2|
|
251
|
-
#not invoked
|
252
|
-
end
|
253
|
-
}.should_not raise_error
|
254
|
-
end
|
255
|
-
|
256
|
-
it 'rejects a callback if the given arity is not exact' do
|
257
|
-
expect {
|
258
|
-
emitter.on(:hello_world) do |param1, param2, param3|
|
259
|
-
#not invoked
|
260
|
-
end
|
261
|
-
}.should raise_error(/Invalid callback arity for event :hello_world \(expected 2, received 3\)/)
|
262
|
-
end
|
263
|
-
|
264
|
-
it 'accepts emitting an event when arity is valid' do
|
290
|
+
it 'emits the event when arity is valid' do
|
291
|
+
emitter.class.emits(:hello_world, arity: 2)
|
265
292
|
expect {
|
266
293
|
emitter.__send__(:emit, :hello_world, "hello", "world")
|
267
294
|
}.should_not raise_error
|
268
295
|
end
|
269
296
|
|
270
297
|
it 'rejects emitting an event when the arity is not exact' do
|
298
|
+
emitter.class.emits(:hello_world, arity: 2)
|
271
299
|
expect {
|
272
300
|
emitter.__send__(:emit, :hello_world, "hello")
|
273
|
-
}.should raise_error(/
|
301
|
+
}.should raise_error(/Arity validation failed for event :hello_world \(expected 2, received 1\)/)
|
274
302
|
end
|
275
303
|
end
|
276
304
|
|
@@ -286,40 +314,76 @@ describe Eventually do
|
|
286
314
|
end
|
287
315
|
end
|
288
316
|
end
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
emitter.on(:jump) do |param1, param2|
|
297
|
-
puts 'hi'
|
298
|
-
end
|
299
|
-
}.should_not raise_error
|
300
|
-
end
|
301
|
-
end
|
302
|
-
context 'and the arity does not match' do
|
303
|
-
it 'raises an error' do
|
304
|
-
Emitter.emits(:jump, arity: 2)
|
305
|
-
expect {
|
306
|
-
emitter.on(:jump) do |param1|
|
307
|
-
puts 'hi'
|
308
|
-
end
|
309
|
-
}.should raise_error(/expected 2, received 1/)
|
310
|
-
end
|
311
|
-
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe '#once' do
|
320
|
+
it 'registers to an event for one time only, then releases the registration' do
|
321
|
+
value = 1
|
322
|
+
emitter.once(:start) do
|
323
|
+
value += 1
|
312
324
|
end
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
325
|
+
listener_count = emitter.num_listeners
|
326
|
+
|
327
|
+
emitter.__send__(:emit, :start)
|
328
|
+
emitter.__send__(:emit, :start)
|
329
|
+
value.should eq 2
|
330
|
+
emitter.num_listeners.should eq(listener_count - 1)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe '#remove_listener' do
|
335
|
+
it 'removes a given event callback' do
|
336
|
+
value = 1
|
337
|
+
cbk = lambda{ value += 1 }
|
338
|
+
emitter.on(:some_event, cbk)
|
339
|
+
emitter.__send__(:emit, :some_event)
|
340
|
+
value.should eq 2
|
341
|
+
emitter.remove_listener(:some_event, cbk)
|
342
|
+
emitter.__send__(:emit, :some_event)
|
343
|
+
value.should eq 2
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
describe '#remove_all_listeners' do
|
348
|
+
it 'removes all listeners for the given event' do
|
349
|
+
value = 1
|
350
|
+
emitter.on(:some_event) do
|
351
|
+
value += 1
|
352
|
+
end
|
353
|
+
emitter.on(:some_event) do
|
354
|
+
value += 5
|
355
|
+
end
|
356
|
+
emitter.on(:some_event) do
|
357
|
+
value += 10
|
322
358
|
end
|
359
|
+
emitter.__send__(:emit, :some_event)
|
360
|
+
value.should eq 17
|
361
|
+
|
362
|
+
emitter.remove_all_listeners(:some_event)
|
363
|
+
emitter.__send__(:emit, :some_event)
|
364
|
+
value.should eq 17
|
323
365
|
end
|
324
366
|
end
|
367
|
+
|
368
|
+
describe '#listeners' do
|
369
|
+
it 'returns a list of listener callbacks for the given event' do
|
370
|
+
cb1 = lambda{}
|
371
|
+
cb2 = lambda{}
|
372
|
+
cb3 = lambda{}
|
373
|
+
emitter.on(:some_event, cb1)
|
374
|
+
emitter.on(:some_event, cb2)
|
375
|
+
emitter.on(:some_event, cb3)
|
376
|
+
emitter.listeners(:some_event).should eq [cb1, cb2, cb3]
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
describe '#num_listeners' do
|
381
|
+
it 'counts all listeners across all events' do
|
382
|
+
emitter.on(:event1, lambda{})
|
383
|
+
emitter.on(:event2, lambda{})
|
384
|
+
emitter.on(:event3, lambda{})
|
385
|
+
emitter.num_listeners.should eq 3
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
325
389
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'eventually/validation/arity'
|
3
|
+
|
4
|
+
unless defined?(ArityEmitter)
|
5
|
+
class ArityEmitter
|
6
|
+
include Eventually
|
7
|
+
emits :started, arity: 1
|
8
|
+
emits :stopped
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Eventually::Validation::Arity do
|
13
|
+
let(:emitter) { ArityEmitter.new }
|
14
|
+
|
15
|
+
describe '#valid?' do
|
16
|
+
context 'when arity defined for event' do
|
17
|
+
context 'and arity does not match' do
|
18
|
+
subject { Eventually::Validation::Arity.new(:started, emitter, 2) }
|
19
|
+
it 'is invalid' do
|
20
|
+
subject.valid?.should eq false
|
21
|
+
subject.expected.should eq 1
|
22
|
+
subject.received.should eq 2
|
23
|
+
end
|
24
|
+
end
|
25
|
+
context 'and arity matches' do
|
26
|
+
subject { Eventually::Validation::Arity.new(:started, emitter, 1) }
|
27
|
+
it 'is valid' do
|
28
|
+
subject.valid?.should eq true
|
29
|
+
subject.expected.should eq 1
|
30
|
+
subject.received.should eq 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context 'when event is defined' do
|
35
|
+
context 'and arity is not defined' do
|
36
|
+
subject { Eventually::Validation::Arity.new(:stopped, emitter, 3) }
|
37
|
+
it 'is valid' do
|
38
|
+
subject.valid?.should eq true
|
39
|
+
subject.expected.should eq -2
|
40
|
+
subject.received.should eq 3
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
context 'when event is not defined' do
|
45
|
+
context 'and arity is not defined' do
|
46
|
+
subject { Eventually::Validation::Arity.new(:something_else, emitter, 3) }
|
47
|
+
it 'is valid' do
|
48
|
+
subject.valid?.should eq true
|
49
|
+
subject.expected.should eq nil
|
50
|
+
subject.received.should eq 3
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#raise_unless_valid!' do
|
57
|
+
subject { Eventually::Validation::Arity.new(:an_event, emitter, 0)}
|
58
|
+
|
59
|
+
context 'when not valid' do
|
60
|
+
it 'does not raise an error' do
|
61
|
+
subject.stub(:valid?).and_return(true)
|
62
|
+
expect { subject.raise_unless_valid! }.should_not raise_error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when not valid' do
|
67
|
+
it 'raises an error' do
|
68
|
+
subject.stub(:valid?).and_return(false)
|
69
|
+
expect { subject.raise_unless_valid! }.should raise_error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'eventually/validation/max_listeners'
|
3
|
+
|
4
|
+
unless defined?(MaxListenerEmitter)
|
5
|
+
class MaxListenerEmitter
|
6
|
+
include Eventually
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Eventually::Validation::MaxListeners do
|
11
|
+
let(:emitter) { MaxListenerEmitter.new }
|
12
|
+
subject { Eventually::Validation::MaxListeners.new(emitter) }
|
13
|
+
|
14
|
+
describe '#valid?' do
|
15
|
+
context 'when max_listeners is non-zero positive integer' do
|
16
|
+
context 'when num_listeners is less than or equal to max_listeners' do
|
17
|
+
it 'is valid' do
|
18
|
+
emitter.class.max_listeners = 5
|
19
|
+
5.times{|i| emitter.on("event"+i.to_s, lambda{}) }
|
20
|
+
subject.valid?.should eq true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
context 'when num_listeners is greater than max_listeners' do
|
24
|
+
it 'is not valid' do
|
25
|
+
emitter.class.max_listeners = 3
|
26
|
+
5.times{|i| emitter.on("event"+i.to_s, lambda{}) }
|
27
|
+
subject.valid?.should eq false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
context 'when max_listeners is zero' do
|
32
|
+
it 'is valid' do
|
33
|
+
emitter.class.max_listeners = 0
|
34
|
+
1000.times{|i| emitter.on("event"+i.to_s, lambda{}) }
|
35
|
+
subject.valid?.should eq true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#warn_unless_valid!' do
|
41
|
+
context 'when not valid' do
|
42
|
+
it 'does not raise an error' do
|
43
|
+
subject.stub(:valid?).and_return(true)
|
44
|
+
$stdout.should_not_receive(:puts)
|
45
|
+
subject.warn_unless_valid!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when not valid' do
|
50
|
+
it 'raises an error' do
|
51
|
+
subject.stub(:valid?).and_return(false)
|
52
|
+
$stdout.should_receive(:puts).with(/Warning: MaxListenerEmitter has more than \d+ registered listeners\./)
|
53
|
+
subject.warn_unless_valid!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eventually
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-12-
|
12
|
+
date: 2011-12-21 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &2161573120 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,29 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2161573120
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
requirement: &2161572700 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2161572700
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: simplecov
|
38
|
+
requirement: &2152246440 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2152246440
|
25
47
|
description: Eventually is an event library built to loosely mirror the NodeJS EventEmitter
|
26
48
|
API.
|
27
49
|
email:
|
@@ -40,8 +62,16 @@ files:
|
|
40
62
|
- examples/basic.rb
|
41
63
|
- examples/strict.rb
|
42
64
|
- lib/eventually.rb
|
65
|
+
- lib/eventually/callable.rb
|
66
|
+
- lib/eventually/event.rb
|
67
|
+
- lib/eventually/validation/arity.rb
|
68
|
+
- lib/eventually/validation/max_listeners.rb
|
43
69
|
- lib/eventually/version.rb
|
70
|
+
- spec/lib/callable_spec.rb
|
71
|
+
- spec/lib/event_spec.rb
|
44
72
|
- spec/lib/eventually_spec.rb
|
73
|
+
- spec/lib/validation/arity_spec.rb
|
74
|
+
- spec/lib/validation/max_listeners_spec.rb
|
45
75
|
- spec/spec_helper.rb
|
46
76
|
homepage: http://www.rand9.com
|
47
77
|
licenses: []
|
@@ -69,5 +99,10 @@ specification_version: 3
|
|
69
99
|
summary: Eventually is an event library built to loosely mirror the NodeJS EventEmitter
|
70
100
|
API.
|
71
101
|
test_files:
|
102
|
+
- spec/lib/callable_spec.rb
|
103
|
+
- spec/lib/event_spec.rb
|
72
104
|
- spec/lib/eventually_spec.rb
|
105
|
+
- spec/lib/validation/arity_spec.rb
|
106
|
+
- spec/lib/validation/max_listeners_spec.rb
|
73
107
|
- spec/spec_helper.rb
|
108
|
+
has_rdoc:
|