method_proxy 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,178 @@
1
+ ### WHAT'S THIS?
2
+
3
+ 'method_proxy' gem allows to 'tap' into instance method or class method calls
4
+ on objects of specific class or on specific classes.
5
+
6
+
7
+ ### API
8
+
9
+ * `MethodProxy.proxy_instance_method(SomeClass, :some_instance_method, &block)`
10
+
11
+ Replaces original instance method SomeClass#some_instance_method with method
12
+ generated from supplied block. Supplied block should expect following
13
+ arguments:
14
+ 1) object on which the call is made;
15
+ 2) original method bound to the object on which call is made;
16
+ *args - arguments of original method call;
17
+ &block - block passed to the original method call.
18
+
19
+ An important thing to remember, is the &block should not use 'return <result>'
20
+ and should use 'next <result>' construct instead - as 'return' will result in
21
+ exception:
22
+
23
+ LocalJumpError: unexpected return
24
+
25
+
26
+ * `MethodProxy.unproxy_instance_method(SomeClass, :some_instance_method)`
27
+
28
+ Restore original instance method SomeClass#some_instance_method.
29
+
30
+
31
+ * `MethodProxy.proxy_class_method(SomeClass, :some_class_method, &block)`
32
+
33
+ Replaces original class method with method generated from supplied block.
34
+ Supplied block should expect following arguments:
35
+ 1) class on which the call is made;
36
+ 2) original method bound to that class;
37
+ *args - arguments of original method call;
38
+ &block - block passed to the original method call.
39
+
40
+ Just like in case of proxy_instance_method, &block should use 'next <result>'
41
+ construct instead of 'return <result>' or LocalJumpError will be raised.
42
+
43
+
44
+ * `MethodProxy.unproxy_class_method(SomeClass, :some_class_method)`
45
+ Restore original class method SomeClass.some_class_method.
46
+
47
+
48
+ * `MethodProxy.classes_with_proxied_instance_methods`
49
+
50
+ Returns Array of classes that have at least one instance method tapped into by
51
+ MethodProxy.
52
+
53
+
54
+ * `MethodProxy.proxied_instance_methods_for(klass)`
55
+
56
+ Returns Array of instance method names for class klass that have been altered by
57
+ MethodProxy.
58
+
59
+
60
+ * `MethodProxy.classes_with_proxied_class_methods`
61
+
62
+ Returns Array of classes that have at least one class method tapped into by
63
+ MethodProxy.
64
+
65
+
66
+ * `MethodProxy.proxied_class_methods_for(klass)`
67
+
68
+ Returns Array of class method names for class klass that have been altered by
69
+ MethodProxy.
70
+
71
+
72
+ ### EXAMPLES
73
+
74
+ **Example 1.** Non-intrusive debugging. Here's a way to dynamically "inject" call
75
+ to debugger before an instance method of interest UsersController#create, with
76
+ the help of MethodProxy:
77
+
78
+ require 'method_proxy'
79
+ require 'ruby-debug'
80
+ MethodProxy.proxy_instance_method(UsersController, :create) do |users_controller_obj, orig_create_meth, *args|
81
+ debugger
82
+ res = orig_create_meth.call *args
83
+ next res
84
+ end
85
+
86
+
87
+ **Example 1b.** Debug on exception:
88
+
89
+ require 'method_proxy'
90
+ require 'ruby-debug'
91
+ MethodProxy.proxy_instance_method(UsersController, :create) do |users_controller_obj, orig_create_meth, *args|
92
+ begin
93
+ res = orig_create_meth.call *args
94
+ rescue Exception => e
95
+ debugger
96
+ end
97
+ next res
98
+ end
99
+
100
+
101
+ **Example 2.** Automatic recording of system actions during QA process. With the help of MethodProxy one can
102
+ arrange interception of actions of interest (say, those that change database state), and storing of the
103
+ actions' parameters:
104
+
105
+ tbd = {
106
+ UsersController => [:create, :update, :delete],
107
+ PostsController => [:create, :update, :delete]
108
+ }
109
+
110
+ tbd.each_pair do |cntrlr, actns|
111
+ actns.each do |actn|
112
+ MethodProxy.proxy_instance_method(cntrlr, actn) do |controller, meth, *args|
113
+ record_hash = Hash.new
114
+ record_hash[:controller] = controller.class.name.to_sym
115
+ record_hash[:action] = actn
116
+ record_hash[:http_meth] = controller.request.method
117
+ record_hash[:params] = controller.request.params
118
+
119
+ store_action_record(record_hash) # store the information - implement according to your needs!
120
+
121
+ meth.call *args
122
+ end
123
+ end
124
+ end
125
+
126
+
127
+ Later, the resulting "records" can be "replayed" in automated tests.
128
+
129
+
130
+ ### CAVEATS
131
+
132
+ There is a number of known issues:
133
+
134
+ - have to remember to use "next <result>" instead of "return <result>" syntax;
135
+ - if one needs to tap into methods that are dynamically created, e.g.
136
+ 'find_user_by_id' created on-the-fly with the help of 'method_missing' in
137
+ Rails, the code should make sure that method has been defined before attempting
138
+ to tap.
139
+
140
+
141
+ ### RELATION TO AOP
142
+
143
+ "Tapping" into method calls the way 'method_proxy' does is similar to creating
144
+ joint points in Aspect-Oriented Programming, and the same task can be
145
+ accomplished with AOP frameworks like Aquarium. 'method_proxy' however is
146
+ intended as a simple tool focused on its task - with no attempt to align it
147
+ with AOP concepts and terminology.
148
+
149
+
150
+ ### LICENSE
151
+
152
+ Usage, distribution or modification, for both commercial or non-profit purposes,
153
+ are not limited whatsoever.
154
+
155
+
156
+ ### DISCLAIMER
157
+
158
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
159
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
160
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
161
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
162
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
163
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
164
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
165
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
166
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
167
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
168
+
169
+
170
+ ### CREDITS
171
+ Special thanks for their invaluable feedback, advice, suggestions and fixes to:
172
+ wtaysom, adehvadeh, shaokun and rainchen.
173
+
174
+
175
+ ### PROJECT ON THE WEB
176
+
177
+ The project is hosted on GitHub:
178
+ https://github.com/kudelabs/method_proxy/
@@ -0,0 +1,218 @@
1
+ require 'thread' if RUBY_VERSION <"1.9"
2
+
3
+ class MethodProxyException < Exception
4
+
5
+ end
6
+
7
+ class MethodProxy
8
+ ##################################### CLASS VARIABLES #############################################
9
+
10
+ @@mx = Mutex.new
11
+ @@proxied_instance_methods = {}
12
+ @@proxied_class_methods = {}
13
+ @@tmp_binding = nil
14
+
15
+ #################################### SOME HELPER METHODS ##########################################
16
+ def self.classes_with_proxied_instance_methods
17
+ @@mx.synchronize do
18
+ return @@proxied_instance_methods.keys.collect{|k| Class.const_get(k)}
19
+ end
20
+ end
21
+
22
+ def self.proxied_instance_methods_for(klass)
23
+ raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
24
+ @@mx.synchronize do
25
+ meth_hash_for_klass = @@proxied_instance_methods[klass.name.to_sym]
26
+ return [] if !meth_hash_for_klass || meth_hash_for_klass.empty?
27
+ return meth_hash_for_klass.keys
28
+ end
29
+ end
30
+
31
+
32
+ def self.classes_with_proxied_class_methods
33
+ @@mx.synchronize do
34
+ return @@proxied_class_methods.keys.collect{|k| Class.const_get(k)}
35
+ end
36
+ end
37
+
38
+ def self.proxied_class_methods_for(klass)
39
+ raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
40
+ @@mx.synchronize do
41
+ meth_hash_for_klass = @@proxied_class_methods[klass.name.to_sym]
42
+ return [] if !meth_hash_for_klass || meth_hash_for_klass.empty?
43
+ return meth_hash_for_klass.keys
44
+ end
45
+ end
46
+
47
+
48
+ #### WARNING: NON-THREAD-SAFE methods for internal use; generally, they should not be called by ####
49
+ #### any external code ####
50
+ def self.register_original_instance_method(klass, meth_name, meth_obj)
51
+ @@proxied_instance_methods[klass.name.to_sym][meth_name] = meth_obj
52
+ end
53
+
54
+ def self.original_instance_method(klass, meth_name)
55
+ @@proxied_instance_methods[klass.name.to_sym][meth_name]
56
+ end
57
+
58
+ def self.tmp_binding
59
+ @@tmp_binding
60
+ end
61
+
62
+ protected
63
+ def self.capture_tmp_binding!(bndg)
64
+ @@tmp_binding = bndg
65
+ end
66
+
67
+ def self.reset_tmp_binding!
68
+ @@tmp_binding = nil
69
+ end
70
+
71
+ ####################################### MAIN STUFF #################################################
72
+ public
73
+
74
+ # "Tap" into instance method calls - subvert original method with the supplied block; preserve
75
+ # reference to the original method so that it can still be called or restored later on.
76
+ #
77
+ # Common idiom:
78
+ # MethodProxy.proxy_instance_method(SomeClass, :some_instance_method) do |obj, original_instance_meth, *args, &block|
79
+ #
80
+ # # do stuff before calling original method
81
+ # ... ... ...
82
+ #
83
+ # # call the original method (already bound to object obj), with supplied arguments
84
+ # result = original_instance_meth.call(*args, &block)
85
+ #
86
+ # # do stuff after calling original method
87
+ # ... ... ...
88
+ #
89
+ # # return the actual return value
90
+ # result
91
+ # end
92
+ def self.proxy_instance_method(klass, meth, &block)
93
+ raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
94
+ raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
95
+ raise "must supply block argument" unless block_given?
96
+
97
+ proc = Proc.new(&block)
98
+
99
+ @@mx.synchronize do
100
+ @@proxied_instance_methods[klass.name.to_sym] ||= {}
101
+ if @@proxied_instance_methods[klass.name.to_sym][meth]
102
+ raise ::MethodProxyException, "The method has already been proxied"
103
+ end
104
+
105
+ klass.class_eval do
106
+
107
+ MethodProxy.register_original_instance_method(klass, meth, instance_method(meth))
108
+
109
+ undef_method(meth)
110
+
111
+ define_method meth do |*args, &blk|
112
+ ret = proc.call(self, MethodProxy.original_instance_method(klass, meth).bind(self), *args, &blk)
113
+ return ret
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # Restore the original instance method for objects of class klass
120
+ def self.unproxy_instance_method(klass, meth)
121
+ raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
122
+ raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
123
+
124
+ @@mx.synchronize do
125
+ return unless @@proxied_instance_methods[klass.name.to_sym][meth].is_a?(UnboundMethod) # pass-through rather than raise
126
+ proc = @@proxied_instance_methods[klass.name.to_sym][meth]
127
+
128
+ klass.class_eval{ define_method(meth, proc) }
129
+
130
+ # clean up storage
131
+ @@proxied_instance_methods[klass.name.to_sym].delete(meth)
132
+ remaining_proxied_instance_methods = @@proxied_instance_methods[klass.name.to_sym]
133
+ @@proxied_instance_methods.delete(klass.name.to_sym) if remaining_proxied_instance_methods.empty?
134
+ end
135
+ end
136
+
137
+ # "Tap" into class method calls - subvert original method with the supplied block; preserve
138
+ # reference to the original method so that it can still be called or restored later on.
139
+ #
140
+ # Common idiom:
141
+ # MethodProxy.proxy_class_method(SomeClass, :some_class_method) do |klass, original_class_meth, *args, &block|
142
+ #
143
+ # # do stuff before calling original method
144
+ # ... ... ...
145
+ #
146
+ # # call original method (already bound to SomeClass), with supplied arguments
147
+ # result = original_class_meth.call(*args, &block)
148
+ #
149
+ # # do stuff after calling original method
150
+ # ... ... ...
151
+ #
152
+ # # return the actual return value
153
+ # result
154
+ # end
155
+ def self.proxy_class_method klass, meth, &block
156
+ raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
157
+ raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
158
+ raise "must supply block argument" unless block_given?
159
+
160
+ @@mx.synchronize do
161
+ proc = Proc.new(&block)
162
+
163
+ capture_tmp_binding! binding
164
+
165
+ @@proxied_class_methods[klass.name.to_sym] ||= {}
166
+
167
+ class << klass
168
+
169
+ klass, meth, proc = eval "[klass, meth, proc]", MethodProxy.tmp_binding
170
+
171
+ self.instance_eval do
172
+ if @@proxied_class_methods[klass.name.to_sym][meth]
173
+ raise ::MethodProxyException, "The method has already been proxied"
174
+ end
175
+ @@proxied_class_methods[klass.name.to_sym][meth] = instance_method meth
176
+ end
177
+
178
+ remove_method meth
179
+
180
+ self.instance_eval do
181
+ define_method(meth) do |*args, &blk|
182
+ ret = proc.call(self, @@proxied_class_methods[klass.name.to_sym][meth].bind(self), *args, &blk)
183
+ return ret
184
+ end
185
+ end
186
+ end
187
+
188
+ reset_tmp_binding!
189
+ end
190
+ end
191
+
192
+ # Restore the original class method for klass
193
+ def self.unproxy_class_method klass, meth
194
+ raise "klass argument must be a Class" unless klass.is_a?(Class) || klass.is_a?(Module)
195
+ raise "method argument must be a Symbol" unless meth.is_a?(Symbol)
196
+
197
+ @@mx.synchronize do
198
+ return unless (class_entries = @@proxied_class_methods[klass.name.to_sym])
199
+ return unless (orig_unbound_meth = class_entries[meth])
200
+
201
+ capture_tmp_binding! binding
202
+
203
+ class << klass
204
+ meth, orig_unbound_meth = eval "[meth, orig_unbound_meth]", MethodProxy.tmp_binding
205
+ self.instance_eval do
206
+ define_method meth, orig_unbound_meth
207
+ end
208
+ end
209
+
210
+ reset_tmp_binding!
211
+
212
+ # clean up storage
213
+ @@proxied_class_methods[klass.name.to_sym].delete(meth)
214
+ remaining_proxied_class_methods = @@proxied_class_methods[klass.name.to_sym]
215
+ @@proxied_class_methods.delete(klass.name.to_sym) if remaining_proxied_class_methods.empty?
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,126 @@
1
+ class GuineaPig
2
+ def gp_instance_method a, b
3
+ return "#{a}-#{b}"
4
+ end
5
+
6
+ def gp_instance_method_w_block a, b, &block
7
+ interm = "*#{a}*#{b}*"
8
+ res = yield interm
9
+ res
10
+ end
11
+
12
+ def self.gp_class_method c, d
13
+ return "#{c}$#{d}"
14
+ end
15
+
16
+ def self.gp_class_method_w_block c, d, &block
17
+ interm = "-=#{c}O#{d}=-"
18
+ res = yield interm
19
+ res
20
+ end
21
+ end
22
+
23
+ require File.join(File.expand_path(File.dirname(__FILE__)), "../lib/method_proxy")
24
+ require "test/unit"
25
+
26
+ class MethodProxyTest < Test::Unit::TestCase
27
+ def test_proxy_unproxy_instance_method
28
+ gpig = GuineaPig.new
29
+ assert_equal "2-3", gpig.gp_instance_method(2, 3)
30
+ assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
31
+ assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
32
+
33
+ MethodProxy.proxy_instance_method(GuineaPig, :gp_instance_method) do |obj, meth, a, b|
34
+ res = meth.call a, b
35
+ next "<#{res}>" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
36
+ end
37
+ assert_equal "<2-3>", gpig.gp_instance_method(2, 3)
38
+ assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_instance_methods)
39
+ assert_equal [:gp_instance_method], MethodProxy.proxied_instance_methods_for(GuineaPig)
40
+
41
+ MethodProxy.unproxy_instance_method(GuineaPig, :gp_instance_method)
42
+ assert_equal "2-3", gpig.gp_instance_method(2, 3)
43
+ assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
44
+ assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
45
+ end
46
+
47
+ def test_proxy_unproxy_instance_method_w_block
48
+ gpig = GuineaPig.new
49
+ before_res = gpig.gp_instance_method_w_block(6, 7) do |interm|
50
+ "#{interm}<-->#{interm}"
51
+ end
52
+ assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
53
+ assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
54
+ assert_equal "*6*7*<-->*6*7*", before_res
55
+
56
+ MethodProxy.proxy_instance_method(GuineaPig, :gp_instance_method_w_block) do |obj, meth, a, b, &block|
57
+ res = meth.call a, b, &block
58
+ next "!!!__#{res}__!!!" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
59
+ end
60
+
61
+ after_res = gpig.gp_instance_method_w_block(6, 7) do |interm|
62
+ "#{interm}<-->#{interm}"
63
+ end
64
+
65
+ assert_equal "!!!__*6*7*<-->*6*7*__!!!", after_res
66
+ assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_instance_methods)
67
+ assert_equal [:gp_instance_method_w_block], MethodProxy.proxied_instance_methods_for(GuineaPig)
68
+
69
+ MethodProxy.unproxy_instance_method(GuineaPig, :gp_instance_method_w_block)
70
+ after_un_res = gpig.gp_instance_method_w_block(6, 7) do |interm|
71
+ "#{interm}<-->#{interm}"
72
+ end
73
+ assert_equal before_res, after_un_res
74
+ assert [].eql?(MethodProxy.classes_with_proxied_instance_methods)
75
+ assert_equal [], MethodProxy.proxied_instance_methods_for(GuineaPig)
76
+ end
77
+
78
+ def test_proxy_unproxy_class_method
79
+ assert_equal "4$5", GuineaPig.gp_class_method(4, 5)
80
+ assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
81
+ assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
82
+
83
+ MethodProxy.proxy_class_method(GuineaPig, :gp_class_method) do |obj, meth, c, d|
84
+ res = meth.call c, d
85
+ next "[#{res}]" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
86
+ end
87
+ assert_equal "[4$5]", GuineaPig.gp_class_method(4, 5)
88
+ assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_class_methods)
89
+ assert_equal [:gp_class_method], MethodProxy.proxied_class_methods_for(GuineaPig)
90
+
91
+ MethodProxy.unproxy_class_method(GuineaPig, :gp_class_method)
92
+ assert_equal "4$5", GuineaPig.gp_class_method(4, 5)
93
+ assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
94
+ assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
95
+ end
96
+
97
+ def test_proxy_unproxy_class_method_w_block
98
+ before_res = GuineaPig.gp_class_method_w_block(8, 9) do |interm|
99
+ "#{interm}>--<#{interm}"
100
+ end
101
+ assert_equal "-=8O9=->--<-=8O9=-", before_res
102
+ assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
103
+ assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
104
+
105
+ MethodProxy.proxy_class_method(GuineaPig, :gp_class_method_w_block) do |obj, meth, a, b, &block|
106
+ res = meth.call a, b, &block
107
+ next "???__#{res}__???" # within Proc's should be 'next' instead of 'return' -- in order to avoid returning from the caller method
108
+ end
109
+
110
+ after_res = GuineaPig.gp_class_method_w_block(8, 9) do |interm|
111
+ "#{interm}>--<#{interm}"
112
+ end
113
+
114
+ assert_equal "???__-=8O9=->--<-=8O9=-__???", after_res
115
+ assert [GuineaPig].eql?(MethodProxy.classes_with_proxied_class_methods)
116
+ assert_equal [:gp_class_method_w_block], MethodProxy.proxied_class_methods_for(GuineaPig)
117
+
118
+ MethodProxy.unproxy_class_method(GuineaPig, :gp_class_method_w_block)
119
+ after_un_res = GuineaPig.gp_class_method_w_block(8, 9) do |interm|
120
+ "#{interm}>--<#{interm}"
121
+ end
122
+ assert_equal before_res, after_un_res
123
+ assert [].eql?(MethodProxy.classes_with_proxied_class_methods)
124
+ assert_equal [], MethodProxy.proxied_class_methods_for(GuineaPig)
125
+ end
126
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: method_proxy
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 1
9
+ version: 0.2.1
10
+ platform: ruby
11
+ authors:
12
+ - Jevgenij Solovjov
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-12-01 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: |-
22
+ Provides convenient means of "taping" into instance or class method calls. The intercepting
23
+ code is provided with reference to the object, reference to the original method and list of arguments.
24
+ email: jevgenij@kudelabs.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - lib/method_proxy.rb
33
+ - test/method_proxy_test.rb
34
+ - README.markdown
35
+ has_rdoc: true
36
+ homepage: https://github.com/kudelabs/method_proxy
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ segments:
50
+ - 0
51
+ version: "0"
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.7
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Ruby method call interception tool.
67
+ test_files: []
68
+