nakajima-booty-call 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+