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.
@@ -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