cheap_advice 1.0.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/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +70 -0
- data/Rakefile +79 -0
- data/VERSION +1 -0
- data/cheap_advice.slides.textile +257 -0
- data/example/ex01.rb +51 -0
- data/example/ex02-trace-0.yml +17 -0
- data/example/ex02-trace-1.yml +17 -0
- data/example/ex02-trace-2.yml +23 -0
- data/example/ex02-trace-4.yml +20 -0
- data/example/ex02-trace-5.yml +22 -0
- data/example/ex02-trace-6.yml +25 -0
- data/example/ex02.rb +99 -0
- data/lib/cheap_advice.rb +511 -0
- data/lib/cheap_advice/configuration.rb +217 -0
- data/lib/cheap_advice/trace.rb +228 -0
- data/spec/cheap_advice_spec.rb +342 -0
- data/spec/spec_helper.rb +12 -0
- metadata +192 -0
data/example/ex01.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
$: << File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'cheap_advice'
|
4
|
+
require 'pp'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
class MyClass
|
8
|
+
def foo
|
9
|
+
42
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MyOtherClass
|
14
|
+
def bar
|
15
|
+
43
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
a = MyClass.new
|
20
|
+
b = MyOtherClass.new
|
21
|
+
|
22
|
+
trace_advice = CheapAdvice.new(:around) do | ar, body |
|
23
|
+
ar.advice[:log] << "#{Time.now.iso8601(6)} " <<
|
24
|
+
"#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id}\n"
|
25
|
+
body.call
|
26
|
+
ar.advice[:log] << "#{Time.now.iso8601(6)} " <<
|
27
|
+
"#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id} " <<
|
28
|
+
"=> #{ar.result.inspect}\n"
|
29
|
+
end
|
30
|
+
trace_advice[:log] = $stderr # File.open("trace.log", "a+")
|
31
|
+
|
32
|
+
puts "\nWithout advice:"
|
33
|
+
pp a.foo
|
34
|
+
pp b.bar
|
35
|
+
|
36
|
+
puts "\nWith advice enabled:"
|
37
|
+
trace_advice.advise!(MyClass, :foo)
|
38
|
+
trace_advice.advise!(MyOtherClass, :bar)
|
39
|
+
pp a.foo
|
40
|
+
pp b.bar
|
41
|
+
|
42
|
+
puts "\nWith advice disabled:"
|
43
|
+
trace_advice.disable!
|
44
|
+
pp a.foo
|
45
|
+
pp b.bar
|
46
|
+
|
47
|
+
puts "\nWith advice re-enabled:"
|
48
|
+
trace_advice.enable!
|
49
|
+
pp a.foo
|
50
|
+
pp b.bar
|
51
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
:advice:
|
3
|
+
~:
|
4
|
+
:enabled: true
|
5
|
+
:options:
|
6
|
+
:trace:
|
7
|
+
:logger:
|
8
|
+
:name: :default
|
9
|
+
:default:
|
10
|
+
:log_prefix: "YO "
|
11
|
+
|
12
|
+
MyClass:
|
13
|
+
:advice: trace
|
14
|
+
|
15
|
+
MyClass#foo: ~
|
16
|
+
MyClass#bar:
|
17
|
+
:options:
|
18
|
+
:trace:
|
19
|
+
:logger:
|
20
|
+
:name: :alternate
|
21
|
+
:log_after: false
|
22
|
+
:log_result: false
|
23
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
:advice:
|
3
|
+
~:
|
4
|
+
:enabled: false
|
5
|
+
:options:
|
6
|
+
:trace:
|
7
|
+
:logger:
|
8
|
+
:name: :default
|
9
|
+
|
10
|
+
MyClass:
|
11
|
+
:advice: trace
|
12
|
+
|
13
|
+
MyClass#foo:
|
14
|
+
:enabled: true
|
15
|
+
:options:
|
16
|
+
:trace:
|
17
|
+
:log_before: false
|
18
|
+
:log_after: true
|
19
|
+
:log_result: true
|
20
|
+
|
21
|
+
MyClass#bar: false
|
22
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
:advice:
|
3
|
+
~:
|
4
|
+
:enabled: false
|
5
|
+
:options:
|
6
|
+
:trace:
|
7
|
+
:logger:
|
8
|
+
:name: :default
|
9
|
+
|
10
|
+
MyClass:
|
11
|
+
:advice: trace
|
12
|
+
|
13
|
+
MyClass#foo:
|
14
|
+
:enabled: true
|
15
|
+
:options:
|
16
|
+
:trace:
|
17
|
+
:log_before: true
|
18
|
+
:log_args: true
|
19
|
+
:log_after: true
|
20
|
+
:log_result: false
|
21
|
+
|
22
|
+
MyClass#bar: false
|
23
|
+
|
24
|
+
MyClass#raise_error: true
|
25
|
+
|
data/example/ex02.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
$: << File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'cheap_advice'
|
4
|
+
require 'cheap_advice/configuration'
|
5
|
+
require 'cheap_advice/trace'
|
6
|
+
require 'yaml'
|
7
|
+
require 'benchmark'
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
=begin
|
11
|
+
gem 'ruby-debug'
|
12
|
+
require 'ruby-debug'
|
13
|
+
=end
|
14
|
+
|
15
|
+
##################################################
|
16
|
+
# Target
|
17
|
+
|
18
|
+
class MyClass
|
19
|
+
def foo(arg)
|
20
|
+
42 + arg
|
21
|
+
end
|
22
|
+
def bar(arg)
|
23
|
+
24 + arg
|
24
|
+
end
|
25
|
+
def raise_error(arg)
|
26
|
+
raise ArgumentError, "#{arg.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class MySubclass < MyClass
|
31
|
+
end
|
32
|
+
|
33
|
+
trace_advice = nil
|
34
|
+
trace_config = nil
|
35
|
+
|
36
|
+
Benchmark.bm(40) do | bm |
|
37
|
+
|
38
|
+
##################################################
|
39
|
+
# Advice
|
40
|
+
|
41
|
+
bm.report("trace_advice setup") do
|
42
|
+
trace_advice = CheapAdvice::Trace.new
|
43
|
+
|
44
|
+
# Configure Trace loggers by name.
|
45
|
+
trace_advice.logger[:default] = {
|
46
|
+
:target => File.open(File.expand_path("../ex02-default.log", __FILE__), "w"),
|
47
|
+
}
|
48
|
+
|
49
|
+
trace_advice.logger[:alternate] = {
|
50
|
+
:target => File.open(File.expand_path("../ex02-alternate.log", __FILE__), "w"),
|
51
|
+
:formatter => CheapAdvice::Trace::YamlFormatter
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
##################################################
|
56
|
+
# Advice Configuration
|
57
|
+
|
58
|
+
|
59
|
+
bm.report("trace_config setup") do
|
60
|
+
# Register Trace advice with configuration.
|
61
|
+
trace_config = CheapAdvice::Configuration.new
|
62
|
+
trace_config.advice[:trace] = trace_advice
|
63
|
+
end
|
64
|
+
|
65
|
+
config_yml = File.expand_path("../ex02-trace-*.yml", __FILE__)
|
66
|
+
|
67
|
+
Dir[config_yml].sort.each do | config_yml |
|
68
|
+
# Configure.
|
69
|
+
config_hash = YAML.load_file(config_yml)
|
70
|
+
trace_config.config = config_hash[:advice]
|
71
|
+
|
72
|
+
bm.report("configure! using #{File.basename(config_yml)}") do
|
73
|
+
trace_config.configure!
|
74
|
+
end
|
75
|
+
|
76
|
+
msg = "Using #{config_yml}"
|
77
|
+
# puts msg
|
78
|
+
trace_advice.log_all(msg)
|
79
|
+
|
80
|
+
##################################################
|
81
|
+
# Target activity
|
82
|
+
|
83
|
+
bm.report("Target activity") do
|
84
|
+
a = MyClass.new
|
85
|
+
a.foo(123)
|
86
|
+
a.bar(456)
|
87
|
+
b = MySubclass.new
|
88
|
+
b.foo(789)
|
89
|
+
b.bar(012)
|
90
|
+
begin
|
91
|
+
a.raise_error(:aSymbol)
|
92
|
+
rescue Exception
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
|
data/lib/cheap_advice.rb
ADDED
@@ -0,0 +1,511 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
# Provides cheap advice mechanism for Ruby.
|
4
|
+
# See: http://github.com/kstephens/cheap_advice
|
5
|
+
#
|
6
|
+
class CheapAdvice
|
7
|
+
EMPTY_Hash = { }.freeze
|
8
|
+
EMPTY_Array = [ ].freeze
|
9
|
+
EMPTY_String = ''.freeze
|
10
|
+
|
11
|
+
class Error < ::Exception; end
|
12
|
+
|
13
|
+
module Options
|
14
|
+
# Hash accesible by #[] and #[]=.
|
15
|
+
attr_accessor :options
|
16
|
+
def initialize
|
17
|
+
@mutex = Mutex.new
|
18
|
+
@options = { }
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](k)
|
22
|
+
# @mutex.synchronize do
|
23
|
+
@options[k]
|
24
|
+
# end
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(k, v)
|
28
|
+
# @mutex.synchronize do
|
29
|
+
@options[k] = v
|
30
|
+
# end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
include Options
|
34
|
+
|
35
|
+
# Procs called before, after, and around the original method.
|
36
|
+
attr_accessor :before, :after, :around
|
37
|
+
|
38
|
+
# Collection of Advised method bindings.
|
39
|
+
attr_accessor :advised
|
40
|
+
|
41
|
+
# Module or Array of Modules to extend new Advised objects with.
|
42
|
+
attr_accessor :advised_extend
|
43
|
+
|
44
|
+
NULL_PROC = lambda { | ar | }
|
45
|
+
NULL_AROUND_PROC = lambda { | ar, body | body.call }
|
46
|
+
|
47
|
+
# options:
|
48
|
+
# :before
|
49
|
+
# :after
|
50
|
+
# :around
|
51
|
+
def initialize *opts, &blk
|
52
|
+
super()
|
53
|
+
|
54
|
+
@advised = [ ]
|
55
|
+
@advised_for = { }
|
56
|
+
|
57
|
+
opts_hash = Hash === opts[-1] ? opts.pop : { }
|
58
|
+
opts_key = opts.shift
|
59
|
+
@options = opts_hash
|
60
|
+
|
61
|
+
@before = (opts_key == :before ? blk : opts_hash[:before]) ||
|
62
|
+
NULL_PROC
|
63
|
+
@after = (opts_key == :after ? blk : opts_hash[:after]) ||
|
64
|
+
NULL_PROC
|
65
|
+
@around = (opts_key == :around ? blk : opts_hash[:around]) ||
|
66
|
+
NULL_AROUND_PROC
|
67
|
+
|
68
|
+
@blk = blk
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Apply advice a method on a Module (or Class).
|
73
|
+
#
|
74
|
+
# The advised method is enabled immediately (this may change in a future release).
|
75
|
+
#
|
76
|
+
# Returns an Advised object that describes what method was advised.
|
77
|
+
#
|
78
|
+
# mod can be a String, a Module or an Array of either.
|
79
|
+
# method can be a String, a Symbol or an Array of either.
|
80
|
+
# if either are Arrays the result will be an Array of Advised objects.
|
81
|
+
#
|
82
|
+
# The type of method scope can be specified by:
|
83
|
+
# * :instance (default)
|
84
|
+
# * :class
|
85
|
+
# * :module
|
86
|
+
#
|
87
|
+
# Any additional Hash options are propagated to the Advised binding object, which
|
88
|
+
# can be accessed from the ActivationRecord passed to the Advice block(s).
|
89
|
+
#
|
90
|
+
# Examples:
|
91
|
+
# advice = CheapAdvice(:around) { | ar, body | ...; body.call; ... }
|
92
|
+
# advice.advise! MyClass, :instance_method, options_hash
|
93
|
+
# advice.advise! MyClass, :class_method, :class
|
94
|
+
#
|
95
|
+
# Each Advised object is extended with #advised_extend.
|
96
|
+
# The #advised Array lists all Advised object.
|
97
|
+
#
|
98
|
+
def advise! mod, method, *opts
|
99
|
+
return mod.map { | x | advise! x, method, *opts } if
|
100
|
+
Array === mod
|
101
|
+
return method.map { | x | advise! mod, x, *opts } if
|
102
|
+
Array === method
|
103
|
+
|
104
|
+
opts_hash = Hash === opts[-1] ? opts.pop : { }
|
105
|
+
kind = opts.shift
|
106
|
+
kind ||= :instance
|
107
|
+
|
108
|
+
method = method.to_sym
|
109
|
+
|
110
|
+
@mutex.synchronize do
|
111
|
+
unless @enabled_once
|
112
|
+
self.enabled!
|
113
|
+
@enabled_once = true
|
114
|
+
end
|
115
|
+
|
116
|
+
advised = advised_for mod, method, kind, opts_hash
|
117
|
+
|
118
|
+
advised.enable! # Should this really be automatically enabled??
|
119
|
+
|
120
|
+
advised
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Called once the first time this advice is enabled.
|
125
|
+
# Instances can override this method.
|
126
|
+
def enabled!
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def advised_select mod, meth, kind
|
131
|
+
@advised.select do | ad |
|
132
|
+
(mod ? mod == ad.mod : true) &&
|
133
|
+
(meth ? meth == ad.meth : true) &&
|
134
|
+
(kind ? kind == ad.kind : true)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns the existing Advised binding or creates a new one.
|
139
|
+
def advised_for mod, meth, kind, opts
|
140
|
+
(@advised_for[[ mod, meth, kind ]] ||=
|
141
|
+
construct_advised_for(mod, meth, kind)).set_options!(opts)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Constructs an Advised binding from this Advice.
|
145
|
+
def construct_advised_for mod, meth, kind
|
146
|
+
advice = self
|
147
|
+
|
148
|
+
advised = Advised.new(advice, mod, meth, kind)
|
149
|
+
|
150
|
+
case @advised_extend
|
151
|
+
when nil
|
152
|
+
when Module
|
153
|
+
advised.extend(@advised_extend)
|
154
|
+
when Array
|
155
|
+
@advised_extend.each { | m | advised.extend(m) }
|
156
|
+
else
|
157
|
+
raise TypeError, "advised_extend: expected nil, Module or Array of Modules, given #{@advised_extend.class}"
|
158
|
+
end
|
159
|
+
|
160
|
+
advised.register_advice_methods!
|
161
|
+
|
162
|
+
advised.define_new_method!
|
163
|
+
|
164
|
+
@advised << advised
|
165
|
+
|
166
|
+
advised
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Disables all currently Advised methods.
|
171
|
+
def disable!
|
172
|
+
@mutex.synchronize do
|
173
|
+
@advised.each { | x | x.disable! }
|
174
|
+
end
|
175
|
+
self
|
176
|
+
end
|
177
|
+
alias :unadvise! :disable!
|
178
|
+
|
179
|
+
|
180
|
+
# Enables all currently Advised methods.
|
181
|
+
def enable!
|
182
|
+
@mutex.synchronize do
|
183
|
+
@advised.each { | x | x.enable! }
|
184
|
+
end
|
185
|
+
self
|
186
|
+
end
|
187
|
+
alias :readvise! :enable!
|
188
|
+
|
189
|
+
|
190
|
+
# Represents the application/binding of advice to a class and method.
|
191
|
+
class Advised
|
192
|
+
include Options
|
193
|
+
|
194
|
+
@@mutex = Mutex.new
|
195
|
+
@@advised_id ||= 0
|
196
|
+
|
197
|
+
# The Advice being applied to the Module and method.
|
198
|
+
attr_reader :advice
|
199
|
+
# The Module, method and kind (instance, class or module method)
|
200
|
+
attr_reader :mod, :meth, :kind
|
201
|
+
alias :cls :mod # Deprecated.
|
202
|
+
|
203
|
+
# The unique Advised id used to generate unique method names.
|
204
|
+
attr_reader :advised_id
|
205
|
+
|
206
|
+
# The name of the old and new method being patched in.
|
207
|
+
attr_reader :old_meth, :new_meth
|
208
|
+
|
209
|
+
# The name of the before, after and around methods.
|
210
|
+
attr_reader :before_meth, :after_meth, :around_meth
|
211
|
+
|
212
|
+
# True if the Advised methods are currently installed.
|
213
|
+
attr_reader :enabled
|
214
|
+
|
215
|
+
def initialize *args
|
216
|
+
@mutex = Mutex.new
|
217
|
+
@advice, @mod, @meth, @kind, @options = *args
|
218
|
+
|
219
|
+
case @kind
|
220
|
+
when :instance, :class, :module
|
221
|
+
else
|
222
|
+
raise ArgumentError, "invalid kind #{kind.inspect}"
|
223
|
+
end
|
224
|
+
|
225
|
+
@options ||= { }
|
226
|
+
|
227
|
+
@@mutex.synchronize do
|
228
|
+
@advised_id = @@advised_id += 1
|
229
|
+
end
|
230
|
+
|
231
|
+
@old_meth = :"__advice_old_#{@@advised_id}_#{@meth}"
|
232
|
+
@new_meth = :"__advice_new_#{@@advised_id}_#{@meth}"
|
233
|
+
|
234
|
+
@before_meth = :"__advice_before_#{@@advised_id}_#{@meth}"
|
235
|
+
@after_meth = :"__advice_after_#{@@advised_id}_#{@meth}"
|
236
|
+
@around_meth = :"__advice_around_#{@@advised_id}_#{@meth}"
|
237
|
+
|
238
|
+
@enabled =
|
239
|
+
@advice_methods_applied = false
|
240
|
+
end
|
241
|
+
|
242
|
+
def set_options! options
|
243
|
+
@options = options || { }
|
244
|
+
self
|
245
|
+
end
|
246
|
+
|
247
|
+
INSTANCE_SEP = '#'.freeze
|
248
|
+
MODULE_SEP = '.'.freeze
|
249
|
+
|
250
|
+
# The string name for the method.
|
251
|
+
# Returns "Foo#bar" for an instance method named :bar on class Foo.
|
252
|
+
# Returns "Foo.bar" for a class or module method named .bar on class Foo.
|
253
|
+
def meth_to_s
|
254
|
+
@meth_to_s ||=
|
255
|
+
"#{@mod}#{@kind == :instance ? INSTANCE_SEP : MODULE_SEP}#{@meth}".freeze
|
256
|
+
end
|
257
|
+
|
258
|
+
# True if the advice, mod, method and kind are equal.
|
259
|
+
def == x
|
260
|
+
return false unless self.class === x
|
261
|
+
@advice == x.advice && @mod == x.mod && @meth == x.meth && @kind == x.kind
|
262
|
+
end
|
263
|
+
|
264
|
+
# Support for Hash.
|
265
|
+
def hash
|
266
|
+
@advice.hash ^ @mod.hash ^ @meth.hash ^ @kind.hash
|
267
|
+
end
|
268
|
+
|
269
|
+
# Returns the target Module for the kind of method.
|
270
|
+
def mod_target
|
271
|
+
case @kind
|
272
|
+
when :instance
|
273
|
+
mod_resolve
|
274
|
+
when :class, :module
|
275
|
+
(class << mod_resolve; self; end)
|
276
|
+
else
|
277
|
+
raise ArgumentError, "mod_target: invalid kind #{kind.inspect}"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Resolves mod Strings to the actual target Module.
|
282
|
+
def mod_resolve
|
283
|
+
case @mod
|
284
|
+
when Module
|
285
|
+
@mod
|
286
|
+
when String, Symbol
|
287
|
+
@mod.to_s.split('::').
|
288
|
+
reject { | name | name.empty?}.
|
289
|
+
inject(Object) { | namespace, name | namespace.const_get(name) }
|
290
|
+
else
|
291
|
+
raise TypeError, "mod_resolve: expected Module, String, Symbol, given #{@mod.class}"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Registers the before, after and around advice methods.
|
296
|
+
def register_advice_methods!
|
297
|
+
scope # force calculation of scope before aliasing methods.
|
298
|
+
|
299
|
+
@mutex.synchronize do
|
300
|
+
return self if @advice_methods_registered
|
301
|
+
|
302
|
+
this = self
|
303
|
+
mod_target.instance_eval do
|
304
|
+
define_method(this.before_meth, &this.advice.before)
|
305
|
+
define_method(this.after_meth, &this.advice.after)
|
306
|
+
define_method(this.around_meth, &this.advice.around)
|
307
|
+
end
|
308
|
+
|
309
|
+
@advice_methods_registered = true
|
310
|
+
end
|
311
|
+
self
|
312
|
+
end
|
313
|
+
|
314
|
+
# Defines the new advised method in the target Module.
|
315
|
+
def define_new_method!
|
316
|
+
advised = self
|
317
|
+
|
318
|
+
advised.mod_target.instance_eval do
|
319
|
+
define_method advised.new_meth do | *args, &block |
|
320
|
+
ar = ActivationRecord.new(advised, self, args, block)
|
321
|
+
|
322
|
+
# Proc to invoke the old method with :before and :after advise hooks.
|
323
|
+
body = Proc.new do
|
324
|
+
self.__send__(advised.before_meth, ar)
|
325
|
+
begin
|
326
|
+
ar.result = self.__send__(advised.old_meth, *ar.args, &ar.block)
|
327
|
+
rescue ::Object => err
|
328
|
+
ar.error = err
|
329
|
+
ensure
|
330
|
+
self.__send__(advised.after_meth, ar)
|
331
|
+
end
|
332
|
+
ar.result
|
333
|
+
end
|
334
|
+
|
335
|
+
# Invoke the :around advice with the body Proc.
|
336
|
+
self.__send__(advised.around_meth, ar, body)
|
337
|
+
|
338
|
+
# Reraise Exception, if occured.
|
339
|
+
raise ar.error if ar.error
|
340
|
+
|
341
|
+
# Return the message result to caller.
|
342
|
+
ar.result
|
343
|
+
end # define_method
|
344
|
+
end # instance_eval
|
345
|
+
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
if RUBY_VERSION =~ /^1\.8/
|
350
|
+
def scope
|
351
|
+
@scope ||= @mutex.synchronize do
|
352
|
+
this = self
|
353
|
+
mod_target.instance_eval do
|
354
|
+
case
|
355
|
+
when private_instance_methods(false).include?(this.meth.to_s)
|
356
|
+
:private
|
357
|
+
when protected_instance_methods(false).include?(this.meth.to_s)
|
358
|
+
:protected
|
359
|
+
else
|
360
|
+
:public
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
else
|
366
|
+
def scope
|
367
|
+
@scope ||= @mutex.synchronize do
|
368
|
+
this = self
|
369
|
+
mod_target.instance_eval do
|
370
|
+
case
|
371
|
+
when private_instance_methods(false).include?(this.meth)
|
372
|
+
:private
|
373
|
+
when protected_instance_methods(false).include?(this.meth)
|
374
|
+
:protected
|
375
|
+
else
|
376
|
+
:public
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Enables the advice on this method.
|
384
|
+
def enable!
|
385
|
+
@mutex.synchronize do
|
386
|
+
return self if @enabled
|
387
|
+
|
388
|
+
this = self
|
389
|
+
mod_target.instance_eval do
|
390
|
+
case this.scope
|
391
|
+
when :public
|
392
|
+
else
|
393
|
+
public this.meth
|
394
|
+
end
|
395
|
+
|
396
|
+
alias_method this.old_meth, this.meth if
|
397
|
+
! method_defined? this.old_meth
|
398
|
+
|
399
|
+
alias_method this.meth, this.new_meth
|
400
|
+
|
401
|
+
case this.scope
|
402
|
+
when :private
|
403
|
+
private this.meth
|
404
|
+
when :protected
|
405
|
+
protected this.meth
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
enabled!
|
410
|
+
@enabled = true
|
411
|
+
end
|
412
|
+
self
|
413
|
+
end
|
414
|
+
alias :advise! :enable!
|
415
|
+
|
416
|
+
# Disables the advice on this method.
|
417
|
+
def disable!
|
418
|
+
@mutex.synchronize do
|
419
|
+
return self if ! @enabled
|
420
|
+
|
421
|
+
this = self
|
422
|
+
mod_target.instance_eval do
|
423
|
+
if method_defined? this.old_meth
|
424
|
+
alias_method this.meth, this.old_meth
|
425
|
+
|
426
|
+
case this.scope
|
427
|
+
when :private
|
428
|
+
private this.meth
|
429
|
+
when :protected
|
430
|
+
protected this.meth
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
disabled!
|
436
|
+
@enabled = false
|
437
|
+
end
|
438
|
+
|
439
|
+
self
|
440
|
+
end
|
441
|
+
alias :unadvise! :disable!
|
442
|
+
|
443
|
+
# Called when Advised is enabled.
|
444
|
+
# Instances can override this method.
|
445
|
+
def enabled!
|
446
|
+
self
|
447
|
+
end
|
448
|
+
|
449
|
+
# Called when Advised is enabled.
|
450
|
+
# Instances can override this method.
|
451
|
+
def disabled!
|
452
|
+
self
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|
456
|
+
|
457
|
+
|
458
|
+
# Represents the activation record of a method invocation.
|
459
|
+
class ActivationRecord
|
460
|
+
# The Advised method binding object.
|
461
|
+
attr_reader :advised
|
462
|
+
|
463
|
+
# The original message receiver, arguments and block (if given).
|
464
|
+
# Can be modified by the advice blocks.
|
465
|
+
attr_accessor :rcvr, :args, :block
|
466
|
+
|
467
|
+
# The original message return result available in the :around or :after advice blocks.
|
468
|
+
# Value can be changed in the advice blocks to alter the return result.
|
469
|
+
attr_accessor :result
|
470
|
+
|
471
|
+
# Any Exception rescued from the original method.
|
472
|
+
# Usually nil if no exception was raised;
|
473
|
+
# if not nil, Exception is reraised after the :around advice block.
|
474
|
+
# Can be modified by the :after advice block.
|
475
|
+
attr_accessor :error
|
476
|
+
alias :exception :error
|
477
|
+
alias :exception= :error=
|
478
|
+
|
479
|
+
# Arbitrary data accessed by #[], #[]=.
|
480
|
+
# Advice blocks can use this to store arbitrary data.
|
481
|
+
# May be a frozen, empty Hash.
|
482
|
+
attr_accessor :data
|
483
|
+
|
484
|
+
def initialize *args
|
485
|
+
@advised, @rcvr, @args, @block = *args
|
486
|
+
end
|
487
|
+
|
488
|
+
def data
|
489
|
+
@data || EMPTY_Hash
|
490
|
+
end
|
491
|
+
|
492
|
+
def [] key
|
493
|
+
(@data || EMPTY_Hash)[key]
|
494
|
+
end
|
495
|
+
|
496
|
+
def []= key, value
|
497
|
+
(@data ||= { })[key] = value
|
498
|
+
end
|
499
|
+
|
500
|
+
# This methods are delegated to #advised.
|
501
|
+
DELEGATE_TO_ADVISED = [ :advice, :mod, :meth, :kind, :meth_to_s ].freeze
|
502
|
+
eval(DELEGATE_TO_ADVISED.map{|m| "def #{m}; @advised.#{m}; end; "} * "\n")
|
503
|
+
|
504
|
+
# The call stack with CheapAdvice methods filtered out.
|
505
|
+
def caller(offset = 0)
|
506
|
+
::Kernel.caller(offset + 2)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
end
|
511
|
+
|