hookr 1.0.1 → 1.1.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 +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
|