aspectr 0.3.7

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,309 @@
1
+ # AspectR - simple Aspect-Oriented Programming (AOP) in Ruby.
2
+ # Version 0.3.6, 2006-03-11. NOTE! API has changed somewhat from 0.2 so beware!
3
+ # Last modification: Benjamin Alterauge Email: benjamin_alterauge@web.de
4
+ #
5
+ # Copyright (c) 2001 Avi Bryant (avi@beta4.com) and
6
+ # Robert Feldt (feldt@ce.chalmers.se).
7
+ #
8
+ # This library is free software; you can redistribute it and/or
9
+ # modify it under the terms of the GNU Library General Public
10
+ # License as published by the Free Software Foundation; either
11
+ # version 2 of the License, or (at your option) any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ # Library General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Library General Public
19
+ # License along with this library; if not, write to the Free Software
20
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
+ #
22
+ module AspectR
23
+ class AspectRException < Exception; end
24
+
25
+ class Aspect
26
+ PRE = :PRE
27
+ POST = :POST
28
+
29
+ def initialize(never_wrap = "^$ ")
30
+ @never_wrap = /^__|^send$|^object_id$|^class$|#{never_wrap}/
31
+ end
32
+
33
+ def wrap(target, pre, post, *args)
34
+ get_methods(target, args).each do |method_to_wrap|
35
+ add_advice(target, PRE, method_to_wrap, pre)
36
+ add_advice(target, POST, method_to_wrap, post)
37
+ end
38
+ end
39
+
40
+ def unwrap(target, pre, post, *args)
41
+ get_methods(target, args).each do |method_to_unwrap|
42
+ remove_advice(target, PRE, method_to_unwrap, pre)
43
+ remove_advice(target, POST, method_to_unwrap , post)
44
+ end
45
+ end
46
+
47
+ # Sticky and faster wrap (can't be unwrapped).
48
+ def wrap_with_code(target, preCode, postCode, *args)
49
+ prepare(target)
50
+ get_methods(target, args).each do |method_to_wrap|
51
+ target.__aop_wrap_with_code(method_to_wrap, preCode, postCode)
52
+ end
53
+ end
54
+
55
+ def add_advice(target, joinpoint, method, advice)
56
+ prepare(target)
57
+ if advice
58
+ target.__aop_install_dispatcher(method)
59
+ target.__aop_add_advice(joinpoint, method, self, advice)
60
+ end
61
+ end
62
+
63
+ def remove_advice(target, joinpoint, method, advice)
64
+ target.__aop_remove_advice(joinpoint, method, self, advice) if advice
65
+ end
66
+
67
+ @@__aop_dispatch = true
68
+
69
+ def Aspect.dispatch?
70
+ @@__aop_dispatch
71
+ end
72
+
73
+ def disable_advice_dispatching
74
+ begin
75
+ @@__aop_dispatch = false
76
+ yield
77
+ ensure
78
+ @@__aop_dispatch = true
79
+ end
80
+ end
81
+
82
+ def get_methods(target, args)
83
+ if args.first.is_a? Regexp
84
+ if target.kind_of?(Class)
85
+ methods = target.instance_methods(true)
86
+ else
87
+ methods = target.methods
88
+ end
89
+ methods = methods.grep(args.first).collect{|e| e.intern}
90
+ else
91
+ methods = args
92
+ end
93
+ methods.select {|method| wrappable?(method)}
94
+ end
95
+
96
+ def wrappable?(method)
97
+ method.to_s !~ @never_wrap
98
+ end
99
+
100
+ def prepare(target)
101
+ unless target.respond_to?("__aop_init")
102
+ target.extend AspectSupport
103
+ target.__aop_init
104
+ end
105
+ end
106
+
107
+ module AspectSupport
108
+
109
+ def __aop_init
110
+ if self.is_a? Class
111
+ extend ClassSupport
112
+ else
113
+ extend InstanceSupport
114
+ end
115
+ @__aop_advice_methods = {}
116
+ end
117
+
118
+ def __aop_advice_list(joinpoint, method)
119
+ method = method.to_s
120
+ unless (method_hash = @__aop_advice_methods[joinpoint])
121
+ method_hash = @__aop_advice_methods[joinpoint] = {}
122
+ end
123
+ unless (advice_list = method_hash[method])
124
+ advice_list = method_hash[method] = []
125
+ end
126
+ advice_list
127
+ end
128
+
129
+ def __aop_add_advice(joinpoint, method, aspect, advice)
130
+ __aop_advice_list(joinpoint, method) << [aspect, advice]
131
+ end
132
+
133
+ def __aop_remove_advice(joinpoint, method, aspect, advice)
134
+ __aop_advice_list(joinpoint, method).delete_if do |asp, adv|
135
+ asp == aspect && adv == advice
136
+ end
137
+ # Reinstall original method if there are no advices left for this meth!
138
+ # - except that then we could have problems with singleton instances
139
+ # of this class? see InstanceSupport#aop_alias... /AB
140
+ end
141
+
142
+ def __aop_call_advice(joinpoint, method, *args)
143
+ __aop_advice_list(joinpoint, method).each do |aspect, advice|
144
+ begin
145
+ aspect.send(advice, method, *args)
146
+ rescue Exception
147
+ a = $!
148
+ raise AspectRException, "#{a.class} '#{a}' in advice #{advice}"
149
+ end
150
+ end
151
+ end
152
+
153
+ def __aop_generate_args(method)
154
+ arity = __aop_class.instance_method(method).arity
155
+ if arity < 0
156
+ args = (0...(-1-arity)).to_a.collect{|i| "a#{i}"}.join(",")
157
+ args += "," if arity < -1
158
+ args + "*args,&block"
159
+ elsif arity != 0
160
+ ((0...arity).to_a.collect{|i| "a#{i}"} + ["&block"]).join(",")
161
+ else
162
+ "&block" # could be a yield in there...
163
+ end
164
+ end
165
+
166
+ def __aop_generate_syntax(method)
167
+ args = __aop_generate_args(method)
168
+ mangled_method = __aop_mangle(method)
169
+ call = "send(\"#{mangled_method}\".to_sym, #{args})"
170
+ return args, call, mangled_method
171
+ end
172
+
173
+ def __aop_advice_call_syntax(joinpoint, method, args)
174
+ "#{__aop_target}.__aop_call_advice(:#{joinpoint}, '#{method}', self, exit_status#{args.length>0 ? ',' + args : ''})"
175
+ end
176
+
177
+ def __aop_install_dispatcher(method)
178
+ args, call, mangled_method = __aop_generate_syntax(method)
179
+ return if __aop_private_methods.include? mangled_method
180
+ new_method = """
181
+ def #{method}(#{args})
182
+ return (#{call}) unless Aspect.dispatch?
183
+ begin
184
+ exit_status = nil
185
+ #{__aop_advice_call_syntax(PRE, method, args)}
186
+ exit_status = #{call}
187
+ return exit_status
188
+ rescue Exception
189
+ exit_status = true
190
+ raise
191
+ ensure
192
+ #{__aop_advice_call_syntax(POST, method, args)}
193
+ end
194
+ end
195
+ """
196
+ __aop_alias(mangled_method, method)
197
+ __aop_eval(new_method)
198
+ end
199
+
200
+ def __aop_wrap_with_code(method, preCode, postCode)
201
+ args, call, mangled_method = __aop_generate_syntax(method)
202
+ return if __aop_private_methods.include? mangled_method
203
+ comma = args != "" ? ", " : ""
204
+ preCode.gsub!('INSERT_ARGS', comma + args)
205
+ postCode.gsub!('INSERT_ARGS', comma + args)
206
+ new_method = """
207
+ def #{method}(#{args})
208
+ #{preCode}
209
+ begin
210
+ #{call}
211
+ ensure
212
+ #{postCode}
213
+ end
214
+ end
215
+ """
216
+ __aop_alias(mangled_method, method)
217
+ __aop_eval(new_method)
218
+ end
219
+
220
+ module ClassSupport
221
+ def __aop_target
222
+ "self.class"
223
+ end
224
+
225
+ def __aop_class
226
+ self
227
+ end
228
+
229
+ def __aop_mangle(method)
230
+ "__aop__#{self.object_id}_#{method.object_id}"
231
+ end
232
+
233
+ def __aop_alias(new, old, private = true)
234
+ alias_method new, old
235
+ private new if private
236
+ end
237
+
238
+ def __aop_private_methods
239
+ private_instance_methods
240
+ end
241
+
242
+ def __aop_eval(text)
243
+ begin
244
+ class_eval text
245
+ rescue Exception
246
+ puts "class_eval '#{text}'"
247
+ end
248
+ end
249
+ end
250
+
251
+ module InstanceSupport
252
+ def __aop_target
253
+ "self"
254
+ end
255
+
256
+ def __aop_class
257
+ self.class
258
+ end
259
+
260
+ def __aop_mangle(method)
261
+ "__aop__singleton_#{method}"
262
+ end
263
+
264
+ def __aop_alias(new, old, private = true)
265
+ # Install in class since otherwise the non-dispatcher version of the class version of the method
266
+ # gets locked away, and so if we wrap a singleton before wrapping its class,
267
+ # later wrapping the class has no effect on that singleton /AB
268
+ # of course, this depends on exactly what behavior we want for inheritance... Decide for future release...
269
+ unless self.class.respond_to?("__aop_init")
270
+ self.class.extend AspectSupport
271
+ self.class.__aop_init
272
+ end
273
+ self.class.__aop_install_dispatcher(old)
274
+ eval "class << self; alias_method '#{new}', '#{old}'; end;"
275
+ eval "class << self; private '#{new}'; end" if private
276
+ end
277
+
278
+ def __aop_private_methods
279
+ private_methods
280
+ end
281
+
282
+ def __aop_eval(text)
283
+ instance_eval text
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ # NOTE! Somewhat experimental so API will likely change on this method!
290
+ def wrap_classes(aspect, pre, post, classes, *methods)
291
+ classes = all_classes(classes) if classes.kind_of?(Regexp)
292
+ classes.each {|klass| aspect.wrap(klass, pre, post, *methods)}
293
+ end
294
+ module_function :wrap_classes
295
+
296
+ # TODO: Speed this up by recursing from Object.constants instead of sifting
297
+ # through all object in the ObjectSpace (might be slow if many objects).
298
+ # Is there a still faster/better way?
299
+ # NOTE! Somewhat experimental so API will likely change on this method!
300
+ def all_classes(regexp = /^.+$/)
301
+ classes = []
302
+ ObjectSpace.each_object(Class) do |c|
303
+ classes.push c if c.inspect =~ regexp
304
+ end
305
+ classes
306
+ end
307
+ end
308
+
309
+
@@ -0,0 +1,249 @@
1
+ class Logger < Aspect
2
+ def initialize(io = STDOUT)
3
+ @io = io
4
+ end
5
+
6
+ def _tick; "#{Time.now.strftime('%Y-%m-%d %X')}"; end
7
+
8
+ def enter(method, object, exitstatus, *args)
9
+ @io.puts "enter: #{_tick} #{object.class}##{method}: called with #{args.inspect}"
10
+ end
11
+
12
+ def exit(method, object, exitstatus, *args)
13
+ @io.print "exit: #{_tick} #{object.class}##{method}: exited "
14
+ if exitstatus.kind_of?(Array)
15
+ @io.puts "normally returning #{exitstatus[0].inspect}"
16
+ elsif exitstatus == true
17
+ @io.puts "with exception '#{$!}'"
18
+ else
19
+ @io.puts 'normally'
20
+ end
21
+ end
22
+
23
+ def senter(method, object, exitstatus, *args)
24
+ @io.puts "senter: #{_tick} Entering #{object.object_id}##{method}"
25
+ end
26
+
27
+ def sexit(method, object, exitstatus, *args)
28
+ @io.puts "sexit: #{_tick} Exiting #{object.object_id}##{method}"
29
+ end
30
+ end
31
+
32
+ class Counter < Aspect
33
+ attr_reader :incrementer_cnt
34
+
35
+ def initialize
36
+ @counters = {}
37
+ end
38
+
39
+ def wrap(target, *methods)
40
+ super(target, :inc, nil, *methods)
41
+ end
42
+
43
+ def unwrap(target, *methods)
44
+ super(target, :inc, nil, *methods)
45
+ end
46
+
47
+ def [](object, method)
48
+ method = method.id2name if method.kind_of?(Symbol)
49
+ @counters[object][method]
50
+ end
51
+
52
+ private
53
+ def inc(method, object, *args)
54
+ begin
55
+ @counters[object][method] += 1
56
+ rescue NameError
57
+ @counters[object] = {} unless @counters[object]
58
+ @counters[object][method] = 1
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+
65
+
66
+ class TestHello
67
+ attr_reader :tt
68
+
69
+ def say(hello)
70
+ return hello + " world"
71
+ end
72
+
73
+ def inspect; object_id.inspect; end
74
+
75
+ def sayz(*args)
76
+ raise NotImplementedError, 'NYI!'
77
+ end
78
+ end
79
+
80
+ class HistoryStdOut
81
+ attr_reader :history
82
+ def initialize
83
+ @history = Array.new
84
+ end
85
+ def puts(str)
86
+ if @last_was_print
87
+ @history[-1] += str
88
+ else
89
+ @history.push str
90
+ end
91
+ @last_was_print = false
92
+ end
93
+ def print(str)
94
+ @history.push str
95
+ @last_was_print = true
96
+ end
97
+ def last; @history.last; end
98
+ end
99
+
100
+ class CodeWrapper < Aspect
101
+ alias old_wrap wrap
102
+ def wrap(target, *methods)
103
+ wrap_with_code(target, "p 'pre'", "p 'post'", *methods)
104
+ end
105
+ def code_wrap(target, *methods)
106
+ wrap_with_code(target,"CodeWrapper.enter","CodeWrapper.leave", *methods)
107
+ end
108
+ @@counter = 0
109
+ def CodeWrapper.enter; @@counter += 1; end
110
+ def CodeWrapper.leave; @@counter += 1; end
111
+ def CodeWrapper.counter; @@counter; end
112
+ end
113
+
114
+ class T2; def m; end; end
115
+
116
+ class TestAspectR < Test::Unit::TestCase
117
+ # Just so that we can use previously written tests. Clean up when there is
118
+ # time!
119
+ @@logger = Logger.new(@@hso = HistoryStdOut.new)
120
+ @@counter = Counter.new
121
+ @@t, @@u = TestHello.new, TestHello.new
122
+
123
+ def setup
124
+ end
125
+
126
+ def test_01_wrap
127
+ @@counter.wrap(TestHello, :say) # Wrap counter on TestHello#say
128
+ assert_equal('hello world', @@t.say('hello'))
129
+ assert_equal('hello world', @@u.say('hello'))
130
+ assert_equal(1, @@counter[@@t, :say])
131
+ assert_equal(1, @@counter[@@u, :say])
132
+ @@t.say("my")
133
+ assert_equal(2, @@counter[@@t, :say])
134
+ assert_equal(1, @@counter[@@u, :say])
135
+ end
136
+
137
+ def test_02_class_wrapping
138
+ @@logger.wrap(TestHello, :enter, :exit, /sa/)
139
+ @@t.say('t: hello')
140
+ assert_match(/enter.*TestHello#say: called with \[.*\]/, @@hso.history[-2] )
141
+ assert_match(/exit.*TestHello#say: exited/, @@hso.history[-1] )
142
+ @@u.say('u: hello')
143
+ assert_match(/enter.*TestHello#say: called with \[.*\]/,@@hso.history[-2])
144
+ assert_match(/exit.*TestHello#say: exited/,@@hso.history[-1])
145
+ end
146
+
147
+ def test_03_singleton_wrapping
148
+ @@logger.wrap(@@t, :senter, :sexit, :say)
149
+ @@t.say('t: hello')
150
+ assert_match( /senter/, @@hso.history[-4])
151
+ assert_match( /enter/, @@hso.history[-3])
152
+ assert_match( /exit/, @@hso.history[-2])
153
+ assert_match(/sexit/, @@hso.history[-1])
154
+ @@u.say('u: hello')
155
+ assert_match( /enter/, @@hso.history[-2])
156
+ assert_match(/exit/, @@hso.history[-1])
157
+ end
158
+
159
+ def test_04_unwrapping
160
+ @@logger.unwrap(TestHello, :enter, :exit, /sa/)
161
+ @@t.say('t: hello') # senter and sexit should still be there...
162
+ assert_match(/senter/, @@hso.history[-2])
163
+ assert_match(/sexit/, @@hso.history[-1])
164
+ l = @@hso.history.length
165
+ @@u.say('u: hello')
166
+ assert_equal(l, @@hso.history.length) # Nothing printed!
167
+ end
168
+
169
+ def test_05_dynamically_changing_advice
170
+ Logger.class_eval <<-'EOC'
171
+ def senter(*args)
172
+ @io.puts "senter version 2.0"
173
+ end
174
+ EOC
175
+ @@t.say('t: hello')
176
+ assert_match(/senter version 2\.0/, @@hso.history[-2])
177
+ end
178
+
179
+ def test_06_partial_unwrap
180
+ @@logger.unwrap(@@t, :senter, nil, :say)
181
+ l = @@hso.history.length
182
+ @@t.say('t: hello') # Should use sexit but not senter
183
+ assert_equal(l+1, @@hso.history.length)
184
+ assert_match(/sexit/, @@hso.history[-1])
185
+ end
186
+
187
+ def test_07_exception_in_method
188
+ @@logger.wrap(@@t, nil, :exit, :sayz)
189
+ l = @@hso.history.length
190
+ begin
191
+ @@t.sayz 1
192
+ rescue Exception; end
193
+ assert_equal(l+1, @@hso.history.length)
194
+ assert_match(/exit/, @@hso.history[-1])
195
+ end
196
+
197
+ def test_08_counter
198
+ assert_equal(7, @@counter[@@t, :say])
199
+ @@counter.unwrap(TestHello, :say)
200
+ @@t.say('t: hello')
201
+ assert_equal(7, @@counter[@@t, :say])
202
+ end
203
+
204
+ def test_09_transparency
205
+ assert_equal(1, TestHello.instance_method(:say).arity)
206
+ assert_equal(-1, TestHello.instance_method(:sayz).arity)
207
+
208
+ @@counter.wrap(TestHello, :tt)
209
+ assert_equal(0, TestHello.instance_method(:tt).arity)
210
+ end
211
+
212
+ def test_10_special_methods
213
+ @@logger.wrap(Array, :enter, nil, :[], :[]=, :initialize)
214
+ a = Array.new(2)
215
+ assert_match(/enter/, @@hso.history.last); l = @@hso.history.length
216
+ a[1]
217
+ assert_equal(l+1, @@hso.history.length)
218
+ assert_match(/enter/, @@hso.history.last); l = @@hso.history.length
219
+ a[1] = 10
220
+ assert_equal(l+1, @@hso.history.length)
221
+ assert_match(/enter/, @@hso.history.last); l = @@hso.history.length
222
+ @@logger.unwrap(Array, :[], :[]=, :initialize)
223
+ end
224
+
225
+ def test_11_code_wrapping
226
+ assert_equal(0, CodeWrapper.counter)
227
+ CodeWrapper.new.code_wrap(T2, :m)
228
+ T2.new.m
229
+ assert_equal(2, CodeWrapper.counter)
230
+ end
231
+ end
232
+
233
+
234
+ class T3; def m; end; def m2; end; end
235
+
236
+ def time(n = 100_000, &block)
237
+ start = Process.times.utime
238
+ n.times{block.call}
239
+ Process.times.utime - start
240
+ end
241
+
242
+ def test_overhead
243
+ puts "\nOverhead"
244
+ t = T3.new
245
+ puts "Time for unwrapped: #{tuw = time(50_000) {t.m}}"
246
+ CodeWrapper.new.code_wrap(T3, :m)
247
+ puts "Time for code-wrapped: #{tw = time(50_000) {t.m}}"
248
+ puts "A slowdown of #{tw/tuw}"
249
+ end
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+
3
+ require "#{__FILE__[/^tests/]?'.' : '..'}/lib/aspectr.rb"
4
+ include AspectR
5
+
6
+ Dir["#{__FILE__[/^tests/]?'./tests' : '.'}/atest*_*.rb"].each { |f| require f }
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: aspectr
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.3.7
7
+ date: 2007-03-19 00:00:00 +01:00
8
+ summary: Aspect-oriented programming concepts to Ruby.
9
+ require_paths:
10
+ - lib
11
+ email: bunny@kultkiste.de
12
+ homepage: http://aspectr.rubyforge.org/
13
+ rubyforge_project: aspectr
14
+ description: Essentially it allows you to wrap code around existing methods in your classes.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors: []
30
+
31
+ files:
32
+ - lib/aspectr.rb
33
+ test_files:
34
+ - tests/atest01_aspectr.rb
35
+ - tests/runtests.rb
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ requirements: []
45
+
46
+ dependencies: []
47
+