nakajima-booty-call 0.0.1

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.
@@ -0,0 +1,89 @@
1
+ module BootyCall
2
+ class Callbacker
3
+ attr_reader :klass
4
+
5
+ def initialize(klass)
6
+ @klass = klass
7
+ extend_klass
8
+ end
9
+
10
+ def before(method_id, *symbols, &block)
11
+ add_callback(:before, method_id, *symbols, &block)
12
+ end
13
+
14
+ def after(method_id, *symbols, &block)
15
+ add_callback(:after, method_id, *symbols, &block)
16
+ end
17
+
18
+ private
19
+
20
+ def extend_klass
21
+ klass.class_eval do
22
+ meta_eval { attr_reader :pristine_cache, :callback_cache }
23
+ @pristine_cache = Hash.new
24
+ @callback_cache = { :after => Hash.new([]), :before => Hash.new([]) }
25
+ extend ClassMethods
26
+ include InstanceMethods
27
+ end
28
+ end
29
+
30
+ def add_callback(position, method_id, *symbols, &block)
31
+ klass.callback_cache[position][method_id].tap do |slot|
32
+ slot.push(block) if block_given?
33
+ slot.concat(symbols)
34
+ slot.tap.compact!.uniq!
35
+ end
36
+
37
+ klass.pristine_cache[method_id] ||= begin
38
+ klass.instance_method(method_id).tap do
39
+ redefine_method method_id
40
+ end
41
+ end
42
+ end
43
+
44
+ def redefine_method(method_id)
45
+ klass.class_eval(<<-EOS, "(__DELEGATION__)", 1)
46
+ def #{method_id}(*args, &block)
47
+ catch(#{method_id.to_sym.inspect}) do
48
+ return unless self.class.run_callbacks_for(self, :before, #{method_id.inspect})
49
+ result = __PRISTINE__(#{method_id.inspect}, *args, &block)
50
+ self.class.run_callbacks_for(self, :after, #{method_id.inspect}, result)
51
+ return result
52
+ end
53
+ end
54
+
55
+ def #{method_id}_without_callbacks(*args, &block)
56
+ __PRISTINE__(#{method_id.inspect}, *args, &block)
57
+ end
58
+ EOS
59
+ end
60
+
61
+ module ClassMethods
62
+ def run_callbacks_for(target, position, method_id, *results)
63
+ callbacks = callback_cache[position][method_id.to_sym]
64
+
65
+ handler = proc do |fn|
66
+ fn.is_a?(Proc) ? fn : begin
67
+ target.method(fn).arity.abs == results.length ?
68
+ proc { send(fn, *results) } :
69
+ proc { send(fn) }
70
+ end
71
+ end
72
+
73
+ callbacks.empty? ? true : callbacks.map { |fn|
74
+ target.instance_exec(*results, &handler.call(fn))
75
+ }.all?
76
+ end
77
+ end
78
+
79
+ module InstanceMethods
80
+ def __PRISTINE__(method_id, *args, &block)
81
+ if method = self.class.pristine_cache[method_id]
82
+ method.bind(self).call(*args, &block)
83
+ else
84
+ raise NoMethodError, "No method named #{method_id.inspect}"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,31 @@
1
+ module BootyCall
2
+ module Hook
3
+ def self.included(klass)
4
+ klass.class_eval do
5
+ extend(ClassMethods)
6
+ @callbacker = BootyCall::Callbacker.new(self)
7
+ @introspector = BootyCall::Introspector.new(self)
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def before(method_id, *args, &block)
13
+ callback(:before, method_id, *args, &block)
14
+ end
15
+
16
+ def after(method_id, *args, &block)
17
+ callback(:after, method_id, *args, &block)
18
+ end
19
+
20
+ def observe(method_id, &block)
21
+ @introspector.observe(method_id, &block)
22
+ end
23
+
24
+ def callback(position, method_id, *args, &block)
25
+ @introspector.defined_methods.include?(method_id) ?
26
+ @callbacker.send(position, method_id, *args, &block) :
27
+ observe(method_id) { send(position, method_id, *args, &block) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ module BootyCall
2
+ class Introspector
3
+ attr_reader :klass
4
+
5
+ def initialize(klass)
6
+ @klass = klass
7
+ @observed_methods = { }
8
+ end
9
+
10
+ def observe_klass!
11
+ @observed ||= begin
12
+ this = self
13
+ klass.meta_def(:method_added) do |m|
14
+ this.check_method(m)
15
+ end and true
16
+ end
17
+ end
18
+
19
+ def observing?(method_id)
20
+ not not @observed_methods[method_id]
21
+ end
22
+
23
+ def observe(method_id, &block)
24
+ observe_klass!
25
+ @observed_methods[method_id] ||= []
26
+ @observed_methods[method_id].tap do |set|
27
+ set.push(block) if block_given?
28
+ set.tap.compact!.uniq!
29
+ end
30
+ end
31
+
32
+ def check_method(method_id)
33
+ handlers = @observed_methods.delete(method_id)
34
+ handlers.each(&:call) rescue nil
35
+ end
36
+
37
+ def defined_methods
38
+ (klass.instance_methods - Object.instance_methods).map(&:to_sym)
39
+ end
40
+ end
41
+ end
data/lib/booty_call.rb ADDED
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) + '/booty_call'
2
+
3
+ module BootyCall
4
+ VERSION = '0.0.1'
5
+ end
6
+
7
+ require 'rubygems'
8
+ require 'nakajima'
9
+ require 'callbacker'
10
+ require 'introspector'
11
+ require 'hook'
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nakajima-booty-call
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pat Nakajima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-12 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: nakajima-nakajima
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description:
25
+ email: patnakajima@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - lib/booty_call
34
+ - lib/booty_call/callbacker.rb
35
+ - lib/booty_call/hook.rb
36
+ - lib/booty_call/introspector.rb
37
+ - lib/booty_call.rb
38
+ has_rdoc: false
39
+ homepage: http://github.com/nakajima/booty-call
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Callbacks for your Ruby
64
+ test_files: []
65
+