hookr 1.0.0

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