signal_lamp 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.
@@ -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