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 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: