emittance 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec_status +29 -0
- data/.ruby-version +1 -0
- data/.yardopts +4 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +51 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/emittance.gemspec +39 -0
- data/lib/.DS_Store +0 -0
- data/lib/emittance/.DS_Store +0 -0
- data/lib/emittance/action.rb +199 -0
- data/lib/emittance/broker.rb +73 -0
- data/lib/emittance/emitter.rb +107 -0
- data/lib/emittance/event/event_builder.rb +74 -0
- data/lib/emittance/event.rb +25 -0
- data/lib/emittance/registration.rb +13 -0
- data/lib/emittance/version.rb +3 -0
- data/lib/emittance/watcher.rb +9 -0
- data/lib/emittance.rb +26 -0
- metadata +150 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 000a59df54901139fde2f395a810c5f8a7baf8fd
|
4
|
+
data.tar.gz: c23462f13256fbc860bd5bf56ae244f49e09d2bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eafdc2ad4b68f42a04f075aefee0a3359bea3c765b58130d33571e9bf1e802b124d6151e6ac183922cce24b2d84ad780fb0b2c7f94ab93d2adb9245d4fb47cea
|
7
|
+
data.tar.gz: cdc43830eb7f439764fb8e284bcf6c87bf415c69d09f1195282c4297f018cbe1dacab6c52fc2e2aa1fdb5ae46f14b35d3889c156b0b5afd7cf78952042ca47e9
|
data/.gitignore
ADDED
data/.rspec_status
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
example_id | status | run_time |
|
2
|
+
----------------------------------------------------- | ------ | --------------- |
|
3
|
+
./spec/emittance/action_spec.rb[1:1:1] | passed | 0.00947 seconds |
|
4
|
+
./spec/emittance/action_spec.rb[1:1:2] | passed | 0.0006 seconds |
|
5
|
+
./spec/emittance/broker_spec.rb[1:1:1] | passed | 0.00151 seconds |
|
6
|
+
./spec/emittance/broker_spec.rb[1:2:1] | passed | 0.0004 seconds |
|
7
|
+
./spec/emittance/broker_spec.rb[1:2:2] | passed | 0.00086 seconds |
|
8
|
+
./spec/emittance/broker_spec.rb[1:3:1] | passed | 0.00018 seconds |
|
9
|
+
./spec/emittance/broker_spec.rb[1:3:2] | passed | 0.00023 seconds |
|
10
|
+
./spec/emittance/broker_spec.rb[1:4:1] | passed | 0.00016 seconds |
|
11
|
+
./spec/emittance/broker_spec.rb[1:4:2] | passed | 0.00022 seconds |
|
12
|
+
./spec/emittance/emitter_spec.rb[1:1:1] | passed | 0.00038 seconds |
|
13
|
+
./spec/emittance/emitter_spec.rb[1:1:2] | passed | 0.00016 seconds |
|
14
|
+
./spec/emittance/emitter_spec.rb[1:2:1] | passed | 0.00033 seconds |
|
15
|
+
./spec/emittance/emitter_spec.rb[1:2:2] | passed | 0.00026 seconds |
|
16
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:1] | passed | 0.00102 seconds |
|
17
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:2] | passed | 0.00025 seconds |
|
18
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:3] | passed | 0.00017 seconds |
|
19
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:4] | passed | 0.0002 seconds |
|
20
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:5] | passed | 0.0002 seconds |
|
21
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:6] | passed | 0.00022 seconds |
|
22
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:7] | passed | 0.00019 seconds |
|
23
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:8] | passed | 0.00018 seconds |
|
24
|
+
./spec/emittance/event/event_builder_spec.rb[1:1:1:9] | passed | 0.00019 seconds |
|
25
|
+
./spec/emittance/event/event_builder_spec.rb[1:2:1] | passed | 0.00018 seconds |
|
26
|
+
./spec/emittance/event/event_builder_spec.rb[1:2:2] | passed | 0.00016 seconds |
|
27
|
+
./spec/emittance/watcher_spec.rb[1:1:1] | passed | 0.0003 seconds |
|
28
|
+
./spec/emittance/watcher_spec.rb[1:1:2] | passed | 0.00027 seconds |
|
29
|
+
./spec/emittance/watcher_spec.rb[1:1:3] | passed | 0.00039 seconds |
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.2
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
emittance (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.2)
|
10
|
+
diff-lcs (1.3)
|
11
|
+
docile (1.1.5)
|
12
|
+
json (2.1.0)
|
13
|
+
method_source (0.9.0)
|
14
|
+
pry (0.11.3)
|
15
|
+
coderay (~> 1.1.0)
|
16
|
+
method_source (~> 0.9.0)
|
17
|
+
rake (10.5.0)
|
18
|
+
rspec (3.7.0)
|
19
|
+
rspec-core (~> 3.7.0)
|
20
|
+
rspec-expectations (~> 3.7.0)
|
21
|
+
rspec-mocks (~> 3.7.0)
|
22
|
+
rspec-core (3.7.0)
|
23
|
+
rspec-support (~> 3.7.0)
|
24
|
+
rspec-expectations (3.7.0)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.7.0)
|
27
|
+
rspec-mocks (3.7.0)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.7.0)
|
30
|
+
rspec-support (3.7.0)
|
31
|
+
simplecov (0.15.1)
|
32
|
+
docile (~> 1.1.0)
|
33
|
+
json (>= 1.8, < 3)
|
34
|
+
simplecov-html (~> 0.10.0)
|
35
|
+
simplecov-html (0.10.2)
|
36
|
+
yard (0.9.12)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
bundler (~> 1.15)
|
43
|
+
emittance!
|
44
|
+
pry
|
45
|
+
rake (~> 10.0)
|
46
|
+
rspec (~> 3.0)
|
47
|
+
simplecov
|
48
|
+
yard
|
49
|
+
|
50
|
+
BUNDLED WITH
|
51
|
+
1.16.0
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Emittance
|
2
|
+
|
3
|
+
Emittance is a flexible eventing library that provides a clean interface for both emitting and capturing events. It follows the following workflow:
|
4
|
+
|
5
|
+
1. Objects (and therefore, classes) can emit events, identified by a symbol.
|
6
|
+
2. Events are objects that know who emitted them. Their
|
7
|
+
3. Objects (and therefore, classes) can watch for events that get emitted.
|
8
|
+
|
9
|
+
Per this pattern, objects are responsible for knowing what events they want to listen to. While this is pragmatically the same as a "push"-style message system (watchers don't need to go check a topic themselves), the semantics are a little different.
|
10
|
+
|
11
|
+
I created this library because I was dissatisfied with the options currently available, and I wanted to see if I could make something that I would enjoy using.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'emittance'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install emittance
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
Coming soon!
|
32
|
+
|
33
|
+
## Development
|
34
|
+
|
35
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
36
|
+
|
37
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/aastronauts/emittance.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'emittance'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require 'pry'
|
11
|
+
Pry.start
|
12
|
+
|
13
|
+
# require "irb"
|
14
|
+
# IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/emittance.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'emittance/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'emittance'
|
8
|
+
spec.version = Emittance::VERSION
|
9
|
+
spec.authors = ['Tyler Guillen']
|
10
|
+
spec.email = ['tyler@tylerguillen.com']
|
11
|
+
|
12
|
+
spec.summary = %q{A robust and flexible eventing library for Ruby.}
|
13
|
+
spec.description = %q{A robust and flexible eventing library for Ruby.}
|
14
|
+
spec.homepage = 'https://github.com/aastronautss/emittance'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata['allowed_push_host'] = 'TODO: Set to 'http://mygemserver.com''
|
21
|
+
else
|
22
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
23
|
+
'public gem pushes.'
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = 'exe'
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
|
33
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
34
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
35
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
36
|
+
spec.add_development_dependency 'pry'
|
37
|
+
spec.add_development_dependency 'yard'
|
38
|
+
spec.add_development_dependency 'simplecov'
|
39
|
+
end
|
data/lib/.DS_Store
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,199 @@
|
|
1
|
+
##
|
2
|
+
# There are certain classes (ergo objects) that represent an action taken by another object. This pattern goes like so:
|
3
|
+
#
|
4
|
+
# class Foo
|
5
|
+
# def assign
|
6
|
+
# Assignment.new(self).call
|
7
|
+
# end
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# class Assignment
|
11
|
+
# attr_reader :assignable
|
12
|
+
#
|
13
|
+
# def initialize(assignable)
|
14
|
+
# @assignable = assignable
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def call
|
18
|
+
# do_stuff
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# This pattern is useful for maintaining the single responsibility principle, delegating complex tasks to other objects
|
25
|
+
# even when (in this particular case), it might be sensible for the +assign+ message to be sent to +Foo+. This has
|
26
|
+
# numerous benefits, including the ability for actions like +Assignment+ to take a duck type.
|
27
|
+
#
|
28
|
+
# However, this can easily just become a proxy for the same antipattern it was made to solve. We might wind up with a
|
29
|
+
# +#call+ method like the following:
|
30
|
+
#
|
31
|
+
# class Assignment
|
32
|
+
# # ...
|
33
|
+
#
|
34
|
+
# def call
|
35
|
+
# do_stuff
|
36
|
+
# do_stuff_to_another_object
|
37
|
+
# do_stuff_to_something_else
|
38
|
+
# do_stuff_to_yet_another_thing
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # ...
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# +Assignment+ is suddenly collaborating with a whole bunch of objects! This isn't bad in itself, but it might cause
|
45
|
+
# some problems further on down the road as we add more responsibilities to +Assignment+. Do we really want all these
|
46
|
+
# things to happen every time an +Assignment+ happens? If not, assuming this pattern we'll have to add a bunch of
|
47
|
+
# control flow:
|
48
|
+
#
|
49
|
+
# class Assignment
|
50
|
+
# # ...
|
51
|
+
#
|
52
|
+
# def call
|
53
|
+
# do_stuff
|
54
|
+
#
|
55
|
+
# if some_condition
|
56
|
+
# do_stuff_to_another_object
|
57
|
+
#
|
58
|
+
# if some_other_condition
|
59
|
+
# do_stuff_to_something_else
|
60
|
+
# else
|
61
|
+
# do_stuff_to_yet_another_thing
|
62
|
+
# end
|
63
|
+
# elsif yet_another_condition
|
64
|
+
# do_other_stuff_to_that_other_object
|
65
|
+
# else
|
66
|
+
# dont_actually_do_anything_but_notify_someone
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# # ...
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# This is obviously an extreme example (but not unheard of!), but it gets to the core of what this module tries to
|
74
|
+
# solve. +Emittance::Action+ helps facilitate the single responsibility principle by emitting an event whenever we
|
75
|
+
# invoke +#call+ on an object like +Assignment+.
|
76
|
+
#
|
77
|
+
# == Usage
|
78
|
+
#
|
79
|
+
# First, define a class and include this module:
|
80
|
+
#
|
81
|
+
# class Assignment
|
82
|
+
# include Emittance::Action
|
83
|
+
#
|
84
|
+
# attr_reader :assignable
|
85
|
+
#
|
86
|
+
# def initialize(assignable)
|
87
|
+
# @assignable = assignable
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# Per the pattern explained above, instances of this class are representations of an action being carried out. This
|
92
|
+
# class should have a very minimal interface (maybe, at most, some getter methods for its instance variables so the
|
93
|
+
# handler can make decisions based on its state).
|
94
|
+
#
|
95
|
+
# Next, we'll implement the +#call+ instance method. +Emittance::Action+ will take care of the dirty work for us:
|
96
|
+
#
|
97
|
+
# class Assignment
|
98
|
+
# # ...
|
99
|
+
#
|
100
|
+
# def call
|
101
|
+
# do_one_and_i_mean_only_one_thing
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# # ...
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# Again, this method should do a single thing. From here, your code should be able to run without error! You might
|
108
|
+
# notice, though, that a mysterious class will have been defined after loading this file.
|
109
|
+
#
|
110
|
+
# defined? AssignmentHandler
|
111
|
+
# => "constant"
|
112
|
+
#
|
113
|
+
# Next, we can open up this class to implement the event handler. +Emittance+ will look for a method called
|
114
|
+
# +#handle_call+, and invoke it whenever, in this example, +Assignment#call+ is called.
|
115
|
+
#
|
116
|
+
# class AssignmentHandler
|
117
|
+
# def handle_call
|
118
|
+
# notify_someone(action)
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# # ...
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# The "Action" object is stored as the instance variable +@action+, made available with a getter class +#action+. This
|
125
|
+
# will allow us to access its data and make decisions based on it.
|
126
|
+
#
|
127
|
+
# Now, this seems like we're passing the buck of all that control flow to yet another object, but this pattern has
|
128
|
+
# several advantages. First, we can disable +Emittance+ at will, so if we ever want to shut +Assignment+ actions
|
129
|
+
# off from their listeners, that is always an option to us. Second, to address the concern raised at the beginning of
|
130
|
+
# this paragraph, this paradigm puts us into the mindset of spreading the flow of our program out across multiple
|
131
|
+
# action/handler pairs, allowing us to think more clearly about what our code is doing.
|
132
|
+
#
|
133
|
+
# One possible disadvantage of this pattern is that it suggests a one-to-one pairing between events and handlers.
|
134
|
+
#
|
135
|
+
module Emittance::Action
|
136
|
+
EMITTING_METHOD = :call
|
137
|
+
HANDLER_METHOD_NAME = "handle_#{EMITTING_METHOD}".to_sym
|
138
|
+
|
139
|
+
class NoHandlerMethodError < StandardError; end
|
140
|
+
|
141
|
+
# @private
|
142
|
+
class << self
|
143
|
+
def included(action_klass)
|
144
|
+
handler_klass_name = Emittance::Action.handler_klass_name(action_klass)
|
145
|
+
handler_klass = Emittance::Action.find_or_create_klass(handler_klass_name)
|
146
|
+
|
147
|
+
action_klass.class_eval do
|
148
|
+
extend Emittance::Emitter
|
149
|
+
|
150
|
+
class << self
|
151
|
+
# @private
|
152
|
+
def method_added(method_name)
|
153
|
+
emitting_method = Emittance::Action::EMITTING_METHOD
|
154
|
+
emits_on method_name if method_name == emitting_method
|
155
|
+
super
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
handler_klass.class_eval do
|
161
|
+
attr_reader :action
|
162
|
+
|
163
|
+
extend Emittance::Watcher
|
164
|
+
|
165
|
+
def initialize(action_obj)
|
166
|
+
@action = action_obj
|
167
|
+
end
|
168
|
+
|
169
|
+
watch Emittance::Action.emitting_event_name(action_klass) do |event|
|
170
|
+
handler_obj = new(event.emitter)
|
171
|
+
handler_method_name = Emittance::Action::HANDLER_METHOD_NAME
|
172
|
+
|
173
|
+
if handler_obj.respond_to? handler_method_name
|
174
|
+
handler_obj.send handler_method_name
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# @private
|
181
|
+
def handler_klass_name(action_klass)
|
182
|
+
"#{action_klass}Handler"
|
183
|
+
end
|
184
|
+
|
185
|
+
# @private
|
186
|
+
def emitting_event_name(action_klass)
|
187
|
+
Emittance::Emitter.emitting_method_event(action_klass, Emittance::Action::EMITTING_METHOD)
|
188
|
+
end
|
189
|
+
|
190
|
+
# @private
|
191
|
+
def find_or_create_klass(klass_name)
|
192
|
+
unless Object.const_defined? klass_name
|
193
|
+
Object.const_set klass_name, Class.new(Object)
|
194
|
+
end
|
195
|
+
|
196
|
+
Object.const_get klass_name
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# @private
|
2
|
+
class Emittance::Broker
|
3
|
+
@registrations = {}
|
4
|
+
@enabled = true
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :enabled
|
8
|
+
|
9
|
+
def process_event(event)
|
10
|
+
return unless enabled?
|
11
|
+
|
12
|
+
registrations_for(event).each do |registration|
|
13
|
+
registration.call event
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def register(identifier, &callback)
|
18
|
+
identifier = normalize_identifier identifier
|
19
|
+
@registrations[identifier] ||= []
|
20
|
+
registrations_for(identifier) << Emittance::Registration.new(identifier, &callback)
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_method_call(identifier, object, method_name)
|
24
|
+
register identifier, &lambda_for_method_call(object, method_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_registrations!
|
28
|
+
@registrations.keys.each do |identifier|
|
29
|
+
self.clear_registrations_for! identifier
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_registrations_for!(identifier)
|
34
|
+
identifier = normalize_identifier identifier
|
35
|
+
@registrations[identifier].clear
|
36
|
+
end
|
37
|
+
|
38
|
+
def registrations_for(identifier)
|
39
|
+
identifier = normalize_identifier identifier
|
40
|
+
@registrations[identifier] || []
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def normalize_identifier(identifier)
|
46
|
+
if is_event_klass?(identifier) || is_event_object?(identifier)
|
47
|
+
identifier.identifier
|
48
|
+
else
|
49
|
+
coerce_identifier_type identifier
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def lambda_for_method_call(object, method_name)
|
54
|
+
->(event) { object.send method_name, event }
|
55
|
+
end
|
56
|
+
|
57
|
+
def is_event_klass?(identifier)
|
58
|
+
identifier.is_a?(Class) && identifier < Emittance::Event
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_event_object?(identifier)
|
62
|
+
identifier.is_a? Emittance::Event
|
63
|
+
end
|
64
|
+
|
65
|
+
def coerce_identifier_type(identifier)
|
66
|
+
identifier.to_sym
|
67
|
+
end
|
68
|
+
|
69
|
+
def enabled?
|
70
|
+
enabled
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
##
|
2
|
+
# An emitter is any object that has the power to emit an event. Extend this module in any class whose singleton or
|
3
|
+
# instances you would like to have emit events.
|
4
|
+
#
|
5
|
+
# == Usage
|
6
|
+
#
|
7
|
+
# Whenever something warrants the emission of an event, you just need to call +#emit+ on that object. It is generally
|
8
|
+
# a good practice for an object to emit its own events, but I'm not your mother so you can emit events from wherever
|
9
|
+
# you want. It's probably not the best idea to do that, though. +#emit+ takes 2 params. First, it takes the identifier
|
10
|
+
# for the event object type (which can also be the {Emittance::Event} class itself). See the "identifiers" section
|
11
|
+
# of {Emittance::Event} for more info on this. The second argument is the payload. This is basically whatever you
|
12
|
+
# want it to be, but you might want to standardize this on a per-event basis. The +Emittance+ will then (at this
|
13
|
+
# time, synchronously) trigger each callback registered to listen for events of that identifier.
|
14
|
+
#
|
15
|
+
# +Emitter+ also provides a vanity class method that allows you to emit an event whenever a given method is called.
|
16
|
+
# This event gets triggered whenever an instance of the class finishes executing a method. This event is emitted (and
|
17
|
+
# therefore, all listening callbacks are triggered) between the point at which the method finishes executing and the
|
18
|
+
# return value is passed to its invoker.
|
19
|
+
#
|
20
|
+
module Emittance::Emitter
|
21
|
+
class << self
|
22
|
+
# @private
|
23
|
+
def extended(extender)
|
24
|
+
Emittance::Emitter.emitter_eval(extender) do
|
25
|
+
include ClassAndInstanceMethods
|
26
|
+
extend ClassAndInstanceMethods
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
def non_emitting_method_for(method_name)
|
32
|
+
"_non_emitting_#{method_name}".to_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
def emitting_method_event(emitter_klass, method_name)
|
37
|
+
Emittance::Event.event_klass_for(emitter_klass)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @private
|
41
|
+
def emitter_eval(klass, *args, &blk)
|
42
|
+
if klass.respond_to? :class_eval
|
43
|
+
klass.class_eval *args, &blk
|
44
|
+
else
|
45
|
+
klass.singleton_class.class_eval *args, &blk
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Included and extended whenever {Emittance::Emitter} is extended.
|
51
|
+
module ClassAndInstanceMethods
|
52
|
+
# Emits an {Emittance::Event event object} to watchers.
|
53
|
+
#
|
54
|
+
# @param identifier [Symbol, Emittance::Event] either an explicit Event object or the identifier that can be
|
55
|
+
# parsed into an Event object.
|
56
|
+
# @param payload [*] any additional information that might be helpful for an event's handler to have. Can be
|
57
|
+
# standardized on a per-event basis by pre-defining the class associated with the
|
58
|
+
def emit(identifier, payload)
|
59
|
+
now = Time.now
|
60
|
+
event_klass = _event_klass_for identifier
|
61
|
+
event = event_klass.new(self, now, payload)
|
62
|
+
_send_to_broker event
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# @private
|
68
|
+
def _event_klass_for(*identifiers)
|
69
|
+
Emittance::Event.event_klass_for *identifiers
|
70
|
+
end
|
71
|
+
|
72
|
+
# @private
|
73
|
+
def _send_to_broker(event)
|
74
|
+
Emittance::Broker.process_event event
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Tells the class to emit an event when a any of the given set of methods. By default, the event classes are named
|
79
|
+
# accordingly: If a +Foo+ object +emits_on+ +:bar+, then the event's class will be named +FooBarEvent+, and will be
|
80
|
+
# a subclass of +Emittance::Event+.
|
81
|
+
#
|
82
|
+
# The payload for this event will be the value returned from the method call.
|
83
|
+
#
|
84
|
+
# @param method_names [Symbol, String, Array<Symbol, String>] the methods whose calls emit an event
|
85
|
+
def emits_on(*method_names)
|
86
|
+
method_names.each do |method_name|
|
87
|
+
non_emitting_method = Emittance::Emitter.non_emitting_method_for method_name
|
88
|
+
|
89
|
+
Emittance::Emitter.emitter_eval(self) do
|
90
|
+
if method_defined?(non_emitting_method)
|
91
|
+
warn "Already emitting on #{method_name.inspect}"
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method non_emitting_method, method_name
|
96
|
+
|
97
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
98
|
+
def #{method_name}(*args, &blk)
|
99
|
+
return_value = #{non_emitting_method}(*args, &blk)
|
100
|
+
emit self.class, return_value
|
101
|
+
return_value
|
102
|
+
end
|
103
|
+
RUBY
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# @private
|
2
|
+
class Emittance::Event::EventBuilder
|
3
|
+
KLASS_NAME_SUFFIX = 'Event'.freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def object_to_klass(obj)
|
7
|
+
return obj if pass_klass_through?(obj)
|
8
|
+
|
9
|
+
klass_name = klassable_name_for obj
|
10
|
+
klass_name = dress_up_klass_name klass_name
|
11
|
+
find_or_create_event_klass klass_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def klass_to_identifier(klass)
|
15
|
+
identifier_str = klass.name
|
16
|
+
identifier_str = undress_klass_name identifier_str
|
17
|
+
identifier_str = snake_case identifier_str
|
18
|
+
|
19
|
+
identifier_str.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def pass_klass_through?(obj)
|
25
|
+
obj.is_a?(Class) && obj < Emittance::Event
|
26
|
+
end
|
27
|
+
|
28
|
+
def klassable_name_for(obj)
|
29
|
+
name_str = obj.to_s
|
30
|
+
name_str = camel_case name_str
|
31
|
+
name_str = clean_up_punctuation name_str
|
32
|
+
|
33
|
+
name_str
|
34
|
+
end
|
35
|
+
|
36
|
+
def camel_case(str)
|
37
|
+
str = str.sub(/^[a-z\d]*/) { $&.capitalize }
|
38
|
+
str.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }
|
39
|
+
end
|
40
|
+
|
41
|
+
def snake_case(str)
|
42
|
+
str.gsub(/::/, '_')
|
43
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
44
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
45
|
+
.tr("-", "_")
|
46
|
+
.downcase
|
47
|
+
end
|
48
|
+
|
49
|
+
def clean_up_punctuation(str)
|
50
|
+
str.gsub /[^A-Za-z\d]/, ''
|
51
|
+
end
|
52
|
+
|
53
|
+
def dress_up_klass_name(klass_name)
|
54
|
+
"#{klass_name}#{KLASS_NAME_SUFFIX}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def undress_klass_name(klass_name_str)
|
58
|
+
klass_name_str.gsub /#{KLASS_NAME_SUFFIX}$/, ''
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_or_create_event_klass(klass_name)
|
62
|
+
unless Object.const_defined? klass_name
|
63
|
+
create_event_klass klass_name
|
64
|
+
end
|
65
|
+
|
66
|
+
Object.const_get klass_name
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_event_klass(klass_name)
|
70
|
+
new_klass = Class.new(Emittance::Event)
|
71
|
+
Object.const_set klass_name, new_klass
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Emittance::Event
|
2
|
+
class << self
|
3
|
+
# @return [Symbol] the identifier that can be used by the {Emittance::Broker broker} to find event handlers.
|
4
|
+
def identifier
|
5
|
+
Emittance::Event::EventBuilder.klass_to_identifier self
|
6
|
+
end
|
7
|
+
|
8
|
+
# @private
|
9
|
+
def event_klass_for(identifier)
|
10
|
+
Emittance::Event::EventBuilder.object_to_klass identifier
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :emitter, :timestamp, :payload
|
15
|
+
|
16
|
+
def initialize(emitter, timestamp, payload)
|
17
|
+
@emitter = emitter
|
18
|
+
@timestamp = timestamp
|
19
|
+
@payload = payload
|
20
|
+
end
|
21
|
+
|
22
|
+
def identifier
|
23
|
+
self.class.identifier
|
24
|
+
end
|
25
|
+
end
|
data/lib/emittance.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'emittance/version'
|
2
|
+
require 'emittance/registration'
|
3
|
+
require 'emittance/event'
|
4
|
+
require 'emittance/event/event_builder'
|
5
|
+
require 'emittance/emitter'
|
6
|
+
require 'emittance/watcher'
|
7
|
+
require 'emittance/action'
|
8
|
+
require 'emittance/broker'
|
9
|
+
|
10
|
+
module Emittance
|
11
|
+
class << self
|
12
|
+
@enabled = true
|
13
|
+
|
14
|
+
def enabled?
|
15
|
+
!!@enabled
|
16
|
+
end
|
17
|
+
|
18
|
+
def enable
|
19
|
+
@enabled = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def disable
|
23
|
+
@enabled = false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: emittance
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tyler Guillen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A robust and flexible eventing library for Ruby.
|
98
|
+
email:
|
99
|
+
- tyler@tylerguillen.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec_status"
|
106
|
+
- ".ruby-version"
|
107
|
+
- ".yardopts"
|
108
|
+
- Gemfile
|
109
|
+
- Gemfile.lock
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- emittance.gemspec
|
115
|
+
- lib/.DS_Store
|
116
|
+
- lib/emittance.rb
|
117
|
+
- lib/emittance/.DS_Store
|
118
|
+
- lib/emittance/action.rb
|
119
|
+
- lib/emittance/broker.rb
|
120
|
+
- lib/emittance/emitter.rb
|
121
|
+
- lib/emittance/event.rb
|
122
|
+
- lib/emittance/event/event_builder.rb
|
123
|
+
- lib/emittance/registration.rb
|
124
|
+
- lib/emittance/version.rb
|
125
|
+
- lib/emittance/watcher.rb
|
126
|
+
homepage: https://github.com/aastronautss/emittance
|
127
|
+
licenses:
|
128
|
+
- MIT
|
129
|
+
metadata: {}
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project:
|
146
|
+
rubygems_version: 2.6.13
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: A robust and flexible eventing library for Ruby.
|
150
|
+
test_files: []
|