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.
- data/lib/booty_call/callbacker.rb +89 -0
- data/lib/booty_call/hook.rb +31 -0
- data/lib/booty_call/introspector.rb +41 -0
- data/lib/booty_call.rb +11 -0
- metadata +65 -0
@@ -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
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
|
+
|