eventful 1.0.0

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,4 @@
1
+ === 1.0.0 / 2009-06-18
2
+
3
+ * Initial release
4
+
@@ -0,0 +1,6 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/eventful.rb
6
+ test/test_eventful.rb
@@ -0,0 +1,124 @@
1
+ = Eventful
2
+
3
+ * http://github.com/jcoglan/eventful
4
+
5
+ +Eventful+ is a small extension on top of Ruby's +Observable+ module that
6
+ implements named events, block listeners and event bubbling. It allows
7
+ much more flexible event handling behaviour than is typically allowed
8
+ by +Observable+, which requires listeners to be objects that implement
9
+ +update+ and provides no simple way of calling subsets of observers based
10
+ on event type.
11
+
12
+
13
+ == Installation
14
+
15
+ sudo gem install eventful
16
+
17
+
18
+ == Examples
19
+
20
+ Make a class listenable by mixing +Eventful+ into it:
21
+
22
+ class Watcher
23
+ include Eventful
24
+ end
25
+
26
+ Register event listeners using +on+ with an event name and a block.
27
+ Publish events using +fire+ with the event name. The block accepts
28
+ the object that published the event, along with any parameters passed
29
+ to +fire+.
30
+
31
+ w = Watcher.new
32
+
33
+ w.on(:filechange) { |watcher, path| puts path }
34
+ w.on(:filedelete) { |watcher, path| puts "#{ watcher } deleted #{ path }" }
35
+
36
+ w.fire(:filechange, '/path/to/file.txt')
37
+ w.fire(:filedelete, '/tmp/pids/event.pid')
38
+
39
+ # prints...
40
+ # /path/to/file.txt
41
+ # #<Watcher:0xb7b485a4> deleted /tmp/pids/event.pid
42
+
43
+ The +on+ method returns the +Observer+ object used to represent the listener,
44
+ so you can remove it using +delete_observer+.
45
+
46
+ obs = w.on(:filechange) { |watcher| ... }
47
+
48
+ # listener will not fire after this
49
+ w.delete_observer(obs)
50
+
51
+
52
+ === Method chains instead of blocks
53
+
54
+ Instead of passing a block, you can add behaviour to objects by chaining
55
+ method calls after the +on+ call. For example:
56
+
57
+ class Logger
58
+ include Eventful
59
+
60
+ def print(message)
61
+ puts message
62
+ end
63
+ end
64
+
65
+ log = Logger.new
66
+ log.on(:receive).print "Received message"
67
+
68
+ # Calls `log.print "Received message"`
69
+ log.fire(:receive)
70
+
71
+
72
+ === Events that bubble
73
+
74
+ When you +fire+ an event, the event 'bubbles' up the type system. What
75
+ this means is that you can listen to events on all the instances of a
76
+ class just by placing an event listener on the class itself. As above,
77
+ the listener is called with the instance that fired the event.
78
+
79
+ Logger.on(:receive) { |log, msg| puts "#{ log } :: #{ msg }" }
80
+
81
+ l1, l2 = Logger.new, Logger.new
82
+
83
+ l1.fire(:receive, 'The first message')
84
+ l2.fire(:receive, 'Another event')
85
+
86
+ # prints...
87
+ # #<Logger:0xb7bf103c> :: The first message
88
+ # #<Logger:0xb7bf1028> :: Another event
89
+
90
+ Method chains can also be used, and they will be replayed on the instance
91
+ that initiated the event.
92
+
93
+ # Calls `log.print "Received message"`
94
+
95
+ Logger.on(:receive).print "Received message"
96
+
97
+ log = Logger.new
98
+ log.fire(:receive)
99
+
100
+
101
+ == License
102
+
103
+ (The MIT License)
104
+
105
+ Copyright (c) 2009 James Coglan
106
+
107
+ Permission is hereby granted, free of charge, to any person obtaining
108
+ a copy of this software and associated documentation files (the
109
+ 'Software'), to deal in the Software without restriction, including
110
+ without limitation the rights to use, copy, modify, merge, publish,
111
+ distribute, sublicense, and/or sell copies of the Software, and to
112
+ permit persons to whom the Software is furnished to do so, subject to
113
+ the following conditions:
114
+
115
+ The above copyright notice and this permission notice shall be
116
+ included in all copies or substantial portions of the Software.
117
+
118
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
119
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
120
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
121
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
122
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
123
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
124
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec 'eventful' do |p|
7
+ # self.rubyforge_name = 'eventfulx' # if different than 'eventful'
8
+ p.developer('James Coglan', 'jcoglan@googlemail.com')
9
+ p.extra_deps = %w[methodphitamine]
10
+ end
11
+
12
+ # vim: syntax=ruby
@@ -0,0 +1,108 @@
1
+ require 'observer'
2
+ require 'rubygems'
3
+ require 'methodphitamine'
4
+
5
+ # Adds named event publishing capabilities to the class that includes it.
6
+ # Actually composed of three modules: +Observable+ (from Ruby stdlib),
7
+ # +ObservableWithBlocks+, and +Eventful+ itself. See +README+ for examples.
8
+ module Eventful
9
+ VERSION = '1.0.0'
10
+
11
+ # The +Observer+ class is used to wrap blocks in an object that implements
12
+ # +update+, so it can be used with +Observable+. It extends <tt>Methodphitamine::It</tt>,
13
+ # meaning it can store any methods called on it and replay them later on any
14
+ # object. This is used to implement blockless event handlers.
15
+ class Observer < Methodphitamine::It
16
+ # Initalize using a block. The block will be called when the observed
17
+ # object sends notifications.
18
+ def initialize(&block)
19
+ super()
20
+ @block = block
21
+ end
22
+
23
+ # Called by the observed object inside +Observable+ to publish events.
24
+ def update(*args)
25
+ @block.call(*args)
26
+ end
27
+
28
+ # Patch these back in after Methodphitamine removes them. They are
29
+ # needed for +Observable+ to handle the object properly.
30
+ %w[respond_to? hash send].each do |sym|
31
+ define_method(sym) do |*args|
32
+ Object.instance_method(sym).bind(self).call(*args)
33
+ end
34
+ end
35
+ end
36
+
37
+ # Extends the +Observable+ module and allows it to accept blocks as observers.
38
+ # This is a distinct module because I often want to do this without using
39
+ # named events.
40
+ module ObservableWithBlocks
41
+ include Observable
42
+
43
+ # Adds an observer to the object. The observer may be an object implementing
44
+ # +update+, or a block that will be called when the object notifies observers.
45
+ # If a block is passed, we return the wrapping +Observer+ object so it can
46
+ # removed using +delete_observer+.
47
+ def add_observer(*args, &block)
48
+ return super unless block_given?
49
+ observer = Observer.new(&block)
50
+ add_observer(observer)
51
+ observer
52
+ end
53
+ end
54
+
55
+ # Mix in block observer support
56
+ include ObservableWithBlocks
57
+
58
+ # Registers a named event handler on the target object that will only fire
59
+ # when the object publishes events with the given name. The handler should
60
+ # be a block that will accept the object that fired the event, along with
61
+ # and data published with the event. Returns a <tt>Methodphitamine::It</tt>
62
+ # instance that will be replayed on the publishing object when the event fires.
63
+ #
64
+ # See +README+ for examples.
65
+ def on(event, &block)
66
+ observer = add_observer do |*args|
67
+ type, data = args[1], [args[0]] + args[2..-1]
68
+ if type == event
69
+ block ||= observer.to_proc
70
+ block.call(*data)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Fires a named event on the target object. The first argument should be a
76
+ # symbol representing the event name. Any subsequent arguments are passed
77
+ # listeners along with the publishing object. The event bubbles up the type
78
+ # system so that you can listen to all objects of a given type by regsitering
79
+ # listeners on their class.
80
+ def fire(*args)
81
+ return self if defined?(@observer_state) and not @observer_state
82
+
83
+ receiver = (Hash === args.first) ? args.shift[:receiver] : self
84
+ args = [receiver] + args
85
+
86
+ changed(true)
87
+ notify_observers(*args)
88
+ changed(true)
89
+
90
+ args[0] = {:receiver => receiver}
91
+ self.class.ancestors.grep(Eventful).each &it.fire(*args)
92
+
93
+ self
94
+ end
95
+
96
+ # Classes that +include+ +Eventful+ are also extended with it, so that event
97
+ # listeners can be registered on a class to fire whenever an instance of
98
+ # that class publishes an event.
99
+ def self.included(base)
100
+ base.extend(self)
101
+ end
102
+
103
+ # Extend +Eventful+ with itself so you can listen to all eventful objects
104
+ # using <tt>Eventful.on(:eventname) { handle_event() }</tt>.
105
+ extend(self)
106
+
107
+ end
108
+
@@ -0,0 +1,85 @@
1
+ require "test/unit"
2
+ require "eventful"
3
+ require "set"
4
+
5
+ class Foo
6
+ include Eventful
7
+ attr_reader :count
8
+
9
+ def initialize
10
+ @count = 0
11
+ end
12
+
13
+ def bump!(x = 1)
14
+ @count += x
15
+ end
16
+ end
17
+
18
+ class Bar
19
+ include Eventful
20
+ end
21
+
22
+ class TestEventful < Test::Unit::TestCase
23
+ def setup
24
+ [Foo, Bar, Eventful].each &it.delete_observers
25
+ end
26
+
27
+ def test_named_events
28
+ ayes, noes = 0, 0
29
+ f = Foo.new
30
+ f.on(:aye) { |foo, x| ayes += x }
31
+ obs = f.on(:noe) { |foo, x| noes += x }
32
+
33
+ f.fire(:aye, 1)
34
+ assert_equal 1, ayes
35
+ assert_equal 0, noes
36
+ f.fire(:noe, 3)
37
+ assert_equal 1, ayes
38
+ assert_equal 3, noes
39
+
40
+ f.delete_observer(obs)
41
+ f.fire(:noe, 3)
42
+ assert_equal 1, ayes
43
+ assert_equal 3, noes
44
+ end
45
+
46
+ def test_chaining
47
+ f = Foo.new
48
+ f.on(:aye).bump! 2
49
+ f.on(:noe).bump! -1
50
+
51
+ 2.times { f.fire(:aye) }
52
+ f.fire(:noe)
53
+
54
+ assert_equal 3, f.count
55
+ end
56
+
57
+ def test_bubbling
58
+ bar1, bar2 = Bar.new, Bar.new
59
+ list = []
60
+ Bar.on(:aye) { |r| list << r }
61
+ Eventful.on(:aye) { |r| list << r }
62
+ Eventful.on(:noe) { |r| list << r }
63
+
64
+ bar1.fire(:aye)
65
+ assert_equal [bar1, bar1], list
66
+ bar2.fire(:noe)
67
+ assert_equal [bar1, bar1, bar2], list
68
+
69
+ Bar.fire(:aye)
70
+ assert_equal [bar1, bar1, bar2, Bar], list
71
+ Bar.fire(:noe)
72
+ assert_equal [bar1, bar1, bar2, Bar], list
73
+ end
74
+
75
+ def test_chaining_on_bubble
76
+ f1, f2 = Foo.new, Foo.new
77
+ Foo.on(:aye).bump! 5
78
+ f1.fire(:aye)
79
+ assert_equal 5, f1.count
80
+ assert_equal 0, f2.count
81
+ f2.fire(:aye)
82
+ assert_equal 5, f1.count
83
+ assert_equal 5, f2.count
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eventful
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - James Coglan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-18 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: methodphitamine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.0
34
+ version:
35
+ description: ""
36
+ email:
37
+ - jcoglan@googlemail.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - README.txt
46
+ files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.txt
50
+ - Rakefile
51
+ - lib/eventful.rb
52
+ - test/test_eventful.rb
53
+ has_rdoc: true
54
+ homepage: http://github.com/jcoglan/eventful
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --main
60
+ - README.txt
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project: eventful
78
+ rubygems_version: 1.3.3
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: ""
82
+ test_files:
83
+ - test/test_eventful.rb