nakajima-aspectory 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+