nakajima-booty-call 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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.3'
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].tap do |slot|
31
- slot.push(block) if block_given?
32
- slot.concat(symbols)
33
- slot.tap.compact!.uniq!
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!(/=/, '__EQUALS__')
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
- return unless self.class.run_callbacks_for(self, :before, #{method_id.inspect})
51
- result = __PRISTINE__(#{method_id.inspect}, *args, &block)
52
- self.class.run_callbacks_for(self, :after, #{method_id.inspect}, result)
53
- return result
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}_without_callbacks(*args, &block)
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)
@@ -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
- @introspector.defined_methods.include?(method_id) ?
28
- @callbacker.send(position, method_id, *args, &block) :
29
- observe(method_id) { send(position, method_id, *args, &block) }
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
- this = self
13
- hook = options[:meta] ? :singleton_method_added : :method_added
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 observing?(method_id)
21
- not not @observed_methods[method_id]
23
+ def defined_methods
24
+ (klass.instance_methods - Object.instance_methods).map(&:to_sym)
22
25
  end
23
26
 
24
- def observe(method_id, options={}, &block)
25
- observe_klass!
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 defined_methods
31
- (klass.instance_methods - Object.instance_methods).map(&:to_sym)
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
- stop_observing(method_id) do
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 stop_observing(method_id, &block)
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.try(:call, observer)
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
- @callbacks = []
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
- @callbacks.each { |fn| fn.call(sym) }
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)/) or @count < @times
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
- @callbacks += args
28
- @callbacks.tap.compact!.uniq!
32
+ @handlers += args
33
+ @handlers.tap.compact!.uniq!
29
34
  end
30
35
  end
31
36
  end
@@ -0,0 +1,7 @@
1
+ class Array
2
+ def unshift?(item)
3
+ tap do
4
+ unshift(item) if item
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class Method
2
+ def arity_match?(collection)
3
+ arity == -1 or arity == collection.length
4
+ end
5
+ 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.3
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: