rhook 0.1.5 → 0.1.6
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/.yardopts +2 -0
- data/README.rdoc +24 -6
- data/Rakefile +12 -8
- data/VERSION +1 -1
- data/examples/log_socket_data.rb +34 -0
- data/lib/rhook.rb +148 -12
- data/rhook.gemspec +25 -18
- data/spec/examples/log_buffer_example_spec.rb +66 -0
- data/spec/rhook_spec.rb +45 -0
- data/spec/spec_helper.rb +43 -1
- metadata +31 -7
- data/.gitignore +0 -21
data/.yardopts
ADDED
data/README.rdoc
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
= rhook - Easily drive AOP & hacking existing library with Ruby
|
2
2
|
|
3
|
+
- Homepage & Source -- http://github.com/kaorukobo/rhook
|
4
|
+
- Download -- http://rubygems.org/gems/rhook
|
5
|
+
- API Documentation (RDoc) -- http://rubydoc.info/github/kaorukobo/rhook/master/frames
|
6
|
+
|
7
|
+
== Summary
|
8
|
+
|
3
9
|
- You can provide hook point in your code,
|
4
10
|
- and can customize its behavior from outside.
|
5
11
|
- Also you can 'hack' (== injecting hook point from outside) any methods in existing code.
|
6
12
|
|
13
|
+
|
7
14
|
== Install
|
8
15
|
|
9
16
|
gem install rhook
|
@@ -65,20 +72,21 @@
|
|
65
72
|
|
66
73
|
=== about '_rhook'
|
67
74
|
|
68
|
-
Once you require 'rhook', any objects has '_rhook' method to access any rhook services. (that returns RHook::RHookService object.)
|
75
|
+
Once you require 'rhook', any objects has '_rhook' method to access any rhook services. (that returns {RHook::RHookService} object.)
|
69
76
|
|
70
77
|
any_object = Object.new
|
71
78
|
any_object._rhook.RHOOK_METHOD
|
72
79
|
|
80
|
+
See http://rubydoc.info/github/kaorukobo/rhook/master/RHook/RHookService
|
81
|
+
|
73
82
|
=== What is 'inv' ?
|
74
83
|
|
75
|
-
RHook::Invocation object, that contains:
|
84
|
+
{RHook::Invocation} object, that contains:
|
76
85
|
|
77
86
|
- call() method to proceed method invocation.
|
78
|
-
- receiver
|
79
|
-
|
80
|
-
|
81
|
-
- returned value by call()
|
87
|
+
- receiver, arguments/blcoks passed to method, and the other informations. See {RHook::Invocation}.
|
88
|
+
|
89
|
+
See http://rubydoc.info/github/kaorukobo/rhook/master/RHook/Invocation
|
82
90
|
|
83
91
|
=== If you want 'bind' to not only an object, but any instances of class:
|
84
92
|
|
@@ -113,6 +121,16 @@ for class method:
|
|
113
121
|
|
114
122
|
Please see spec: http://github.com/kaorukobo/rhook/blob/master/spec/rhook_spec.rb
|
115
123
|
|
124
|
+
== Practical Examples
|
125
|
+
|
126
|
+
=== 1. Log Buffer
|
127
|
+
|
128
|
+
Log Buffer allows you to capture any messages written to Logger and keep in buffer.
|
129
|
+
|
130
|
+
For example, you can examine the target program's log messages in testing code.
|
131
|
+
|
132
|
+
http://github.com/kaorukobo/rhook/blob/master/spec/examples/log_buffer_example_spec.rb
|
133
|
+
|
116
134
|
== ...
|
117
135
|
|
118
136
|
=== Note on Patches/Pull Requests
|
data/Rakefile
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
|
4
|
+
require "logger"
|
5
|
+
log = Logger.new(STDERR)
|
6
|
+
|
4
7
|
begin
|
5
8
|
require 'jeweler'
|
6
9
|
Jeweler::Tasks.new do |gem|
|
@@ -11,6 +14,8 @@ begin
|
|
11
14
|
gem.homepage = "http://github.com/kaorukobo/rhook"
|
12
15
|
gem.authors = ["Kaoru Kobo"]
|
13
16
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
17
|
+
gem.add_development_dependency "yard", "~> 0.6.0"
|
18
|
+
gem.has_rdoc = "yard"
|
14
19
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
20
|
end
|
16
21
|
Jeweler::GemcutterTasks.new
|
@@ -34,12 +39,11 @@ task :spec => :check_dependencies
|
|
34
39
|
|
35
40
|
task :default => :spec
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
42
|
+
begin
|
43
|
+
gem "yard"
|
44
|
+
require "yard"
|
45
|
+
YARD::Rake::YardocTask.new do |t|
|
46
|
+
end
|
47
|
+
rescue Gem::LoadError
|
48
|
+
log.warn "Install YARD to generate document."
|
45
49
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.6
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "rhook"
|
5
|
+
|
6
|
+
# Trace TCPSocket output data.
|
7
|
+
TCPSocket._rhook.hack(:write) do |inv|
|
8
|
+
STDERR.print("> ", inv.args[0].inspect, "\n")
|
9
|
+
inv.call
|
10
|
+
end
|
11
|
+
|
12
|
+
# Trace TCPSocket input data.
|
13
|
+
TCPSocket._rhook.hack(:read) do |inv|
|
14
|
+
inv.call
|
15
|
+
STDERR.print("< ", inv.returned.inspect, "\n")
|
16
|
+
inv.returned
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
require "rack"
|
21
|
+
require "rack/handler/webrick"
|
22
|
+
|
23
|
+
th = Thread.start {
|
24
|
+
Rack::Handler::WEBrick.run(lambda { |env|
|
25
|
+
[200, {}, []]
|
26
|
+
}, :Port => 9292)
|
27
|
+
}
|
28
|
+
sleep 1
|
29
|
+
|
30
|
+
sock = TCPSocket.new("localhost", 9292)
|
31
|
+
sock.print("GET / HTTP/1.0\n\n")
|
32
|
+
sock.flush
|
33
|
+
|
34
|
+
sock.read
|
data/lib/rhook.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module RHook
|
2
|
+
# @private
|
3
|
+
# The registry contains information globally shared. (like cache)
|
4
|
+
# You don't need to use this class.
|
2
5
|
class Registry
|
3
6
|
attr_reader :class_cached_flag_map
|
4
7
|
def initialize
|
@@ -6,19 +9,44 @@ module RHook
|
|
6
9
|
end
|
7
10
|
end
|
8
11
|
|
12
|
+
# @private
|
13
|
+
# Get global +Registry+ object.
|
9
14
|
def self.registry
|
10
15
|
@registry ||= Registry.new
|
11
16
|
end
|
12
17
|
|
18
|
+
# Most important class on rhook.
|
19
|
+
#
|
20
|
+
# If you call obj._rhook, it returns a RHookService object bound to +obj+.
|
13
21
|
class RHookService
|
22
|
+
# @private
|
14
23
|
attr_reader :hooks_map
|
15
24
|
|
25
|
+
# @private
|
16
26
|
def initialize(obj)
|
17
27
|
@obj = obj
|
18
28
|
@hooks_map = {}
|
19
29
|
@class_cached_hooks_map = {} if Class === @obj
|
20
30
|
end
|
21
31
|
|
32
|
+
# Returns the object bound to this RHookService.
|
33
|
+
# ( +obj+ of +obj._rhook+ )
|
34
|
+
def bound_object
|
35
|
+
@obj
|
36
|
+
end
|
37
|
+
|
38
|
+
# ========================================================================
|
39
|
+
# @group Methods for hook-side(outside)
|
40
|
+
# ========================================================================
|
41
|
+
|
42
|
+
# Add hook to the {#bound_object}. If it is a kind of Class, the hook is affected to all instance & subclasses of the class.
|
43
|
+
#
|
44
|
+
# @param [Symbol] name hook-point's name (commonly equals to method name)
|
45
|
+
# @option opt [true] :disable Create hook but make disabled. (by default, automatically enabled.)
|
46
|
+
# @yield [inv] The hook block.
|
47
|
+
# @yieldparam [Invocation] inv
|
48
|
+
# @yieldreturn The result value. (Returned value of called method.)
|
49
|
+
# @return [Hook] Created hook.
|
22
50
|
def bind(name, opt = {}, &block)
|
23
51
|
hook = Hook.new
|
24
52
|
hook.hook_proc = block
|
@@ -29,6 +57,15 @@ module RHook
|
|
29
57
|
hook
|
30
58
|
end
|
31
59
|
|
60
|
+
# Injects hook-point (hack) to the paticular method in {#bound_object}, and add hook same as {#bind}.
|
61
|
+
#
|
62
|
+
# The hook-point injection is done by 'alias' method.
|
63
|
+
# If the hook-point is already injected, this just does {#bind}.
|
64
|
+
#
|
65
|
+
# @param [Symbol] name The name of method to hack.
|
66
|
+
# @yield [inv]
|
67
|
+
# @return [Hook]
|
68
|
+
# @see #bind See #bind for other param/return.
|
32
69
|
def hack(name, opt = {}, &block)
|
33
70
|
success = false
|
34
71
|
if Class === @obj
|
@@ -44,13 +81,21 @@ module RHook
|
|
44
81
|
bind(name, opt, &block)
|
45
82
|
end
|
46
83
|
|
84
|
+
# @group Methods for hook-side(outside)
|
85
|
+
#
|
86
|
+
# Unbind all hooks bound to {#bound_object}.
|
87
|
+
# @return [self]
|
47
88
|
def unbind_all
|
48
89
|
@hooks_map.clear
|
49
90
|
@class_cached_hooks_map.clear
|
91
|
+
self
|
50
92
|
end
|
51
93
|
|
52
|
-
#
|
53
|
-
#
|
94
|
+
# ========================================================================
|
95
|
+
# @endgroup
|
96
|
+
# ========================================================================
|
97
|
+
|
98
|
+
# @private
|
54
99
|
class Caller
|
55
100
|
def initialize(rhook, opt)
|
56
101
|
@rhook = rhook
|
@@ -62,13 +107,22 @@ module RHook
|
|
62
107
|
end
|
63
108
|
end #/Caller
|
64
109
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
|
110
|
+
# ========================================================================
|
111
|
+
# @group Methods for target-side (for providing hook-point)
|
112
|
+
# ========================================================================
|
113
|
+
|
114
|
+
# Wraps {#bound_object}'s method call to be hookable.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# _rhook.to.method_name(arg1, arg2)
|
118
|
+
#
|
119
|
+
# @option opt [Hash] :hint Hint values used through {Invocation#hint}.
|
120
|
+
# @return Proxy to call method.
|
68
121
|
def to(opt = {})
|
69
122
|
Caller.new(self, opt)
|
70
123
|
end
|
71
124
|
|
125
|
+
# @private
|
72
126
|
def call_method(name, method_name, args, block, opt = {})
|
73
127
|
hooks = concat_hooks([], name)
|
74
128
|
hooks.empty? and return @obj.__send__(method_name, *args, &block)
|
@@ -84,6 +138,15 @@ module RHook
|
|
84
138
|
inv.proceed()
|
85
139
|
end
|
86
140
|
|
141
|
+
# Wraps the code block to be hookable.
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
# _rhook.does(:hook_name) { do_something; }
|
145
|
+
#
|
146
|
+
# @param [Symbol] name The hook-point's name specified on {#bind}.
|
147
|
+
# @option opt [Hash] :hint Hint values used through {Invocation#hint}.
|
148
|
+
# @yield The code block to be hooked.
|
149
|
+
# @return The result of code block. (Replaced if it is changed by hook.)
|
87
150
|
def does(name, opt = {}, &block)
|
88
151
|
hooks = concat_hooks([], name)
|
89
152
|
hooks.empty? and return yield
|
@@ -99,6 +162,18 @@ module RHook
|
|
99
162
|
inv.proceed()
|
100
163
|
end
|
101
164
|
|
165
|
+
# Wraps the defined method to be hookable.
|
166
|
+
#
|
167
|
+
# If possible, using {#to} is recommended than {#on_method}, because if the subclass override the hookable method, the subclasse's code become out of hook target.
|
168
|
+
#
|
169
|
+
# @example
|
170
|
+
# on_method :method_name
|
171
|
+
#
|
172
|
+
# @overload on_method(*names, opt = {})
|
173
|
+
# @param [Symbol] names The method name(s).
|
174
|
+
# @option opt [Boolean] :ifdef If true, it doesn't raise error whenever the method is not defined.
|
175
|
+
# @raise [NameError] If the method is not defined.
|
176
|
+
# @return [Boolean] When :ifdef => true, returns sucess or not, otherwise always true.
|
102
177
|
def on_method(*names_and_opt)
|
103
178
|
success = true
|
104
179
|
|
@@ -122,9 +197,11 @@ module RHook
|
|
122
197
|
success
|
123
198
|
end
|
124
199
|
|
125
|
-
#
|
126
|
-
#
|
127
|
-
|
200
|
+
# ========================================================================
|
201
|
+
# @endgroup
|
202
|
+
# ========================================================================
|
203
|
+
|
204
|
+
# @private
|
128
205
|
def concat_hooks(dest, name)
|
129
206
|
if Class === @obj
|
130
207
|
concat_class_hooks(dest, name)
|
@@ -135,6 +212,7 @@ module RHook
|
|
135
212
|
dest
|
136
213
|
end
|
137
214
|
|
215
|
+
# @private
|
138
216
|
def concat_class_hooks(dest, name)
|
139
217
|
# use cached one if available
|
140
218
|
if RHook.registry.class_cached_flag_map[name]
|
@@ -145,7 +223,7 @@ module RHook
|
|
145
223
|
end
|
146
224
|
|
147
225
|
hooks = []
|
148
|
-
|
226
|
+
|
149
227
|
# collect hooks including ancestor classes
|
150
228
|
begin
|
151
229
|
concat_hooks_internal(hooks, name)
|
@@ -168,25 +246,39 @@ module RHook
|
|
168
246
|
dest.concat(hooks)
|
169
247
|
end
|
170
248
|
|
249
|
+
# @private
|
171
250
|
def concat_hooks_internal(dest, name)
|
172
251
|
hooks = @hooks_map[name]
|
173
252
|
hooks and dest.concat(hooks)
|
174
253
|
end
|
175
254
|
end #/RHookService
|
176
255
|
|
256
|
+
# The object contains the invocation information.
|
257
|
+
#
|
258
|
+
# @attr_reader [Object] target The target object that the hook is applied. (Usually same to {#receiver})
|
259
|
+
# @attr_reader [Object] receiver The receiver object of this method invocation.
|
260
|
+
# @attr [Array<Object>] args The arguments given to the method invocation.
|
261
|
+
# @attr [Proc] block The block given to the method invocation
|
262
|
+
# @attr_reader [Object] returned The returned value by the method invocation. (Don't set this. To change it, just return by the alternative value from the hook procedure.)
|
263
|
+
# @attr_reader [Array<Hook>] hooks (Internally used) The applied hooks on this invocation.
|
264
|
+
# @attr [Proc] target_proc (Internally used) The procedure to execute the target method/procedure.
|
265
|
+
# @attr [Hash] hint Hint data given by {RHookService#does} / {RHookService#to}.
|
177
266
|
class Invocation < Struct.new(:target, :receiver, :args, :block, :returned, :hooks, :target_proc, :hint)
|
267
|
+
# @private
|
178
268
|
def initialize
|
179
269
|
@hook_index = 0
|
180
270
|
end
|
181
271
|
|
272
|
+
# Proceed to execute the next one on hooks-chain. If no more hooks, execute the target method/procedure.
|
273
|
+
# @return The returned value from the target method/procedure. (may changed by hook)
|
182
274
|
def proceed
|
183
275
|
hook = hooks[@hook_index]
|
184
276
|
# -- If no more hook was found, calls target procedure and return
|
185
|
-
hook or return target_proc.call(*args, &block)
|
277
|
+
hook or return self.returned = target_proc.call(*args, &block)
|
186
278
|
# -- Set hook pointer to next, then call next hook
|
187
279
|
@hook_index += 1
|
188
280
|
begin
|
189
|
-
hook.call(self)
|
281
|
+
self.returned = hook.call(self)
|
190
282
|
ensure
|
191
283
|
@hook_index -= 1
|
192
284
|
end
|
@@ -195,15 +287,26 @@ module RHook
|
|
195
287
|
alias call proceed
|
196
288
|
end #/Invocation
|
197
289
|
|
290
|
+
# The registered hook instance returned by #{RHookService#bind}.
|
198
291
|
class Hook
|
292
|
+
# Whether this hook is enabled.
|
293
|
+
# @return [Boolean]
|
199
294
|
attr_accessor :enabled
|
295
|
+
# The hook procedure registered by {RHookService#bind}.
|
296
|
+
# @return [Proc]
|
200
297
|
attr_accessor :hook_proc
|
201
298
|
|
299
|
+
# @private
|
202
300
|
def call(inv)
|
203
301
|
@enabled or return inv.proceed()
|
204
302
|
hook_proc.call(inv)
|
205
303
|
end
|
206
304
|
|
305
|
+
# Enable this hook.
|
306
|
+
# @overload enable()
|
307
|
+
# @overload enable(&block)
|
308
|
+
# @yield If block was given, the hook is enabled only within the given code block. (Note: This is not thread-safe.)
|
309
|
+
# @return [self]
|
207
310
|
def enable(&block)
|
208
311
|
@enabled = true
|
209
312
|
if block_given?
|
@@ -216,31 +319,47 @@ module RHook
|
|
216
319
|
self
|
217
320
|
end
|
218
321
|
|
322
|
+
# Disable this hook.
|
323
|
+
# @return [self]
|
219
324
|
def disable
|
220
325
|
@enabled = false
|
221
326
|
self
|
222
327
|
end
|
223
328
|
end #/Hook
|
224
329
|
|
330
|
+
#
|
225
331
|
class ::Object
|
332
|
+
# Get {RHook::RHookService} object bound to this object.
|
333
|
+
# @return [RHook::RHookService]
|
226
334
|
def _rhook
|
227
335
|
@_rhook ||= RHook::RHookService.new(self)
|
228
336
|
end
|
229
337
|
|
338
|
+
# @private
|
230
339
|
def _has_rhook?
|
231
340
|
@_rhook ? true : false
|
232
341
|
end
|
233
342
|
end
|
234
343
|
|
344
|
+
# Object to group the hooks for the certain purpose.
|
345
|
+
# You can enable/disable the grouped hooks at once.
|
346
|
+
#
|
347
|
+
# Don't instantiate this class. Use {RHook.group} method.
|
235
348
|
class HookGroup
|
349
|
+
# @private
|
236
350
|
def initialize
|
237
351
|
@hooks = []
|
238
352
|
end
|
239
353
|
|
354
|
+
# Add a new hook to this group.
|
355
|
+
# @return [self]
|
240
356
|
def add(hook)
|
241
357
|
@hooks << hook
|
358
|
+
self
|
242
359
|
end
|
243
360
|
|
361
|
+
# Add any hooks to this group that was registered by #{RHookService#bind} in the given block code.
|
362
|
+
# @return [self]
|
244
363
|
def wrap(&block)
|
245
364
|
group_stack = (Thread.current["rhook_group"] ||= [])
|
246
365
|
group_stack << self
|
@@ -252,6 +371,9 @@ module RHook
|
|
252
371
|
self
|
253
372
|
end
|
254
373
|
|
374
|
+
# Enable the hooks.
|
375
|
+
# @return [self]
|
376
|
+
# @see Hook#enable
|
255
377
|
def enable(&block)
|
256
378
|
@hooks.each do |h|
|
257
379
|
h.enable
|
@@ -266,6 +388,9 @@ module RHook
|
|
266
388
|
self
|
267
389
|
end
|
268
390
|
|
391
|
+
# Disable the hooks.
|
392
|
+
# @return [self]
|
393
|
+
# @see Hook#disable
|
269
394
|
def disable
|
270
395
|
@hooks.each do |h|
|
271
396
|
h.disable
|
@@ -273,13 +398,24 @@ module RHook
|
|
273
398
|
self
|
274
399
|
end
|
275
400
|
|
401
|
+
# @private
|
276
402
|
def self.add_to_current_groups(hook)
|
277
|
-
|
403
|
+
(Thread.current["rhook_group"] || []).each do |group|
|
278
404
|
group.add(hook)
|
279
405
|
end
|
280
406
|
end
|
281
407
|
end #/HookGroup
|
282
408
|
|
409
|
+
# Create the {HookGroup}, and add any hooks to this group that was registered by #{RHookService#bind} in the given block code.
|
410
|
+
#
|
411
|
+
# @example
|
412
|
+
# XXX_feature_for_library = RHook.group {
|
413
|
+
# Target._rhook.bind(...) {...}
|
414
|
+
# Target2._rhook.bind(...) {...}
|
415
|
+
# }
|
416
|
+
# XXX_feature_for_library.disable
|
417
|
+
#
|
418
|
+
# @return [HookGroup]
|
283
419
|
def self.group(&block)
|
284
420
|
HookGroup.new.wrap(&block)
|
285
421
|
end
|
data/rhook.gemspec
CHANGED
@@ -1,44 +1,48 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rhook}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.6"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Kaoru Kobo"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-12-01}
|
13
13
|
s.description = %q{You can provide hook point in your code, and can customize its behavior from outside. Also you can 'hack' (== injecting hook point from outside) any methods in existing code.}
|
14
14
|
s.email = %q{}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE",
|
17
|
-
|
17
|
+
"README.rdoc"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
".yardopts",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"examples/log_socket_data.rb",
|
27
|
+
"lib/rhook.rb",
|
28
|
+
"rhook.gemspec",
|
29
|
+
"spec/examples/log_buffer_example_spec.rb",
|
30
|
+
"spec/rhook_minor_spec.rb",
|
31
|
+
"spec/rhook_spec.rb",
|
32
|
+
"spec/spec.opts",
|
33
|
+
"spec/spec_helper.rb"
|
32
34
|
]
|
35
|
+
s.has_rdoc = %q{yard}
|
33
36
|
s.homepage = %q{http://github.com/kaorukobo/rhook}
|
34
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
35
37
|
s.require_paths = ["lib"]
|
36
38
|
s.rubygems_version = %q{1.3.7}
|
37
39
|
s.summary = %q{Easily drive AOP & hacking existing library with Ruby}
|
38
40
|
s.test_files = [
|
41
|
+
"examples/log_socket_data.rb",
|
42
|
+
"spec/examples/log_buffer_example_spec.rb",
|
39
43
|
"spec/rhook_minor_spec.rb",
|
40
|
-
|
41
|
-
|
44
|
+
"spec/rhook_spec.rb",
|
45
|
+
"spec/spec_helper.rb"
|
42
46
|
]
|
43
47
|
|
44
48
|
if s.respond_to? :specification_version then
|
@@ -47,11 +51,14 @@ Gem::Specification.new do |s|
|
|
47
51
|
|
48
52
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
49
53
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
54
|
+
s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
|
50
55
|
else
|
51
56
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
57
|
+
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
52
58
|
end
|
53
59
|
else
|
54
60
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
61
|
+
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "rhook examples" do
|
4
|
+
describe "log buffer (hack Logger)" do
|
5
|
+
require "logger"
|
6
|
+
|
7
|
+
# Captures any messages written to Logger and keep in buffer.
|
8
|
+
# You can read captured messages by LogBuffer#get
|
9
|
+
# or LogBuffer##pull ( also clears buffer ).
|
10
|
+
class LogBuffer
|
11
|
+
def initialize
|
12
|
+
@buf = ""
|
13
|
+
# Any log messages are passed to Logger#format_message. Hack it!!
|
14
|
+
Logger._rhook.hack(:format_message) do |inv|
|
15
|
+
result = inv.call
|
16
|
+
@buf << result
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear
|
22
|
+
@buf = ""
|
23
|
+
end
|
24
|
+
|
25
|
+
def get
|
26
|
+
@buf
|
27
|
+
end
|
28
|
+
|
29
|
+
def pull
|
30
|
+
result = get()
|
31
|
+
clear()
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end #/LogBuffer
|
35
|
+
|
36
|
+
# The example application to test.
|
37
|
+
# You cannot know whether it success or failed, (by return value or exception)
|
38
|
+
# ... it only logs.
|
39
|
+
class TargetApp
|
40
|
+
def initialize
|
41
|
+
@log = Logger.new(STDERR)
|
42
|
+
end
|
43
|
+
|
44
|
+
def success
|
45
|
+
@log.info "Success!"
|
46
|
+
end
|
47
|
+
|
48
|
+
def fail
|
49
|
+
@log.error "Failed!"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
example "Use LogBuffer to write test" do
|
54
|
+
app = TargetApp.new
|
55
|
+
|
56
|
+
# start to capture log.
|
57
|
+
logbuf = LogBuffer.new
|
58
|
+
|
59
|
+
app.success
|
60
|
+
logbuf.pull.should match(/Success!/)
|
61
|
+
|
62
|
+
app.fail
|
63
|
+
logbuf.pull.should match(/Failed!/)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/rhook_spec.rb
CHANGED
@@ -216,6 +216,51 @@ describe "rhook (advanced usage)" do
|
|
216
216
|
end
|
217
217
|
# ================================================================
|
218
218
|
|
219
|
+
# ========================================================================
|
220
|
+
describe "Invocation object" do
|
221
|
+
class Target
|
222
|
+
def invocation_object(*args, &block)
|
223
|
+
_rhook.to(:hint => {:hintkey => :hintval}).invocation_object_target(*args, &block)
|
224
|
+
end
|
225
|
+
|
226
|
+
def invocation_object_target(*args, &block)
|
227
|
+
:returned_value
|
228
|
+
end
|
229
|
+
end #/Target
|
230
|
+
|
231
|
+
example "attributes" do
|
232
|
+
args = [1, 2]
|
233
|
+
block = lambda do
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
lambda { |called|
|
238
|
+
t = Target.new
|
239
|
+
t._rhook.bind(:invocation_object_target) { |inv|
|
240
|
+
# first hook
|
241
|
+
called.yes
|
242
|
+
inv.call()
|
243
|
+
inv.returned.should == :returned_value
|
244
|
+
:returned_value_hooked
|
245
|
+
}
|
246
|
+
t._rhook.bind(:invocation_object_target) { |inv|
|
247
|
+
# second hook
|
248
|
+
called.yes
|
249
|
+
inv.args.should == args
|
250
|
+
inv.block.should == block
|
251
|
+
inv.hint[:hintkey].should == :hintval
|
252
|
+
inv.receiver.should == t
|
253
|
+
inv.target.should == t
|
254
|
+
inv.returned.should be_nil
|
255
|
+
inv.call()
|
256
|
+
inv.returned.should == :returned_value_hooked
|
257
|
+
}
|
258
|
+
t.invocation_object(*args, &block)
|
259
|
+
}.should calls_hook(:times => 2)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
# ========================================================================
|
263
|
+
|
219
264
|
end
|
220
265
|
|
221
266
|
# ================================================================
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
gem 'rspec'
|
2
|
+
gem 'rspec', "< 2.0"
|
3
3
|
|
4
4
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
5
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
@@ -10,3 +10,45 @@ require 'spec/autorun'
|
|
10
10
|
Spec::Runner.configure do |config|
|
11
11
|
|
12
12
|
end
|
13
|
+
|
14
|
+
# ========================================================================
|
15
|
+
# Defines custom matcher to test the hook was called.
|
16
|
+
|
17
|
+
module ::Spec::Matchers
|
18
|
+
class HookCalledMatcher
|
19
|
+
def initialize(opt)
|
20
|
+
@times = opt[:times]
|
21
|
+
@count = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def yes
|
25
|
+
@count += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def matches?(block)
|
29
|
+
block.call(self)
|
30
|
+
if @times
|
31
|
+
begin
|
32
|
+
@count.should == @times
|
33
|
+
rescue
|
34
|
+
@times_msg = " (Exactly called count: #{$!})"
|
35
|
+
false
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@count > 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def failure_message
|
43
|
+
"Expected the hook to be called.#{@times_msg}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def negative_failure_message
|
47
|
+
"Expected the hook not to be called.#{@times_msg}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def calls_hook(opt = {})
|
52
|
+
HookCalledMatcher.new(opt)
|
53
|
+
end
|
54
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rhook
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
9
|
+
- 6
|
10
|
+
version: 0.1.6
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Kaoru Kobo
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-12-01 00:00:00 +09:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
@@ -25,6 +26,7 @@ dependencies:
|
|
25
26
|
requirements:
|
26
27
|
- - ">="
|
27
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
28
30
|
segments:
|
29
31
|
- 1
|
30
32
|
- 2
|
@@ -32,6 +34,22 @@ dependencies:
|
|
32
34
|
version: 1.2.9
|
33
35
|
type: :development
|
34
36
|
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: yard
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 6
|
49
|
+
- 0
|
50
|
+
version: 0.6.0
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
35
53
|
description: You can provide hook point in your code, and can customize its behavior from outside. Also you can 'hack' (== injecting hook point from outside) any methods in existing code.
|
36
54
|
email: ""
|
37
55
|
executables: []
|
@@ -43,24 +61,26 @@ extra_rdoc_files:
|
|
43
61
|
- README.rdoc
|
44
62
|
files:
|
45
63
|
- .document
|
46
|
-
- .
|
64
|
+
- .yardopts
|
47
65
|
- LICENSE
|
48
66
|
- README.rdoc
|
49
67
|
- Rakefile
|
50
68
|
- VERSION
|
69
|
+
- examples/log_socket_data.rb
|
51
70
|
- lib/rhook.rb
|
52
71
|
- rhook.gemspec
|
72
|
+
- spec/examples/log_buffer_example_spec.rb
|
53
73
|
- spec/rhook_minor_spec.rb
|
54
74
|
- spec/rhook_spec.rb
|
55
75
|
- spec/spec.opts
|
56
76
|
- spec/spec_helper.rb
|
57
|
-
has_rdoc:
|
77
|
+
has_rdoc: yard
|
58
78
|
homepage: http://github.com/kaorukobo/rhook
|
59
79
|
licenses: []
|
60
80
|
|
61
81
|
post_install_message:
|
62
|
-
rdoc_options:
|
63
|
-
|
82
|
+
rdoc_options: []
|
83
|
+
|
64
84
|
require_paths:
|
65
85
|
- lib
|
66
86
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -68,6 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
88
|
requirements:
|
69
89
|
- - ">="
|
70
90
|
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
71
92
|
segments:
|
72
93
|
- 0
|
73
94
|
version: "0"
|
@@ -76,6 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
97
|
requirements:
|
77
98
|
- - ">="
|
78
99
|
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
79
101
|
segments:
|
80
102
|
- 0
|
81
103
|
version: "0"
|
@@ -87,6 +109,8 @@ signing_key:
|
|
87
109
|
specification_version: 3
|
88
110
|
summary: Easily drive AOP & hacking existing library with Ruby
|
89
111
|
test_files:
|
112
|
+
- examples/log_socket_data.rb
|
113
|
+
- spec/examples/log_buffer_example_spec.rb
|
90
114
|
- spec/rhook_minor_spec.rb
|
91
115
|
- spec/rhook_spec.rb
|
92
116
|
- spec/spec_helper.rb
|