swerling-cosell 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg/
2
+ doc/
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ == 0.0.1 / 2009-07-07
2
+
3
+ * initial release
data/README.rdoc ADDED
@@ -0,0 +1,187 @@
1
+ cosell
2
+ by Steven Swerling
3
+ http://tab-a.slot-z.net
4
+
5
+ == DESCRIPTION:
6
+
7
+ Cosell is a minimal implementation of the 'Announcements' observer
8
+ framework, originally introduced in VisualWorks Smalltalk 7.4 as a
9
+ replacement for 'triggerEvent' style of event notification. Instead of
10
+ triggering events identified by symbols, the events are first class
11
+ objects. For rationale, please see the original blog posting by Vassili
12
+ Bykov (refs below).
13
+
14
+ *Lineage*
15
+
16
+ This implementation is loosely based on Lukas Renggli's tweak of Colin Putney's
17
+ Squeak implementation of Vassili Bykov's Announcements framework for
18
+ VisualWorks Smalltalk. (Specifically Announcements-lr.13.mcz was used as
19
+ a reference.)
20
+
21
+ Liberties where taken during the port. In particular, the Announcer class
22
+ in the Smalltalk version is implemented here as a ruby module which can be
23
+ mixed into any object. Also, in this implementation any object (or class)
24
+ can serve as an announcement, so no Announcement class is implemented.
25
+
26
+ The ability to queue announcements in the background is built into cosell.
27
+
28
+ <b>The Name 'Cosell'</b>
29
+
30
+ I chose the name 'Cosell' because
31
+
32
+ a. Howard Cosell is an iconic event announcer
33
+ b. Googling for 'Ruby Announcements', 'Ruby Event Announcements', etc., produced scads of results about ruby meetups, conferences, and the like. So I went with something a bit cryptic but hopefully a little more searchable.
34
+
35
+ *See*
36
+
37
+ * {Original blog posting describing Announcments by Vassili Bykov}[http://www.cincomsmalltalk.com/userblogs/vbykov/blogView?entry=3310034894]
38
+ * {More info on the Announcements Framework}[http://wiki.squeak.org/squeak/5734]
39
+
40
+ == FEATURE
41
+
42
+ * Announcements-style event observer framework
43
+ * Easy way to queue events in background
44
+
45
+ == PROBLEMS
46
+
47
+ * None known. Should work in ruby 1.8 and 1.9.
48
+
49
+ == SYNOPSIS:
50
+
51
+
52
+ (this example is in the [gem]/example/basic_example.rb file)
53
+
54
+ #
55
+ # Will produce the following output:
56
+ #
57
+ # And now a word from our sponsor: 'the'
58
+ # End of round 1
59
+ # End of round 2
60
+ # End of round 3
61
+ # End of round 4
62
+ # End of round 5
63
+ # End of round 6
64
+ # End of round 7
65
+ # End of round 8
66
+ # End of round 9
67
+ # End of round 10
68
+ # End of round 11
69
+ # End of round 12
70
+ # End of round 13
71
+ # End of round 14
72
+ # TKO!
73
+
74
+ require 'rubygems'
75
+ require 'cosell'
76
+
77
+ # An announcer
78
+ class Howard
79
+ include Cosell
80
+ end
81
+
82
+ # a receiver of the announcements
83
+ class Television
84
+ def show(ann, opts={})
85
+ puts ann.to_s(opts)
86
+ end
87
+ end
88
+
89
+ # Some announcements
90
+ class Announcement
91
+ def to_s(opts={})
92
+ self.class.to_s + '!'
93
+ end
94
+ end
95
+ class WordFromOurSponsor < Announcement
96
+ attr_accessor :word
97
+ def to_s(opts={})
98
+ "And now a word from our sponsor: '#{word}'"
99
+ end
100
+ end
101
+ class EndOfRound < Announcement
102
+ def to_s(opts={})
103
+ "End of round #{opts[:round]}"
104
+ end
105
+ end
106
+ class KnockOut < Announcement; end
107
+ class TKO < KnockOut; end
108
+
109
+
110
+ # ------- Start announcing -------
111
+
112
+ # Create an announcer, and a subscriber
113
+ round = 1
114
+ howard = Howard.new
115
+ tv = Television.new
116
+ howard.when_announcing(WordFromOurSponsor, KnockOut) { |ann| tv.show(ann) }
117
+
118
+ # Make an announcement
119
+ announcement = WordFromOurSponsor.new
120
+ announcement.word = 'the'
121
+ howard.announce(announcement)
122
+ # => And know a word from our sponsors: 'the'
123
+
124
+ # Make another announcement
125
+ howard.announce(EndOfRound)
126
+ # => nothing, you haven't subscribed yet to EndOfRound. Tree fell, nobody heard. Didn't happen.
127
+
128
+ # Create a second subscription
129
+ eor_subscription = lambda do |ann|
130
+ tv.show(ann, :round => round)
131
+ round += 1
132
+ end
133
+ howard.when_announcing(EndOfRound, &eor_subscription)
134
+
135
+ # Tell the announcer to use a background announcments queue
136
+ # Only allow the announcer to broadcast 5 announcments at a time
137
+ # before going to sleep for 0.05 seconds
138
+ howard.queue_announcements!(:sleep_time => 0.05, :announcements_per_cycle => 5)
139
+
140
+ # Start making announcments (they will be queueud in the background)
141
+ 14.times {howard.announce(EndOfRound)}
142
+
143
+ sleep 0.05 # announcements for the first 5 rounds appear
144
+ sleep 0.05 # announcements for the next 5 rounds
145
+ sleep 0.05 # announcements for end of the next 4 rounds (there is not 15th round
146
+ sleep 0.05 # no announcements, all the announcements have been announced
147
+
148
+ # queue the final announcment
149
+ howard.announce(TKO)
150
+ # => TKO!
151
+
152
+ sleep 0.05 # the TKO is broadcast
153
+
154
+ == REQUIREMENTS:
155
+
156
+ * ruby, rubygems
157
+
158
+ == INSTALL:
159
+
160
+
161
+ gem install swerling-cosell --source http://gems.github.com
162
+
163
+
164
+ == LICENSE:
165
+
166
+ (The MIT License)
167
+
168
+ Copyright (c) 2009
169
+
170
+ Permission is hereby granted, free of charge, to any person obtaining
171
+ a copy of this software and associated documentation files (the
172
+ 'Software'), to deal in the Software without restriction, including
173
+ without limitation the rights to use, copy, modify, merge, publish,
174
+ distribute, sublicense, and/or sell copies of the Software, and to
175
+ permit persons to whom the Software is furnished to do so, subject to
176
+ the following conditions:
177
+
178
+ The above copyright notice and this permission notice shall be
179
+ included in all copies or substantial portions of the Software.
180
+
181
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
182
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
183
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
184
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
185
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
186
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
187
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.txt ADDED
@@ -0,0 +1,187 @@
1
+ cosell
2
+ by Steven Swerling
3
+ http://tab-a.slot-z.net
4
+
5
+ == DESCRIPTION:
6
+
7
+ Cosell is a minimal implementation of the 'Announcements' observer
8
+ framework, originally introduced in VisualWorks Smalltalk 7.4 as a
9
+ replacement for 'triggerEvent' style of event notification. Instead of
10
+ triggering events identified by symbols, the events are first class
11
+ objects. For rationale, please see the original blog posting by Vassili
12
+ Bykov (refs below).
13
+
14
+ *Lineage*
15
+
16
+ This implementation is loosely based on Lukas Renggli's tweak of Colin Putney's
17
+ Squeak implementation of Vassili Bykov's Announcements framework for
18
+ VisualWorks Smalltalk. (Specifically Announcements-lr.13.mcz was used as
19
+ a reference.)
20
+
21
+ Liberties where taken during the port. In particular, the Announcer class
22
+ in the Smalltalk version is implemented here as a ruby module which can be
23
+ mixed into any object. Also, in this implementation any object (or class)
24
+ can serve as an announcement, so no Announcement class is implemented.
25
+
26
+ The ability to queue announcements in the background is built into cosell.
27
+
28
+ <b>The Name 'Cosell'</b>
29
+
30
+ I chose the name 'Cosell' because
31
+
32
+ a. Howard Cosell is an iconic event announcer
33
+ b. Googling for 'Ruby Announcements', 'Ruby Event Announcements', etc., produced scads of results about ruby meetups, conferences, and the like. So I went with something a bit cryptic but hopefully a little more searchable.
34
+
35
+ *See*
36
+
37
+ * {Original blog posting describing Announcments by Vassili Bykov}[http://www.cincomsmalltalk.com/userblogs/vbykov/blogView?entry=3310034894]
38
+ * {More info on the Announcements Framework}[http://wiki.squeak.org/squeak/5734]
39
+
40
+ == FEATURE
41
+
42
+ * Announcements-style event observer framework
43
+ * Easy way to queue events in background
44
+
45
+ == PROBLEMS
46
+
47
+ * None known. Should work in ruby 1.8 and 1.9.
48
+
49
+ == SYNOPSIS:
50
+
51
+
52
+ (this example is in the [gem]/example/basic_example.rb file)
53
+
54
+ #
55
+ # Will produce the following output:
56
+ #
57
+ # And now a word from our sponsor: 'the'
58
+ # End of round 1
59
+ # End of round 2
60
+ # End of round 3
61
+ # End of round 4
62
+ # End of round 5
63
+ # End of round 6
64
+ # End of round 7
65
+ # End of round 8
66
+ # End of round 9
67
+ # End of round 10
68
+ # End of round 11
69
+ # End of round 12
70
+ # End of round 13
71
+ # End of round 14
72
+ # TKO!
73
+
74
+ require 'rubygems'
75
+ require 'cosell'
76
+
77
+ # An announcer
78
+ class Howard
79
+ include Cosell
80
+ end
81
+
82
+ # a receiver of the announcements
83
+ class Television
84
+ def show(ann, opts={})
85
+ puts ann.to_s(opts)
86
+ end
87
+ end
88
+
89
+ # Some announcements
90
+ class Announcement
91
+ def to_s(opts={})
92
+ self.class.to_s + '!'
93
+ end
94
+ end
95
+ class WordFromOurSponsor < Announcement
96
+ attr_accessor :word
97
+ def to_s(opts={})
98
+ "And now a word from our sponsor: '#{word}'"
99
+ end
100
+ end
101
+ class EndOfRound < Announcement
102
+ def to_s(opts={})
103
+ "End of round #{opts[:round]}"
104
+ end
105
+ end
106
+ class KnockOut < Announcement; end
107
+ class TKO < KnockOut; end
108
+
109
+
110
+ # ------- Start announcing -------
111
+
112
+ # Create an announcer, and a subscriber
113
+ round = 1
114
+ howard = Howard.new
115
+ tv = Television.new
116
+ howard.when_announcing(WordFromOurSponsor, KnockOut) { |ann| tv.show(ann) }
117
+
118
+ # Make an announcement
119
+ announcement = WordFromOurSponsor.new
120
+ announcement.word = 'the'
121
+ howard.announce(announcement)
122
+ # => And know a word from our sponsors: 'the'
123
+
124
+ # Make another announcement
125
+ howard.announce(EndOfRound)
126
+ # => nothing, you haven't subscribed yet to EndOfRound. Tree fell, nobody heard. Didn't happen.
127
+
128
+ # Create a second subscription
129
+ eor_subscription = lambda do |ann|
130
+ tv.show(ann, :round => round)
131
+ round += 1
132
+ end
133
+ howard.when_announcing(EndOfRound, &eor_subscription)
134
+
135
+ # Tell the announcer to use a background announcments queue
136
+ # Only allow the announcer to broadcast 5 announcments at a time
137
+ # before going to sleep for 0.05 seconds
138
+ howard.queue_announcements!(:sleep_time => 0.05, :announcements_per_cycle => 5)
139
+
140
+ # Start making announcments (they will be queueud in the background)
141
+ 14.times {howard.announce(EndOfRound)}
142
+
143
+ sleep 0.05 # announcements for the first 5 rounds appear
144
+ sleep 0.05 # announcements for the next 5 rounds
145
+ sleep 0.05 # announcements for end of the next 4 rounds (there is not 15th round
146
+ sleep 0.05 # no announcements, all the announcements have been announced
147
+
148
+ # queue the final announcment
149
+ howard.announce(TKO)
150
+ # => TKO!
151
+
152
+ sleep 0.05 # the TKO is broadcast
153
+
154
+ == REQUIREMENTS:
155
+
156
+ * ruby, rubygems
157
+
158
+ == INSTALL:
159
+
160
+
161
+ gem install swerling-cosell --source http://gems.github.com
162
+
163
+
164
+ == LICENSE:
165
+
166
+ (The MIT License)
167
+
168
+ Copyright (c) 2009
169
+
170
+ Permission is hereby granted, free of charge, to any person obtaining
171
+ a copy of this software and associated documentation files (the
172
+ 'Software'), to deal in the Software without restriction, including
173
+ without limitation the rights to use, copy, modify, merge, publish,
174
+ distribute, sublicense, and/or sell copies of the Software, and to
175
+ permit persons to whom the Software is furnished to do so, subject to
176
+ the following conditions:
177
+
178
+ The above copyright notice and this permission notice shall be
179
+ included in all copies or substantial portions of the Software.
180
+
181
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
182
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
183
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
184
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
185
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
186
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
187
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'cosell'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'cosell'
22
+ PROJ.authors = 'Steven Swerling'
23
+ PROJ.email = 'sswerling@yahoo.com'
24
+ PROJ.url = 'http://github.com/swerling/TODO'
25
+ PROJ.version = Cosell::VERSION
26
+ PROJ.rubyforge.name = 'cosell'
27
+
28
+ PROJ.spec.opts << '--color'
29
+
30
+ PROJ.rdoc.opts = ["--inline-source"]
31
+
32
+ namespace :my do
33
+ namespace :gem do
34
+ task :package => [:clobber] do
35
+ sh "rm -rf #{File.join(File.dirname(__FILE__), 'pkg')}"
36
+ sh "rm -rf #{File.join(File.dirname(__FILE__), 'doc')}"
37
+ Rake::Task['gem:package'].invoke
38
+ end
39
+ end
40
+ end
41
+
data/cosell.gemspec ADDED
@@ -0,0 +1,64 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{cosell}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Steven Swerling"]
9
+ s.date = %q{2009-07-18}
10
+ s.description = %q{Cosell is a minimal implementation of the 'Announcements' observer
11
+ framework, originally introduced in VisualWorks Smalltalk 7.4 as a
12
+ replacement for 'triggerEvent' style of event notification. Instead of
13
+ triggering events identified by symbols, the events are first class
14
+ objects. For rationale, please see the original blog posting by Vassili
15
+ Bykov (refs below).
16
+
17
+ *Lineage*
18
+
19
+ This implementation is loosely based on Lukas Renggli's tweak of Colin Putney's
20
+ Squeak implementation of Vassili Bykov's Announcements framework for
21
+ VisualWorks Smalltalk. (Specifically Announcements-lr.13.mcz was used as
22
+ a reference.)
23
+
24
+ Liberties where taken during the port. In particular, the Announcer class
25
+ in the Smalltalk version is implemented here as a ruby module which can be
26
+ mixed into any object. Also, in this implementation any object (or class)
27
+ can serve as an announcement, so no Announcement class is implemented.
28
+
29
+ The ability to queue announcements in the background is built into cosell.
30
+
31
+ <b>The Name 'Cosell'</b>
32
+
33
+ I chose the name 'Cosell' because
34
+
35
+ a. Howard Cosell is an iconic event announcer
36
+ b. Googling for 'Ruby Announcements', 'Ruby Event Announcements', etc., produced scads of results about ruby meetups, conferences, and the like. So I went with something a bit cryptic but hopefully a little more searchable.
37
+
38
+ *See*
39
+
40
+ * {Original blog posting describing Announcments by Vassili Bykov}[http://www.cincomsmalltalk.com/userblogs/vbykov/blogView?entry=3310034894]
41
+ * {More info on the Announcements Framework}[http://wiki.squeak.org/squeak/5734]}
42
+ s.email = %q{sswerling@yahoo.com}
43
+ s.extra_rdoc_files = ["History.txt", "README.rdoc", "README.txt"]
44
+ s.files = [".gitignore", "History.txt", "README.rdoc", "README.txt", "Rakefile", "cosell.gemspec", "example/basic_example.rb", "example/cat_whisperer.rb", "lib/cosell.rb", "lib/cosell/announcer.rb", "lib/cosell/monkey.rb", "spec/cosell_spec.rb", "spec/spec_helper.rb"]
45
+ s.homepage = %q{http://github.com/swerling/TODO}
46
+ s.rdoc_options = ["--inline-source", "--main", "README.txt"]
47
+ s.require_paths = ["lib"]
48
+ s.rubyforge_project = %q{cosell}
49
+ s.rubygems_version = %q{1.3.3}
50
+ s.summary = %q{Cosell is a minimal implementation of the 'Announcements' observer framework, originally introduced in VisualWorks Smalltalk 7}
51
+
52
+ if s.respond_to? :specification_version then
53
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
57
+ s.add_development_dependency(%q<bones>, [">= 2.4.0"])
58
+ else
59
+ s.add_dependency(%q<bones>, [">= 2.4.0"])
60
+ end
61
+ else
62
+ s.add_dependency(%q<bones>, [">= 2.4.0"])
63
+ end
64
+ end
@@ -0,0 +1,99 @@
1
+ #
2
+ # Will produce the following output:
3
+ #
4
+ # And now a word from our sponsor: 'the'
5
+ # End of round 1
6
+ # End of round 2
7
+ # End of round 3
8
+ # End of round 4
9
+ # End of round 5
10
+ # End of round 6
11
+ # End of round 7
12
+ # End of round 8
13
+ # End of round 9
14
+ # End of round 10
15
+ # End of round 11
16
+ # End of round 12
17
+ # End of round 13
18
+ # End of round 14
19
+ # TKO!
20
+
21
+ require 'rubygems'
22
+ require 'cosell'
23
+
24
+ # An announcer
25
+ class Howard
26
+ include Cosell
27
+ end
28
+
29
+ # a receiver of the announcements
30
+ class Television
31
+ def show(ann, opts={})
32
+ puts ann.to_s(opts)
33
+ end
34
+ end
35
+
36
+ # Some announcements
37
+ class Announcement
38
+ def to_s(opts={})
39
+ self.class.to_s + '!'
40
+ end
41
+ end
42
+ class WordFromOurSponsor < Announcement
43
+ attr_accessor :word
44
+ def to_s(opts={})
45
+ "And now a word from our sponsor: '#{word}'"
46
+ end
47
+ end
48
+ class EndOfRound < Announcement
49
+ def to_s(opts={})
50
+ "End of round #{opts[:round]}"
51
+ end
52
+ end
53
+ class KnockOut < Announcement; end
54
+ class TKO < KnockOut; end
55
+
56
+
57
+ # ------- Start announcing -------
58
+
59
+ # Create an announcer, and a subscriber
60
+ round = 1
61
+ howard = Howard.new
62
+ tv = Television.new
63
+ howard.when_announcing(WordFromOurSponsor, KnockOut) { |ann| tv.show(ann) }
64
+
65
+ # Make an announcement
66
+ announcement = WordFromOurSponsor.new
67
+ announcement.word = 'the'
68
+ howard.announce(announcement)
69
+ # => And know a word from our sponsors: 'the'
70
+
71
+ # Make another announcement
72
+ howard.announce(EndOfRound)
73
+ # => nothing, you haven't subscribed yet to EndOfRound. Tree fell, nobody heard. Didn't happen.
74
+
75
+ # Create a second subscription
76
+ eor_subscription = lambda do |ann|
77
+ tv.show(ann, :round => round)
78
+ round += 1
79
+ end
80
+ howard.when_announcing(EndOfRound, &eor_subscription)
81
+
82
+ # Tell the announcer to use a background announcments queue
83
+ # Only allow the announcer to broadcast 5 announcments at a time
84
+ # before going to sleep for 0.05 seconds
85
+ howard.queue_announcements!(:sleep_time => 0.05, :announcements_per_cycle => 5)
86
+
87
+ # Start making announcments (they will be queueud in the background)
88
+ 14.times {howard.announce(EndOfRound)}
89
+
90
+ sleep 0.05 # announcements for the first 5 rounds appear
91
+ sleep 0.05 # announcements for the next 5 rounds
92
+ sleep 0.05 # announcements for end of the next 4 rounds (there is not 15th round
93
+ sleep 0.05 # no announcements, all the announcements have been announced
94
+
95
+ # queue the final announcment
96
+ howard.announce(TKO)
97
+ # => TKO!
98
+
99
+ sleep 0.05 # the TKO is broadcast
@@ -0,0 +1,87 @@
1
+ #
2
+ # Sits by window, talks to cats
3
+ #
4
+ class CatWhisperer
5
+ include Cosell::Announcer
6
+ end
7
+
8
+ #
9
+ # events that occur in the home
10
+ #
11
+ class SomeoneEnteringHome
12
+ attr_accessor :who_is_entering
13
+ end
14
+ class OwnerEnteringHome < SomeoneEnteringHome; end
15
+ class BurglerEnteringHome < SomeoneEnteringHome; end
16
+ class DogEnteringHome < SomeoneEnteringHome; end
17
+ class BirdEnteringHome < SomeoneEnteringHome; end
18
+
19
+ #
20
+ # participants
21
+ #
22
+ class Cat
23
+ end
24
+ class NormalSizedCat < Cat
25
+ def deal_with_dog(dog)
26
+ if dog.running_at_cat?(self)
27
+ puts "run to bookshelf; climb bookshelf; feign indifference"
28
+ else
29
+ puts "feign indifference; saunter to bookshelf; climb bookshelf; feign indifference"
30
+ end
31
+ end
32
+ def deal_with_burgler(burgler)
33
+ puts "feign indifference"
34
+ end
35
+ def deal_with_owner(owner)
36
+ puts "feign indifference while slowly sauntering towards owner; " \
37
+ + "nuzzle owner; receive affection; try to feign indifference; break down and start purring"
38
+ end
39
+
40
+ end
41
+
42
+ class ReallyBigCat < Cat
43
+ def eat(who)
44
+ puts "Really big cat eats #{who}"
45
+ end
46
+ def deal_with_burgler(burgler)
47
+ eat('burgler')
48
+ end
49
+ def deal_with_owner(owner)
50
+ eat('owner')
51
+ end
52
+ def deal_with_dog(owner)
53
+ eat('dog')
54
+ end
55
+ end
56
+
57
+ cat_whisperer = CatWhisperer.new
58
+ really_big_cat = ReallyBigCat.new
59
+ regular_cat = NormalSizedCat.new
60
+
61
+ #
62
+ # wire up events
63
+ #
64
+ cat_whisperer.when_announcing(DogEnteringHome) do |event|
65
+ cat.deal_with_dog(event.who_is_entering)
66
+ rally_big_cat.deal_with_dog(event.who_is_entering)
67
+ end
68
+ cat_whisperer.when_announcing(DogEnteringHome, :send => |event|
69
+ cat.deal_with_dog(event.who_is_entering)
70
+ rally_big_cat.deal_with_dog(event.who_is_entering)
71
+ end
72
+
73
+ cat_whisperer.when_announcing(OwnerEnteringHome) do |event|
74
+ cat.deal_with_person(dog_entering_event.who_is_entering)
75
+ rally_big_cat.deal_with_dog(dog_entering_event.who_is_entering)
76
+ end
77
+
78
+ cat_whisperer.when_announcing(BurglerEnteringHome) do |event|
79
+ cat.deal_with_person(event.who_is_entering)
80
+ rally_big_cat.deal_with_dog(event.who_is_entering)
81
+ end
82
+
83
+ #
84
+ # Mayhem ensues
85
+ #
86
+ cat_whisperer.announce(OwnerEnteringHome.new)
87
+ # =>
data/lib/cosell.rb ADDED
@@ -0,0 +1,48 @@
1
+ module Cosell
2
+
3
+ # :stopdoc:
4
+ VERSION = '0.0.1'
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+
8
+ # Returns the version string for the library.
9
+ #
10
+ def self.version
11
+ VERSION
12
+ end
13
+
14
+ # Returns the library path for the module. If any arguments are given,
15
+ # they will be joined to the end of the libray path using
16
+ # <tt>File.join</tt>.
17
+ #
18
+ def self.libpath( *args )
19
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
20
+ end
21
+
22
+ # Returns the lpath for the module. If any arguments are given,
23
+ # they will be joined to the end of the path using
24
+ # <tt>File.join</tt>.
25
+ #
26
+ def self.path( *args )
27
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
28
+ end
29
+
30
+ # Utility method used to require all files ending in .rb that lie in the
31
+ # directory below this file that has the same name as the filename passed
32
+ # in. Optionally, a specific _directory_ name can be passed in such that
33
+ # the _filename_ does not have to be equivalent to the directory.
34
+ #
35
+ def self.require_all_libs_relative_to( fname, dir = nil )
36
+ dir ||= ::File.basename(fname, '.*')
37
+ search_me = ::File.expand_path(
38
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
39
+
40
+ Dir.glob(search_me).sort.each {|rb| require rb}
41
+ end
42
+ # :startdoc:
43
+
44
+ end # module Cosell
45
+
46
+ Cosell.require_all_libs_relative_to(__FILE__)
47
+
48
+ # EOF
@@ -0,0 +1,223 @@
1
+ require 'logger'
2
+
3
+ module Cosell
4
+
5
+ def initialize *args
6
+ initialize_cosell!
7
+ super
8
+ end
9
+
10
+ #
11
+ #
12
+ # ANNOUNCEMENTS QUEUE
13
+ #
14
+ #
15
+
16
+ # Place all announcments in a queue, and make announcements in a background thread.
17
+ #
18
+ # Options:
19
+ # :sleep_time => how long to sleep (in seconds) after making a batch of announchements
20
+ # default: 0.01
21
+ # :announcements_per_cycle => how many announcements to make before sleeping for sleep_time
22
+ # default: 25
23
+ # :logger => a logger. Where to log exceptions and warnings.
24
+ #
25
+ # Note: at the moment, this method may only be called once, and cannot be undone. There is
26
+ # no way to interrupt the thread.
27
+
28
+ def queue_announcements!(opts = {})
29
+
30
+ self.initialize_cosell_if_needed
31
+
32
+ # kill off the last queue first
33
+ if self.announcements_thread
34
+ kill_queue!
35
+ sleep 0.01
36
+ queue_announcements! opts
37
+ end
38
+
39
+ self.should_queue_announcements = true
40
+ @__announcements_queue ||= Queue.new
41
+
42
+ how_many_per_cycle = opts[:announcements_per_cycle] || 25
43
+ cycle_duration = opts[:sleep_time] || 0.01
44
+ self.queue_logger = opts[:logger]
45
+ count = 0
46
+
47
+ self.announcements_thread = Thread.new do
48
+ begin
49
+ loop do
50
+ if queue_killed?
51
+ self.kill_announcement_queue = false
52
+ self.announcements_thread = nil
53
+ log "Announcement queue killed with #{self.announcements_queue.size} announcements still queued", :info
54
+ break
55
+ else
56
+ self.announce_now! self.announcements_queue.pop
57
+ count += 1
58
+ if (count%how_many_per_cycle).eql?(0)
59
+ log "Announcement queue finished batch of #{how_many_per_cycle}, sleeping for #{cycle_duration} sec", :debug
60
+ count = 0
61
+ sleep cycle_duration
62
+ end
63
+ end
64
+ end
65
+ rescue Exception => x
66
+ log "Exception: #{x}, trace: \n\t#{x.backtrace.join("\n\t")}", :error
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ #
73
+ #
74
+ # SUBSCRIBE/MAKE ANNOUNCEMENTS
75
+ #
76
+ #
77
+
78
+ # Pass in an anouncement class (or array of announcement classes), along with a block defining the
79
+ # action to be taken when an announcment of one of the specified classes is announced by this announcer.
80
+ # (see Cossell::Announcer for full explanation)
81
+ def subscribe *announce_classes, &block
82
+
83
+ self.initialize_cosell_if_needed
84
+
85
+ Array(announce_classes).each do |announce_class|
86
+ raise "Can only subscribe to classes. Not a class: #{announce_class}" unless announce_class.is_a?(Class)
87
+ self.subscriptions[announce_class] ||= []
88
+ self.subscriptions[announce_class] << lambda(&block)
89
+ end
90
+ end
91
+ alias_method :when_announcing, :subscribe
92
+
93
+ # Stop announcing for a given announcement class (or array of classes)
94
+ def unsubscribe *announce_classes
95
+ Array(announce_classes).each do |announce_class|
96
+ self.subscriptions.delete announce_class
97
+ end
98
+ end
99
+
100
+ # If queue_announcements? true, puts announcement in a Queue.
101
+ # Otherwise, calls announce_now!
102
+ # Queued announcements are announced in a background thread in batches
103
+ # (see the #initialize method doc for details).
104
+ def announce announcement
105
+ if self.queue_announcements?
106
+ self.announcements_queue << announcement
107
+ else
108
+ self.announce_now! announcement
109
+ end
110
+ end
111
+
112
+ #
113
+ # First, an announcement is made by calling 'as_announcement' on an_announcement_or_announcement_factory,
114
+ # and subscribers to the announcement's class are then notified
115
+ #
116
+ # subscribers to this announcer will be filtered to those that match to the announcement's class,
117
+ # and those subscriptions will be 'fired'. Subscribers should use the 'subscribe' method (also
118
+ # called 'when_announcing') to configure actions to take when a given announcement is made.
119
+ #
120
+ # Typically, an announcement is passed in for an_announcement_factory, in
121
+ # which case as_announcement does nothing but return the announcement. But any class can override
122
+ # as_announcement to adapt into an anouncement as they see fit.
123
+ #
124
+ # (see Cossell::Announcer for full explanation)
125
+ #
126
+ def announce_now! an_announcement_or_announcement_factory
127
+ announcement = an_announcement_or_announcement_factory.as_announcement
128
+
129
+ self.subscriptions.each do |subscription_type, subscriptions_for_type |
130
+ if announcement.is_a?(subscription_type)
131
+ subscriptions_for_type.each{|subscription| subscription.call(announcement) }
132
+ end
133
+ end
134
+
135
+ return announcement
136
+ end
137
+
138
+ #
139
+ #
140
+ # DEBUG
141
+ #
142
+ #
143
+
144
+ #
145
+ # Log a message every time this announcer makes an announcement
146
+ #
147
+ # Options:
148
+ # :on => Which class of announcements to spy on. Default is Object (ie. all announcements)
149
+ # :logger => The log to log to. Default is a logger on STDOUT
150
+ # :level => The log level to log with. Default is :info
151
+ # :preface => A message to prepend to all log messages. Default is "Announcement Spy: "
152
+ def spy!(opts = {})
153
+ on = opts[:on] || Object
154
+ logger = opts[:logger] || Logger.new(STDOUT)
155
+ level = opts[:level] || :info
156
+ preface = opts[:preface_with] || "Announcement Spy: "
157
+ self.subscribe(on){|ann| logger.send(level, "#{preface} #{ann.as_announcement_trace}")}
158
+ end
159
+
160
+ # lazy initialization of cosell.
161
+ # Optional -- calling this will get rid of any subsequent warnings about uninitialized ivs
162
+ # In most cases not necessary, and should never have an effect except to get rid of some warnings.
163
+ def initialize_cosell_if_needed
164
+ self.initialize_cosell! if @__subscriptions.nil?
165
+ end
166
+
167
+ # Will blow away any queue, and reset all state.
168
+ # Should not be necessary to call this, but left public for testing.
169
+ def initialize_cosell!
170
+ # Using pseudo-scoped var names.
171
+ # Unfortunately cant lazily init these w/out ruby warnings going berzerk in verbose mode,
172
+ # So explicitly declaring them here.
173
+ @__queue_announcements ||= false
174
+ @__announcements_queue ||= nil
175
+ @__kill_announcement_queue ||= false
176
+ @__announcements_thread ||= nil
177
+ @__subscriptions ||= {}
178
+ @__queue_logger ||= {}
179
+ end
180
+
181
+ # Kill the announcments queue.
182
+ # This is called automatically if you call queue_announcements!, before starting the next
183
+ # announcments thread, so it's optional. A way of stopping announcments.
184
+ def kill_queue!
185
+ @__kill_announcement_queue = true
186
+ end
187
+
188
+ # return whether annoucements are queued or sent out immediately when the #announce method is called.
189
+ def queue_announcements?
190
+ return @__queue_announcements.eql?(true)
191
+ end
192
+
193
+ protected
194
+
195
+ #:stopdoc:
196
+
197
+ def log(msg, level = :info)
198
+ self.queue_logger.send(level, msg) if self.queue_logger
199
+ end
200
+
201
+ # return whether the queue was killed by kill_queue!
202
+ def queue_killed?
203
+ @__kill_announcement_queue.eql?(true)
204
+ end
205
+
206
+ def queue_logger; @__queue_logger; end
207
+ def queue_logger= x; @__queue_logger = x; end
208
+ def announcements_queue; @__announcements_queue; end
209
+ def announcements_queue= x; @__announcements_queue = x; end
210
+ def announcements_thread; @__announcements_thread; end
211
+ def announcements_thread= x; @__announcements_thread = x; end
212
+ def kill_announcement_queue= x; @__kill_announcement_queue = x; end
213
+ def should_queue_announcements= x; @__queue_announcements = x; end
214
+ def subscriptions= x; @__subscriptions = x; end
215
+ def subscriptions; @__subscriptions; end
216
+
217
+ #:startdoc:
218
+ public
219
+
220
+
221
+ end
222
+
223
+
@@ -0,0 +1,32 @@
1
+ #
2
+ # Cosell is intended to be way for objects to
3
+ # communicate throughout the Object graph. It is _supposed_ to be
4
+ # pervasive. As such, it has a few top-level methods that all objects inherit.
5
+ #
6
+ class Object
7
+
8
+ # When an object (or class) is announced, :as_announcement is called, the result of which
9
+ # becomes the announcement. By default just returns self, but can be overridden if appropriate.
10
+ # By default, simply return self.
11
+ def as_announcement
12
+ return self
13
+ end
14
+
15
+ # When cosell is configured to "spy!", the result of announement.as_announcement_trace is what
16
+ # is sent to the spy log. By default just calls 'to_s'.
17
+ def as_announcement_trace
18
+ self.to_s
19
+ end
20
+
21
+ # When a class is used as an announcment, an empty new instance is created using #allocate.
22
+ # Will raise an exception for those rare classes that cannot #allocate a new instance.
23
+ def self.as_announcement
24
+ new_inst = self.allocate rescue nil
25
+ raise "Cannot create an announcement out of #{self}. Please implement 'as_announcement' as a class method of #{self}." if new_inst.nil?
26
+ new_inst
27
+ end
28
+
29
+ end
30
+
31
+
32
+
@@ -0,0 +1,179 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ #
4
+ # A few announcments
5
+ #
6
+ class AWordFromOurSponsor
7
+ attr_accessor :word
8
+ end
9
+ class KnockOut; end
10
+ class TKO < KnockOut; end
11
+
12
+ #
13
+ # A class whose objects can act as announcers
14
+ #
15
+ class AnyOldClass; include Cosell; end
16
+
17
+ #
18
+ # The tests
19
+ #
20
+ describe Cosell do
21
+
22
+ before(:each) do
23
+ @announcer = AnyOldClass.new
24
+ end
25
+
26
+ it "should instantiate announcement instance from class if needed" do
27
+ @announcer.announce(AWordFromOurSponsor).class.should be_eql(AWordFromOurSponsor)
28
+ @announcer.announce(AWordFromOurSponsor.new).class.should be_eql(AWordFromOurSponsor)
29
+ end
30
+
31
+ it "should execute block specified by subscription" do
32
+
33
+ #@announcer.spy!
34
+
35
+ # After subscribing to KnockOut, make sure it fires whenever
36
+ # KnockOut or it's subclass TKO are announced.
37
+
38
+ what_was_announced = nil
39
+ @announcer.when_announcing(AWordFromOurSponsor, KnockOut) { |ann| what_was_announced = ann }
40
+
41
+ what_was_announced = nil
42
+ @announcer.announce KnockOut
43
+ what_was_announced.should_not be_nil
44
+ what_was_announced.class.should be_eql KnockOut
45
+
46
+ what_was_announced = nil
47
+ @announcer.announce TKO
48
+ what_was_announced.should_not be_nil
49
+ what_was_announced.class.should be_eql TKO
50
+
51
+
52
+ # Do the same thing as above, but announce instances (test above used the class as the announcement)
53
+ # make sure if an announcement instance is announced, that the exact instance is what is announced
54
+
55
+ what_was_announced = nil
56
+ announcement = AWordFromOurSponsor.new
57
+ announcement.word = 'the'
58
+ @announcer.announce announcement
59
+ what_was_announced.should_not be_nil
60
+ what_was_announced.class.should be_eql AWordFromOurSponsor
61
+ what_was_announced.word.should be_eql('the')
62
+
63
+ what_was_announced = nil
64
+ @announcer.announce TKO.new
65
+ what_was_announced.should_not be_nil
66
+ what_was_announced.class.should be_eql TKO
67
+
68
+ end
69
+
70
+ it "should take actions only on announcements of events for which there is a subscription" do
71
+ # Make sure the subscription block fires when an AWordFromOurSponsor is
72
+ # announced, setting what_was_announced to the announcement)
73
+ what_was_announced = nil
74
+ @announcer.when_announcing(KnockOut) { |ann| what_was_announced = ann }
75
+
76
+ @announcer.announce AWordFromOurSponsor
77
+ what_was_announced.should be_nil
78
+
79
+ @announcer.announce AWordFromOurSponsor.new # also test announcement instances
80
+ what_was_announced.should be_nil
81
+
82
+ @announcer.announce TKO # subclass of Knockout, should be announced
83
+ what_was_announced.should_not be_nil
84
+ end
85
+
86
+ it "should be able to subscribe to set of announcements types" do
87
+ what_was_announced = nil
88
+ @announcer.when_announcing(AWordFromOurSponsor, KnockOut) { |ann| what_was_announced = ann }
89
+
90
+ what_was_announced = nil
91
+ @announcer.announce AWordFromOurSponsor
92
+ what_was_announced.should_not be_nil
93
+
94
+ what_was_announced = nil
95
+ @announcer.announce KnockOut
96
+ what_was_announced.should_not be_nil
97
+ end
98
+
99
+ it "should not take actions after unsubscribing" do
100
+ what_was_announced = nil
101
+ @announcer.when_announcing(AWordFromOurSponsor, KnockOut) { |ann| what_was_announced = ann }
102
+ @announcer.announce AWordFromOurSponsor
103
+ what_was_announced.should_not be_nil
104
+
105
+ @announcer.unsubscribe(AWordFromOurSponsor)
106
+ what_was_announced = nil
107
+ @announcer.announce AWordFromOurSponsor
108
+ what_was_announced.should be_nil
109
+ @announcer.announce KnockOut
110
+ what_was_announced.should_not be_nil
111
+ end
112
+
113
+ it "should be able to queue announcements" do
114
+ what_was_announced = nil
115
+ count = 0
116
+ sleep_time = 0.1
117
+ how_many_each_cycle = 77
118
+ @announcer.queue_announcements!(:sleep_time => sleep_time,
119
+ :logger => Logger.new(STDOUT),
120
+ :announcements_per_cycle => how_many_each_cycle)
121
+ @announcer.when_announcing(AWordFromOurSponsor) { |ann| count += 1 }
122
+
123
+ little_bench("time to queue 10_000 announcements"){10_000.times {@announcer.announce AWordFromOurSponsor}}
124
+
125
+ # @announcer.spy! #dbg
126
+
127
+ # Allow announcer thread to do a few batches of announcements, checking the
128
+ # count after each batch. Since we may get to this part of the thread after
129
+ # the announcer has already made a few announcements, use the count at
130
+ # this moment as the starting_count
131
+ start_count = count
132
+ #puts "-------start count: #{count}" # dbg
133
+
134
+ sleep sleep_time + 0.01
135
+ #puts "-------count: #{count}" # dbg
136
+ count.should be_eql(start_count + 1*how_many_each_cycle)
137
+
138
+ sleep sleep_time
139
+ #puts "-------count: #{count}" # dbg
140
+ count.should be_eql(start_count + 2*how_many_each_cycle)
141
+
142
+ sleep sleep_time
143
+ #puts "-------count: #{count}" # dbg
144
+ count.should be_eql(start_count + 3*how_many_each_cycle)
145
+
146
+ # See if killing the queue stops announcments that where queued
147
+ @announcer.kill_queue!
148
+ count_after_queue_stopped = count
149
+ #puts "-------count after stopping: #{count}" # dbg
150
+ sleep sleep_time * 2
151
+ count.should be_eql(count_after_queue_stopped)
152
+
153
+ end
154
+
155
+ # it "should suppress announcements during suppress_announcements block" do
156
+ # # TODO: support for this idiom:
157
+ # notifier.suppress_announcements_during {
158
+ # }
159
+ # and
160
+ # notifier.suppress_announcements(EventType,
161
+ # :during => lambda { "some operation" },
162
+ # :send_unique_events_when_done => true)
163
+ # and
164
+ # notifier.suppress_announcements(EventType,
165
+ # :during => lambda { "some operation" },
166
+ # :send_all_events_when_done => true)
167
+ # end
168
+
169
+ protected
170
+
171
+ def little_bench(msg, &block)
172
+ start = Time.now
173
+ result = block.call
174
+ puts "#{msg}: #{Time.now - start} sec"
175
+ return result
176
+ end
177
+ end
178
+
179
+ # EOF
@@ -0,0 +1,14 @@
1
+ require File.join(File.dirname(__FILE__), '../lib/cosell')
2
+
3
+ Spec::Runner.configure do |config|
4
+ # == Mock Framework
5
+ #
6
+ # RSpec uses it's own mocking framework by default. If you prefer to
7
+ # use mocha, flexmock or RR, uncomment the appropriate line:
8
+ #
9
+ # config.mock_with :mocha
10
+ # config.mock_with :flexmock
11
+ # config.mock_with :rr
12
+ end
13
+
14
+ # EOF
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swerling-cosell
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steven Swerling
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-18 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bones
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.4.0
24
+ version:
25
+ description: Cosell is a minimal implementation of the 'Announcements' observer framework, originally introduced in VisualWorks Smalltalk 7.4 as a replacement for 'triggerEvent' style of event notification. Instead of triggering events identified by symbols, the events are first class objects. For rationale, please see the original blog posting by Vassili Bykov (refs below). *Lineage* This implementation is loosely based on Lukas Renggli's tweak of Colin Putney's Squeak implementation of Vassili Bykov's Announcements framework for VisualWorks Smalltalk. (Specifically Announcements-lr.13.mcz was used as a reference.) Liberties where taken during the port. In particular, the Announcer class in the Smalltalk version is implemented here as a ruby module which can be mixed into any object. Also, in this implementation any object (or class) can serve as an announcement, so no Announcement class is implemented. The ability to queue announcements in the background is built into cosell. <b>The Name 'Cosell'</b> I chose the name 'Cosell' because a. Howard Cosell is an iconic event announcer b. Googling for 'Ruby Announcements', 'Ruby Event Announcements', etc., produced scads of results about ruby meetups, conferences, and the like. So I went with something a bit cryptic but hopefully a little more searchable. *See* * {Original blog posting describing Announcments by Vassili Bykov}[http://www.cincomsmalltalk.com/userblogs/vbykov/blogView?entry=3310034894] * {More info on the Announcements Framework}[http://wiki.squeak.org/squeak/5734]
26
+ email: sswerling@yahoo.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - History.txt
33
+ - README.rdoc
34
+ - README.txt
35
+ files:
36
+ - .gitignore
37
+ - History.txt
38
+ - README.rdoc
39
+ - README.txt
40
+ - Rakefile
41
+ - cosell.gemspec
42
+ - example/basic_example.rb
43
+ - example/cat_whisperer.rb
44
+ - lib/cosell.rb
45
+ - lib/cosell/announcer.rb
46
+ - lib/cosell/monkey.rb
47
+ - spec/cosell_spec.rb
48
+ - spec/spec_helper.rb
49
+ has_rdoc: false
50
+ homepage: http://github.com/swerling/TODO
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --inline-source
54
+ - --main
55
+ - README.txt
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project: cosell
73
+ rubygems_version: 1.2.0
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Cosell is a minimal implementation of the 'Announcements' observer framework, originally introduced in VisualWorks Smalltalk 7
77
+ test_files: []
78
+