eventually 0.0.1
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/.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
|