rcapture 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+
2
+ module RCapture
3
+
4
+ module Internal
5
+
6
+ # Pre-capture single method.
7
+ # This version is used on Ruby 1.9.x where define_method
8
+ # allows for block arguments
9
+ def Internal.capture_single_pre(klass, method, additional_args, &callback)
10
+ prev = klass.instance_method(method)
11
+ # http://eigenclass.org/hiki/Changes+in+Ruby+1.9#l32
12
+ is_private = klass.private_instance_methods.include?(method)
13
+
14
+ klass.class_eval do
15
+ # http://eigenclass.org/hiki/Changes+in+Ruby+1.9#l11
16
+ define_method "#{method}" do |*args, &b|
17
+ status = CaptureStatus.current
18
+ call_prev = true; alt_ret = nil
19
+ if status.on?
20
+ status.use(false) do
21
+ ci = CapturedInfo.current
22
+ ci.fill(args, self, method, b)
23
+ callback.call(ci)
24
+ alt_ret = ci.return
25
+ call_prev = ci.predicate
26
+ end
27
+ end
28
+ if call_prev
29
+ prev.bind(self).call(*args, &b)
30
+ else
31
+ alt_ret
32
+ end
33
+ end
34
+ private "#{method}" if is_private
35
+ end
36
+ end
37
+
38
+ # Post-capture single method.
39
+ # This version is used on Ruby 1.9.x where define_method
40
+ # allows for block arguments
41
+ def Internal.capture_single_post(klass, method, additional_args, &callback)
42
+ prev = klass.instance_method(method)
43
+ # http://eigenclass.org/hiki/Changes+in+Ruby+1.9#l32
44
+ is_private = klass.private_instance_methods.include?(method)
45
+
46
+ klass.class_eval do
47
+ define_method "#{method}" do |*args, &b|
48
+ ret = prev.bind(self).call(*args, &b)
49
+ status = CaptureStatus.current
50
+ if status.on?
51
+ status.use(false) do
52
+ ci = CapturedInfo.current
53
+ ci.fill(args, self, method, b, ret)
54
+ callback.call(ci)
55
+ ret = ci.return
56
+ end
57
+ end
58
+ ret
59
+ end
60
+ private "#{method}" if is_private
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,53 @@
1
+ require 'thread'
2
+
3
+ module RCapture
4
+
5
+ # Stack-based boolean enabled/disabled status.
6
+ # CaptureStatus is used to disable capturing from
7
+ # within callbacks which could otherwise lead to
8
+ # infinite recursion or undesired results.
9
+ class CaptureStatus
10
+
11
+ def initialize(value = true)
12
+ @status = value
13
+ @stack = []
14
+ end
15
+
16
+ # Access thread-local capture status variable
17
+ def CaptureStatus.current
18
+ Thread.current[:capture_status] ||= CaptureStatus.new(true)
19
+ end
20
+
21
+ # Status is set to +value+ while operating on the block.
22
+ def use(value)
23
+ begin
24
+ self.set(value)
25
+ yield
26
+ ensure
27
+ self.restore
28
+ end
29
+ end
30
+
31
+ # Set/push new status
32
+ def set(value)
33
+ @stack.unshift @status
34
+ @status = value
35
+ end
36
+
37
+ # Query status
38
+ def on?
39
+ @status
40
+ end
41
+
42
+ # Query status
43
+ def off?
44
+ !@status
45
+ end
46
+
47
+ # Pop status from stack
48
+ def restore
49
+ @status = @stack.shift if @stack.size > 0
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,62 @@
1
+ require 'thread'
2
+
3
+ module RCapture
4
+
5
+ # Holds captured method information and acts as a communicator
6
+ # between callback and RCapture.
7
+ #
8
+ # === Post Capture
9
+ # When called from a method previously captured by +capture_post+,
10
+ # the following meaning holds true:
11
+ # args:: Array of input arguments passed to the method.
12
+ # sender:: Instance the method was called on.
13
+ # method:: Symbol of method called.
14
+ # return:: Value returned by method. When modified, the modified value is
15
+ # returned from the method.
16
+ # block:: Block passed to method or nil.
17
+ # predicate:: Unused.
18
+ #
19
+ # === Pre Capture
20
+ # When called from a method that was previously captured by +capture_pre+,
21
+ # the following meaning holds true:
22
+ # args:: Array of input arguments passed to the method. Modifyable.
23
+ # sender:: Instance the method was called on.
24
+ # method:: Symbol of method called.
25
+ # block:: Block passed to method or nil.
26
+ # predicate:: Initially true. If set to false the captured method will not be invoked.
27
+ # return:: Initially nil. If +predicate+ is false, then the value of +return+ is returned
28
+ # from the method.
29
+ #
30
+ class CapturedInfo
31
+ attr_accessor :args
32
+ attr_accessor :sender
33
+ attr_accessor :method
34
+ attr_accessor :return
35
+ attr_accessor :predicate
36
+ attr_accessor :block
37
+
38
+ # Access thread-local CapturedInfo variable
39
+ def CapturedInfo.current
40
+ Thread.current[:captured_info] ||= CapturedInfo.new
41
+ end
42
+
43
+ # Fill with values
44
+ def fill(args, sender, method, block = nil, ret = nil)
45
+ @args = args
46
+ @sender = sender
47
+ @method = method
48
+ @return = ret
49
+ @block = block
50
+ @predicate = true
51
+ end
52
+
53
+ alias_method :a, :args
54
+ alias_method :s, :sender
55
+ alias_method :m, :method
56
+ alias_method :r, :return
57
+ alias_method :r=, :return=
58
+ alias_method :b, :block
59
+ alias_method :p, :predicate
60
+ alias_method :p=, :predicate=
61
+ end
62
+ end
@@ -0,0 +1,93 @@
1
+
2
+ module RCapture
3
+
4
+ # = Introduction
5
+ # Interceptable is a module mixin to enrich the receiver with capturing capatibilities.
6
+ #
7
+ # It can be used at a class scope:
8
+ #
9
+ # class Array
10
+ # include RCapture::Interceptable
11
+ # end
12
+ #
13
+ # This will provide the methods +capture+, +capture_pre+, and +capture_post+.
14
+ # at class and instance level:
15
+ #
16
+ # Array.capture :methods => :push do
17
+ # puts 'pushed'
18
+ # end
19
+ #
20
+ # will affect all instances of Array. On the other hand
21
+ #
22
+ # a = []
23
+ # a.capture :methods => :push do
24
+ # puts 'pushed'
25
+ # end
26
+ #
27
+ # will affect only the instance +a+.
28
+ #
29
+ # Instances can be extended by Interceptable to provide capturing capatibilities
30
+ # for the selected instance only:
31
+ #
32
+ # a = []
33
+ # a.extend(RCapture::Interceptable)
34
+ # a.capture :methods => :push do
35
+ # puts 'pushed'
36
+ # end
37
+ module Interceptable
38
+
39
+ module ClassMethods # :nodoc:
40
+ def capture(args, &callback)
41
+ RCapture::Internal.capture_post(self, args, &callback)
42
+ end
43
+ alias_method :capture_post, :capture
44
+
45
+ def capture_pre(args, &callback)
46
+ RCapture::Internal.capture_pre(self, args, &callback)
47
+ end
48
+ end
49
+
50
+ def self.included(klass) # :nodoc:
51
+ klass.extend(Interceptable::ClassMethods)
52
+ end
53
+
54
+ # Perform a post-capture on the given class/instance.
55
+ #
56
+ # === Arguments
57
+ # args:: A hash of options. The following are recognized
58
+ # [:methods] Instance methods to capture. Either a single method name/symbol or
59
+ # and array of names/symbols.
60
+ # [:class_methods] Class methods to capture. Either a single method name/symbol or
61
+ # and array of names/symbols.
62
+ # callback:: The block to be invoked after captured method was invoked. The callback will be given
63
+ # an instance of CapturedInfo.
64
+ def capture_post(args, &callback)
65
+ RCapture::Internal.capture_post(
66
+ RCapture::Internal.singleton_class(self),
67
+ args,
68
+ &callback
69
+ )
70
+ end
71
+ alias_method :capture, :capture_post
72
+
73
+ # Perform a pre-capture on the given class/instance.
74
+ #
75
+ # === Arguments
76
+ # args:: A hash of options. The following are recognized
77
+ # [:methods] Instance methods to capture. Either a single method name/symbol or
78
+ # and array of names/symbols.
79
+ # [:class_methods] Class methods to capture. Either a single method name/symbol or
80
+ # and array of names/symbols.
81
+ # callback:: The block to be invoked before the captured method is invoked. The callback will be given
82
+ # an instance of CapturedInfo.
83
+ def capture_pre(args, &callback)
84
+ RCapture::Internal.capture_pre(
85
+ RCapture::Internal.singleton_class(self),
86
+ args,
87
+ &callback
88
+ )
89
+ end
90
+
91
+ end
92
+ end
93
+
@@ -0,0 +1,61 @@
1
+
2
+ # = Introduction
3
+ # The module RCapture is your single entry point when it comes to
4
+ # method capturing/hooking.
5
+ #
6
+ # To hook/capture methods you need to
7
+ # - enrich target(s) with capturing capatibilities. This is provided by the module mixin Interceptable.
8
+ # - capture the methods by invoking methods from Interceptable.
9
+ #
10
+ # = Examples
11
+ # Here is a set of examples that should help you getting started. Those examples are part of
12
+ # the RCapture distribution
13
+ #
14
+ # == Hello World
15
+ # The following example illustrates basic class and instance level hooking and
16
+ # makes use of the passed CapturedInfo instance to query details.
17
+ # :include:example/hello_world.rb
18
+ #
19
+ # == Class Methods
20
+ # In this example quickly shows how to hook class methods.
21
+ # :include:example/class_methods.rb
22
+ #
23
+ # == Modifying Arguments and Return Values
24
+ # The following example illustrates capturing methods in order to modify input and return arguments.
25
+ # Further, it illustrates how capture methods of a single instance only.
26
+ # :include:example/modify_arguments.rb
27
+ #
28
+ # == Filtering Method Calls
29
+ # Additionally to modifying arguments you can prevent captured method from being called using a Interceptable#capture_pre hook.
30
+ # :include:example/filter_method_calls.rb
31
+ #
32
+ # == Callbacks Are Capture-Free
33
+ # During processing of a callback, capturing is disabled. Otherwise, this could lead to infinite recursion as the following
34
+ # examples demonstrates.
35
+ # :include:example/no_endless_recursion.rb
36
+ #
37
+ # == Inheritance
38
+ # Captured methods propagate through derivates as long as they are not overridden by a class.
39
+ # :include:example/inheritance.rb
40
+ #
41
+ # == Multithreading
42
+ # RCapture is a lock-free implementation that supports multithreading. There is no restriction on the number of threads
43
+ # that call a shared callback. Captures, however, should be called only from a single thread or guaranteed to be synchronized
44
+ # by the user. Since callbacks can be invoked from multiple threads in parallel, shared resources from within the callbacks
45
+ # need to be synchronized by the user. The passed CapturedInfo is a thread-local variable and needs not to be synchronized.
46
+ # The following example illustrates this.
47
+ # :include:example/multithreaded.rb
48
+ #
49
+ # == New with Block
50
+ # Here is one final example that stimulate your imagination of what can be done with RCapture: given any class, it is often
51
+ # desireable to work with newly created instance by passing a block to new. If that class does not support this you can use
52
+ # RCapture to enable this behaviour.
53
+ # :include:example/new_with_block.rb
54
+ #
55
+ # = Benchmark
56
+ # RCapture works on Ruby 1.8.x and 1.9.x. Due to API changes between 1.8 and 1.9, RCapture selects an appropriate
57
+ # implementation of its core methods. Here is a benchmark that illustrates the overhead on 1.8 and 1.9 when
58
+ # a captured method is called.
59
+ # :include:test/benchmark/benchmark_capture.rb
60
+ module RCapture
61
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module RCapture
3
+ module Internal
4
+ # Access singleton class of given object
5
+ def Internal.singleton_class(t)
6
+ class << t
7
+ self
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,7 @@
1
+
2
+ # Extend Symbol to override default to_a behaviour which is deprecated.
3
+ class Symbol
4
+ def to_a
5
+ return [self]
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ #
2
+ # (c) Christoph Heindl, 2010
3
+ # http://cheind.wordpress.com
4
+ #
5
+
6
+ require 'test/unit'
7
+
8
+ require 'rcapture'
9
+
10
+ class Array
11
+ include RCapture::Interceptable
12
+ end
13
+
14
+ class TestAcceptanceArrray < Test::Unit::TestCase
15
+
16
+ def test_array
17
+ op_count = 0
18
+ a = Array.new
19
+
20
+ a.capture :methods => [:push, :<<] do |ci|
21
+ puts "intercepted"
22
+ end
23
+
24
+ Array.capture_pre :class_methods => :new do |ci|
25
+ p "new called"
26
+ end
27
+
28
+ r = Array.new(3)
29
+
30
+ a << 1 << 2
31
+ [] << 1
32
+ p a
33
+
34
+
35
+ end
36
+ end
@@ -0,0 +1,61 @@
1
+ #
2
+ # (c) Christoph Heindl, 2010
3
+ # http://cheind.wordpress.com
4
+ #
5
+
6
+ require 'test/unit'
7
+
8
+ require 'rcapture'
9
+ include RCapture
10
+
11
+ # Closed polygon dummy
12
+ class Polygon
13
+ include Interceptable
14
+ def vertices
15
+ self.edges
16
+ end
17
+ end
18
+
19
+ class Triangle < Polygon
20
+ def edges
21
+ 3
22
+ end
23
+ end
24
+
25
+ # Degenerate Polygon
26
+ class Digon < Polygon
27
+ def edges
28
+ 1
29
+ end
30
+
31
+ def vertices
32
+ 2
33
+ end
34
+ end
35
+
36
+ class Quadliteral < Polygon
37
+ def edges
38
+ 4
39
+ end
40
+
41
+ def vertices
42
+ super
43
+ end
44
+ end
45
+
46
+ class AcceptanceInheritance < Test::Unit::TestCase
47
+ def test_inheritance
48
+ called = 0
49
+ Polygon.capture :methods => :vertices do |ci|
50
+ called += 1
51
+ end
52
+
53
+ assert_equal(3, Triangle.new.vertices)
54
+ assert_equal(1, called)
55
+
56
+ assert_equal(2, Digon.new.vertices)
57
+ assert_equal(1, called)
58
+ assert_equal(4, Quadliteral.new.vertices)
59
+ assert_equal(2, called)
60
+ end
61
+ end