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.
data/License ADDED
@@ -0,0 +1,26 @@
1
+
2
+ # = License
3
+ #
4
+ # Copyright (c) 2010, Christoph Heindl
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without modification,
8
+ # are permitted provided that the following conditions are met:
9
+ # - Redistributions of source code must retain the above copyright notice, this list
10
+ # of conditions and the following disclaimer.
11
+ # - Redistributions in binary form must reproduce the above copyright notice, this list
12
+ # of conditions and the following disclaimer in the documentation and/or other materials
13
+ # provided with the distribution.
14
+ # - Neither the name of the Christoph Heindl nor the names of its contributors may be
15
+ # used to endorse or promote products derived from this software without specific prior
16
+ # written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23
+ # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
24
+ # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
26
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README ADDED
@@ -0,0 +1,46 @@
1
+
2
+ = RCapture
3
+ This package contains the module RCapture, a collection of intuitive methods to capture method invocations.
4
+
5
+ RCapture has the following features
6
+ - Capturing of instance and class methods of individual objects or entire population of objects.
7
+ - Capturing pre or post method invocation.
8
+ - Multiple capturings per method.
9
+ - Modify method arguments and return values.
10
+ - Filter method calls.
11
+ - Developed with multithreaded environments in mind.
12
+ and many more.
13
+
14
+ = Simple Example
15
+ The example below will capture insertion methods of arrays and output statistics upon
16
+ invocation. For more examples see RCapture module documentation.
17
+
18
+ require 'rcapture'
19
+
20
+ class Array
21
+ include RCapture::Interceptable
22
+ end
23
+
24
+ Array.capture_post :methods => [:<<, :push] do |cs|
25
+ puts "#{cs.args.first} was inserted to array #{cs.sender}"
26
+ end
27
+
28
+ [] << 1 << 2
29
+ [].push 3
30
+
31
+ #=> 1 was inserted to array [1]
32
+ #=> 2 was inserted to array [1, 2]
33
+ #=> 3 was inserted to array [3]
34
+
35
+ = Requirements
36
+ Non except Ruby. This distribution was tested on Ruby 1.8.6 and Ruby 1.9.1.
37
+
38
+ = License
39
+ RCapture is Copyright (c) 2010 Christoph Heindl. It is free software, and may be redistributed under the terms
40
+ specified in the {License}[link:files/License.html] file.
41
+
42
+ = Support
43
+ The RCapture homepage is http://code.google.com/p/cheind-blog-files. There you will find links report
44
+ {issues}[http://code.google.com/p/cheind-blog-files/issues/list] (use tag component-rcapture) and latest source code.
45
+ You might find additional help on the author's homepage http://cheind.wordpress.com. For general questions contact the
46
+ author via email at {christoph.heindl@gmail.com}[mailto:christoph.heindl@gmail.com]
@@ -0,0 +1,38 @@
1
+ #
2
+ # (c) Christoph Heindl, 2010
3
+ # http://cheind.wordpress.com
4
+ #
5
+
6
+ require 'rake'
7
+ require 'rake/testtask'
8
+ require 'rake/rdoctask'
9
+
10
+ task :default => [:test_units]
11
+
12
+ desc "Run unit tests"
13
+ Rake::TestTask.new("test") { |t|
14
+ t.pattern = FileList['test/unit/test_*.rb']
15
+ t.verbose = true
16
+ t.warning = false
17
+ }
18
+
19
+ desc "Run acceptance tests"
20
+ Rake::TestTask.new("acceptance") { |t|
21
+ t.pattern = FileList['test/acceptance/acc_*.rb']
22
+ t.verbose = false
23
+ t.warning = false
24
+ }
25
+
26
+ desc "Run benchmarks"
27
+ Rake::TestTask.new("benchmark") { |t|
28
+ t.pattern = FileList['test/benchmark/benchmark_*.rb']
29
+ t.verbose = false
30
+ t.warning = false
31
+ }
32
+
33
+ desc "Generate rdoc documentation"
34
+ Rake::RDocTask.new do |rd|
35
+ rd.main = 'README'
36
+ rd.rdoc_dir = "doc"
37
+ rd.rdoc_files.include('README', 'License', 'rcapture/**/*.rb')
38
+ end
@@ -0,0 +1,14 @@
1
+ require 'rcapture'
2
+
3
+ # Enrich Math module
4
+ module Math
5
+ include RCapture::Interceptable
6
+ end
7
+
8
+ Math.capture_pre :class_methods => [:cos, :acos, :sin, :asin] do
9
+ puts "Hello Trigonometry!"
10
+ end
11
+
12
+ Math.cos(0)
13
+
14
+ #=> Hello Trigonometry!
@@ -0,0 +1,24 @@
1
+ require 'rcapture'
2
+
3
+ # This array will only accept even numbers
4
+ x = []
5
+ x.extend(RCapture::Interceptable)
6
+
7
+ even_filter = Proc.new do |ci|
8
+ # Define the predicate that must evaluate to true
9
+ # in order to call the captured method
10
+ ci.predicate = (ci.args.first % 2 == 0)
11
+
12
+ # In case the predicate evaluates to false you
13
+ # can use the return property to control
14
+ # what is returned from the captured method instead
15
+ # Insertion to array returns the array itself:
16
+ ci.return = ci.sender
17
+ end
18
+
19
+ x.capture_pre :methods => [:<<, :push], &even_filter
20
+
21
+ x << 2 << 3 << 4 << 5 << 6
22
+ x.push(7).push(8)
23
+ p x
24
+ #=> [2,4,6,8]
@@ -0,0 +1,35 @@
1
+ require 'rcapture'
2
+
3
+ # Enrich Array class.
4
+ class Array
5
+ include RCapture::Interceptable
6
+ end
7
+
8
+ # Install a hook at insertion methods of Array.
9
+ # Calling capture methods at class level will capture
10
+ # insertions from all instances of Array.
11
+ Array.capture_pre :methods => [:<<, :push] do |cs|
12
+ puts "'#{cs.method}' will be called with #{cs.args.first}"
13
+ end
14
+
15
+ [] << 1 << 2
16
+ [].push :hello
17
+
18
+ # Installing a hook on instance level will
19
+ # affect only that single instance
20
+ a = []
21
+ a.capture_post :methods => :length do |cs|
22
+ puts "Length of 'a' is #{cs.return}"
23
+ end
24
+
25
+ a << 1
26
+ a.length
27
+
28
+ # Any other array will not be affected
29
+ [].length
30
+
31
+ #=> '<<' was called with 1!
32
+ #=> '<<' was called with 2!
33
+ #=> 'push' was called with hello!
34
+ #=> '<<' was called with 1!
35
+ #=> Length of 'a' is 1
@@ -0,0 +1,36 @@
1
+ require 'rcapture'
2
+ include RCapture
3
+
4
+ # Closed polygon
5
+ class Polygon
6
+ include Interceptable
7
+ def vertices; self.edges; end
8
+ end
9
+
10
+ # Triangle
11
+ class Triangle < Polygon
12
+ def edges; 3; end
13
+ end
14
+
15
+ # Degenerate polygon
16
+ class Digon < Polygon
17
+ def edges; 1; end
18
+ def vertices; 2; end
19
+ end
20
+
21
+ # Four sided polygon
22
+ class Quadliteral < Polygon
23
+ def edges; 4; end
24
+ def vertices; super; end
25
+ end
26
+
27
+ called = 0
28
+ Polygon.capture :methods => :vertices do
29
+ called += 1
30
+ end
31
+
32
+ Triangle.new.vertices # +1
33
+ Digon.new.vertices # -
34
+ Quadliteral.new.vertices # +1
35
+
36
+ p called #=> 2
@@ -0,0 +1,39 @@
1
+ require 'rcapture'
2
+
3
+ # Enrich single instance
4
+ x = []
5
+ x.extend(RCapture::Interceptable)
6
+
7
+ # Define procs that will modify the input arguments
8
+ inc = Proc.new { |ci| ci.args[0] += 1 }
9
+ dec = Proc.new { |ci| ci.args[0] -= 1 }
10
+ mul = Proc.new { |ci| ci.args[0] *= 2 }
11
+
12
+ # Capture ':<<' multiple times.
13
+ x.capture_pre :methods => :<<, &inc
14
+ x.capture_pre :methods => :<<, &mul
15
+ x.capture_pre :methods => :<<, &inc
16
+ x.capture_pre :methods => :<<, &dec
17
+ x.capture_pre :methods => :<<, &dec
18
+
19
+ x << 2 << 4
20
+ p x
21
+ #=> [3,7]
22
+
23
+ # Similarily, you can modify return values
24
+ y = []
25
+ y.extend(RCapture::Interceptable)
26
+
27
+ inc = Proc.new { |ci| ci.return += 1 }
28
+ dec = Proc.new { |ci| ci.return -= 1 }
29
+ mul = Proc.new { |ci| ci.return *= 2 }
30
+
31
+ y.capture_post :methods => :[], &inc
32
+ y.capture_post :methods => :[], &mul
33
+ y.capture_post :methods => :[], &inc
34
+ y.capture_post :methods => :[], &dec
35
+ y.capture_post :methods => :[], &dec
36
+
37
+ y << 1 << 4
38
+ p y[0] #=> 3
39
+ p y[1] #=> 9
@@ -0,0 +1,32 @@
1
+ require 'rcapture'
2
+ require 'thread'
3
+
4
+ # Will keep track of Enumberable#inject results
5
+ inject_results = []
6
+ m = Mutex.new
7
+
8
+ module Enumerable
9
+ include RCapture::Interceptable
10
+ end
11
+
12
+ Enumerable.capture :methods => :inject do |cs|
13
+ # Callbacks can be invoked from multiple threads.
14
+ # So we need to synchronize access to shared resources.
15
+ m.synchronize do
16
+ inject_results << cs.return
17
+ end
18
+ end
19
+
20
+ array = []
21
+ 10000.times {array << rand(100)}
22
+
23
+ threads = []
24
+ 5.times do
25
+ t = Thread.new do
26
+ sum = array.inject(0) {|memo, e| memo + e}
27
+ end
28
+ threads << t
29
+ end
30
+ threads.each {|t| t.join }
31
+
32
+ p inject_results #=> [493311, 493311, 493311, 493311, 493311] or similar
@@ -0,0 +1,30 @@
1
+ require 'rcapture'
2
+ include RCapture
3
+
4
+ class X
5
+ include Interceptable
6
+ def initialize(name); @name = name; end
7
+ def say_hello; puts "Hello, #{@name}!"; end
8
+ end
9
+
10
+ class Y < X
11
+ def initialize; super("Y"); end
12
+ end
13
+
14
+ X.capture :class_methods => :new do |ci|
15
+ ci.block.call(ci.return) if ci.block
16
+ end
17
+
18
+ # Now you can use X and Y as if it supports
19
+ # blocks
20
+ x = X.new("Christoph") do |x|
21
+ x.say_hello #=> "Hello, Christoph!"
22
+ end
23
+
24
+ y = Y.new do |y|
25
+ y.say_hello #=> "Hello, Y!"
26
+ end
27
+
28
+ # Or leaf the block argument away
29
+ x = X.new("Christoph")
30
+ y = Y.new
@@ -0,0 +1,16 @@
1
+ require 'rcapture'
2
+
3
+ class Fixnum
4
+ include RCapture::Interceptable
5
+ end
6
+
7
+ op_count = 0
8
+ Fixnum.capture :methods => :+ do |cs|
9
+ # Here we use Fixnum#+ inside the callback.
10
+ # RCapture takes special care that anything that
11
+ # happens inside a callback remains capture-free.
12
+ op_count = op_count + 1
13
+ end
14
+
15
+ x = 1 + 1 + 2 + 3
16
+ p op_count #=> 3
@@ -0,0 +1,19 @@
1
+ #
2
+ # (c) Christoph Heindl, 2010
3
+ # http://cheind.wordpress.com
4
+ #
5
+
6
+ require 'rcapture/symbol'
7
+ require 'rcapture/capture_status'
8
+ require 'rcapture/captured_info'
9
+ require 'rcapture/singleton_class'
10
+ require 'rcapture/interceptable'
11
+ require 'rcapture/capture'
12
+ if RUBY_VERSION >= "1.9" # will fail when ruby reaches 1.10
13
+ require 'rcapture/capture_19x'
14
+ else
15
+ require 'rcapture/capture_18x'
16
+ end
17
+
18
+
19
+
@@ -0,0 +1,60 @@
1
+
2
+ module RCapture
3
+
4
+ # RCapture internal methods
5
+ module Internal # :nodoc:
6
+
7
+ # Perform a pre-capture on the given class.
8
+ def Internal.capture_pre(klass, args, &callback)
9
+ CaptureStatus.current.use(false) do
10
+ RCapture::Internal.capture(klass, { :type => :pre }.merge(args), &callback)
11
+ end
12
+ end
13
+
14
+ # Perform a post-capture on the given class.
15
+ def Internal.capture_post(klass, args, &callback)
16
+ CaptureStatus.current.use(false) do
17
+ RCapture::Internal.capture(klass, { :type => :post }.merge(args), &callback)
18
+ end
19
+ end
20
+
21
+ # Dispatches to internal methods by arguments provided.
22
+ def Internal.capture(klass, args, &callback)
23
+ args = { :methods => [], :class_methods => [] }.merge(args)
24
+
25
+ RCapture::Internal.capture_methods(
26
+ klass,
27
+ args[:methods].to_a,
28
+ args,
29
+ &callback
30
+ )
31
+
32
+ RCapture::Internal.capture_methods(
33
+ RCapture::Internal.singleton_class(klass),
34
+ args[:class_methods].to_a,
35
+ args,
36
+ &callback
37
+ )
38
+ end
39
+
40
+ # Iterate over each method and install capturing code
41
+ def Internal.capture_methods(klass, methods, additional_args, &callback)
42
+ type = additional_args[:type]
43
+ methods.each do |m|
44
+ RCapture::Internal.capture_single_method(klass, m.to_sym, type, additional_args, &callback)
45
+ end
46
+ end
47
+
48
+ # Install capturing code at single method.
49
+ # Dispatches based on the type of capturing (pre/post)
50
+ def Internal.capture_single_method(klass, method, type, additional_args, &callback)
51
+ case type
52
+ when :post
53
+ RCapture::Internal.capture_single_post(klass, method, additional_args, &callback)
54
+ when :pre
55
+ RCapture::Internal.capture_single_pre(klass, method, additional_args, &callback)
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,78 @@
1
+
2
+ module RCapture
3
+
4
+ module Internal
5
+
6
+ # Pre-capture single method.
7
+ # This version is used on Ruby 1.8.x where define_method
8
+ # does not allow for block arguments. See http://tinyurl.com/yczowjx
9
+ def Internal.capture_single_pre(klass, method, additional_args, &callback)
10
+ prev = klass.instance_method(method)
11
+ id = prev.object_id
12
+ is_private = klass.private_instance_methods.include?(method.to_s)
13
+
14
+ klass.class_eval do
15
+ define_method "#{method}_#{id}_" do |b, *args|
16
+ status = CaptureStatus.current
17
+ call_prev = true; alt_ret = nil
18
+ if status.on?
19
+ status.use(false) do
20
+ ci = CapturedInfo.current
21
+ ci.fill(args, self, method, b)
22
+ callback.call(ci)
23
+ alt_ret = ci.return
24
+ call_prev = ci.predicate
25
+ end
26
+ end
27
+ if call_prev
28
+ prev.bind(self).call(*args, &b)
29
+ else
30
+ alt_ret
31
+ end
32
+ end
33
+ private "#{method}_#{id}_"
34
+ end
35
+
36
+ klass.class_eval <<-EOF
37
+ def #{method} *args, &block
38
+ self.send "#{method}_#{id}_", block, *args
39
+ end
40
+ private "#{method}" if #{is_private}
41
+ EOF
42
+ end
43
+
44
+ # Post-capture single method.
45
+ # This version is used on Ruby 1.8.x where define_method
46
+ # does not allow for block arguments. See http://tinyurl.com/yczowjx
47
+ def Internal.capture_single_post(klass, method, additional_args, &callback)
48
+ prev = klass.instance_method(method)
49
+ id = prev.object_id
50
+ is_private = klass.private_instance_methods.include?(method.to_s)
51
+
52
+ klass.class_eval do
53
+ define_method "#{method}_#{id}_" do |b, *args|
54
+ ret = prev.bind(self).call(*args, &b)
55
+ status = CaptureStatus.current
56
+ if status.on?
57
+ status.use(false) do
58
+ ci = CapturedInfo.current
59
+ ci.fill(args, self, method, b, ret)
60
+ callback.call(ci)
61
+ ret = ci.return
62
+ end
63
+ end
64
+ ret
65
+ end
66
+ private "#{method}_#{id}_"
67
+ end
68
+
69
+ klass.class_eval <<-EOF
70
+ def #{method} *args, &block
71
+ self.send "#{method}_#{id}_", block, *args
72
+ end
73
+ private "#{method}" if #{is_private}
74
+ EOF
75
+ end
76
+ end
77
+ end
78
+