hookr 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,8 @@
1
+ == 1.1.0 / 2010-09-13
2
+
3
+ * 1 minor ehancement
4
+ * Better support for defining hooks in singleton classes and modules
5
+
1
6
  == 1.0.1 / 2008-12-03
2
7
 
3
8
  * 1 bug fix
data/Rakefile CHANGED
@@ -1,30 +1,32 @@
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.
1
+ gem 'bones', '~> 3.0'
4
2
 
5
3
  begin
6
4
  require 'bones'
7
- Bones.setup
8
5
  rescue LoadError
9
- load 'tasks/setup.rb'
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
- PROJ.name = 'hookr'
18
- PROJ.authors = 'Avdi Grimm'
19
- PROJ.email = 'avdi@avdi.org'
20
- PROJ.url = 'http://hookr.rubyforge.org'
21
- PROJ.version = HookR::VERSION
22
- PROJ.rubyforge.name = 'hookr'
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
- # TODO I want to be able to use -w here, but RSpec produces a hojillion warnings
25
- PROJ.ruby_opts = []
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
@@ -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.1'
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.freeze
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 ||= self.class.hooks.deep_copy
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.freeze
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
@@ -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