nakajima-booty-call 0.0.3 → 0.0.5
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.rb +4 -1
- data/lib/booty_call/callbacker.rb +41 -29
- data/lib/booty_call/hook.rb +21 -3
- data/lib/booty_call/introspector.rb +26 -16
- data/lib/booty_call/observed_method.rb +11 -6
- data/lib/core_ext/array.rb +7 -0
- data/lib/core_ext/method.rb +5 -0
- metadata +4 -1
data/lib/booty_call.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
$LOAD_PATH << File.dirname(__FILE__) + '/booty_call'
|
2
|
+
$LOAD_PATH << File.dirname(__FILE__) + '/core_ext'
|
2
3
|
|
3
4
|
module BootyCall
|
4
|
-
VERSION = '0.0.
|
5
|
+
VERSION = '0.0.5'
|
5
6
|
end
|
6
7
|
|
7
8
|
require 'rubygems'
|
8
9
|
require 'nakajima'
|
10
|
+
require 'array'
|
11
|
+
require 'method'
|
9
12
|
require 'callbacker'
|
10
13
|
require 'observed_method'
|
11
14
|
require 'introspector'
|
@@ -15,23 +15,26 @@ module BootyCall
|
|
15
15
|
add_callback(:after, method_id, *symbols, &block)
|
16
16
|
end
|
17
17
|
|
18
|
+
def around(method_id, *symbols, &block)
|
19
|
+
add_callback(:around, method_id, *symbols, &block)
|
20
|
+
end
|
21
|
+
|
18
22
|
private
|
19
23
|
|
20
24
|
def extend_klass
|
21
25
|
klass.class_eval do
|
22
26
|
@pristine_cache = Hash.new
|
23
|
-
@callback_cache = { :after => Hash.new([]), :before => Hash.new([]) }
|
27
|
+
@callback_cache = { :after => Hash.new([]), :before => Hash.new([]), :around => Hash.new([]) }
|
24
28
|
extend ClassMethods
|
25
29
|
include InstanceMethods
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
29
33
|
def add_callback(position, method_id, *symbols, &block)
|
30
|
-
klass.callback_cache[position][method_id]
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
34
|
+
klass.callback_cache[position][method_id] << block if block_given?
|
35
|
+
klass.callback_cache[position][method_id] += symbols
|
36
|
+
klass.callback_cache[position][method_id].compact!
|
37
|
+
klass.callback_cache[position][method_id].uniq!
|
35
38
|
|
36
39
|
klass.pristine_cache[method_id] ||= begin
|
37
40
|
klass.instance_method(method_id).tap do
|
@@ -42,19 +45,19 @@ module BootyCall
|
|
42
45
|
|
43
46
|
def redefine_method(method_id)
|
44
47
|
safe_method_id = method_id.to_s
|
45
|
-
safe_method_id.gsub!(
|
46
|
-
safe_method_id.gsub!(/\?/, '__PREDICATE__')
|
48
|
+
safe_method_id.gsub!(/([\w_]+)(\?|!|=|\b)/) { |m| "#{$1}_without_callbacks#{$2}" }
|
47
49
|
klass.class_eval(<<-EOS, "(__DELEGATION__)", 1)
|
48
50
|
def #{method_id}(*args, &block)
|
49
51
|
catch(#{method_id.to_sym.inspect}) do
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
res = nil
|
53
|
+
run_callbacks(:before, #{method_id.inspect})
|
54
|
+
run_callbacks(:around, #{method_id.inspect}) { res = __PRISTINE__(#{method_id.inspect}, *args, &block) }
|
55
|
+
run_callbacks(:after, #{method_id.inspect}, res)
|
56
|
+
res
|
54
57
|
end
|
55
58
|
end
|
56
59
|
|
57
|
-
def #{safe_method_id}
|
60
|
+
def #{safe_method_id}(*args, &block)
|
58
61
|
__PRISTINE__(#{method_id.inspect}, *args, &block)
|
59
62
|
end
|
60
63
|
EOS
|
@@ -68,25 +71,34 @@ module BootyCall
|
|
68
71
|
def callback_cache
|
69
72
|
@callback_cache || superclass.callback_cache
|
70
73
|
end
|
71
|
-
|
72
|
-
def run_callbacks_for(target, position, method_id, *results)
|
73
|
-
callbacks = callback_cache[position][method_id.to_sym]
|
74
|
-
|
75
|
-
handler = proc do |fn|
|
76
|
-
fn.is_a?(Proc) ? fn : begin
|
77
|
-
target.method(fn).arity.abs == results.length ?
|
78
|
-
proc { send(fn, *results) } :
|
79
|
-
proc { send(fn) }
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
callbacks.empty? ? true : callbacks.map { |fn|
|
84
|
-
target.instance_exec(*results, &handler.call(fn))
|
85
|
-
}.all?
|
86
|
-
end
|
87
74
|
end
|
88
75
|
|
89
76
|
module InstanceMethods
|
77
|
+
def callbacks_for(position, method_id, *results, &block)
|
78
|
+
callbacks = self.class.callback_cache[position][method_id.to_sym]
|
79
|
+
|
80
|
+
if callbacks.empty?
|
81
|
+
block ? instance_eval(&block) : true
|
82
|
+
else
|
83
|
+
callbacks.map { |callback|
|
84
|
+
if callback.is_a?(Proc)
|
85
|
+
instance_exec(*results.unshift?(block), &callback)
|
86
|
+
else
|
87
|
+
method(callback).arity_match?(results) ?
|
88
|
+
send(callback, *results, &block) :
|
89
|
+
send(callback, &block)
|
90
|
+
end
|
91
|
+
}.all?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def run_callbacks(position, method_id, *args, &block)
|
96
|
+
callbacks_for(position, method_id, *args, &block).tap do |result|
|
97
|
+
# Halt method propagation if before callbacks return false
|
98
|
+
throw(method_id, false) if position.eql?(:before) and result.eql?(false)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
90
102
|
def __PRISTINE__(method_id, *args, &block)
|
91
103
|
if method = self.class.pristine_cache[method_id]
|
92
104
|
method.bind(self).call(*args, &block)
|
data/lib/booty_call/hook.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
module BootyCall
|
2
|
+
def self.included(klass)
|
3
|
+
klass.send(:include, Hook)
|
4
|
+
end
|
5
|
+
|
2
6
|
module Hook
|
3
7
|
def self.included(klass)
|
4
8
|
klass.class_eval do
|
@@ -18,15 +22,29 @@ module BootyCall
|
|
18
22
|
callback(:after, method_id, *args, &block)
|
19
23
|
end
|
20
24
|
|
25
|
+
def around(method_id, *args, &block)
|
26
|
+
callback(:around, method_id, *args, &block)
|
27
|
+
end
|
28
|
+
|
21
29
|
def observe(method_id, options={}, &block)
|
22
30
|
observer = options[:meta] ? @meta_introspector : @introspector
|
23
31
|
observer.observe(method_id, options, &block)
|
24
32
|
end
|
25
33
|
|
26
34
|
def callback(position, method_id, *args, &block)
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
case method_id
|
36
|
+
when Regexp
|
37
|
+
return if @introspector.observing?(method_id)
|
38
|
+
observe(method_id, :times => :all) { |m| callback(position, m, *(args << block)) }
|
39
|
+
@introspector.defined_methods \
|
40
|
+
.select { |m| m.to_s =~ method_id } \
|
41
|
+
.reject { |m| @introspector.observing?(m) } \
|
42
|
+
.each { |m| send(position, m, *args, &block) }
|
43
|
+
when Symbol
|
44
|
+
@introspector.has_method?(method_id) ?
|
45
|
+
@callbacker.send(position, method_id, *args, &block) :
|
46
|
+
observe(method_id) { send(position, method_id, *(args << block)) }
|
47
|
+
end
|
30
48
|
end
|
31
49
|
end
|
32
50
|
end
|
@@ -7,42 +7,52 @@ module BootyCall
|
|
7
7
|
@observed_methods = { }
|
8
8
|
end
|
9
9
|
|
10
|
+
def observe(method_id, options={}, &block)
|
11
|
+
observe_klass!
|
12
|
+
@observed_methods[method_id] ||= ObservedMethod.new(method_id, options)
|
13
|
+
@observed_methods[method_id].push(block) if block_given?
|
14
|
+
end
|
15
|
+
|
10
16
|
def observe_klass!
|
11
17
|
@observed ||= begin
|
12
|
-
|
13
|
-
|
14
|
-
klass.meta_def(hook) do |m|
|
15
|
-
this.check_method(m)
|
16
|
-
end; true
|
18
|
+
name = options[:meta] ? :singleton_method_added : :method_added
|
19
|
+
install_hook(name) or true
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
20
|
-
def
|
21
|
-
|
23
|
+
def defined_methods
|
24
|
+
(klass.instance_methods - Object.instance_methods).map(&:to_sym)
|
22
25
|
end
|
23
26
|
|
24
|
-
def
|
25
|
-
|
26
|
-
@observed_methods[method_id] ||= ObservedMethod.new(method_id, options)
|
27
|
-
@observed_methods[method_id].push(block) if block_given?
|
27
|
+
def has_method?(method_id)
|
28
|
+
klass.instance_method(method_id) rescue false
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
|
31
|
+
def observing?(method_id)
|
32
|
+
not not @observed_methods[method_id]
|
32
33
|
end
|
33
34
|
|
35
|
+
private
|
36
|
+
|
37
|
+
def install_hook(name)
|
38
|
+
this = self
|
39
|
+
klass.meta_def(name) do |m|
|
40
|
+
this.send :check_method, m
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
34
44
|
def check_method(sym)
|
35
45
|
@observed_methods.each do |method_id, observer|
|
36
|
-
|
46
|
+
without_observers(method_id) do
|
37
47
|
observer.match(sym)
|
38
48
|
observer.valid?
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
43
|
-
def
|
53
|
+
def without_observers(method_id, &block)
|
44
54
|
@observed_methods.delete(method_id).tap do |observer|
|
45
|
-
@observed_methods[method_id] = observer if block
|
55
|
+
@observed_methods[method_id] = observer if block[observer]
|
46
56
|
end
|
47
57
|
end
|
48
58
|
end
|
@@ -4,28 +4,33 @@ module BootyCall
|
|
4
4
|
|
5
5
|
def initialize(method_id, options={})
|
6
6
|
@method_id = method_id
|
7
|
-
@
|
7
|
+
@handlers = []
|
8
8
|
@count = 0
|
9
9
|
@times = options[:times] || 1
|
10
10
|
end
|
11
11
|
|
12
12
|
def match(sym)
|
13
13
|
return unless case method_id
|
14
|
-
when Symbol then valid?
|
14
|
+
when Symbol then valid? and method_id === sym
|
15
15
|
when Regexp then valid? and method_id.try(:match, sym.to_s)
|
16
16
|
else ; nil
|
17
17
|
end
|
18
|
-
@
|
18
|
+
@handlers.each { |fn| fn[sym] }
|
19
19
|
@count += 1
|
20
20
|
end
|
21
21
|
|
22
22
|
def valid?
|
23
|
-
@times.to_s.match(/^(inf|any|all|every)/)
|
23
|
+
if @times.to_s.match(/^(inf|any|all|every)/)
|
24
|
+
def self.valid?; true end
|
25
|
+
true # memoizing the result
|
26
|
+
else
|
27
|
+
@count < @times
|
28
|
+
end
|
24
29
|
end
|
25
30
|
|
26
31
|
def push(*args)
|
27
|
-
@
|
28
|
-
@
|
32
|
+
@handlers += args
|
33
|
+
@handlers.tap.compact!.uniq!
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nakajima-booty-call
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pat Nakajima
|
@@ -37,6 +37,9 @@ files:
|
|
37
37
|
- lib/booty_call/callbacker.rb
|
38
38
|
- lib/booty_call/introspector.rb
|
39
39
|
- lib/booty_call/observed_method.rb
|
40
|
+
- lib/core_ext
|
41
|
+
- lib/core_ext/array.rb
|
42
|
+
- lib/core_ext/method.rb
|
40
43
|
has_rdoc: false
|
41
44
|
homepage: http://github.com/nakajima/booty-call
|
42
45
|
post_install_message:
|