markevans-method_call_recorder 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Mark Evans
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,26 @@
1
+ Method call recorder
2
+ ===================
3
+ A method call recorder is an object on which you can call any method, with any arguments (and continue doing so on child objects).
4
+
5
+ rec = MethodCallRecorder.new
6
+ rec[:this].is(:a, 'nice')[:little, :object].isnt.it? # This does very little, but it works!
7
+
8
+ You can then play methods back on other objects.
9
+
10
+ rec[1].upcase # first record the method chain
11
+ rec._play(['hello','there']) # => 'THERE'
12
+
13
+ You can also inspect the method chain if you wish (shows all methods called including arguments). This is an array of `MethodCall` objects.
14
+ Using the last example:
15
+
16
+ method_call = rec._method_chain.first
17
+ method_call.method # :[] (a symbol representing the method call)
18
+ method_call.args # [1] (an array of the args)
19
+
20
+ Install
21
+ -------
22
+ Install from github gems in usual way
23
+
24
+ Copyright
25
+ --------
26
+ Copyright (c) 2009 Mark Evans. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "method_call_recorder"
8
+ gem.summary = "An object on which you can call anything, where nothing happens, but the whole method chain is recorded"
9
+ gem.email = "mark@new-bamboo.co.uk"
10
+ gem.homepage = "http://github.com/markevans/method_call_recorder"
11
+ gem.authors = ["Mark Evans"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.spec_files = FileList['spec/**/*_spec.rb']
23
+ end
24
+
25
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+
32
+ task :default => :spec
33
+
34
+ require 'rake/rdoctask'
35
+ Rake::RDocTask.new do |rdoc|
36
+ if File.exist?('VERSION.yml')
37
+ config = YAML.load(File.read('VERSION.yml'))
38
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
39
+ else
40
+ version = ""
41
+ end
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "method_call_recorder #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
48
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,70 @@
1
+ class MethodCall
2
+
3
+
4
+ def initialize(meth, *args, &blk)
5
+ @method, @args = meth, args
6
+ @block = blk
7
+ end
8
+
9
+ attr_reader :method, :args, :block
10
+
11
+ def call_on(obj)
12
+ obj.send(method, *args)
13
+ end
14
+
15
+ def to_a
16
+ [method, *args]
17
+ end
18
+
19
+ def setter?
20
+ !!(method.to_s =~ /=$/)
21
+ end
22
+
23
+ def getter?
24
+ !setter?
25
+ end
26
+
27
+ def to_setter(value)
28
+ new_method_call = self.deep_dup
29
+ if setter?
30
+ new_method_call.args[-1] = value
31
+ else
32
+ new_method_call.args << value
33
+ new_method_call.method = "#{new_method_call.method}=".to_sym
34
+ end
35
+ new_method_call
36
+ end
37
+
38
+ def type
39
+ case method.to_s
40
+ when '[]' then (args.first.is_a?(Fixnum) ? :array_reader : :hash_reader)
41
+ when '[]=' then (args.first.is_a?(Fixnum) ? :array_writer : :hash_writer)
42
+ when /\=$/ then :attr_writer
43
+ else :attr_reader
44
+ end
45
+ end
46
+
47
+ def guess_receiver_type
48
+ case type
49
+ when :array_reader, :array_writer then Array
50
+ when :hash_reader, :hash_writer then Hash
51
+ end
52
+ end
53
+
54
+ def ==(other)
55
+ self.method == other.method && self.args == other.args
56
+ end
57
+
58
+ def deep_dup
59
+ self.class.new(method, *args, &block)
60
+ end
61
+
62
+ def to_s
63
+ to_a.to_s
64
+ end
65
+
66
+ protected
67
+
68
+ attr_writer :method
69
+
70
+ end
@@ -0,0 +1,58 @@
1
+ class MethodCallRecorder
2
+
3
+ def _play(object, &blk)
4
+ i = 0
5
+ _method_chain.inject(object) do |obj, method_call|
6
+ i += 1
7
+ yield(obj, method_call, _method_chain[i]) if block_given?
8
+ method_call.call_on(obj)
9
+ end
10
+ end
11
+
12
+ def _first_method
13
+ _method_chain.first
14
+ end
15
+
16
+ def _empty?
17
+ _method_chain.empty?
18
+ end
19
+
20
+ def to_s
21
+ _method_chain.inspect
22
+ end
23
+
24
+ def _method_chain
25
+ @_method_chain ||= []
26
+ end
27
+
28
+ def _to_setter(value)
29
+ new_rec = self.dup
30
+ new_rec._method_chain = self._method_chain.dup
31
+ new_rec._method_chain[-1] = self._method_chain[-1].to_setter(value)
32
+ new_rec
33
+ end
34
+
35
+ def _reset!
36
+ _method_chain = []
37
+ end
38
+
39
+ def _select(meth, args=nil)
40
+ _method_chain.select do |method_call|
41
+ selected = (method_call.method == meth)
42
+ selected &&= (method_call.args == args) if args
43
+ selected
44
+ end
45
+ end
46
+
47
+ protected
48
+
49
+ attr_writer :_method_chain
50
+
51
+ private
52
+
53
+ def method_missing(meth, *args)
54
+ _method_chain << MethodCall.new(meth, *args)
55
+ self
56
+ end
57
+
58
+ end
@@ -0,0 +1,3 @@
1
+ %w{method_call method_call_recorder}.each do |file|
2
+ require File.dirname(__FILE__)+'/method_call_recorder/'+file
3
+ end
@@ -0,0 +1,51 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{method_call_recorder}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Mark Evans"]
9
+ s.date = %q{2009-07-04}
10
+ s.email = %q{mark@new-bamboo.co.uk}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.markdown"
14
+ ]
15
+ s.files = [
16
+ ".document",
17
+ ".gitignore",
18
+ "LICENSE",
19
+ "README.markdown",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "lib/method_call_recorder.rb",
23
+ "lib/method_call_recorder/method_call.rb",
24
+ "lib/method_call_recorder/method_call_recorder.rb",
25
+ "method_call_recorder.gemspec",
26
+ "spec/method_call_recorder_spec.rb",
27
+ "spec/method_call_spec.rb",
28
+ "spec/spec_helper.rb"
29
+ ]
30
+ s.has_rdoc = true
31
+ s.homepage = %q{http://github.com/markevans/method_call_recorder}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.1}
35
+ s.summary = %q{An object on which you can call anything, where nothing happens, but the whole method chain is recorded}
36
+ s.test_files = [
37
+ "spec/method_call_recorder_spec.rb",
38
+ "spec/method_call_spec.rb",
39
+ "spec/spec_helper.rb"
40
+ ]
41
+
42
+ if s.respond_to? :specification_version then
43
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
44
+ s.specification_version = 2
45
+
46
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
47
+ else
48
+ end
49
+ else
50
+ end
51
+ end
@@ -0,0 +1,125 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ def stub_method_call(*args)
4
+ method_call = mock('method_call')
5
+ MethodCall.stub!(:new).with(*args).and_return method_call
6
+ method_call
7
+ end
8
+
9
+ describe MethodCallRecorder do
10
+
11
+ before(:each) do
12
+ @rec = MethodCallRecorder.new
13
+ end
14
+
15
+ it "should allow any combination of chained methods on it" do
16
+ lambda do
17
+ MethodCallRecorder.new.egg[23]['fish'].another = 5
18
+ MethodCallRecorder.new[:twenty].what.the / 8 * 4 + 1
19
+ end.should_not raise_error
20
+ end
21
+
22
+ it "should save methods called on it in it's 'method chain'" do
23
+ mc1 = stub_method_call(:this, :should)
24
+ mc2 = stub_method_call(:[], 'be')
25
+ mc3 = stub_method_call(:saved)
26
+ @rec.this(:should)['be'].saved
27
+ @rec._method_chain.should == [mc1, mc2, mc3]
28
+ end
29
+
30
+ it "should be able to play back its method chain on another object" do
31
+ inner = mock('inner', :duck => 'hello')
32
+ struct = mock('struct', :fish => inner)
33
+ obj = { :a => [struct, 2] }
34
+ @rec[:a][0].fish.duck
35
+ @rec._play(obj).should == 'hello'
36
+ end
37
+
38
+ it "should just return the object on play if its method chain is empty" do
39
+ obj = Object.new
40
+ @rec._play(obj).should == obj
41
+ end
42
+
43
+ it "should record append to the method chain if you record twice" do
44
+ mc1 = stub_method_call(:once)
45
+ mc2 = stub_method_call(:twice)
46
+ @rec.once
47
+ @rec._method_chain.should == [mc1]
48
+ @rec.twice
49
+ @rec._method_chain.should == [mc1, mc2]
50
+ end
51
+
52
+ it "should allow resetting the method chain" do
53
+ mc1 = stub_method_call(:once)
54
+ mc2 = stub_method_call(:twice)
55
+ @rec.once
56
+ @rec._method_chain.should == [mc1]
57
+ @rec._reset!
58
+ @rec.twice
59
+ @rec._method_chain.should == [mc1, mc2]
60
+ end
61
+
62
+ it "should yield the current sub object, and the next two methods to be called as it plays back" do
63
+ mc1 = MethodCall.new(:[], :hello)
64
+ mc2 = MethodCall.new(:[], 2)
65
+ mc3 = MethodCall.new(:eggy_bread)
66
+ str = 'yes'
67
+ def str.eggy_bread; 'egg'; end
68
+ @rec[:hello][2].eggy_bread
69
+ yielded_values = []
70
+ @rec._play({:hello => ['no','and',str]}) do |obj, method_call, next_method_call|
71
+ yielded_values << [obj, method_call, next_method_call]
72
+ end
73
+ yielded_values.should == [
74
+ [ {:hello => ['no','and',str]}, mc1, mc2 ],
75
+ [ ['no','and',str], mc2, mc3 ],
76
+ [ str, mc3, nil ]
77
+ ]
78
+ end
79
+
80
+ it "should return the first method called on it" do
81
+ @rec.hello.how.are['you']
82
+ @rec._first_method.should == MethodCall.new(:hello)
83
+ end
84
+
85
+ it "should return itself" do
86
+ @rec.hello[4].this(:is).now('innit').should == @rec
87
+ end
88
+
89
+ it "should return a clone of itself with the last method as a setter" do
90
+ mc1 = MethodCall.new(:[], :hello)
91
+ mc2 = MethodCall.new(:there)
92
+ mc3 = MethodCall.new(:there=, 4)
93
+ @rec[:hello].there
94
+ other_rec = @rec._to_setter(4)
95
+ @rec._method_chain.should == [mc1, mc2]
96
+ other_rec._method_chain.should == [mc1, mc3]
97
+ end
98
+
99
+ it "should say if empty" do
100
+ @rec._empty?.should be_true
101
+ end
102
+
103
+ it "should say if not empty" do
104
+ @rec.hello._empty?.should be_false
105
+ end
106
+
107
+ describe "selecting methods" do
108
+ before :each do
109
+ @rec.this[:is, 2].a(3, 'go').metho[:call].a('yo')
110
+ @m1, @m2, @m3, @m4, @m5, @m6 = @rec._method_chain
111
+ end
112
+
113
+ it "should select matching only on method" do
114
+ @rec._select(:[]).should == [@m2, @m5]
115
+ @rec._select(:a).should == [@m3, @m6]
116
+ end
117
+
118
+ it "should select matching on method and args" do
119
+ @rec._select(:[], [:call]).should == [@m5]
120
+ @rec._select(:a, ['yo']).should == [@m6]
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,161 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ describe MethodCall do
4
+
5
+ before(:each) do
6
+ @method_call = MethodCall.new(:hello, 4, 'yo')
7
+ end
8
+
9
+ it "should return the method name" do
10
+
11
+ @method_call.method.should == :hello
12
+ end
13
+ it "should return the args" do
14
+ @method_call.args.should == [4, 'yo']
15
+ end
16
+ it "should return the block as nil if not given" do
17
+ @method_call.block.should be_nil
18
+ end
19
+ it "should return the block if given" do
20
+ method_call = MethodCall.new(:hi){ |doobie| puts doobie }
21
+ method_call.block.should be_kind_of(Proc)
22
+ end
23
+
24
+ describe "call_on" do
25
+ it "should call the method on the passed in object and return the value" do
26
+ string = 'yoh washup'
27
+ method_call = MethodCall.new(:gsub, 'h', 'e')
28
+ method_call.call_on(string).should == 'yoe waseup'
29
+ end
30
+ end
31
+
32
+ describe "to array" do
33
+ it "should return the method and args as an array" do
34
+ MethodCall.new(:egg, 'cheese', 5).to_a.should == [:egg, 'cheese', 5]
35
+ end
36
+ end
37
+
38
+ describe "setter?" do
39
+ it "should return true for a setter" do
40
+ MethodCall.new(:doobie=, '4').setter?.should be_true
41
+ MethodCall.new(:[]=, 2, 3).setter?.should be_true
42
+ end
43
+ it "should return false for a getter" do
44
+ MethodCall.new(:doobie, '4').setter?.should be_false
45
+ MethodCall.new(:[], 2, 3).setter?.should be_false
46
+ end
47
+ end
48
+
49
+ describe "getter?" do
50
+ it "should be false if setter is true" do
51
+ @method_call.should_receive(:setter?).and_return true
52
+ @method_call.getter?.should be_false
53
+ end
54
+ it "should be true if setter is false" do
55
+ @method_call.should_receive(:setter?).and_return false
56
+ @method_call.getter?.should be_true
57
+ end
58
+ end
59
+
60
+ describe "duplication" do
61
+ before(:each) do
62
+ @mc1 = MethodCall.new(:[], 3)
63
+ @mc2 = @mc1.deep_dup
64
+ end
65
+ it "should be able to duplicate itself" do
66
+ @mc2.should be_instance_of(MethodCall)
67
+ end
68
+ it "should deep clone its method" do
69
+ @mc2.send(:method=, :hi)
70
+ @mc2.method.should == :hi # Just to check
71
+ @mc1.method.should == :[]
72
+ end
73
+ it "should deep clone its args" do
74
+ @mc2.args << :gog
75
+ @mc2.args.should == [3, :gog] # Just to check
76
+ @mc1.args.should == [3]
77
+ end
78
+ end
79
+
80
+ describe "making methods into setters" do
81
+
82
+ it "should turn [] into a setter" do
83
+ MethodCall.new(:[], 3).to_setter('hello').to_a.should == [:[]=, 3, 'hello']
84
+ end
85
+
86
+ it "should leave []= as it is but assign new value" do
87
+ MethodCall.new(:[]=, 3, 5).to_setter('hello').to_a.should == [:[]=, 3, 'hello']
88
+ end
89
+
90
+ it "should turn an arbitrary method call into a setter by adding the value at the end of the args" do
91
+ MethodCall.new(:hello, 'there').to_setter(65).to_a.should == [:hello=, 'there', 65]
92
+ end
93
+
94
+ it "should not change an arbitrary method which is already a setter but assign a new value" do
95
+ MethodCall.new(:hello=, 'there').to_setter(65).to_a.should == [:hello=, 65]
96
+ end
97
+
98
+ it "should not change itself" do
99
+ method_call = MethodCall.new(:[], 3)
100
+ setter = method_call.to_setter('yo')
101
+ method_call.to_a.should == [:[], 3]
102
+ end
103
+
104
+ end
105
+
106
+ describe "getting the type" do
107
+ it "should detect hash readers" do
108
+ MethodCall.new(:[], 'there').type.should == :hash_reader
109
+ end
110
+ it "should detect hash writers" do
111
+ MethodCall.new(:[]=, 'there', 4).type.should == :hash_writer
112
+ end
113
+ it "should detect array readers" do
114
+ MethodCall.new(:[], 7).type.should == :array_reader
115
+ end
116
+ it "should detect array writer" do
117
+ MethodCall.new(:[]=, 3, 5).type.should == :array_writer
118
+ end
119
+ it "should detect getters" do
120
+ MethodCall.new(:undre).type.should == :attr_reader
121
+ end
122
+ it "should detect setters" do
123
+ MethodCall.new(:bob=, 'there').type.should == :attr_writer
124
+ end
125
+
126
+ end
127
+
128
+ describe "comparing with other method calls" do
129
+ it "should return as equal if both have same method and args" do
130
+ MethodCall.new(:yes, 'sadf', :ue, 4).should == MethodCall.new(:yes, 'sadf', :ue, 4)
131
+ end
132
+ it "should return as not equal if same method but different args" do
133
+ MethodCall.new(:yes, 'sadf', 4).should_not == MethodCall.new(:yes, 'sadf', :ue, 4)
134
+ end
135
+ it "should return as not equal if same args but different method" do
136
+ MethodCall.new(:yes, 'sadf', :ue, 4).should_not == MethodCall.new(:no, 'sadf', :ue, 4)
137
+ end
138
+ it "should return as not equal if different method and args" do
139
+ MethodCall.new(:yes, 'sadf', :ue, 4).should_not == MethodCall.new(:no, 3, 5, 'sdf')
140
+ end
141
+ end
142
+
143
+ describe "guessing receiver type for square brackets calls" do
144
+
145
+ [:hello, 'hello', 4.3].each do |key|
146
+ it "should return Hash if key is #{key.inspect}" do
147
+ MethodCall.new(:[],:hello).guess_receiver_type.should == Hash
148
+ end
149
+ end
150
+
151
+ it "should return Array if key is an integer" do
152
+ MethodCall.new(:[],4).guess_receiver_type.should == Array
153
+ end
154
+
155
+ it "should return nil if it can't guess" do
156
+ MethodCall.new(:yo_man, 'diggerdy').guess_receiver_type.should be_nil
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,2 @@
1
+ require 'spec'
2
+ require File.dirname(__FILE__)+'/../lib/method_call_recorder'
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: markevans-method_call_recorder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mark Evans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: mark@new-bamboo.co.uk
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.markdown
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - LICENSE
29
+ - README.markdown
30
+ - Rakefile
31
+ - VERSION
32
+ - lib/method_call_recorder.rb
33
+ - lib/method_call_recorder/method_call.rb
34
+ - lib/method_call_recorder/method_call_recorder.rb
35
+ - method_call_recorder.gemspec
36
+ - spec/method_call_recorder_spec.rb
37
+ - spec/method_call_spec.rb
38
+ - spec/spec_helper.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/markevans/method_call_recorder
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --charset=UTF-8
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.2.0
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: An object on which you can call anything, where nothing happens, but the whole method chain is recorded
65
+ test_files:
66
+ - spec/method_call_recorder_spec.rb
67
+ - spec/method_call_spec.rb
68
+ - spec/spec_helper.rb