eventful 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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