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.
- data/.document +5 -0
- data/.irbrc +3 -0
- data/.rspec +1 -0
- data/.rvmrc +3 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +66 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +66 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/examples/around_example.rb +39 -0
- data/examples/aspector_apply_example.rb +35 -0
- data/examples/aspector_example.rb +33 -0
- data/lib/aspector/advice.rb +76 -0
- data/lib/aspector/advice_metadata.rb +17 -0
- data/lib/aspector/aspect.rb +75 -0
- data/lib/aspector/aspect_instance.rb +200 -0
- data/lib/aspector/aspect_instances.rb +11 -0
- data/lib/aspector/deferred_logic.rb +15 -0
- data/lib/aspector/method_matcher.rb +32 -0
- data/lib/aspector/module_extension.rb +49 -0
- data/lib/aspector/object_extension.rb +22 -0
- data/lib/aspector/return_this.rb +9 -0
- data/lib/aspector.rb +13 -0
- data/spec/aspect_on_eigen_class_spec.rb +86 -0
- data/spec/aspect_on_object_spec.rb +32 -0
- data/spec/aspector/after_spec.rb +51 -0
- data/spec/aspector/around_spec.rb +55 -0
- data/spec/aspector/aspect_spec.rb +57 -0
- data/spec/aspector/before_spec.rb +72 -0
- data/spec/aspector_spec.rb +65 -0
- data/spec/aspects_combined_spec.rb +55 -0
- data/spec/spec_helper.rb +12 -0
- metadata +321 -0
@@ -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,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)
|
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
|