nakajima-aspectory 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.
@@ -0,0 +1,111 @@
1
+ module Aspectory
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
+ def around(method_id, *symbols, &block)
19
+ add_callback(:around, method_id, *symbols, &block)
20
+ end
21
+
22
+ private
23
+
24
+ def extend_klass
25
+ klass.class_eval do
26
+ @pristine_cache = Hash.new
27
+ @callback_cache = { :after => Hash.new([]), :before => Hash.new([]), :around => Hash.new([]) }
28
+ extend ClassMethods
29
+ include InstanceMethods
30
+ end
31
+ end
32
+
33
+ def add_callback(position, method_id, *symbols, &block)
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!
38
+
39
+ klass.pristine_cache[method_id] ||= begin
40
+ klass.instance_method(method_id).tap do
41
+ redefine_method method_id
42
+ end
43
+ end
44
+ end
45
+
46
+ def redefine_method(method_id)
47
+ safe_method_id = method_id.to_s
48
+ safe_method_id.gsub!(/([\w_]+)(\?|!|=|\b)/) { |m| "#{$1}_without_callbacks#{$2}" }
49
+ klass.class_eval(<<-EOS, "(__DELEGATION__)", 1)
50
+ def #{method_id}(*args, &block)
51
+ catch(#{method_id.to_sym.inspect}) do
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
57
+ end
58
+ end
59
+
60
+ def #{safe_method_id}(*args, &block)
61
+ __PRISTINE__(#{method_id.inspect}, *args, &block)
62
+ end
63
+ EOS
64
+ end
65
+
66
+ module ClassMethods
67
+ def pristine_cache
68
+ @pristine_cache || superclass.pristine_cache
69
+ end
70
+
71
+ def callback_cache
72
+ @callback_cache || superclass.callback_cache
73
+ end
74
+ end
75
+
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.enqueue(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
+
102
+ def __PRISTINE__(method_id, *args, &block)
103
+ if method = self.class.pristine_cache[method_id]
104
+ method.bind(self).call(*args, &block)
105
+ else
106
+ raise NoMethodError, "No method named #{method_id.inspect}"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,51 @@
1
+ module Aspectory
2
+ def self.included(klass)
3
+ klass.send(:include, Hook)
4
+ end
5
+
6
+ module Hook
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ extend(ClassMethods)
10
+ @callbacker = Aspectory::Callbacker.new(self)
11
+ @introspector = Aspectory::Introspector.new(self)
12
+ @meta_introspector = Aspectory::Introspector.new(self, :meta => true)
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def before(method_id, *args, &block)
18
+ callback(:before, method_id, *args, &block)
19
+ end
20
+
21
+ def after(method_id, *args, &block)
22
+ callback(:after, method_id, *args, &block)
23
+ end
24
+
25
+ def around(method_id, *args, &block)
26
+ callback(:around, method_id, *args, &block)
27
+ end
28
+
29
+ def observe(method_id, options={}, &block)
30
+ observer = options[:meta] ? @meta_introspector : @introspector
31
+ observer.observe(method_id, options, &block)
32
+ end
33
+
34
+ def callback(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
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,59 @@
1
+ module Aspectory
2
+ class Introspector
3
+ attr_reader :klass, :options
4
+
5
+ def initialize(klass, options={})
6
+ @klass, @options = klass, options
7
+ @observed_methods = { }
8
+ end
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
+
16
+ def observe_klass!
17
+ @observed ||= begin
18
+ name = options[:meta] ? :singleton_method_added : :method_added
19
+ install_hook(name) or true
20
+ end
21
+ end
22
+
23
+ def defined_methods
24
+ (klass.instance_methods - Object.instance_methods).map(&:to_sym)
25
+ end
26
+
27
+ def has_method?(method_id)
28
+ klass.instance_method(method_id) rescue false
29
+ end
30
+
31
+ def observing?(method_id)
32
+ not not @observed_methods[method_id]
33
+ end
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
+
44
+ def check_method(sym)
45
+ @observed_methods.each do |method_id, observer|
46
+ without_observers(method_id) do
47
+ observer.match(sym)
48
+ observer.valid?
49
+ end
50
+ end
51
+ end
52
+
53
+ def without_observers(method_id, &block)
54
+ @observed_methods.delete(method_id).tap do |observer|
55
+ @observed_methods[method_id] = observer if block[observer]
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ module Aspectory
2
+ class ObservedMethod
3
+ attr_reader :method_id
4
+
5
+ def initialize(method_id, options={})
6
+ @method_id = method_id
7
+ @handlers = []
8
+ @count = 0
9
+ @times = options[:times] || 1
10
+ end
11
+
12
+ def match(sym)
13
+ return unless case method_id
14
+ when Symbol then valid? and method_id === sym
15
+ when Regexp then valid? and method_id.try(:match, sym.to_s)
16
+ else ; nil
17
+ end
18
+ @handlers.each { |fn| fn[sym] }
19
+ @count += 1
20
+ end
21
+
22
+ def valid?
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
29
+ end
30
+
31
+ def push(*args)
32
+ @handlers += args
33
+ @handlers.tap.compact!.uniq!
34
+ end
35
+ end
36
+ end
data/lib/aspectory.rb ADDED
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) + '/aspectory'
2
+ $LOAD_PATH << File.dirname(__FILE__) + '/core_ext'
3
+
4
+ module BootyCall
5
+ VERSION = '0.0.5'
6
+ end
7
+
8
+ require 'rubygems'
9
+ require 'nakajima'
10
+ require 'array'
11
+ require 'method'
12
+ require 'callbacker'
13
+ require 'observed_method'
14
+ require 'introspector'
15
+ require 'hook'
@@ -0,0 +1,7 @@
1
+ class Array
2
+ def enqueue(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 ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nakajima-aspectory
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
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
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description:
26
+ email: patnakajima@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/aspectory
35
+ - lib/aspectory.rb
36
+ - lib/aspectory/hook.rb
37
+ - lib/aspectory/callbacker.rb
38
+ - lib/aspectory/introspector.rb
39
+ - lib/aspectory/observed_method.rb
40
+ - lib/core_ext
41
+ - lib/core_ext/array.rb
42
+ - lib/core_ext/method.rb
43
+ has_rdoc: false
44
+ homepage: http://github.com/nakajima/aspectory
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.2.0
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: Callbacks for your Ruby
69
+ test_files: []
70
+