aspector 0.5.0

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,200 @@
1
+ module Aspector
2
+ class AspectInstance
3
+
4
+ METHOD_TEMPLATE = ERB.new <<-CODE
5
+ wrapped_method = instance_method(:<%= method %>)
6
+
7
+ define_method :<%= method %> do |*args, &block|
8
+ result = nil
9
+
10
+ # Before advices
11
+ <% before_advices.each do |definition| %>
12
+ <% if definition.options[:method_name_arg] %>
13
+ result = <%= definition.with_method %> '<%= method %>', *args
14
+ <% else %>
15
+ result = <%= definition.with_method %> *args
16
+ <% end %>
17
+
18
+ return result.value if result.is_a? ::Aspector::ReturnThis
19
+ <% if definition.options[:skip_if_false] %>
20
+ return unless result
21
+ <% end %>
22
+ <% end %>
23
+
24
+ <% if around_advice %>
25
+ # around advice
26
+ <% if around_advice.options[:method_name_arg] %>
27
+ result = <%= around_advice.with_method %> '<%= method %>', *args do |*args|
28
+ wrapped_method.bind(self).call *args, &block
29
+ end
30
+ <% else %>
31
+ result = <%= around_advice.with_method %> *args do |*args|
32
+ wrapped_method.bind(self).call *args, &block
33
+ end
34
+ <% end %>
35
+ <% else %>
36
+ # Invoke wrapped method
37
+ result = wrapped_method.bind(self).call *args, &block
38
+ <% end %>
39
+
40
+ # After advices
41
+ <% after_advices.each do |definition| %>
42
+ <% if definition.options[:method_name_arg] and definition.options[:result_arg] %>
43
+ result = <%= definition.with_method %> '<%= method %>', result, *args
44
+ <% elsif definition.options[:method_name_arg] %>
45
+ <%= definition.with_method %> '<%= method %>', *args
46
+ <% elsif definition.options[:result_arg] %>
47
+ result = <%= definition.with_method %> result, *args
48
+ <% else %>
49
+ <%= definition.with_method %> *args
50
+ <% end %>
51
+ <% end %>
52
+ result
53
+ end
54
+ CODE
55
+
56
+
57
+ def initialize target, aspect, options = {}
58
+ @target = target
59
+ @aspect = aspect
60
+ @options = options.merge(aspect.options)
61
+ @context = get_context # Context is where advices will be applied (i.e. where methods are modified)
62
+ end
63
+
64
+ def apply
65
+ invoke_deferred_logics
66
+ define_methods_for_advice_blocks
67
+ add_to_instances
68
+ apply_to_methods
69
+ add_method_hooks
70
+ end
71
+
72
+ def deferred_logic_results logic
73
+ @deferred_logic_results[logic]
74
+ end
75
+
76
+ def apply_to_methods
77
+ @context.instance_methods.each do |method|
78
+ apply_to_method(method)
79
+ end
80
+ end
81
+
82
+ def apply_to_method method
83
+ advices = advices_for_method method
84
+ return if advices.empty?
85
+
86
+ recreate_method method, advices
87
+ end
88
+
89
+ def to_hash
90
+ {
91
+ "type" => self.class.name,
92
+ "context" => @context.name,
93
+ "options" => @options,
94
+ "aspect" => @aspect.to_hash
95
+ }
96
+ end
97
+
98
+ private
99
+
100
+ def get_context
101
+ return @target if @target.is_a?(Module) and not @options[:eigen_class]
102
+
103
+ class << @target
104
+ self
105
+ end
106
+ end
107
+
108
+ def invoke_deferred_logics
109
+ return unless @aspect.deferred_logics
110
+
111
+ @deferred_logic_results ||= {}
112
+ @aspect.deferred_logics.each do |logic|
113
+ @deferred_logic_results[logic] = @context.class_eval(logic.code)
114
+ end
115
+ end
116
+
117
+ def define_methods_for_advice_blocks
118
+ @aspect.advices.each do |advice|
119
+ next unless advice.advice_block
120
+ @context.send :define_method, advice.with_method, advice.advice_block
121
+ end
122
+ end
123
+
124
+ def add_to_instances
125
+ aspect_instances = @context.instance_variable_get(:@aspector_instances)
126
+ unless aspect_instances
127
+ aspect_instances = AspectInstances.new
128
+ @context.instance_variable_set(:@aspector_instances, aspect_instances)
129
+ end
130
+ aspect_instances << self
131
+ end
132
+
133
+ def add_method_hooks
134
+ if @options[:eigen_class]
135
+ return unless @target.is_a?(Module)
136
+
137
+ eigen_class = class << @target; self; end
138
+ orig_singleton_method_added = @target.method(:singleton_method_added)
139
+
140
+ eigen_class.send :define_method, :singleton_method_added do |method|
141
+ singleton_method_added_aspector(method) do
142
+ orig_singleton_method_added.call(method)
143
+ end
144
+ end
145
+ else
146
+ eigen_class = class << @target; self; end
147
+
148
+ if @target.is_a? Module
149
+ orig_method_added = @target.method(:method_added)
150
+ else
151
+ orig_method_added = eigen_class.method(:method_added)
152
+ end
153
+
154
+ eigen_class.send :define_method, :method_added do |method|
155
+ method_added_aspector(method) do
156
+ orig_method_added.call(method)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def advices_for_method method
163
+ @aspect.advices.select do |advice|
164
+ advice.match?(method, self)
165
+ end
166
+ end
167
+
168
+ def recreate_method method, advices
169
+ @context.instance_variable_set(:@aspector_creating_method, true)
170
+ grouped_advices = []
171
+
172
+ advices.each do |advice|
173
+ if advice.around? and not grouped_advices.empty?
174
+ recreate_method_with_advices method, grouped_advices
175
+
176
+ grouped_advices = []
177
+ end
178
+
179
+ grouped_advices << advice
180
+ end
181
+
182
+ # create wrap method for before/after advices which are not wrapped inside around advice.
183
+ recreate_method_with_advices method, grouped_advices unless grouped_advices.empty?
184
+ ensure
185
+ @context.instance_variable_set(:@aspector_creating_method, nil)
186
+ end
187
+
188
+ def recreate_method_with_advices method, advices
189
+ before_advices = advices.select {|advice| advice.before? or advice.before_filter? }
190
+ after_advices = advices.select {|advice| advice.after? }
191
+ around_advice = advices.first if advices.first.around?
192
+
193
+ code = METHOD_TEMPLATE.result(binding)
194
+ #puts code
195
+ # line no is the actual line no of METHOD_TEMPLATE + 5
196
+ @context.class_eval code, __FILE__, 5
197
+ end
198
+
199
+ end
200
+ end
@@ -0,0 +1,11 @@
1
+ module Aspector
2
+ class AspectInstances < Array
3
+
4
+ def apply_to_method method
5
+ each do |aspect_instance|
6
+ aspect_instance.apply_to_method(method)
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Aspector
2
+ class DeferredLogic
3
+
4
+ attr_reader :code, :value
5
+
6
+ def initialize code
7
+ @code = code
8
+ end
9
+
10
+ def apply target
11
+ @value = target.class_eval(@code)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module Aspector
2
+ class MethodMatcher
3
+ def initialize *match_data
4
+ @match_data = match_data
5
+ end
6
+
7
+ def match? method, context = nil
8
+ @match_data.detect do |item|
9
+ case item
10
+ when String
11
+ item == method
12
+ when Regexp
13
+ item =~ method
14
+ when Symbol
15
+ item.to_s == method
16
+ when DeferredLogic
17
+ new_matcher = MethodMatcher.new(context.deferred_logic_results[item])
18
+ new_matcher.match?(item)
19
+ end
20
+ end
21
+ end
22
+
23
+ def has_regular_expressions?
24
+ @has_regexps ||= @match_data.detect { |item| item.is_a? Regexp }
25
+ end
26
+
27
+ def to_s
28
+ @match_data.map {|item| item.inspect }.join ", "
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,49 @@
1
+ module Aspector
2
+ module ModuleExtension
3
+ Module.send :include, self
4
+
5
+ def method_added_aspector method
6
+ return (block_given? and yield) if
7
+ @aspector_creating_method or
8
+ @aspector_instances.nil? or @aspector_instances.empty?
9
+
10
+ aspects_applied_flag = :"@aspector_applied_#{method}"
11
+ return (block_given? and yield) if instance_variable_get(aspects_applied_flag)
12
+
13
+ begin
14
+ instance_variable_set(aspects_applied_flag, true)
15
+
16
+ @aspector_instances.apply_to_method(method.to_s)
17
+
18
+ yield if block_given?
19
+ ensure
20
+ instance_variable_set(aspects_applied_flag, nil)
21
+ end
22
+ end
23
+
24
+ def singleton_method_added_aspector method
25
+ # Note: methods involved are on eigen class
26
+ eigen_class = class << self; self; end
27
+
28
+ return (block_given? and yield) if eigen_class.instance_variable_get(:@aspector_creating_method)
29
+
30
+ aspect_instances = eigen_class.instance_variable_get(:@aspector_instances)
31
+ return (block_given? and yield) if aspect_instances.nil? or aspect_instances.empty?
32
+
33
+ aspects_applied_flag = :"@aspector_applied_#{method}"
34
+ return (block_given? and yield) if eigen_class.instance_variable_get(aspects_applied_flag)
35
+
36
+ begin
37
+ eigen_class.instance_variable_set(aspects_applied_flag, true)
38
+
39
+ aspect_instances.apply_to_method(method.to_s)
40
+
41
+ yield if block_given?
42
+ ensure
43
+ eigen_class.instance_variable_set(aspects_applied_flag, nil)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+
@@ -0,0 +1,22 @@
1
+ module Aspector
2
+ module ObjectExtension
3
+
4
+ def aspector *args, &block
5
+ options = args.last.is_a?(Hash) ? args.pop : {}
6
+
7
+ aspect = Aspector::Aspect.new(options, &block)
8
+
9
+ aspect.apply(self) if self.is_a? Module
10
+ args.each {|target| aspect.apply(target) }
11
+
12
+ aspect
13
+ end
14
+
15
+ def Aspector options = {}, &block
16
+ Aspector::Aspect.new(options, &block)
17
+ end
18
+
19
+ end
20
+ end
21
+
22
+ Object.send(:include, Aspector::ObjectExtension)
@@ -0,0 +1,9 @@
1
+ module Aspector
2
+ class ReturnThis
3
+ attr :value
4
+
5
+ def initialize value
6
+ @value = value
7
+ end
8
+ end
9
+ end
data/lib/aspector.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'aspector/object_extension'
2
+ require 'aspector/module_extension'
3
+
4
+ require 'aspector/aspect'
5
+ require 'aspector/advice'
6
+ require 'aspector/advice_metadata'
7
+ require 'aspector/method_matcher'
8
+ require 'aspector/deferred_logic'
9
+
10
+ require 'aspector/aspect_instance'
11
+ require 'aspector/aspect_instances'
12
+
13
+ require 'aspector/return_this'
@@ -0,0 +1,86 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Aspector for eigen class" do
4
+ it "should work" do
5
+ klass = Class.new do
6
+ class << self
7
+ def value
8
+ @value ||= []
9
+ end
10
+
11
+ def test
12
+ value << "test"
13
+ end
14
+
15
+ def do_before
16
+ value << "do_before"
17
+ end
18
+
19
+ def do_after result
20
+ value << "do_after"
21
+ result
22
+ end
23
+
24
+ def do_around &block
25
+ value << "do_around_before"
26
+ result = block.call
27
+ value << "do_around_after"
28
+ result
29
+ end
30
+ end
31
+ end
32
+
33
+ aspector(klass, :eigen_class => true) do
34
+ before :test, :do_before
35
+ after :test, :do_after
36
+ around :test, :do_around
37
+ end
38
+
39
+ klass.test
40
+ klass.value.should == %w"do_around_before do_before test do_after do_around_after"
41
+ end
42
+
43
+ it "new methods" do
44
+ klass = Class.new do
45
+ class << self
46
+ def value
47
+ @value ||= []
48
+ end
49
+
50
+ def do_before
51
+ value << "do_before"
52
+ end
53
+
54
+ def do_after result
55
+ value << "do_after"
56
+ result
57
+ end
58
+
59
+ def do_around &block
60
+ value << "do_around_before"
61
+ result = block.call
62
+ value << "do_around_after"
63
+ result
64
+ end
65
+ end
66
+ end
67
+
68
+ aspector(klass, :eigen_class => true) do
69
+ before :test, :do_before
70
+ after :test, :do_after
71
+ around :test, :do_around
72
+ end
73
+
74
+ klass.class_eval do
75
+ class << self
76
+ def test
77
+ value << "test"
78
+ end
79
+ end
80
+ end
81
+
82
+ klass.test
83
+ klass.value.should == %w"do_around_before do_before test do_after do_around_after"
84
+ end
85
+
86
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Aspector for object" do
4
+ it "should work" do
5
+ klass = Class.new do
6
+ def value
7
+ @value ||= []
8
+ end
9
+
10
+ def test
11
+ value << "test"
12
+ end
13
+
14
+ def do_before
15
+ value << "do_before"
16
+ end
17
+ end
18
+
19
+ obj = klass.new
20
+
21
+ aspector(obj) do
22
+ before :test, :do_before
23
+ end
24
+
25
+ obj.test
26
+ obj.value.should == %w"do_before test"
27
+
28
+ obj2 = klass.new
29
+ obj2.test
30
+ obj2.value.should == %w"test"
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "After advices" do
4
+ it "should work" do
5
+ klass = Class.new do
6
+ def value
7
+ @value ||= []
8
+ end
9
+
10
+ def test
11
+ value << "test"
12
+ end
13
+
14
+ def do_this result
15
+ value << "do_this"
16
+ result
17
+ end
18
+ end
19
+
20
+ aspector(klass) do
21
+ after :test, :do_this
22
+ end
23
+
24
+ obj = klass.new
25
+ obj.test
26
+ obj.value.should == %w"test do_this"
27
+ end
28
+
29
+ it "logic in block" do
30
+ klass = Class.new do
31
+ def value
32
+ @value ||= []
33
+ end
34
+
35
+ def test
36
+ value << "test"
37
+ end
38
+ end
39
+
40
+ aspector(klass) do
41
+ after(:test) do |result|
42
+ value << 'do_block'
43
+ result
44
+ end
45
+ end
46
+
47
+ obj = klass.new
48
+ obj.test
49
+ obj.value.should == %w"test do_block"
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Around advices" do
4
+ it "should work" do
5
+ klass = Class.new do
6
+ def value
7
+ @value ||= []
8
+ end
9
+
10
+ def test
11
+ value << "test"
12
+ end
13
+
14
+ def do_this &block
15
+ value << "before"
16
+ result = block.call
17
+ value << "after"
18
+ result
19
+ end
20
+ end
21
+
22
+ aspector(klass) do
23
+ around :test, :do_this
24
+ end
25
+
26
+ obj = klass.new
27
+ obj.test
28
+ obj.value.should == %w"before test after"
29
+ end
30
+
31
+ it "logic in block" do
32
+ klass = Class.new do
33
+ def value
34
+ @value ||= []
35
+ end
36
+
37
+ def test
38
+ value << "test"
39
+ end
40
+ end
41
+
42
+ aspector(klass) do
43
+ around :test do |&block|
44
+ value << "before"
45
+ result = block.call
46
+ value << "after"
47
+ result
48
+ end
49
+ end
50
+
51
+ obj = klass.new
52
+ obj.test
53
+ obj.value.should == %w"before test after"
54
+ end
55
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Aspector::Aspect" do
4
+ it "#apply" do
5
+ klass = Class.new do
6
+ def value
7
+ @value ||= []
8
+ end
9
+
10
+ def test
11
+ value << "test"
12
+ end
13
+
14
+ def do_this
15
+ value << "do_this"
16
+ end
17
+ end
18
+
19
+ aspect = Aspector do
20
+ before :test, :do_this
21
+ end
22
+
23
+ aspect.apply(klass)
24
+
25
+ obj = klass.new
26
+ obj.test
27
+ obj.value.should == %w"do_this test"
28
+ end
29
+
30
+ it "can add method to target" do
31
+ klass = Class.new do
32
+ def value
33
+ @value ||= []
34
+ end
35
+
36
+ def test
37
+ value << "test"
38
+ end
39
+ end
40
+
41
+ aspect = Aspector do
42
+ target '
43
+ def do_this
44
+ value << "do_this"
45
+ end
46
+ '
47
+
48
+ before :test, :do_this
49
+ end
50
+
51
+ aspect.apply(klass)
52
+
53
+ obj = klass.new
54
+ obj.test
55
+ obj.value.should == %w"do_this test"
56
+ end
57
+ end