eventually 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.md +86 -0
- data/Rakefile +1 -0
- data/eventually.gemspec +23 -0
- data/examples/arity.rb +39 -0
- data/examples/basic.rb +53 -0
- data/examples/strict.rb +27 -0
- data/lib/eventually.rb +169 -0
- data/lib/eventually/version.rb +3 -0
- data/spec/lib/eventually_spec.rb +325 -0
- data/spec/spec_helper.rb +3 -0
- metadata +73 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.2@eventually --create
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Eventually
|
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 lambda-ish callback styles is heavily baked in, so using blocks, lambdas, procs, or event 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
|
+
|
5
|
+
```ruby
|
6
|
+
class Car
|
7
|
+
include Eventually
|
8
|
+
def stop
|
9
|
+
#...
|
10
|
+
emit(:stopped, 0)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
car = Car.new
|
15
|
+
car.on(:stopped) do |mph|
|
16
|
+
puts 'the car stopped, sitting at %d mph' % mph
|
17
|
+
end
|
18
|
+
car.stop # this will indirectly invoke the above callback
|
19
|
+
```
|
20
|
+
|
21
|
+
## Pre-define Events
|
22
|
+
|
23
|
+
For documentation purposes, it can often be nice to define up front what events you'll be expecting to emit from the instances of a certain class. Anyone who's ever spent a couple of minutes trying to dig up their database columns from an ActiveRecord model knows what I'm talking about. Annoying. So `Eventually` let's you put that all up front in a nice DSL.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Car
|
27
|
+
include Eventually
|
28
|
+
emits :stopped, :started, :turning
|
29
|
+
emits :reversing
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
The previous snippet acts mostly as documentation.
|
34
|
+
|
35
|
+
*See the **examples/basic.rb** file for a slightly more complicated setup than this one.*
|
36
|
+
|
37
|
+
## Callback arity validation
|
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.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class Car
|
43
|
+
include Eventually
|
44
|
+
emits :driving, :arity => 1
|
45
|
+
end
|
46
|
+
car = Car.new
|
47
|
+
car.on(:driving) do
|
48
|
+
puts 'The car is driving'
|
49
|
+
end
|
50
|
+
# Error will be raise explaining the arity mismatch (expected 1, received -1)
|
51
|
+
```
|
52
|
+
|
53
|
+
*See the **examples/arity.rb** file for more on this.*
|
54
|
+
|
55
|
+
## Strict Mode
|
56
|
+
|
57
|
+
Strict mode is useful if you want to enforce the `#emits` documentation as being the **ONLY** events that your instances can emit or be registered against.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class Car
|
61
|
+
include Eventually
|
62
|
+
|
63
|
+
enable_strict!
|
64
|
+
emits :started, :stopped
|
65
|
+
|
66
|
+
def turn
|
67
|
+
# Emitting :turning event here will throw an error in strict mode
|
68
|
+
emit(:turning)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
car = Car.new
|
73
|
+
# Registering for the :turning event here will throw an error in strict mode
|
74
|
+
car.on(:turning) do
|
75
|
+
puts 'the car is turning'
|
76
|
+
end
|
77
|
+
|
78
|
+
*See the **examples/scrict.rb** file for more on this.*
|
79
|
+
|
80
|
+
## More?
|
81
|
+
|
82
|
+
Further examples can be found in the examples directory. I know, novel idea that one.
|
83
|
+
|
84
|
+
## Contact
|
85
|
+
|
86
|
+
[@localshred](http://twitter.com/localshred) wrote this. He [sometimes blogs](http://www.rand9.com) too.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/eventually.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "eventually/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "eventually"
|
7
|
+
s.version = Eventually::VERSION
|
8
|
+
s.authors = ["BJ Neilsen"]
|
9
|
+
s.email = ["bj.neilsen@gmail.com"]
|
10
|
+
s.homepage = "http://www.rand9.com"
|
11
|
+
s.summary = %q{Eventually is an event library built to loosely mirror the NodeJS EventEmitter API.}
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "eventually"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
end
|
data/examples/arity.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'eventually'
|
3
|
+
|
4
|
+
class Car
|
5
|
+
include Eventually
|
6
|
+
emits(:driving, :arity => 1)
|
7
|
+
emits(:stopped, :arity => 0)
|
8
|
+
emits(:reversing, :arity => -1)
|
9
|
+
emits(:turning, :arity => 3)
|
10
|
+
end
|
11
|
+
|
12
|
+
car = Car.new
|
13
|
+
car.on(:driving) do |mph|
|
14
|
+
puts 'The car is traveling %d mph' % mph
|
15
|
+
end
|
16
|
+
|
17
|
+
# Notice the odd empty "pipes" below...
|
18
|
+
# Checking arity on a block will give -1 for no args.
|
19
|
+
# If you're expecting arity == 0 you have to pass empty pipes (e.g. do ||)
|
20
|
+
# In other words, it doesn't make a ton of sense to
|
21
|
+
# expect an arity of zero, better a -1 validation such
|
22
|
+
# as on the :reversing event
|
23
|
+
car.on(:stopped) do ||
|
24
|
+
puts 'The car stopped'
|
25
|
+
end
|
26
|
+
|
27
|
+
# Validated on -1 (no arguments)
|
28
|
+
car.on(:reversing) do
|
29
|
+
puts 'The car is reversing'
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
car.on(:turning) do |direction|
|
34
|
+
puts 'Car is turning %s' % direction
|
35
|
+
end
|
36
|
+
rescue => e
|
37
|
+
# "Invalid callback arity for event :turning (expected 3, received 1)"
|
38
|
+
puts e.message
|
39
|
+
end
|
data/examples/basic.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'eventually'
|
3
|
+
|
4
|
+
SPEED_LIMIT = 65
|
5
|
+
|
6
|
+
class SpeedingCar
|
7
|
+
include Eventually
|
8
|
+
|
9
|
+
# Document the events we'll likely emit
|
10
|
+
emits :stopped, :driving
|
11
|
+
|
12
|
+
def stop
|
13
|
+
puts 'Car is stopped'
|
14
|
+
emit(:stopped)
|
15
|
+
end
|
16
|
+
|
17
|
+
def go(mph)
|
18
|
+
puts 'Car is driving %d mph' % mph
|
19
|
+
emit(:driving, mph)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class PoliceCar
|
24
|
+
def initialize(speeding_car)
|
25
|
+
speeding_car.on(:stopped, method(:eat_donut))
|
26
|
+
speeding_car.on(:driving, method(:arrest_if_speeding))
|
27
|
+
end
|
28
|
+
|
29
|
+
def eat_donut
|
30
|
+
puts 'CHOMP'
|
31
|
+
end
|
32
|
+
|
33
|
+
def arrest_if_speeding(speed)
|
34
|
+
if speed > SPEED_LIMIT
|
35
|
+
puts 'ARREST THEM!'
|
36
|
+
else
|
37
|
+
eat_donut
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
car = SpeedingCar.new
|
43
|
+
police = PoliceCar.new(car)
|
44
|
+
car.stop
|
45
|
+
car.go(100)
|
46
|
+
|
47
|
+
# The above will write to stdout:
|
48
|
+
#
|
49
|
+
# Car is stopped
|
50
|
+
# CHOMP
|
51
|
+
# Car is driving 100 mph
|
52
|
+
# ARREST THEM!
|
53
|
+
#
|
data/examples/strict.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'eventually'
|
3
|
+
|
4
|
+
class Door
|
5
|
+
include Eventually
|
6
|
+
enable_strict!
|
7
|
+
emits :opened, :closed
|
8
|
+
end
|
9
|
+
|
10
|
+
door = Door.new
|
11
|
+
door.on(:opened) do
|
12
|
+
puts 'door was opened'
|
13
|
+
end
|
14
|
+
|
15
|
+
door.on(:closed) do
|
16
|
+
puts 'door was closed'
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
# This will raise an error!
|
21
|
+
door.on(:slammed) do
|
22
|
+
puts 'oh noes'
|
23
|
+
end
|
24
|
+
rescue => e
|
25
|
+
# "Event type :slammed will not be emitted. Use Door.emits(:slammed)"
|
26
|
+
puts e.message
|
27
|
+
end
|
data/lib/eventually.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require "eventually/version"
|
2
|
+
|
3
|
+
# Eventually is a module that facilitates evented callback
|
4
|
+
# management similar to the EventEmitter API in NodeJS.
|
5
|
+
# Simply include in the class you will be emitting events
|
6
|
+
# from and fire away.
|
7
|
+
#
|
8
|
+
# Support exists for strict mode, pre-defining the events
|
9
|
+
# you plan on emitting, and arity validation on callbacks.
|
10
|
+
# See the docs below or the examples folder for further documentation.
|
11
|
+
module Eventually
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
NO_CHECK_ARITY = -1
|
18
|
+
attr_accessor :emittable_events
|
19
|
+
|
20
|
+
# Define an event or list of events
|
21
|
+
# that instances of this class will potentially emit.
|
22
|
+
#
|
23
|
+
# Usage (list of events):
|
24
|
+
# emits :started, :stopped
|
25
|
+
#
|
26
|
+
# Usage (single event):
|
27
|
+
# emits :started
|
28
|
+
#
|
29
|
+
# Usage (single event with arity validation):
|
30
|
+
# emits :started, :arity => 2
|
31
|
+
#
|
32
|
+
def emits(*evts)
|
33
|
+
if evts && !evts.empty?
|
34
|
+
if evts.all?{|e| e.is_a?(Symbol) }
|
35
|
+
evts.each{|e| emittable[e.to_sym] = NO_CHECK_ARITY }
|
36
|
+
elsif evts.count == 2
|
37
|
+
emittable[evts[0].to_sym] = (evts[1].fetch(:arity) { NO_CHECK_ARITY }).to_i
|
38
|
+
end
|
39
|
+
else
|
40
|
+
emittable.keys
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check if instances of this class have pre-defined
|
45
|
+
# the given event as potentially emittable
|
46
|
+
def emits?(evt)
|
47
|
+
emittable.key?(evt.to_sym)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Puts instances into strict mode. This does two things:
|
51
|
+
# - Raise an error if registering a callback for an event
|
52
|
+
# that has not already been pre-defined (e.g. with #emits)
|
53
|
+
# - Raise an error if instance attempts to emit an event
|
54
|
+
# that has not already been pre-defined (e.g. with #emits)
|
55
|
+
def enable_strict!
|
56
|
+
@strict = true
|
57
|
+
end
|
58
|
+
|
59
|
+
# The default state of an Eventually instance. Does not require
|
60
|
+
# pre-definition of an event to register against it or emit it
|
61
|
+
def disable_strict!
|
62
|
+
@strict = false
|
63
|
+
end
|
64
|
+
|
65
|
+
# Are we strict or not
|
66
|
+
def strict?
|
67
|
+
@strict || false
|
68
|
+
end
|
69
|
+
|
70
|
+
# Determines if the given event has an arity validation assigned
|
71
|
+
def validates_arity?(evt)
|
72
|
+
emits?(evt) && emittable[evt.to_sym] > NO_CHECK_ARITY
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the arity validation, nil if event isn't defined
|
76
|
+
def arity_for_event(evt)
|
77
|
+
emits?(evt) ? emittable[evt.to_sym] : nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# Reset the known emittable events (events defined with #emits)
|
81
|
+
# More useful for tests probably, but leaving it in API just 'cause
|
82
|
+
def emits_none
|
83
|
+
@emittable = {}
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Shorthand predicate to determine if a given event is
|
88
|
+
# "emittable" or "registerable"
|
89
|
+
def can_emit_or_register?(event)
|
90
|
+
!strict? || emits?(event)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def emittable
|
96
|
+
@emittable ||= {}
|
97
|
+
end
|
98
|
+
end # ClassMethods
|
99
|
+
|
100
|
+
# Event registration method. Takes an event to register against and either
|
101
|
+
# a callable object (e.g. proc/lambda/detached method) or a block
|
102
|
+
#
|
103
|
+
# Usage: see Eventually#emit or examples directory
|
104
|
+
#
|
105
|
+
def on(event, callable=nil, &blk)
|
106
|
+
raise "Event type :#{event} will not be emitted. Use #{self.class.name}.emits(:#{event})" unless self.class.can_emit_or_register?(event)
|
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
|
116
|
+
|
117
|
+
# if self.class.validates_arity?(event) && cbk.arity != (expected_arity = self.class.arity_for_event(event))
|
118
|
+
unless valid_event_arity?(event, cbk.arity)
|
119
|
+
raise "Invalid callback arity for event :#{event} (expected #{self.class.arity_for_event(event)}, received #{cbk.arity})"
|
120
|
+
end
|
121
|
+
|
122
|
+
(__registered__[event.to_sym] ||= []) << cbk
|
123
|
+
end
|
124
|
+
|
125
|
+
# Emit the payload arguments back to the registered listeners
|
126
|
+
# on the given event. FIFO calling, and we won't deal with
|
127
|
+
# concurrency, so it should be handled at the callback level.
|
128
|
+
#
|
129
|
+
# Usage:
|
130
|
+
# class Car
|
131
|
+
# include Eventually
|
132
|
+
# def stop
|
133
|
+
# #...
|
134
|
+
# emit(:stopped, 0)
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# car = Car.new
|
139
|
+
# car.on(:stopped) do |mph|
|
140
|
+
# puts 'the car stopped, sitting at %d mph' % mph
|
141
|
+
# end
|
142
|
+
# car.stop # this will indirectly invoke the above callback
|
143
|
+
#
|
144
|
+
def emit(event, *payload)
|
145
|
+
raise "Event type :#{event} cannot be emitted. Use #{self.class.name}.emits(:#{event})" unless self.class.can_emit_or_register?(event)
|
146
|
+
|
147
|
+
unless valid_event_arity?(event, payload.length)
|
148
|
+
raise "Invalid emit arity for event :#{event} (expected #{self.class.arity_for_event(event)}, received #{payload.length})"
|
149
|
+
end
|
150
|
+
|
151
|
+
(__registered__[event.to_sym] || []).each do |cbk|
|
152
|
+
cbk.call(*payload)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Shorthand predicate to determine if the given cbk for this event
|
157
|
+
# has a valid arity amount
|
158
|
+
def valid_event_arity?(event, arity_count)
|
159
|
+
expected_arity = self.class.arity_for_event(event)
|
160
|
+
!self.class.validates_arity?(event) || arity_count == expected_arity
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def __registered__
|
166
|
+
@__registered__ ||= {}
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Emitter
|
4
|
+
include Eventually
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Eventually do
|
8
|
+
before(:each) do
|
9
|
+
Emitter.disable_strict!
|
10
|
+
Emitter.emits_none
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:emitter) { Emitter.new }
|
14
|
+
let(:defined_events) { [:one, :two, :three] }
|
15
|
+
|
16
|
+
context 'external api' do
|
17
|
+
describe '.emits_none' do
|
18
|
+
it 'clears out emitter definitions' do
|
19
|
+
Emitter.emits(:jigger)
|
20
|
+
Emitter.emits?(:jigger).should be_true
|
21
|
+
Emitter.emits_none
|
22
|
+
Emitter.emits?(:jigger).should be_false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.emits' do
|
27
|
+
it 'allows event definition at class level' do
|
28
|
+
Emitter.emits(:jigger)
|
29
|
+
Emitter.emits?(:jigger).should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can register multiple event symbols at once' do
|
33
|
+
Emitter.emits(*defined_events)
|
34
|
+
defined_events.each {|e| Emitter.emits?(e).should be_true }
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'provides a list of pre-defined emittable events' do
|
38
|
+
Emitter.emits(*defined_events)
|
39
|
+
Emitter.emits.should eq defined_events
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.enable_strict!' do
|
43
|
+
it 'requires the event to have been pre-defined for watchers to register callbacks to it' do
|
44
|
+
Emitter.enable_strict!
|
45
|
+
Emitter.emits(:start)
|
46
|
+
Emitter.emits?(:start).should be_true
|
47
|
+
expect {
|
48
|
+
emitter.on(:start, lambda{ puts 'hi' })
|
49
|
+
}.should_not raise_error
|
50
|
+
expect {
|
51
|
+
emitter.on(:stop, lambda{ puts 'hi' })
|
52
|
+
}.should raise_error(/Event type :stop will not be emitted/)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '.disable_strict!' do
|
57
|
+
it 'disables strict mode' do
|
58
|
+
Emitter.disable_strict!
|
59
|
+
Emitter.emits?(:start).should be_false
|
60
|
+
expect {
|
61
|
+
emitter.on(:start, lambda{ puts 'hi' })
|
62
|
+
}.should_not raise_error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '.strict?' do
|
67
|
+
context 'when strict mode is enabled' do
|
68
|
+
it 'returns true' do
|
69
|
+
Emitter.enable_strict!
|
70
|
+
Emitter.strict?.should be_true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when strict mode is disabled' do
|
75
|
+
it 'returns false' do
|
76
|
+
Emitter.disable_strict!
|
77
|
+
Emitter.strict?.should be_false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when providing an arity validation' do
|
83
|
+
it 'sets an arity expectation for future event callbacks' do
|
84
|
+
Emitter.emits(:jigger, arity: 5)
|
85
|
+
Emitter.emits?(:jigger)
|
86
|
+
Emitter.validates_arity?(:jigger).should be_true
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'allows 0 as a specified arity' do
|
90
|
+
Emitter.emits(:jigger, arity: 0)
|
91
|
+
Emitter.emits?(:jigger)
|
92
|
+
Emitter.validates_arity?(:jigger).should be_true
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '.arity_for_event' do
|
96
|
+
it 'reports the arity requirement for the event, if any' do
|
97
|
+
Emitter.emits(:jigger, arity: 5)
|
98
|
+
Emitter.arity_for_event(:jigger).should eq 5
|
99
|
+
Emitter.emits(:pingpong)
|
100
|
+
Emitter.arity_for_event(:pingpong).should eq -1
|
101
|
+
Emitter.arity_for_event(:nonevent).should eq nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
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
|
+
it 'allows multiple registrations for a given event' do
|
127
|
+
emitter.on(:start) { puts 'hello' }
|
128
|
+
emitter.on(:start) { puts 'world' }
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '.can_emit_or_register?' do
|
132
|
+
context 'when strict mode enabled' do
|
133
|
+
before { Emitter.enable_strict! }
|
134
|
+
context 'when given event is registered' do
|
135
|
+
it 'returns true' do
|
136
|
+
Emitter.emits(:known)
|
137
|
+
Emitter.emits?(:known).should be_true
|
138
|
+
Emitter.can_emit_or_register?(:known).should be_true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
context 'when given event is not registered' do
|
142
|
+
it 'returns false' do
|
143
|
+
Emitter.emits?(:unknown).should be_false
|
144
|
+
Emitter.can_emit_or_register?(:unknown).should be_false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when strict mode disabled' do
|
150
|
+
before { Emitter.disable_strict! }
|
151
|
+
context 'when given event is registered' do
|
152
|
+
it 'returns true' do
|
153
|
+
Emitter.emits(:known)
|
154
|
+
Emitter.emits?(:known).should be_true
|
155
|
+
Emitter.can_emit_or_register?(:known).should be_true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
context 'when given event is not registered' do
|
159
|
+
it 'returns true' do
|
160
|
+
Emitter.emits?(:unknown).should be_false
|
161
|
+
Emitter.can_emit_or_register?(:unknown).should be_true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'when emitting events' do
|
169
|
+
let(:emitter) { Emitter.new }
|
170
|
+
let(:watcher) { Watcher.new }
|
171
|
+
|
172
|
+
class Watcher
|
173
|
+
attr_accessor :value
|
174
|
+
def initialize
|
175
|
+
@value = 1
|
176
|
+
end
|
177
|
+
def block_callback
|
178
|
+
Proc.new{|payload| @value += payload }
|
179
|
+
end
|
180
|
+
def lambda_callback
|
181
|
+
lambda{|payload| @value += payload }
|
182
|
+
end
|
183
|
+
def method_callback
|
184
|
+
method(:update_method)
|
185
|
+
end
|
186
|
+
def proc_callback
|
187
|
+
proc{|payload| @value += payload }
|
188
|
+
end
|
189
|
+
|
190
|
+
def update_method(payload)
|
191
|
+
@value += payload
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
shared_examples_for 'emitting an event' do |cbk_type|
|
196
|
+
it "by invoking a #{cbk_type} callback" do
|
197
|
+
expect {
|
198
|
+
case cbk_type
|
199
|
+
when :block then
|
200
|
+
emitter.on(:start, &watcher.block_callback)
|
201
|
+
when :lambda then
|
202
|
+
emitter.on(:start, watcher.lambda_callback)
|
203
|
+
when :method then
|
204
|
+
emitter.on(:start, watcher.method_callback)
|
205
|
+
when :proc then
|
206
|
+
emitter.on(:start, watcher.proc_callback)
|
207
|
+
end
|
208
|
+
}.should_not raise_error(/Cannot register callback/)
|
209
|
+
emitter.__send__(:emit, :start, 1)
|
210
|
+
watcher.value.should eq 2
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it_behaves_like 'emitting an event', :block
|
215
|
+
it_behaves_like 'emitting an event', :lambda
|
216
|
+
it_behaves_like 'emitting an event', :method
|
217
|
+
it_behaves_like 'emitting an event', :proc
|
218
|
+
|
219
|
+
it 'emits nothing when no event callbacks are given' do
|
220
|
+
expect { emitter.__send__(:emit, :hullabaloo) }.should_not raise_error
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'raises an error when a given callback is invalid' do
|
224
|
+
expect { emitter.on(:start, nil, &nil) }.should raise_error(/Cannot register callback/)
|
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/)
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'invokes registered callbacks in a FIFO manner' do
|
230
|
+
watcher1 = Watcher.new
|
231
|
+
watcher1.value = []
|
232
|
+
|
233
|
+
watcher2 = Watcher.new
|
234
|
+
watcher2.value = []
|
235
|
+
|
236
|
+
emitter.on(:push) {|v| watcher1.value << "A"+v }
|
237
|
+
emitter.on(:push) {|v| watcher2.value << "B"+v }
|
238
|
+
emitter.on(:push) {|v| watcher2.value << "C"+v }
|
239
|
+
emitter.on(:push) {|v| watcher1.value << "D"+v }
|
240
|
+
emitter.__send__(:emit, :push, "-VALUE")
|
241
|
+
|
242
|
+
watcher1.value.should eq ["A-VALUE", "D-VALUE"]
|
243
|
+
watcher2.value.should eq ["B-VALUE", "C-VALUE"]
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'when arity validation is enabled' do
|
247
|
+
before { Emitter.emits(:hello_world, arity: 2) }
|
248
|
+
it 'accepts a callback with matching arity' do
|
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
|
265
|
+
expect {
|
266
|
+
emitter.__send__(:emit, :hello_world, "hello", "world")
|
267
|
+
}.should_not raise_error
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'rejects emitting an event when the arity is not exact' do
|
271
|
+
expect {
|
272
|
+
emitter.__send__(:emit, :hello_world, "hello")
|
273
|
+
}.should raise_error(/Invalid emit arity for event :hello_world \(expected 2, received 1\)/)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context 'when strict mode is enabled' do
|
278
|
+
context 'when emitting an event not previously defined' do
|
279
|
+
it 'raises an error concerning the unknown event type' do
|
280
|
+
emitter.class.enable_strict!
|
281
|
+
emitter.class.strict?.should be_true
|
282
|
+
emitter.class.emits?(:stop).should be_false
|
283
|
+
expect {
|
284
|
+
emitter.__send__(:emit, :stop)
|
285
|
+
}.should raise_error(/Event type :stop cannot be emitted/)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe '#valid_event_arity?' do
|
291
|
+
context 'when arity is validated for event' do
|
292
|
+
context 'and the arity matches' do
|
293
|
+
it 'doesn\'t raise an error' do
|
294
|
+
Emitter.emits(:jump, arity: 2)
|
295
|
+
expect {
|
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
|
312
|
+
end
|
313
|
+
context 'when arity is not for event' do
|
314
|
+
it 'doesn\'t raise an error' do
|
315
|
+
Emitter.emits?(:jump).should be_false
|
316
|
+
expect {
|
317
|
+
emitter.on(:jump) do |param1, param2|
|
318
|
+
puts 'hi'
|
319
|
+
end
|
320
|
+
}.should_not raise_error
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eventually
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- BJ Neilsen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-20 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &2153560520 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2153560520
|
25
|
+
description: Eventually is an event library built to loosely mirror the NodeJS EventEmitter
|
26
|
+
API.
|
27
|
+
email:
|
28
|
+
- bj.neilsen@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- .rvmrc
|
35
|
+
- Gemfile
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- eventually.gemspec
|
39
|
+
- examples/arity.rb
|
40
|
+
- examples/basic.rb
|
41
|
+
- examples/strict.rb
|
42
|
+
- lib/eventually.rb
|
43
|
+
- lib/eventually/version.rb
|
44
|
+
- spec/lib/eventually_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
homepage: http://www.rand9.com
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project: eventually
|
66
|
+
rubygems_version: 1.8.10
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Eventually is an event library built to loosely mirror the NodeJS EventEmitter
|
70
|
+
API.
|
71
|
+
test_files:
|
72
|
+
- spec/lib/eventually_spec.rb
|
73
|
+
- spec/spec_helper.rb
|