hookr 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/Rakefile +20 -18
- data/TODO +94 -0
- data/lib/hookr.rb +26 -4
- data/spec/hookr_spec.rb +65 -32
- metadata +35 -29
- data/tasks/ann.rake +0 -80
- data/tasks/bones.rake +0 -20
- data/tasks/gem.rake +0 -192
- data/tasks/git.rake +0 -40
- data/tasks/manifest.rake +0 -48
- data/tasks/notes.rake +0 -27
- data/tasks/post_load.rake +0 -39
- data/tasks/rdoc.rake +0 -50
- data/tasks/rubyforge.rake +0 -55
- data/tasks/setup.rb +0 -279
- data/tasks/spec.rake +0 -54
- data/tasks/svn.rake +0 -47
- data/tasks/test.rake +0 -40
data/History.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,30 +1,32 @@
|
|
1
|
-
|
2
|
-
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
-
# are where the options are used.
|
1
|
+
gem 'bones', '~> 3.0'
|
4
2
|
|
5
3
|
begin
|
6
4
|
require 'bones'
|
7
|
-
Bones.setup
|
8
5
|
rescue LoadError
|
9
|
-
|
6
|
+
abort '### Please install the "bones" gem ###'
|
10
7
|
end
|
11
8
|
|
12
|
-
ensure_in_path 'lib'
|
13
|
-
require 'hookr'
|
14
|
-
|
15
9
|
task :default => 'spec:run'
|
10
|
+
task 'gem:release' => 'spec:run'
|
11
|
+
|
12
|
+
Bones {
|
13
|
+
name 'hookr'
|
14
|
+
authors 'Avdi Grimm'
|
15
|
+
email 'avdi@avdi.org'
|
16
|
+
url 'http://hookr.rubyforge.org'
|
17
|
+
|
18
|
+
summary "A callback hooks framework for Ruby."
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
# ann.email[:from] = 'avdi@avdi.org'
|
21
|
+
# ann.email[:to] = 'ruby-talk@ruby-lang.org'
|
22
|
+
# ann.email[:server] = 'smtp.gmail.com'
|
23
|
+
# ann.email[:domain] = 'avdi.org'
|
24
|
+
# ann.email[:port] = 587
|
25
|
+
# ann.email[:acct] = 'avdi.grimm'
|
26
|
+
# ann.email[:authtype] = :plain
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
PROJ.spec.opts << '--color'
|
28
|
+
depend_on 'fail-fast', '1.0.0'
|
29
|
+
}
|
27
30
|
|
28
|
-
depend_on 'fail-fast', '1.0.0'
|
29
31
|
|
30
32
|
# EOF
|
data/TODO
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# -*- mode: org -*-
|
2
|
+
* DONE Add Hook#each_callback and refactor execution to use it.
|
3
|
+
CLOSED: [2008-12-02 Tue 11:51]
|
4
|
+
* DONE Add ability to clear callbacks from hooks.
|
5
|
+
CLOSED: [2008-12-02 Tue 12:09]
|
6
|
+
* DONE Add ability to disconnect hooks from their parents
|
7
|
+
CLOSED: [2008-12-02 Tue 12:09]
|
8
|
+
Copy each_callback to a new null-parented set.
|
9
|
+
* DONE Make #hooks and #callbacks read-only
|
10
|
+
CLOSED: [2008-12-02 Tue 11:51]
|
11
|
+
Replace writable access with #fetch_or_create_*
|
12
|
+
* DONE Add Listener API
|
13
|
+
CLOSED: [2008-12-01 Mon 17:04]
|
14
|
+
A Listener is an object attached to a wildcard calback. Every event that
|
15
|
+
passes through the callback is sent to the listener as a method call
|
16
|
+
corresponding to the hook name.
|
17
|
+
* DONE Add an (easy) way to remove listeners
|
18
|
+
CLOSED: [2008-12-02 Tue 11:51]
|
19
|
+
* TODO (Maybe) Verify that recursive handlers call either skip, next, or cancel.
|
20
|
+
* TODO Add #skip and #cancel methods to Event.
|
21
|
+
* TODO Consider using Roxy for #hooks and #callbacks extensions.
|
22
|
+
http://ryandaigle.com/articles/2008/11/10/implement-ruby-proxy-objects-with-roxy
|
23
|
+
* TODO Standard, extensable method for deriving handles from objects
|
24
|
+
Driver: we need to be able to remove a listener using the the listener object
|
25
|
+
as an index.
|
26
|
+
* TODO Reconsider fetch_or_create_* methods - they are kinda ugly.
|
27
|
+
* TODO More Hook parent/child unification
|
28
|
+
Most Hook methods should behave as if the hook contained all of its parent's
|
29
|
+
callbacks as well. Specifically:
|
30
|
+
- It should not be possible to add a callback with the same handle as an
|
31
|
+
existing parent callback
|
32
|
+
- #callbacks should probably return a unified set of all callbacks
|
33
|
+
|
34
|
+
* TODO Collect return values when running hooks iteratively
|
35
|
+
* TODO Rewrite iterative model in terms of recursive
|
36
|
+
This will make some of the other TODOs possible/more feasible.
|
37
|
+
|
38
|
+
May want to push the execution models out into separate Event classes,
|
39
|
+
e.g. IterativeEvent and RecursiveEvent. An IterativeEvent could apply itself
|
40
|
+
to a callback generator in such a way that it collected return values into an
|
41
|
+
array.
|
42
|
+
|
43
|
+
* TODO Write example "error strategy filter"
|
44
|
+
A possible implementation for turning exceptions into benign values:
|
45
|
+
|
46
|
+
Wrap callback execution in a begin...rescue block. If an exception is raised,
|
47
|
+
catch it, and wrap it in a new exception which inludes a reference to the
|
48
|
+
callback stack. An exception filter further up the chain can:
|
49
|
+
|
50
|
+
1. Catch the wrapped exception
|
51
|
+
2. Create a Failure object which references the exception
|
52
|
+
3. Replace the top element of the callback stack with a stub that just returns
|
53
|
+
the Failure object.
|
54
|
+
4. Retry the exception.
|
55
|
+
|
56
|
+
* TODO Syntax sugar for before_ and after_ filters
|
57
|
+
: class ZeroWing
|
58
|
+
: define_hook :we_get_signal
|
59
|
+
:
|
60
|
+
: before_we_get_signal :foo
|
61
|
+
: after_we_get_signal :bar
|
62
|
+
: end
|
63
|
+
* TODO Syntax sugar for execute_hook
|
64
|
+
E.g.:
|
65
|
+
|
66
|
+
: execute_we_get_signal(arg1, arg2)
|
67
|
+
: run_we_get_signal(arg1, arg2)
|
68
|
+
|
69
|
+
* TODO Interleave wildcard callback execution based definition order
|
70
|
+
Instead of running all wildcards first or last.
|
71
|
+
|
72
|
+
One possible approach is to combine wildcards and specific callbacks into a
|
73
|
+
single set before executing. This would require reworking how we generate
|
74
|
+
indices, if nothing else. Indices would have to be unique across the whole
|
75
|
+
class.
|
76
|
+
|
77
|
+
Another approach: when adding a wildcard callback, first add it individually
|
78
|
+
to every existing hook. Then add it to a class-wide "wildcard list". When a
|
79
|
+
new hook is defined, add all the wildcards in the wildcard list to it.
|
80
|
+
|
81
|
+
A side-effect of the second approach is that it would be possible to remove
|
82
|
+
wildcard callbacks from individual instances/hooks using remove_callback().
|
83
|
+
This may be a feature.
|
84
|
+
* TODO Make it possible to add a Listener at the class level
|
85
|
+
Both internally and externally to the class
|
86
|
+
* TODO Make callback macros accept listeners
|
87
|
+
* TODO Add ability for listeners to see event object
|
88
|
+
Currently only the event arguments are passed.
|
89
|
+
|
90
|
+
May want to make the core Listener API a single #notify(event) call, and then
|
91
|
+
have SomeClass::Listener add the demultiplexing code.
|
92
|
+
* TODO Split up into multiple files
|
93
|
+
* TODO No methods flog higher than 15
|
94
|
+
* TODO 95% code coverage
|
data/lib/hookr.rb
CHANGED
@@ -10,7 +10,7 @@ module HookR
|
|
10
10
|
# No need to document the boilerplate convenience methods defined by Mr. Bones.
|
11
11
|
# :stopdoc:
|
12
12
|
|
13
|
-
VERSION = '1.0
|
13
|
+
VERSION = '1.1.0'
|
14
14
|
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
15
15
|
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
16
16
|
|
@@ -96,6 +96,13 @@ module HookR
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
+
def extended(object)
|
100
|
+
super(object)
|
101
|
+
class << object
|
102
|
+
include ::HookR::Hooks
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
99
106
|
protected
|
100
107
|
|
101
108
|
def make_hook(name, parent, params)
|
@@ -181,7 +188,7 @@ module HookR
|
|
181
188
|
|
182
189
|
# returns the hooks exposed by this object
|
183
190
|
def hooks
|
184
|
-
fetch_or_create_hooks.dup
|
191
|
+
fetch_or_create_hooks.dup
|
185
192
|
end
|
186
193
|
|
187
194
|
# Execute all callbacks associated with the hook identified by +hook_name+,
|
@@ -272,7 +279,22 @@ module HookR
|
|
272
279
|
end
|
273
280
|
|
274
281
|
def fetch_or_create_hooks
|
275
|
-
@hooks ||=
|
282
|
+
@hooks ||= inherited_hooks.deep_copy
|
283
|
+
end
|
284
|
+
|
285
|
+
def inherited_hooks
|
286
|
+
singleton_class_hooks | class_hooks
|
287
|
+
end
|
288
|
+
|
289
|
+
def singleton_class_hooks
|
290
|
+
(class << self; self; end).hooks
|
291
|
+
end
|
292
|
+
|
293
|
+
def class_hooks
|
294
|
+
self.class.ancestors.inject(HookSet.new) { |hooks, a|
|
295
|
+
a_hooks = a.respond_to?(:hooks) ? a.hooks : HookSet.new
|
296
|
+
hooks | a_hooks
|
297
|
+
}
|
276
298
|
end
|
277
299
|
end
|
278
300
|
|
@@ -316,7 +338,7 @@ module HookR
|
|
316
338
|
end
|
317
339
|
|
318
340
|
def callbacks
|
319
|
-
fetch_or_create_callbacks.dup
|
341
|
+
fetch_or_create_callbacks.dup
|
320
342
|
end
|
321
343
|
|
322
344
|
# Add a callback which will be executed in the context where it was defined
|
data/spec/hookr_spec.rb
CHANGED
@@ -201,6 +201,39 @@ describe HookR::Hooks do
|
|
201
201
|
end
|
202
202
|
end
|
203
203
|
end
|
204
|
+
|
205
|
+
context "included in a singleton class" do
|
206
|
+
before :each do
|
207
|
+
@object = Object.new
|
208
|
+
@class = (class << @object; self; end)
|
209
|
+
@class.module_eval do
|
210
|
+
include HookR::Hooks
|
211
|
+
|
212
|
+
define_hook :foo, :x, :y
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
specify "the singleton object should be able to list its hooks" do
|
217
|
+
@object.hooks.size.should be > 0
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "defined in a module and then added to an object" do
|
222
|
+
before :each do
|
223
|
+
@object = Object.new
|
224
|
+
@module = Module.new do
|
225
|
+
include HookR::Hooks
|
226
|
+
|
227
|
+
define_hook :foo, :x, :y
|
228
|
+
end
|
229
|
+
@object.extend(@module)
|
230
|
+
end
|
231
|
+
|
232
|
+
specify "the singleton object should be able to list its hooks" do
|
233
|
+
pending "FIXME"
|
234
|
+
@object.hooks.size.should be > 1
|
235
|
+
end
|
236
|
+
end
|
204
237
|
end
|
205
238
|
|
206
239
|
describe "a no-param hook named :on_signal" do
|
@@ -250,7 +283,7 @@ describe "a no-param hook named :on_signal" do
|
|
250
283
|
end
|
251
284
|
|
252
285
|
specify "there should be two callbacks total" do
|
253
|
-
@instance_hook.total_callbacks.should == 2
|
286
|
+
@instance_hook.total_callbacks.should be == 2
|
254
287
|
end
|
255
288
|
|
256
289
|
end
|
@@ -276,19 +309,19 @@ describe "a no-param hook named :on_signal" do
|
|
276
309
|
end
|
277
310
|
|
278
311
|
specify ":callback1 should be the first callback" do
|
279
|
-
@class_hook.callbacks[0].handle.should == :callback1
|
312
|
+
@class_hook.callbacks[0].handle.should be == :callback1
|
280
313
|
end
|
281
314
|
|
282
315
|
specify ":callback2 should be the second callback" do
|
283
|
-
@class_hook.callbacks[1].handle.should == :callback2
|
316
|
+
@class_hook.callbacks[1].handle.should be == :callback2
|
284
317
|
end
|
285
318
|
|
286
319
|
specify ":callback1 should execute the given code" do
|
287
|
-
@class_hook.callbacks[:callback1].call(@event).should == 2
|
320
|
+
@class_hook.callbacks[:callback1].call(@event).should be == 2
|
288
321
|
end
|
289
322
|
|
290
323
|
specify ":callback2 should execute the given code" do
|
291
|
-
@class_hook.callbacks[:callback2].call(@event).should == 4
|
324
|
+
@class_hook.callbacks[:callback2].call(@event).should be == 4
|
292
325
|
end
|
293
326
|
end
|
294
327
|
end
|
@@ -322,9 +355,9 @@ describe "a two-param hook named :on_signal" do
|
|
322
355
|
it "should call back with the correct arguments" do
|
323
356
|
@sensor.should_receive(:ping) do |event, color, flavor|
|
324
357
|
event.source.should equal(@instance)
|
325
|
-
event.name.should == :on_signal
|
326
|
-
color.should == :purple
|
327
|
-
flavor.should == :grape
|
358
|
+
event.name.should be == :on_signal
|
359
|
+
color.should be == :purple
|
360
|
+
flavor.should be == :grape
|
328
361
|
end
|
329
362
|
@instance.send(:execute_hook, :on_signal, :purple, :grape)
|
330
363
|
end
|
@@ -403,7 +436,7 @@ describe "a two-param hook named :on_signal" do
|
|
403
436
|
end
|
404
437
|
|
405
438
|
specify "the callback handle should be :do_stuff" do
|
406
|
-
@callback.handle.should == :do_stuff
|
439
|
+
@callback.handle.should be == :do_stuff
|
407
440
|
end
|
408
441
|
end
|
409
442
|
|
@@ -439,10 +472,10 @@ describe "a two-param hook named :on_signal" do
|
|
439
472
|
|
440
473
|
specify "the callback should call back" do
|
441
474
|
@sensor.should_receive(:ping) do |event, arg1, arg2|
|
442
|
-
event.source.should == @instance
|
443
|
-
event.name.should == :on_signal
|
444
|
-
arg1.should == :apple
|
445
|
-
arg2.should == :orange
|
475
|
+
event.source.should be == @instance
|
476
|
+
event.name.should be == :on_signal
|
477
|
+
arg1.should be == :apple
|
478
|
+
arg2.should be == :orange
|
446
479
|
end
|
447
480
|
@instance.send(:execute_hook, :on_signal, :apple, :orange)
|
448
481
|
end
|
@@ -518,7 +551,7 @@ describe "a two-param hook named :on_signal" do
|
|
518
551
|
log(:inner, arg1, arg2)
|
519
552
|
end
|
520
553
|
|
521
|
-
@log.should == [
|
554
|
+
@log.should be == [
|
522
555
|
[:cb3_before, :on_signal, :fizz, :buzz],
|
523
556
|
[:cb2_before, :on_signal, :fizz, :buzz],
|
524
557
|
[:cb1_before, :on_signal, :pish, :tosh],
|
@@ -573,20 +606,20 @@ describe HookR::Hook do
|
|
573
606
|
@block = lambda {}
|
574
607
|
end
|
575
608
|
|
576
|
-
specify { @it.name.should == :foo }
|
609
|
+
specify { @it.name.should be == :foo }
|
577
610
|
|
578
611
|
specify { @it.should have(0).callbacks }
|
579
612
|
|
580
613
|
it "should be equal to any other hook named :foo" do
|
581
614
|
@parent = HookR::Hook.new(:parent)
|
582
615
|
@other = HookR::Hook.new(:foo, @parent)
|
583
|
-
@it.should == @other
|
616
|
+
@it.should be == @other
|
584
617
|
@it.should eql(@other)
|
585
618
|
end
|
586
619
|
|
587
620
|
describe "when adding a callback" do
|
588
621
|
it "should return the handle of the added callback" do
|
589
|
-
@it.add_callback(@callback).should == 123
|
622
|
+
@it.add_callback(@callback).should be == 123
|
590
623
|
end
|
591
624
|
end
|
592
625
|
|
@@ -620,16 +653,16 @@ describe HookR::Hook do
|
|
620
653
|
specify { @it.should have(5).callbacks }
|
621
654
|
|
622
655
|
specify "the handles of the anonymous callbacks should be their indexes" do
|
623
|
-
@it.callbacks[0].handle.should == 0
|
624
|
-
@it.callbacks[2].handle.should == 2
|
656
|
+
@it.callbacks[0].handle.should be == 0
|
657
|
+
@it.callbacks[2].handle.should be == 2
|
625
658
|
end
|
626
659
|
|
627
660
|
specify "the add methods should return handles" do
|
628
|
-
@anon_external_cb.should == 0
|
629
|
-
@named_external_cb.should == :my_external
|
630
|
-
@anon_internal_cb.should == 2
|
631
|
-
@named_internal_cb.should == :my_internal
|
632
|
-
@method_cb.should == :my_method
|
661
|
+
@anon_external_cb.should be == 0
|
662
|
+
@named_external_cb.should be == :my_external
|
663
|
+
@anon_internal_cb.should be == 2
|
664
|
+
@named_internal_cb.should be == :my_internal
|
665
|
+
@method_cb.should be == :my_method
|
633
666
|
end
|
634
667
|
|
635
668
|
specify "the callbacks should have the intended types" do
|
@@ -655,7 +688,7 @@ describe HookR::Hook do
|
|
655
688
|
@it.each_callback do |callback|
|
656
689
|
callbacks << callback.handle
|
657
690
|
end
|
658
|
-
callbacks.should == [@anon_external_cb, @named_external_cb,
|
691
|
+
callbacks.should be == [@anon_external_cb, @named_external_cb,
|
659
692
|
@anon_internal_cb, @named_internal_cb, @method_cb]
|
660
693
|
end
|
661
694
|
|
@@ -664,7 +697,7 @@ describe HookR::Hook do
|
|
664
697
|
@it.each_callback_reverse do |callback|
|
665
698
|
callbacks << callback.handle
|
666
699
|
end
|
667
|
-
callbacks.should == [@method_cb, @named_internal_cb, @anon_internal_cb,
|
700
|
+
callbacks.should be == [@method_cb, @named_internal_cb, @anon_internal_cb,
|
668
701
|
@named_external_cb, @anon_external_cb]
|
669
702
|
end
|
670
703
|
|
@@ -716,7 +749,7 @@ describe HookR::Hook do
|
|
716
749
|
end
|
717
750
|
|
718
751
|
it "should report 2 total callbacks" do
|
719
|
-
@it.total_callbacks.should == 2
|
752
|
+
@it.total_callbacks.should be == 2
|
720
753
|
end
|
721
754
|
|
722
755
|
it "should be able to iterate over own and parent callbacks" do
|
@@ -724,7 +757,7 @@ describe HookR::Hook do
|
|
724
757
|
@it.each_callback do |callback|
|
725
758
|
callbacks << callback
|
726
759
|
end
|
727
|
-
callbacks.should == [@parent_callback, @child_callback]
|
760
|
+
callbacks.should be == [@parent_callback, @child_callback]
|
728
761
|
end
|
729
762
|
|
730
763
|
it "should be able to reverse-iterate over own and parent callbacks" do
|
@@ -732,7 +765,7 @@ describe HookR::Hook do
|
|
732
765
|
@it.each_callback_reverse do |callback|
|
733
766
|
callbacks << callback
|
734
767
|
end
|
735
|
-
callbacks.should == [@child_callback, @parent_callback]
|
768
|
+
callbacks.should be == [@child_callback, @parent_callback]
|
736
769
|
end
|
737
770
|
|
738
771
|
it "should be able to clear its own callbacks, leaving parent callbacks" do
|
@@ -742,7 +775,7 @@ describe HookR::Hook do
|
|
742
775
|
@it.each_callback do |callback|
|
743
776
|
callbacks << callback
|
744
777
|
end
|
745
|
-
callbacks.should == [@parent_callback]
|
778
|
+
callbacks.should be == [@parent_callback]
|
746
779
|
end
|
747
780
|
|
748
781
|
it "should be able to clear all callbacks" do
|
@@ -752,7 +785,7 @@ describe HookR::Hook do
|
|
752
785
|
@it.each_callback do |callback|
|
753
786
|
callbacks << callback
|
754
787
|
end
|
755
|
-
callbacks.should == []
|
788
|
+
callbacks.should be == []
|
756
789
|
end
|
757
790
|
|
758
791
|
it "should leave parent callbacks alone when clearing all" do
|
@@ -804,7 +837,7 @@ describe HookR::CallbackSet do
|
|
804
837
|
end
|
805
838
|
|
806
839
|
it "should sort the callbacks" do
|
807
|
-
@it.to_a.should == [@cb1, @cb2, @cb3]
|
840
|
+
@it.to_a.should be == [@cb1, @cb2, @cb3]
|
808
841
|
end
|
809
842
|
|
810
843
|
it "should be able to locate callbacks by index" do
|