signal_lamp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cb205a2b3a0cab05eae3328eaf5affb360d36d87
4
+ data.tar.gz: a2db4a1d041c62e527d924b96c5714a1ea08942c
5
+ SHA512:
6
+ metadata.gz: e127a9527fc7216aea6142e4f3d5247e1f03862e6a80c9a7eee6333ed3b2e1f595721ed9fb50dc067559527dc03d9ff97505f90df36f15b74d941722f42a62c8
7
+ data.tar.gz: 2aba2811fb0e485e0aacb5095589860ce6328c9731b142444b3fc8e2c084061f4817322c9c86576378c2476fab0a82c95973a66e1937f4c044230776a3fa32dd
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ doc
4
+ pkg
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 James Edward Gray II
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,126 @@
1
+ # Signal Lamp
2
+
3
+ A simple tool for decoupling Ruby object systems.
4
+
5
+ ## Synopsis
6
+
7
+ ```ruby
8
+ require "signal_lamp" # Step 0: load Signal Lamp
9
+
10
+ class Datum
11
+ include SignalLamp::LampHolder # Step 1: include the event methods
12
+
13
+ def initialize(value)
14
+ @value = value
15
+ end
16
+
17
+ attr_reader :value
18
+
19
+ def value=(new_value)
20
+ old_value = @value
21
+ @value = new_value
22
+
23
+ # Step 2: signal events when interesting things happen
24
+ lamp.signal("changed:value", self, old: old_value, new: new_value)
25
+ end
26
+ end
27
+
28
+ class TotalWatcher
29
+ def initialize
30
+ @total = 0
31
+ end
32
+
33
+ attr_reader :total
34
+
35
+ def watch(datum)
36
+ record_value(datum.value)
37
+ # Step 3: watch for those events
38
+ datum.lamp.watch_for("changed:value") do |_, _, details|
39
+ record_value_change(details)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def record_value(value)
46
+ @total += value
47
+ end
48
+
49
+ def record_value_change(old: , new: )
50
+ record_value(new - old)
51
+ end
52
+ end
53
+
54
+ ones = Datum.new(1)
55
+ tens = Datum.new(30)
56
+
57
+ #
58
+ # variation on the theme:
59
+ #
60
+ # 1. You don't have to use objects
61
+ # 2. You can watch for multiple event types at once
62
+ # 3. All signal arguments are passed through to the watcher
63
+ #
64
+ changed_event_regex = /\Achanged:/
65
+ [ones, tens].each do |datum|
66
+ datum.lamp.watch_for(changed_event_regex) do |event, changed_datum, details|
67
+ printf "%s's `%s' changed from %i to %i\n",
68
+ changed_datum.object_id,
69
+ event.sub(changed_event_regex, ""),
70
+ details.fetch(:old),
71
+ details.fetch(:new)
72
+ end
73
+ end
74
+
75
+ # this hooks event signalers to watchers
76
+ watcher = TotalWatcher.new
77
+ watcher.watch(ones)
78
+ watcher.watch(tens)
79
+
80
+ # this signals events
81
+ ones.value += 1
82
+ tens.value += 10
83
+
84
+ # this shows that the objects communicated
85
+ watcher.total # => 42
86
+ # >> 70302533065580's `value' changed from 1 to 2
87
+ # >> 70302533065560's `value' changed from 30 to 40
88
+ ```
89
+
90
+ ## Description
91
+
92
+ This library is pretty much a port of [the Events module in Backbone.js](http://backbonejs.org/#Events), with minor changes:
93
+
94
+ * The interface was made a little more Rubyish with the use of blocks, `===`, etc.
95
+ * All event methods were moved behind an prefix to minimize the impact on an object's API
96
+ * Simple identifiers are used to classify, and optionally remove, watcher to signaler relationships
97
+
98
+ In other words, this is a system for decoupling objects. Some objects become "signalers" that tell anyone who is interested when "events" happen. Other objects act as "watchers" waiting for and acting on those events.
99
+
100
+ This library has nothing to do with multiprocessing. This is just a tool for managing object communication.
101
+
102
+ ## Installation
103
+
104
+ Install the gem:
105
+
106
+ ```
107
+ gem install signal_lamp
108
+ ```
109
+
110
+ or add it to your `Gemfile`:
111
+
112
+ ```ruby
113
+ gem "signal_lamp"
114
+ ```
115
+
116
+ ## Contributing
117
+
118
+ If you have grand ideas about cool new features that could be added to Signal Lamp, I'm probably not interested. Sorry. I very much want this library to stay a simple tool that is easy to fully understand.
119
+
120
+ Of course, I'm very interested in fixing any bugs or other problems with this code. [Please do send those along.](https://github.com/JEG2/signal_lamp/issues)
121
+
122
+ If you're unsure, [feel free to create an issue](https://github.com/JEG2/signal_lamp/issues). I'm happy to discuss it with you. Just don't get too mad if I pass. You are always welcome to release extensions to Signal Lamp as add-on gems.
123
+
124
+ ## Author
125
+
126
+ Signal Lamp was coded up by James Edward Gray II (JEG2).
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require "rdoc/task"
3
+ require "rspec/core/rake_task"
4
+
5
+ RDoc::Task.new do |rdoc|
6
+ rdoc.rdoc_dir = "doc"
7
+ rdoc.main = "README.md"
8
+ rdoc.rdoc_files.include(
9
+ "README.md",
10
+ `git ls-files -z`.split("\x0").grep(%r{\Alib/})
11
+ )
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new(:spec)
15
+ task default: :spec
@@ -0,0 +1,11 @@
1
+ require_relative "signal_lamp/version"
2
+ require_relative "signal_lamp/lamp_holder"
3
+
4
+ #
5
+ # A simple tool for decoupling Ruby object systems.
6
+ #
7
+ # See SignalLamp::Lamp and SignalLamp::LampHolder for documentation.
8
+ #
9
+ module SignalLamp
10
+ # namespace filled in by the files above
11
+ end
@@ -0,0 +1,97 @@
1
+ require "securerandom"
2
+
3
+ module SignalLamp
4
+ #
5
+ # A Lamp keeps track of the watchers waiting for events and allows events to
6
+ # be signaled to those watchers. The primary methods used for this are
7
+ # #watch_for() and #signal().
8
+ #
9
+ class Lamp
10
+ #
11
+ # It's not common to construct these objects directly. Instead, most code
12
+ # should use the SignalLamp::LampHolder mix-in.
13
+ #
14
+ def initialize
15
+ @watchers = { }
16
+ end
17
+
18
+ attr_reader :watchers
19
+ private :watchers
20
+
21
+ #
22
+ # This is the primary interface for watching for events to occur.
23
+ #
24
+ # The +event_matcher+ parameter will be tested against signaled event names
25
+ # using the <tt>===</tt> operator. This allows you to match a +String+
26
+ # directly, use a +Regexp+ to wildcard various event types (I recommend
27
+ # naming events with words separated by colons because these are easy to
28
+ # match), or a +Proc+ that uses any kind of logic to select events. For
29
+ # example, you could match all events signaled on this object with
30
+ # <tt>->(event_name) { true }</tt>.
31
+ #
32
+ # You only need to worry about passing an +identifier+ if you want to later
33
+ # stop watching for the events you will pick up with this call. Alternately
34
+ # you can save the generated +identifier+ returned from this call and later
35
+ # feed that to #stop_watching(). Any Ruby object that can be used as a
36
+ # +Hash+ key is a valid +identifier+. By default, a UUID +String+ will be
37
+ # generated.
38
+ #
39
+ # The block passed to this call is the code that will actually be invoked
40
+ # when an event is signaled and the name is matched. The block will be
41
+ # passed the matched event name (useful with dynamic matchers that could
42
+ # select various events) followed by all arguments passed to #signal() for
43
+ # the event.
44
+ #
45
+ def watch_for(event_matcher, identifier: generate_identifier, &event_handler)
46
+ watchers[identifier] = [event_matcher, event_handler]
47
+ identifier
48
+ end
49
+
50
+ #
51
+ # This is a shortcut used to create a #watch_for() call that will only
52
+ # trigger for one event. After the block is called the first time,
53
+ # #stop_watching() is automatically called.
54
+ #
55
+ def watch_for_one(event_matcher, &event_handler)
56
+ identifier = generate_identifier
57
+ watch_for(event_matcher, identifier: identifier) do |*event_args|
58
+ begin
59
+ event_handler.call(*event_args)
60
+ ensure
61
+ stop_watching(identifier)
62
+ end
63
+ end
64
+ end
65
+
66
+ #
67
+ # Given an +identifier+ passed to or returned from a previous call to
68
+ # #watch_for(), this method will stop that code from being invoked for any
69
+ # future events.
70
+ #
71
+ def stop_watching(identifier)
72
+ watchers.delete(identifier)
73
+ end
74
+
75
+ #
76
+ # This method will invoke the block code of all previous calls to
77
+ # #watch_for() that are still in effect and that match the passed
78
+ # +event_name+.
79
+ #
80
+ # All other arguments passed to this method, assumed to be details related
81
+ # to the event, are passed through to all blocks invoked for this event.
82
+ #
83
+ def signal(event_name, *event_details)
84
+ watchers.each_value do |event_matcher, event_handler|
85
+ if event_matcher === event_name
86
+ event_handler.call(event_name, *event_details)
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def generate_identifier
94
+ SecureRandom.uuid
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "lamp"
2
+
3
+ module SignalLamp
4
+ #
5
+ # The primary interface of SignalLamp. Mix this into an object, then use
6
+ # the #lamp() method to reach any features provided by SignalLamp::Lamp.
7
+ #
8
+ module LampHolder
9
+ #
10
+ # Returns a memoized SignalLamp::Lamp instance that can be used to
11
+ # SignalLamp::Lamp#signal() events and SignalLamp::Lamp#watch_for() events
12
+ # related to the object this module is mixed into.
13
+ #
14
+ def lamp
15
+ @lamp ||= SignalLamp::Lamp.new
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module SignalLamp
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "lib/signal_lamp/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = File.basename(__FILE__, ".gemspec")
5
+ spec.version = SignalLamp::VERSION
6
+ spec.authors = ["James Edward Gray II (JEG2)"]
7
+ spec.email = %w[james@graysoftinc.com]
8
+ spec.summary = "A simple tool for decoupling Ruby object systems."
9
+ spec.description = <<END_DESCRIPTION
10
+ This is a system for decoupling objects. Some objects become "signalers" that
11
+ tell anyone who is interested when "events" happen. Other objects act as
12
+ "watchers" waiting for and acting on those events.
13
+ END_DESCRIPTION
14
+ spec.homepage = "https://github.com/JEG2/signal_lamp"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.test_files = spec.files.grep(%r{\Aspec/})
19
+ spec.extra_rdoc_files = %w[README.md]
20
+ spec.rdoc_options << "--main" << "README.md"
21
+ spec.require_paths = %w[lib]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake", "~> 10.3"
25
+ spec.add_development_dependency "rspec", "~> 2.14"
26
+ spec.add_development_dependency "ZenTest", "~> 4.10"
27
+ spec.add_development_dependency "rdoc", "~> 4.1"
28
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "spec_helper"
2
+
3
+ require_relative "../lib/signal_lamp/lamp_holder"
4
+
5
+ describe SignalLamp::LampHolder do
6
+ let(:object) { Object.new }
7
+
8
+ it "adds the lamp interface to any object" do
9
+ expect(object.respond_to?(:lamp)).not_to be_true
10
+ object.extend(SignalLamp::LampHolder)
11
+ expect(object.respond_to?(:lamp)).to be_true
12
+ end
13
+
14
+ it "always returns the same lamp attached to an object" do
15
+ object.extend(SignalLamp::LampHolder)
16
+ expect(object.lamp).to be(object.lamp)
17
+ end
18
+ end
@@ -0,0 +1,86 @@
1
+ require_relative "spec_helper"
2
+
3
+ require_relative "../lib/signal_lamp/lamp"
4
+
5
+ describe SignalLamp::Lamp do
6
+ let(:events_seen) { [ ] }
7
+
8
+ it "allows you to watch for signaled events" do
9
+ subject.watch_for("test_event") do |event_name|
10
+ events_seen << event_name
11
+ end
12
+ subject.signal("test_event")
13
+ expect(events_seen).to eq(%w[test_event])
14
+ end
15
+
16
+ it "does not notify you of events that don't match" do
17
+ subject.watch_for("match") do |event_name|
18
+ events_seen << event_name
19
+ end
20
+ subject.signal("match")
21
+ subject.signal("no_match")
22
+ expect(events_seen).to eq(%w[match])
23
+ end
24
+
25
+ it "understands dynamic event name matchers" do
26
+ subject.watch_for(/\Achanged:/) do |event_name|
27
+ events_seen << event_name
28
+ end
29
+ subject.signal("changed:font")
30
+ subject.signal("changed:color")
31
+ subject.signal("deleted:content")
32
+ expect(events_seen).to eq(%w[changed:font changed:color])
33
+ end
34
+
35
+ it "passes signaled arguments to the watcher" do
36
+ subject.watch_for("args") do |event_name, *args|
37
+ events_seen << args
38
+ end
39
+ subject.signal("args")
40
+ subject.signal("args", 1)
41
+ subject.signal("args", 2, :three)
42
+ expect(events_seen).to eq([[ ], [1], [2, :three]])
43
+ end
44
+
45
+ it "notifies all watches of matching events" do
46
+ subject.watch_for("changed:value") do |event_name|
47
+ events_seen << "static"
48
+ end
49
+ subject.watch_for(/\Achanged:/) do |event_name|
50
+ events_seen << "dynamic"
51
+ end
52
+ subject.signal("changed:value")
53
+ expect(events_seen.sort).to eq(%w[dynamic static])
54
+ end
55
+
56
+ it "allows you to stop watching for events by identifier" do
57
+ subject.watch_for("identifiers", identifier: :test_watcher) do |event_name|
58
+ events_seen << event_name
59
+ end
60
+ subject.signal("identifiers")
61
+ subject.signal("identifiers")
62
+ subject.stop_watching(:test_watcher)
63
+ subject.signal("identifiers")
64
+ expect(events_seen).to eq(%w[identifiers identifiers])
65
+ end
66
+
67
+ it "returns autogenerated identifiers" do
68
+ identifier = subject.watch_for("generated") do |event_name|
69
+ events_seen << event_name
70
+ end
71
+ subject.signal("generated")
72
+ subject.signal("generated")
73
+ subject.stop_watching(identifier)
74
+ subject.signal("generated")
75
+ expect(events_seen).to eq(%w[generated generated])
76
+ end
77
+
78
+ it "provides a one event shortcut" do
79
+ subject.watch_for_one("once") do |event_name|
80
+ events_seen << event_name
81
+ end
82
+ subject.signal("once")
83
+ subject.signal("once")
84
+ expect(events_seen).to eq(%w[once])
85
+ end
86
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: signal_lamp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - James Edward Gray II (JEG2)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-16 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.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
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.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ZenTest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rdoc
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.1'
83
+ description: |
84
+ This is a system for decoupling objects. Some objects become "signalers" that
85
+ tell anyone who is interested when "events" happen. Other objects act as
86
+ "watchers" waiting for and acting on those events.
87
+ email:
88
+ - james@graysoftinc.com
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files:
92
+ - README.md
93
+ files:
94
+ - ".gitignore"
95
+ - ".rspec"
96
+ - Gemfile
97
+ - LICENSE
98
+ - README.md
99
+ - Rakefile
100
+ - lib/signal_lamp.rb
101
+ - lib/signal_lamp/lamp.rb
102
+ - lib/signal_lamp/lamp_holder.rb
103
+ - lib/signal_lamp/version.rb
104
+ - signal_lamp.gemspec
105
+ - spec/lamp_holder_spec.rb
106
+ - spec/lamp_spec.rb
107
+ - spec/spec_helper.rb
108
+ homepage: https://github.com/JEG2/signal_lamp
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options:
114
+ - "--main"
115
+ - README.md
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.2.2
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: A simple tool for decoupling Ruby object systems.
134
+ test_files:
135
+ - spec/lamp_holder_spec.rb
136
+ - spec/lamp_spec.rb
137
+ - spec/spec_helper.rb