rcapture 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
+