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.
@@ -1,73 +1,26 @@
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
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
- 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
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
- 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
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
@@ -1,106 +1,130 @@
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
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
- # @private
31
- def non_emitting_method_for(method_name)
32
- "_non_emitting_#{method_name}".to_sym
33
- end
34
+ # @private
35
+ def non_emitting_method_for(method_name)
36
+ "_non_emitting_#{method_name}".to_sym
37
+ end
34
38
 
35
- # @private
36
- def emitting_method_event(emitter_klass, method_name)
37
- Emittance::Event.event_klass_for(emitter_klass)
38
- end
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
- # @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
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
- end
53
+ # :nocov:
49
54
 
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
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
- private
71
+ payload
72
+ end
66
73
 
67
- # @private
68
- def _event_klass_for(*identifiers)
69
- Emittance::Event.event_klass_for *identifiers
70
- end
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
- # @private
73
- def _send_to_broker(event)
74
- Emittance::Broker.process_event event
75
- end
76
- end
85
+ payload
86
+ end
77
87
 
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
+ private
88
89
 
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
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
- alias_method non_emitting_method, method_name
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
- 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
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
- RUBY
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