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 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
@@ -0,0 +1,17 @@
1
+ # editor stuff
2
+ .idea
3
+ .idea/dataSources.xml
4
+ tags
5
+ *.sublime-workspace
6
+ *.sublime-project
7
+ .project
8
+
9
+ # OS stuff
10
+ .DS_Store
11
+ tmp/**
12
+
13
+ # CI stuff
14
+ .yardoc
15
+ .yardoc/**
16
+ coverage/**
17
+ doc/**
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
@@ -0,0 +1,4 @@
1
+ --protected
2
+ --no-private
3
+ 'lib/**/*.rb'
4
+ --readme README.md
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in test.gemspec
6
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,13 @@
1
+ # @private
2
+ class Emittance::Registration
3
+ attr_reader :identifier
4
+
5
+ def initialize(identifier, &callback)
6
+ @identifier = identifier
7
+ @callback = callback
8
+ end
9
+
10
+ def call(event)
11
+ @callback.call event
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Emittance
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,9 @@
1
+ module Emittance::Watcher
2
+ def watch(identifier, callback_method = nil, &callback)
3
+ if callback_method
4
+ Emittance::Broker.register_method_call identifier, self, callback_method
5
+ else
6
+ Emittance::Broker.register identifier, &callback
7
+ end
8
+ end
9
+ 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: []