hookr 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.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2008-11-30
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
data/Manifest.txt ADDED
@@ -0,0 +1,22 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/hookr
6
+ lib/hookr.rb
7
+ spec/hookr_spec.rb
8
+ spec/spec_helper.rb
9
+ tasks/ann.rake
10
+ tasks/bones.rake
11
+ tasks/gem.rake
12
+ tasks/git.rake
13
+ tasks/manifest.rake
14
+ tasks/notes.rake
15
+ tasks/post_load.rake
16
+ tasks/rdoc.rake
17
+ tasks/rubyforge.rake
18
+ tasks/setup.rb
19
+ tasks/spec.rake
20
+ tasks/svn.rake
21
+ tasks/test.rake
22
+ test/test_hookr.rb
data/README.txt ADDED
@@ -0,0 +1,491 @@
1
+ = HookR
2
+ by Avdi Grimm
3
+ http://hookr.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ HookR is a publish/subscribe callback hook facility for Ruby.
8
+
9
+ === What is it?
10
+
11
+ HookR can be understood in a few different ways.
12
+
13
+ * If you are familiar with Events and Event Listeners in
14
+ Java[http://java.sun.com/docs/books/tutorial/javabeans/events/index.html] or
15
+ C#[http://msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx];
16
+ Hooks[http://www.gnu.org/software/emacs/manual/html_node/elisp/Hooks.html#Hooks]
17
+ in Emacs-lisp; or signals-and-slots as implemented in the
18
+ Qt[http://doc.trolltech.com/4.4/signalsandslots.html],
19
+ Boost.Signals[http://www.boost.org/doc/libs/1_37_0/doc/html/signals.html], or
20
+ libsigc++[http://libsigc.sourceforge.net/] frameworks - HookR provides a
21
+ very similar facility.
22
+ * If youve ever used the Observer standard library, but wished you could
23
+ have more than one type of notification per observable object, HookR is the
24
+ library for you.
25
+ * HookR is an easy way to add
26
+ Rails-style[http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html]
27
+ before- and after-filters to your own classes.
28
+ * HookR is an
29
+ Inversion-of-Control[http://martinfowler.com/bliki/InversionOfControl.html]
30
+ framework in that it makes it easy to write event-driven code.
31
+ * HookR is a way to support a limited, structured form of Aspect Oriented
32
+ Programming (AOP[http://en.wikipedia.org/wiki/Aspect-oriented_programming])
33
+ where the advisable events are explicitly defined.
34
+
35
+ === What HookR is not:
36
+
37
+ * HookR is not (yet) an asynchronous event notification system. No provision is
38
+ made for multi-threaded operation or event queueing.
39
+ * HookR will show you a good time, but it will not make you breakfast in the
40
+ morning.
41
+
42
+ == FEATURES:
43
+
44
+ * Fully spec'd
45
+ * Provides class-level and instance-level callbacks
46
+ * Inheritance-safe
47
+ * Supports both iterative and recursive callback models
48
+ * "Wildcard" callbacks can observe all events
49
+ * Three types of callback supported - internal (instance-eval'd), external, and
50
+ method callbacks.
51
+
52
+ == SYNOPSIS:
53
+
54
+ require 'rubygems'
55
+ require 'hookr'
56
+
57
+ class ZeroWing
58
+ include HookR::Hooks
59
+ define_hook :we_get_signal, :message
60
+
61
+ def start_game
62
+ execute_hook(:we_get_signal, "How are you gentlemen?")
63
+ end
64
+
65
+ def bomb(event, message)
66
+ puts "somebody set us up the bomb!"
67
+ end
68
+
69
+ we_get_signal do |event, message|
70
+ puts "Main screen turn on!"
71
+ puts "Cats: #{message}"
72
+ end
73
+
74
+ we_get_signal :bomb
75
+
76
+ end
77
+
78
+ zw = ZeroWing.new
79
+ zw.we_get_signal do
80
+ puts "Take off every zig!"
81
+ end
82
+
83
+ zw.start_game
84
+ # >> Main screen turn on!
85
+ # >> Cats: How are you gentlemen?
86
+ # >> somebody set us up the bomb!
87
+ # >> Take off every zig!
88
+
89
+ == DETAILS
90
+
91
+ Pour yourself a drink, loosen your tie, and let's get cozy with HookR.
92
+
93
+ === Hooks
94
+
95
+ Hooks are at the center of HookR's functionality. A hook is a named attachment
96
+ point for arbitrary callbacks. It is the "publish" portion of
97
+ publish/subscribe. From the event-handling perspective, hooks define interesting
98
+ events in an object's lifetime. For example, an XML parser might define hooks
99
+ named <code>:tag_start</code> and <code>:tag_end</code> hooks. A network
100
+ protocol class might define <code>:connected</code> and
101
+ <code>:message_received</code> hooks. A database-backed model might define
102
+ <code>:before_save</code> and <code>:after_save</code> hooks.
103
+
104
+ Hooks are defined at the class level, using the <code>define_hook</code> method:
105
+
106
+ class ZeroWing
107
+ define_hook :we_get_signal
108
+ end
109
+
110
+ ==== Hook Parameters
111
+
112
+ Sometimes we want to pass some data along with our events. Hooks can define
113
+ named parameters by passing extra symbol arguments to <code>define_hook</code>:
114
+
115
+ class ZeroWing
116
+ include HookR::Hooks
117
+ define_hook :we_get_signal, :message
118
+ end
119
+
120
+ ==== Listing Hooks
121
+
122
+ You can access the full set of hooks defined for a particular class by calling
123
+ the #hooks class method:
124
+
125
+ ZeroWing.hooks # => #<Hookr::HookSet: ... >
126
+
127
+ If you are playing along at home you may notice a <code>:__wildcards__</code> hook in this
128
+ list. We'll talk about that in the Advanced section.
129
+
130
+ === Callbacks
131
+
132
+ Hooks aren't much use without callbacks. Callbacks represent a piece of code to
133
+ be run when a hook is executed. They are the "subscribe" part of the
134
+ publish/subscribe duo.
135
+
136
+ HookR defines three types of callback:
137
+
138
+ ==== Internal Callbacks
139
+
140
+ An internal callback represents a block of code which will be run in the context
141
+ of the source object (the object executing the hook). That is, it will be run
142
+ using #instance_eval. In general this type of callback should only be defined
143
+ internally to the class having the hook, since the called code will have full
144
+ access to private members and instance variables of the source object.
145
+
146
+ One drawback of internal callbacks is that due to limitations of
147
+ <code>#instance_eval</code>, they cannot receive arguments.
148
+
149
+ ==== External Callbacks
150
+
151
+ An external callback is a block of code which will be executed in the context in
152
+ which it was defined. That is, a Proc which will be called with the Event
153
+ object (see below) and any parameters defined by the hook.
154
+
155
+ ==== Method Callbacks
156
+
157
+ A method callback is a callback which when executed will call an instance method
158
+ on the source object (the object executing the hook). Like internal callbacks,
159
+ these should usually only be added internally by the source class, since private
160
+ methods may be called. The method will receive as arguments the Event (see
161
+ below), and an argument for each parameter defined on the hook.
162
+
163
+ ==== Named Callbacks
164
+
165
+ A callback may be *named* or *anonymous*. Naming callbacks makes it easier to
166
+ reference them after adding them, for instance if you want to remove a
167
+ callback. Naming calbacks also ensures that only one callback with the given name
168
+ will be added to a hook.
169
+
170
+ ==== Adding Callbacks
171
+
172
+ There are several ways to add callbacks to hooks.
173
+
174
+ ===== Adding callbacks In the class definition
175
+
176
+ The first way to define a callback is to do it in the class definition:
177
+
178
+ class ZeroWing
179
+ include HookR::Hooks
180
+ define_hook :we_get_signal, :message
181
+
182
+ we_get_signal do
183
+ main_screen.turn_on!
184
+ end
185
+ end
186
+
187
+ HookR creates class-level macros for each defined hook. The above example
188
+ demonstrates an anonymous *internal* callback being defined on the hook
189
+ <code>:we_get_signal</code>. Why internal? HookR uses a set of rules to
190
+ determine what kind of callback to generate. If the block passed to the
191
+ callback macro has no arguments, it will generate an internal callback. If,
192
+ however, the block defines arguments:
193
+
194
+ class ZeroWing
195
+ include HookR::Hooks
196
+ define_hook :we_get_signal, :message
197
+
198
+ we_get_signal do |event, message|
199
+ puts message.what_you_say?
200
+ end
201
+ end
202
+
203
+ An *external* callback will be defined. Why external? As discussed earlier,
204
+ it is impossible for <code>instance_eval</code>-ed code to receive arguments. So in order
205
+ to supply the defined parameters an external callback must be defined.
206
+
207
+ If no block is passed, but a method name is supplied, a *method* callback will
208
+ be generated:
209
+
210
+ class ZeroWing
211
+ include HookR::Hooks
212
+
213
+ def take_off_every_zig(event, message)
214
+ # ...
215
+ end
216
+
217
+ define_hook :we_get_signal, :message
218
+
219
+ we_get_signal :take_off_every_zig
220
+ end
221
+
222
+ For any of the variations demonstrated above, an explicit symbolic callback
223
+ handle may be supplied. This handle can then be used to access or remove the
224
+ callback.
225
+
226
+ class ZeroWing
227
+ include HookR::Hooks
228
+
229
+ define_hook :we_get_signal, :message
230
+
231
+ we_get_signal :zig do
232
+ take_off_every_zig
233
+ end
234
+ end
235
+
236
+ ZeroWing.remove_callback(:zig)
237
+
238
+ ===== In instance methods
239
+
240
+ In instance methods of the class defining the hook, it is possible to explicitly
241
+ add the different types of callback using the methods <code>add_external_callback</code>,
242
+ <code>add_internal_callback</code>, and #add_method_callback. See the method documentation
243
+ for details.
244
+
245
+ The methods all return a callback *handle*, which can be used to access or remove
246
+ the callback. This will be the same as the <code>handle</code> argument, if one is supplied.
247
+
248
+ ===== In client code
249
+
250
+ In code that uses a hook-enabled object, callbacks can be easily added using a
251
+ method with the same name as the hook:
252
+
253
+ zw = ZeroWing.new
254
+ zw.we_get_signal do |event, message|
255
+ puts "it's you!"
256
+ end
257
+
258
+ Only *external* callbacks may be added using this method. This is consistent
259
+ with public/private class protection.
260
+
261
+ Like the <code>add_*_callback</code> methods described above, this method may be passed an
262
+ explicit symbolic handle. Whether an explicit handle is supplied or not, it
263
+ will always return a handle which can be used to access or remove the added
264
+ callback.
265
+
266
+ ==== Removing Callbacks
267
+
268
+ <code>remove_callback</code> methods are available at both the class and instance levels.
269
+ They can be used to remove class- or instance-level callbacks, respectively.
270
+ Both forms take either a callback index or a handle.
271
+
272
+ === Listeners
273
+
274
+ Listeners embody an alternative model of publish/subscribe event handling. A
275
+ Listener is an object which "listens" to another object. Instead of attaching
276
+ callbacks to individual hooks, you attach a listener to an entire object.
277
+ Anytime a hook is executed on the object being listened to, a method with a name
278
+ corresponding to the hook is called on the listener. These handler methods
279
+ should take arguments corresponding to the parameters defined on the hook.
280
+
281
+ This model is similar to the SAX XML event model, and to the Java
282
+ Event/EventListener model.
283
+
284
+ For more convenient listener definition, HookR can generate a base class for you
285
+ to base your listeners on. The base class will provide default do-nothing
286
+ methods for each hook, so you only have to redefine the methods you care about.
287
+
288
+ class ZeroWing
289
+ include HookR::Hooks
290
+
291
+ define_hook :we_get_signal, :message
292
+
293
+ define_hook :set_us_up_the_bomb
294
+ end
295
+
296
+ class MyListener < ZeroWing::Listener
297
+ def we_get_signal(message)
298
+ # ...
299
+ end
300
+
301
+ # :set_us_up_the_bomb events are silently ignored
302
+ end
303
+
304
+ zw = ZeroWing.new
305
+ l = MyListener.new
306
+
307
+ zw.add_listener(l)
308
+
309
+ === Events
310
+
311
+ Events represent the execution of a hook. They encapsulate information about
312
+ the hook, the object executing the hook, and any parameters passed when the hook
313
+ was executed (see Execution, below). Events are normally passed as the first
314
+ argument to external callbacks and method callbacks.
315
+
316
+ Events have a few important attributes:
317
+
318
+ ==== Source
319
+
320
+ The event *source* is the object which initiated the hook execution. Ordinarily
321
+ this is an instance of the class which defines the hook.
322
+
323
+ ==== Name
324
+
325
+ Name is the hook name of the hook being executed. For instance, given the
326
+ following hook definition:
327
+
328
+ class ZeroWing
329
+ include HookR::Hooks
330
+
331
+ define_hook :we_get_signal, :message
332
+ end
333
+
334
+ the <code>name</code> would be <code>:we_get_signal</code>.
335
+
336
+ ==== Arguments
337
+
338
+ Event <code>arguments</code> are the extra arguments passed to #execute_hook, corresponding
339
+ to the hook parameters (if any).
340
+
341
+ === Execution
342
+
343
+ An instance of the hook-bearing class can initiate hook execution by calling
344
+ #execute_hook. It takes as arguments the hook name and an argument for every
345
+ parameter defined by the hook. Example:
346
+
347
+ class ZeroWing
348
+ include HookR::Hooks
349
+
350
+ define_hook :we_get_signal, :message
351
+
352
+ def game_start
353
+ execute_hook(:we_get_signal, "You have no chance to survive")
354
+ end
355
+ end
356
+
357
+ There are two models of callback execution. Each is described below.
358
+
359
+ ==== Iterative
360
+
361
+ In the simple case, callback execution follows the iterative model. Each
362
+ callback is executed in turn (in order of addition). Callback return calues are
363
+ ignored.
364
+
365
+ ==== Recursive
366
+
367
+ When #execute_hook is called with a block argument, recursive execution is
368
+ triggered. E.g.:
369
+
370
+ class ZeroWing
371
+ include HookR::Hooks
372
+
373
+ define_hook :we_get_signal, :message
374
+
375
+ def game_start
376
+ execute_hook(:we_get_signal, "You have no chance to survive") do |event, message|
377
+ puts message
378
+ end
379
+ end
380
+ end
381
+
382
+ In this model, the most recently defined hook will be called first. As usual,
383
+ it will be passed an event as its first argument. In order to continue
384
+ execution to the next callback, the first callback must call <code>event.next</code>. This
385
+ will cause the next-most-recently-defined callback to be executed, which will
386
+ again be passed an event with a #next method, and so on. Finally, when the last
387
+ callback is executed and calls <code>event.next</code>, the block passed to #execute_hook
388
+ will be called.
389
+
390
+ In this way, it is possible to "wrap" an event with callbacks or, in the
391
+ language of AOP, "around advice". At any point in the chain, a callback can opt
392
+ to pass new arguments to Event#next, which will then override the original
393
+ arguments for any callbacks further down the chain. This enables callbacks to
394
+ act as "filters" on the callback arguments.
395
+
396
+ WARNING: This area is still under active development, and the API may change.
397
+ Some ideas under consideration include automatically executing the next callback
398
+ even if Event#next is not explicitly called; and an Event#cancel method which
399
+ will prevent further callbacks from running.
400
+
401
+ === Advanced
402
+
403
+ In which we take a look under HookR's clothes.
404
+
405
+ ==== Adding multiple callbacks with the same name
406
+
407
+ When adding callbacks with an explicit handle, only one callback for that handle
408
+ can be added to a given hook. Subsequent attempts to add a callback with the
409
+ same name will silently fail. This makes adding named callbacks an idempotent
410
+ operation.
411
+
412
+ ==== Hook Chaining
413
+
414
+ Every hook has a parent, to which it delegates execution when it is finished
415
+ executing its own callbacks. This is how class inheritance is handles, and how
416
+ it is possible for callbacks to be added at both the class and instanve levels.
417
+ Under normal circumstances, however, this is an implementation detail which Just
418
+ Works, and you can safely ignore it.
419
+
420
+ ==== Wildcard Callbacks
421
+
422
+ It is possible to define a "wildcard" callback which will be called when *any*
423
+ hook is executed, using the #add_wildcard_callback class and instance methods.
424
+
425
+ ==== Callback Arity
426
+
427
+ External and method callbacks must take at least as many arguments as
428
+ there are parameters on the hook. For instance, given the following hook
429
+ definition:
430
+
431
+ class ZeroWing
432
+ include HookR::Hooks
433
+
434
+ define_hook :take_off_every, :what, :why
435
+ end
436
+
437
+ Callbacks for <code>:take_off_every</code> must accept at least two arguments. If they accept
438
+ exactly two arguments, they will be passed the two arguments only. If they
439
+ accept three arguments, they will be passed the event object followed by the two
440
+ arguments. More than three arguments would be an error.
441
+
442
+ ==== Custom Hook Classes
443
+
444
+ In some special cases it may be desirable to customize the specific class of
445
+ Hook generated by #define_hook. When this is the case you may define a custom
446
+ <code>make_hook</code> class method. This method will be passed a hook name, a parent
447
+ hook, and a list of parameters, and should return an instance of a subclass of
448
+ HookR::Hook or something that behaves very similarly.
449
+
450
+ == REQUIREMENTS:
451
+
452
+ * FailFast (http://fail-fast.rubyforge.org)
453
+
454
+ == INSTALL:
455
+
456
+ * sudo gem install hookr
457
+
458
+ == KNOWN BUGS
459
+
460
+ * It is currently not possible to define a method callback before the method has
461
+ been defined. This is either a bug or a feature, depending on your point of
462
+ view.
463
+
464
+ == SUPPORT/CONTRIBUTING
465
+
466
+ Questions, comments, suggestions, bug reports: Email Avdi Grimm at mailto:avdi@avdi.org
467
+
468
+ == LICENSE:
469
+
470
+ (The MIT License)
471
+
472
+ Copyright (c) 2008
473
+
474
+ Permission is hereby granted, free of charge, to any person obtaining
475
+ a copy of this software and associated documentation files (the
476
+ 'Software'), to deal in the Software without restriction, including
477
+ without limitation the rights to use, copy, modify, merge, publish,
478
+ distribute, sublicense, and/or sell copies of the Software, and to
479
+ permit persons to whom the Software is furnished to do so, subject to
480
+ the following conditions:
481
+
482
+ The above copyright notice and this permission notice shall be
483
+ included in all copies or substantial portions of the Software.
484
+
485
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
486
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
487
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
488
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
489
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
490
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
491
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.