rcapture 1.0.4

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,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