emittance 0.0.1 → 0.0.2
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 +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +12 -0
- data/.travis.yml +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -3
- data/lib/emittance/action.rb +174 -181
- data/lib/emittance/broker.rb +18 -65
- data/lib/emittance/brokerage.rb +56 -0
- data/lib/emittance/brokers/synchronous.rb +15 -0
- data/lib/emittance/dispatcher.rb +115 -0
- data/lib/emittance/emitter.rb +112 -88
- data/lib/emittance/errors.rb +13 -0
- data/lib/emittance/event/event_builder.rb +143 -63
- data/lib/emittance/event.rb +66 -18
- data/lib/emittance/registration.rb +13 -9
- data/lib/emittance/version.rb +3 -1
- data/lib/emittance/watcher.rb +10 -6
- data/lib/emittance.rb +23 -8
- data/pkg/emittance-0.0.1.gem +0 -0
- metadata +11 -5
- data/.rspec_status +0 -29
- data/lib/.DS_Store +0 -0
- data/lib/emittance/.DS_Store +0 -0
data/lib/emittance/broker.rb
CHANGED
@@ -1,73 +1,26 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
registrations_for(event).each do |registration|
|
13
|
-
registration.call event
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Emittance
|
4
|
+
##
|
5
|
+
# Base class for event brokers.
|
6
|
+
#
|
7
|
+
class Broker
|
8
|
+
class << self
|
9
|
+
# @param _event [Emittance::Event] the event to be passed off to watchers
|
10
|
+
def process_event(_event)
|
11
|
+
raise NotImplementedError
|
14
12
|
end
|
15
|
-
end
|
16
13
|
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
14
|
+
def inherited(subklass)
|
15
|
+
register_broker subklass
|
16
|
+
super
|
30
17
|
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
18
|
|
43
|
-
|
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
|
19
|
+
def register_broker(broker)
|
20
|
+
Emittance::Brokerage.register_broker broker
|
50
21
|
end
|
51
22
|
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
23
|
end
|
73
24
|
end
|
25
|
+
|
26
|
+
require 'emittance/brokers/synchronous'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Emittance
|
4
|
+
##
|
5
|
+
# The clearinghouse for brokers. Registers brokers, and decides which broker to use when sent an event. First point of
|
6
|
+
# contact for event propagation.
|
7
|
+
#
|
8
|
+
class Brokerage
|
9
|
+
class << self
|
10
|
+
def send_event(event, broker_id)
|
11
|
+
broker = registry.fetch(broker_id)
|
12
|
+
broker.process_event event
|
13
|
+
end
|
14
|
+
|
15
|
+
def register_broker(broker)
|
16
|
+
registry.register broker
|
17
|
+
end
|
18
|
+
|
19
|
+
def registry
|
20
|
+
Emittance::Brokerage::Registry
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @private
|
25
|
+
module Registry
|
26
|
+
@brokers = {}
|
27
|
+
|
28
|
+
class << self
|
29
|
+
attr_reader :brokers
|
30
|
+
|
31
|
+
def register(broker)
|
32
|
+
broker_sym = generate_broker_sym(broker)
|
33
|
+
brokers[broker_sym] = broker
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch(broker_id)
|
37
|
+
brokers[broker_id.to_sym]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def generate_broker_sym(broker)
|
43
|
+
camel_case = broker.name.split('::').last
|
44
|
+
snake_case(camel_case).to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
def snake_case(str)
|
48
|
+
str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
49
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
50
|
+
.tr('-', '_')
|
51
|
+
.downcase
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Emittance
|
4
|
+
##
|
5
|
+
# Synchronously dispatches the event to watchers.
|
6
|
+
#
|
7
|
+
class Synchronous < Emittance::Broker
|
8
|
+
class << self
|
9
|
+
# (@see Emittance::Broker.process_event)
|
10
|
+
def process_event(event)
|
11
|
+
Emittance::Dispatcher.process_event event
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Emittance
|
6
|
+
# @private
|
7
|
+
class Dispatcher
|
8
|
+
@registrations = {}
|
9
|
+
@enabled = true
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def process_event(event)
|
13
|
+
new.process_event event
|
14
|
+
end
|
15
|
+
|
16
|
+
def register(identifier, &callback)
|
17
|
+
identifier = normalize_identifier identifier
|
18
|
+
registrations[identifier] ||= empty_registration
|
19
|
+
registrations_for(identifier) << Emittance::Registration.new(identifier, &callback)
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_method_call(identifier, object, method_name)
|
23
|
+
register identifier, &lambda_for_method_call(object, method_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear_registrations!
|
27
|
+
registrations.keys.each do |identifier|
|
28
|
+
clear_registrations_for! identifier
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear_registrations_for!(identifier)
|
33
|
+
identifier = normalize_identifier identifier
|
34
|
+
registrations[identifier].clear
|
35
|
+
end
|
36
|
+
|
37
|
+
def registrations_for(identifier)
|
38
|
+
identifier = normalize_identifier identifier
|
39
|
+
registrations[identifier] || empty_registration
|
40
|
+
end
|
41
|
+
|
42
|
+
def enable!
|
43
|
+
@enabled = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def disable!
|
47
|
+
@enabled = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def enabled?
|
51
|
+
@enabled
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
attr_accessor :enabled, :registrations
|
57
|
+
|
58
|
+
def empty_registration
|
59
|
+
Set.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def normalize_identifier(identifier)
|
63
|
+
if event_klass?(identifier) || event_object?(identifier)
|
64
|
+
identifier.identifier
|
65
|
+
else
|
66
|
+
coerce_identifier_type identifier
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def lambda_for_method_call(object, method_name)
|
71
|
+
->(event) { object.send method_name, event }
|
72
|
+
end
|
73
|
+
|
74
|
+
def event_klass?(identifier)
|
75
|
+
identifier.is_a?(Class) && identifier < Emittance::Event
|
76
|
+
end
|
77
|
+
|
78
|
+
def event_object?(identifier)
|
79
|
+
identifier.is_a? Emittance::Event
|
80
|
+
end
|
81
|
+
|
82
|
+
def coerce_identifier_type(identifier)
|
83
|
+
identifier.to_sym
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(suppressed = false)
|
88
|
+
@suppressed = suppressed
|
89
|
+
end
|
90
|
+
|
91
|
+
def process_event(event)
|
92
|
+
return unless enabled?
|
93
|
+
|
94
|
+
registrations_for(event).each do |registration|
|
95
|
+
registration.call event
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
attr_reader :suppressed
|
102
|
+
|
103
|
+
def registrations_for(event)
|
104
|
+
self.class.registrations_for event
|
105
|
+
end
|
106
|
+
|
107
|
+
def enabled?
|
108
|
+
self.class.enabled? && !suppressed?
|
109
|
+
end
|
110
|
+
|
111
|
+
def suppressed?
|
112
|
+
suppressed
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/emittance/emitter.rb
CHANGED
@@ -1,106 +1,130 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# want
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Emittance
|
4
|
+
##
|
5
|
+
# An emitter is any object that has the power to emit an event. Extend this module in any class whose singleton or
|
6
|
+
# instances you would like to have emit events.
|
7
|
+
#
|
8
|
+
# == Usage
|
9
|
+
#
|
10
|
+
# Whenever something warrants the emission of an event, you just need to call +#emit+ on that object. It is generally
|
11
|
+
# a good practice for an object to emit its own events, but I'm not your mother so you can emit events from wherever
|
12
|
+
# you want. It's probably not the best idea to do that, though. +#emit+ takes 2 params. First, it takes the identifier
|
13
|
+
# for the event object type (which can also be the {Emittance::Event} class itself). See the "identifiers" section
|
14
|
+
# of {Emittance::Event} for more info on this. The second argument is the payload. This is basically whatever you
|
15
|
+
# want it to be, but you might want to standardize this on a per-event basis. The +Emittance+ will then (at this
|
16
|
+
# time, synchronously) trigger each callback registered to listen for events of that identifier.
|
17
|
+
#
|
18
|
+
# +Emitter+ also provides a vanity class method that allows you to emit an event whenever a given method is called.
|
19
|
+
# This event gets triggered whenever an instance of the class finishes executing a method. This event is emitted (and
|
20
|
+
# therefore, all listening callbacks are triggered) between the point at which the method finishes executing and the
|
21
|
+
# return value is passed to its invoker.
|
22
|
+
#
|
23
|
+
module Emitter
|
24
|
+
# :nocov:
|
25
|
+
class << self
|
26
|
+
# @private
|
27
|
+
def extended(extender)
|
28
|
+
Emittance::Emitter.emitter_eval(extender) do
|
29
|
+
include ClassAndInstanceMethods
|
30
|
+
extend ClassAndInstanceMethods
|
31
|
+
end
|
27
32
|
end
|
28
|
-
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
# @private
|
35
|
+
def non_emitting_method_for(method_name)
|
36
|
+
"_non_emitting_#{method_name}".to_sym
|
37
|
+
end
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
# @private
|
40
|
+
def emitting_method_event(emitter_klass, method_name)
|
41
|
+
Emittance::Event.event_klass_for(emitter_klass, method_name)
|
42
|
+
end
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
# @private
|
45
|
+
def emitter_eval(klass, *args, &blk)
|
46
|
+
if klass.respond_to? :class_eval
|
47
|
+
klass.class_eval *args, &blk
|
48
|
+
else
|
49
|
+
klass.singleton_class.class_eval *args, &blk
|
50
|
+
end
|
46
51
|
end
|
47
52
|
end
|
48
|
-
|
53
|
+
# :nocov:
|
49
54
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
55
|
+
# Included and extended whenever {Emittance::Emitter} is extended.
|
56
|
+
module ClassAndInstanceMethods
|
57
|
+
# Emits an {Emittance::Event event object} to watchers.
|
58
|
+
#
|
59
|
+
# @param identifier [Symbol, Emittance::Event] either an explicit Event object or the identifier that can be
|
60
|
+
# parsed into an Event object.
|
61
|
+
# @param payload [*] any additional information that might be helpful for an event's handler to have. Can be
|
62
|
+
# standardized on a per-event basis by pre-defining the class associated with the
|
63
|
+
#
|
64
|
+
# @return the payload
|
65
|
+
def emit(identifier, payload = nil, broker: :synchronous)
|
66
|
+
now = Time.now
|
67
|
+
event_klass = _event_klass_for identifier
|
68
|
+
event = event_klass.new(self, now, payload)
|
69
|
+
_send_to_broker event, broker
|
64
70
|
|
65
|
-
|
71
|
+
payload
|
72
|
+
end
|
66
73
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
74
|
+
# If you don't know the specific identifier whose event you want to emit, you can send it a bunch of stuff and
|
75
|
+
# +Emitter+ will automatically generate an +Event+ class for you.
|
76
|
+
#
|
77
|
+
# @param identifiers [*] anything that can be used to generate an +Event+ class.
|
78
|
+
# @param payload (@see #emit)
|
79
|
+
def emit_with_dynamic_identifier(*identifiers, payload:, broker: :synchronous)
|
80
|
+
now = Time.now
|
81
|
+
event_klass = _event_klass_for *identifiers
|
82
|
+
event = event_klass.new(self, now, payload)
|
83
|
+
_send_to_broker event, broker
|
71
84
|
|
72
|
-
|
73
|
-
|
74
|
-
Emittance::Broker.process_event event
|
75
|
-
end
|
76
|
-
end
|
85
|
+
payload
|
86
|
+
end
|
77
87
|
|
78
|
-
|
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
|
+
private
|
88
89
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
90
|
+
# @private
|
91
|
+
def _event_klass_for(*identifiers)
|
92
|
+
Emittance::Event.event_klass_for *identifiers
|
93
|
+
end
|
94
|
+
|
95
|
+
# @private
|
96
|
+
def _send_to_broker(event, broker)
|
97
|
+
Emittance::Brokerage.send_event event, broker
|
98
|
+
end
|
94
99
|
|
95
|
-
|
100
|
+
# Tells the class to emit an event when a any of the given set of methods. By default, the event classes are named
|
101
|
+
# accordingly: If a +Foo+ object +emits_on+ +:bar+, then the event's class will be named +FooBarEvent+, and will
|
102
|
+
# be a subclass of +Emittance::Event+.
|
103
|
+
#
|
104
|
+
# The payload for this event will be the value returned from the method call.
|
105
|
+
#
|
106
|
+
# @param method_names [Symbol, String, Array<Symbol, String>] the methods whose calls emit an event
|
107
|
+
def emits_on(*method_names)
|
108
|
+
method_names.each do |method_name|
|
109
|
+
non_emitting_method = Emittance::Emitter.non_emitting_method_for method_name
|
96
110
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
111
|
+
Emittance::Emitter.emitter_eval(self) do
|
112
|
+
if method_defined?(non_emitting_method)
|
113
|
+
warn "Already emitting on #{method_name.inspect}"
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
alias_method non_emitting_method, method_name
|
118
|
+
|
119
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
120
|
+
def #{method_name}(*args, &blk)
|
121
|
+
return_value = #{non_emitting_method}(*args, &blk)
|
122
|
+
emit_with_dynamic_identifier self.class, __method__, payload: return_value
|
123
|
+
return_value
|
124
|
+
end
|
125
|
+
RUBY
|
102
126
|
end
|
103
|
-
|
127
|
+
end
|
104
128
|
end
|
105
129
|
end
|
106
130
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# froze_string_literal: true
|
2
|
+
|
3
|
+
module Emittance
|
4
|
+
# Raised when an identifier (for the purposes of identifying an event class) cannot be parsed, or an event class
|
5
|
+
# can otherwise not be found or generated.
|
6
|
+
class InvalidIdentifierError < StandardError; end
|
7
|
+
|
8
|
+
# Raised when an identifier registration is attempted, but there exists an event registered to the given identifiered.
|
9
|
+
class IdentifierTakenError < StandardError; end
|
10
|
+
|
11
|
+
# Used when a custom event type undergoes payload validation.
|
12
|
+
class InvalidPayloadError < StandardError; end
|
13
|
+
end
|