code_web 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ module CodeWeb
2
+ class MethodCache
3
+ # Map<String,Array<MethodCall>>
4
+ attr_accessor :method_calls
5
+
6
+ # only store the information on these methods
7
+ attr_accessor :method_regex
8
+ attr_accessor :arg_regex
9
+
10
+ def initialize(method_regex = nil)
11
+ @method_calls=[]
12
+ @method_regex = method_regex
13
+ end
14
+
15
+ def <<(mc)
16
+ @method_calls << mc if detect?(mc)
17
+ end
18
+
19
+ def detect?(mc)
20
+ (method_regex.nil? || mc.full_method_name =~ method_regex) &&
21
+ (
22
+ arg_regex.nil? || (
23
+ mc.hash_args? &&
24
+ mc.arg_keys.detect {|key| key =~ arg_regex }
25
+ )
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,116 @@
1
+ module CodeWeb
2
+ # method call reference
3
+ class MethodCall
4
+ # file that has this method call
5
+ attr_accessor :filename
6
+ # line number that has this method
7
+ attr_accessor :line
8
+ # method name
9
+ attr_accessor :name
10
+ # what arguments are passed in
11
+ attr_accessor :args
12
+ alias :arguments :args
13
+ # is this calling a yield block
14
+ attr_accessor :is_yielding
15
+
16
+ def initialize(filename, line, name=nil, args=[], is_yielding=false)
17
+ @filename = filename
18
+ @line = line
19
+ @name = name
20
+ @args = sorted_hash(args)
21
+ @is_yielding = !! is_yielding
22
+ end
23
+
24
+ def args?
25
+ args && !args.empty?
26
+ end
27
+
28
+ def yields?
29
+ is_yielding
30
+ end
31
+
32
+ def method_types
33
+ args.map { |arg| arg_type(arg) }
34
+ end
35
+
36
+ def small_signature
37
+ [arg_type(args.first), args.size]
38
+ end
39
+
40
+ def signature
41
+ "#{full_method_name}(#{sorted_args.to_s})#{" yields" if is_yielding}"
42
+ end
43
+
44
+ def short_method_name
45
+ Array(name).last
46
+ end
47
+
48
+ def full_method_name
49
+ Array(name).compact.join(".")
50
+ end
51
+
52
+ def short_filename
53
+ filename.split("/").last if filename
54
+ end
55
+
56
+ def sorted_args(hash=@args)
57
+ hash.map {|arg| sorted_hash(arg) }.join(", ")
58
+ end
59
+
60
+ def sorted_hash(args)
61
+ case args
62
+ when Hash
63
+ args.each_pair.sort_by {|n,v| n }.inject({}) {|h, (n,v)| h[n]=sorted_hash(v); h}
64
+ when Array
65
+ args
66
+ else
67
+ args
68
+ end
69
+ end
70
+
71
+ def hash_args?
72
+ args.first.class == Hash
73
+ end
74
+
75
+ def args_size
76
+ args.size
77
+ end
78
+
79
+ def hash_arg
80
+ args.first
81
+ end
82
+
83
+ def arg_keys
84
+ args.first.keys
85
+ end
86
+
87
+ def ==(other)
88
+ other &&
89
+ other.name == @name &&
90
+ other.args == @args &&
91
+ other.is_yielding == @is_yielding
92
+ end
93
+
94
+ # used by debugging (not sure if this should be signature)
95
+ def to_s(spaces = '')
96
+ "#{spaces}#{full_method_name}(#{args.map{|arg|arg.inspect}.join(", ")})#{" do" if is_yielding}"
97
+ end
98
+
99
+ private
100
+
101
+ def arg_type(arg)
102
+ case arg
103
+ when Array
104
+ '[]'
105
+ when Hash
106
+ '{}'
107
+ when nil
108
+ "nil"
109
+ when Symbol
110
+ ':'
111
+ else
112
+ 'str'
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,60 @@
1
+ require 'forwardable'
2
+ #collection of similar method calls
3
+ module CodeWeb
4
+ class MethodList
5
+ extend Forwardable
6
+ include Enumerable
7
+
8
+ # what was used in the group by
9
+ attr_accessor :name
10
+ # the collection (actually [[k,[v1,v2]],[k2,[v1,v2]]])
11
+ attr_accessor :collection
12
+
13
+ def initialize(name, collection)
14
+ @name = name
15
+ @collection = collection
16
+ end
17
+
18
+ def group_by(name, arg_regex=nil, sort_by = nil, &block)
19
+ if block.nil?
20
+ if arg_regex.nil?
21
+ block = Proc.new { |m| m.send(name).to_s }
22
+ else
23
+ block = Proc.new {|m|
24
+ if m.hash_args?
25
+ m.hash_arg.collect {|n,v| v if n =~ arg_regex}.compact.join(" ")
26
+ else
27
+ m.signature
28
+ end
29
+ }
30
+ end
31
+ end
32
+ MethodList.new(name, collection.group_by(&block).sort_by {|n, ms| sort_by ? ms.first.send(sort_by) : n })
33
+ end
34
+
35
+ def f
36
+ collection.first
37
+ end
38
+
39
+ delegate [:detect, :each_with_index, :count] => :collection
40
+ delegate [:args_size, :hash_arg, :hash_args?] => :f
41
+
42
+ def detect(&block)
43
+ collection.detect(&block)
44
+ end
45
+
46
+ def each(&block)
47
+ collection.each do |n, c|
48
+ yield MethodList.new(n,c)
49
+ end
50
+ end
51
+
52
+ def arg_keys
53
+ @arg_keys ||= collection.inject(Set.new) {|acc, m| m.arg_keys.each {|k| acc << k} ; acc}.sort_by {|n| n}
54
+ end
55
+
56
+ def self.group_by(collection, name)
57
+ MethodList.new(nil, collection).group_by(name)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,41 @@
1
+ module CodeWeb
2
+ class TextReport
3
+ # @!attribute :method_calls [r]
4
+ # list of all the method_Calls
5
+ # @return [Array<MethodCall>]
6
+ attr_accessor :method_calls
7
+ attr_accessor :arg_regex
8
+ def arg_regex? ; ! arg_regex.nil? ; end
9
+
10
+ def initialize(method_calls, class_map=nil, arg_regex=nil, out=STDOUT)
11
+ @method_calls = method_calls
12
+ @arg_regex = arg_regex
13
+ @out = out
14
+ end
15
+
16
+ def report
17
+ methods_by_name.each do |methods|
18
+ @out.puts "---- #{methods.name} ----"
19
+ methods.group_by(:signature, arg_regex).each do |methods_with_signature|
20
+ if arg_regex?
21
+ @out.puts " --> #{arg_regex.inspect}=#{methods_with_signature.name}"
22
+ else
23
+ @out.puts " --> #{methods_with_signature.name}"
24
+ end
25
+ methods_with_signature.each_with_index do |method, i|
26
+ @out.puts
27
+ @out.puts method.signature
28
+ @out.puts "#{method.filename}:#{method.line}"
29
+ end
30
+ @out.puts
31
+ @out.puts
32
+ end
33
+ @out.puts
34
+ end
35
+ end
36
+
37
+ def methods_by_name
38
+ MethodList.group_by(method_calls, :short_method_name)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module CodeWeb
2
+ VERSION = "0.0.4"
3
+ end
data/lib/code_web.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "code_web/version"
2
+
3
+ module CodeWeb
4
+ end
5
+
6
+ require 'code_web/method_call'
7
+ require 'code_web/method_list'
8
+ require 'code_web/method_cache'
9
+ require 'code_web/code_parser'
10
+ require 'code_web/text_report'
11
+ require 'code_web/html_report'
@@ -0,0 +1,252 @@
1
+ require 'spec_helper'
2
+
3
+ #$debug=true
4
+ #$verbose=true
5
+ describe CodeWeb::CodeParser do
6
+
7
+ context "method call" do
8
+ it 'should add a method' do
9
+ subject << CodeWeb::MethodCall.new(nil, "puts", ['"x"'], false )
10
+ expect(method_calls('puts')).to eq([
11
+ meth('puts',['"x"'])
12
+ ])
13
+ end
14
+
15
+ it 'should support basic method call' do
16
+ parse %{puts}
17
+ expect(method_calls('puts')).to eq([
18
+ meth('puts',[])
19
+ ])
20
+ end
21
+
22
+ it 'should support method call with arguments' do
23
+ parse %{puts "x", :y, true, false}
24
+ expect(method_calls('puts')).to eq([
25
+ meth('puts',['"x"', :y, :true, :false])
26
+ ])
27
+ end
28
+
29
+ it 'should support method calls with hash arguments' do
30
+ parse %{puts(a:5, "b" => 3)}
31
+ expect(method_calls('puts')).to eq([
32
+ meth('puts',{:a => 5, "b" => 3})
33
+ ])
34
+ end
35
+
36
+ it 'should support method call ob objects' do
37
+ parse %{y.puts "x"}
38
+ expect(method_calls('y.puts')).to eq([
39
+ meth('y.puts',['"x"'])
40
+ ])
41
+ end
42
+
43
+ #NOTE the chaining isn't perfect
44
+ it "should support method chaining" do
45
+ parse "x(a:5).y().z(5)"
46
+ expect(method_calls('x')).to eq([
47
+ meth('x', [{a:5}])
48
+ ])
49
+
50
+ expect(method_calls('x(...).y')).to eq([
51
+ meth('x(...).y')
52
+ ])
53
+
54
+ expect(method_calls('x(...).y.z')).to eq([
55
+ meth('x(...).y.z',[5])
56
+ ])
57
+ end
58
+
59
+ it 'should support method calls with method calls' do
60
+ parse "a(b(5))"
61
+ expect(method_calls('b')).to eq([
62
+ meth('b',[5])
63
+ ])
64
+ expect(method_calls('a')).to eq([
65
+ meth('a','b(...)')
66
+ ])
67
+ end
68
+ end
69
+
70
+ context 'blocks' do
71
+ it 'should support if blocks' do
72
+ parse "if a(5) ; b(5) ; else c(5) ; end"
73
+ expect(method_calls('a')).to eq([
74
+ meth('a',[5])
75
+ ])
76
+ expect(method_calls('b')).to eq([
77
+ meth('b',[5])
78
+ ])
79
+ expect(method_calls('c')).to eq([
80
+ meth('c',[5])
81
+ ])
82
+ end
83
+
84
+ it 'should support yield blocks' do
85
+ parse "a(5) { |x| b(x) }"
86
+ expect(method_calls('a')).to eq([
87
+ meth('a',[5], true)
88
+ ])
89
+ expect(method_calls('b')).to eq([
90
+ meth('b',[:x], false)
91
+ ])
92
+ end
93
+
94
+ it 'should support rescue blocks' do
95
+ parse %{
96
+ begin
97
+ a(5)
98
+ rescue => e
99
+ b(5)
100
+ ensure
101
+ c()
102
+ end
103
+ }
104
+ expect(method_calls('a')).to eq([
105
+ meth('a',[5])
106
+ ])
107
+ expect(method_calls('b')).to eq([
108
+ meth('b',[5])
109
+ ])
110
+ expect(method_calls('c')).to eq([
111
+ meth('c')
112
+ ])
113
+ end
114
+
115
+ it 'should support rescue blocks' do
116
+ parse "begin ; a(5) ; rescue ; b(5) ; end"
117
+ expect(method_calls('a')).to eq([
118
+ meth('a',[5])
119
+ ])
120
+ expect(method_calls('b')).to eq([
121
+ meth('b',[5])
122
+ ])
123
+ end
124
+
125
+ it 'should support rescue inline' do
126
+ parse "a(5) rescue b(5)"
127
+ expect(method_calls('a')).to eq([
128
+ meth('a',[5])
129
+ ])
130
+ expect(method_calls('b')).to eq([
131
+ meth('b',[5])
132
+ ])
133
+ end
134
+ end
135
+
136
+ context 'variables' do
137
+ it "should support global variables" do
138
+ parse %{$x=puts}
139
+ expect(method_calls('puts')).to eq([
140
+ meth('puts')
141
+ ])
142
+ end
143
+ it "should support global variable fetch" do
144
+ parse %{$x = puts}
145
+ expect(method_calls('puts')).to eq([
146
+ meth('puts')
147
+ ])
148
+ end
149
+ it "should support constants" do
150
+ parse %{
151
+ ABC=puts
152
+ Class::ABC.runx
153
+ }
154
+ expect(method_calls('puts')).to eq([
155
+ meth('puts')
156
+ ])
157
+ expect(method_calls('Class.ABC.runx')).to eq([
158
+ meth('Class.ABC.runx')
159
+ ])
160
+ end
161
+ end
162
+
163
+ it 'should parse modules' do
164
+ parse %{
165
+ module X
166
+ ABC=abc()
167
+ def method1
168
+ @module_var=mod1()
169
+ end
170
+ def method2
171
+ @@module_var=mod2()
172
+ end
173
+ def method3
174
+ return mod3()
175
+ end
176
+ end
177
+ }
178
+ expect(method_calls('abc')).to eq([
179
+ meth('abc')
180
+ ])
181
+ expect(method_calls('mod1')).to eq([
182
+ meth('mod1')
183
+ ])
184
+ expect(method_calls('mod2')).to eq([
185
+ meth('mod2')
186
+ ])
187
+ expect(method_calls('mod3')).to eq([
188
+ meth('mod3')
189
+ ])
190
+
191
+ end
192
+ it 'should support class' do
193
+ parse %{
194
+ module X
195
+ attr_accessor :var
196
+ class Class1
197
+ def method1
198
+ @var=m1(5)
199
+ end
200
+ end
201
+ class Class2 < Class1
202
+ def method2
203
+ var=m2()
204
+ end
205
+ end
206
+ end
207
+ }
208
+ expect(method_calls('m1')).to eq([
209
+ meth('m1',[5])
210
+ ])
211
+ expect(method_calls('m2')).to eq([
212
+ meth('m2')
213
+ ])
214
+ end
215
+
216
+ it "should interpolate strings" do
217
+ parse 'puts "abc#{subf()}"'
218
+ expect(method_calls('subf')).to eq([
219
+ meth('subf')
220
+ ])
221
+ end
222
+
223
+ it "should support logic" do
224
+ parse 'a() && b() || c() and d() or e()'
225
+ %w(a b c d e).each do |method_name|
226
+ expect(method_calls(method_name)).to eq([
227
+ meth(method_name)
228
+ ])
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ def method_calls(method_name=nil)
235
+ if method_name
236
+ subject.method_calls.select { |mc| mc.full_method_name =~ /#{method_name}/ }
237
+ else
238
+ subject.method_calls
239
+ end
240
+ end
241
+
242
+ def parse(body, require_string=nil)
243
+ test_method_name = caller[0].split(':').first #[0..1].join(':')
244
+ subject.parse(test_method_name, body, require_string)
245
+ end
246
+
247
+ def meth(name, args=[], is_yield=false, source = __FILE__)
248
+ name = [name.to_sym] if name.is_a?(String)
249
+ args = [args] unless args.is_a?(Array)
250
+ CodeWeb::MethodCall.new(source, 1, name, args, is_yield)
251
+ end
252
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe CodeWeb::MethodCache do
4
+ describe "#<<" do
5
+ context "with default regex" do
6
+ it { expect(subject.method_calls).to be_empty }
7
+ context "with method" do
8
+ before { subject << methodcall }
9
+ it { expect(subject.method_calls.size).to eq(1) }
10
+ end
11
+ end
12
+
13
+ context "with regex" do
14
+ subject { described_class.new(/good/)}
15
+ context "with matching regex" do
16
+ before { subject << methodcall("goodone") }
17
+ it { expect(subject.method_calls.size).to eq(1) }
18
+ end
19
+ context "with non-matching regex" do
20
+ before { subject << methodcall("badone") }
21
+ it { expect(subject.method_calls.size).to eq(0) }
22
+ end
23
+ end
24
+ end
25
+
26
+ def methodcall(name = "method", args = [])
27
+ CodeWeb::MethodCall.new("file.rb", 5, name, args)
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe CodeWeb::MethodCall do
4
+ subject { described_class }
5
+ it "should compare" do
6
+ expect(meth('puts', ['a', 'b'])).to eq(meth('puts',['a','b'], false, ['sample.rb']))
7
+ end
8
+
9
+ describe "#args?" do
10
+ it { expect(meth("method", nil)).not_to be_args}
11
+ it { expect(meth("method", [])).not_to be_args}
12
+ it { expect(meth("method", [:a])).to be_args}
13
+ it { expect(meth("method", [:a => 'a'])).to be_args}
14
+ end
15
+
16
+ describe "#yields?" do
17
+ it { expect(meth("method", [], false)).not_to be_yield }
18
+ it { expect(meth("method", [], true)).to be_yield }
19
+ end
20
+
21
+ describe "#method_types" do
22
+ it { expect(meth("method", [%w(a b), {'a' => 'b'}, nil, :a, 'b']).method_types).to eq(%w([] {} nil : str)) }
23
+ end
24
+
25
+ describe "#small_signature" do
26
+ it { expect(meth("method", [%w(a b)]).small_signature).to eq(["[]", 1]) }
27
+ it { expect(meth("method", [[], [], []]).small_signature).to eq(["[]", 3]) }
28
+ it { expect(meth("method", [{}, []]).small_signature).to eq(["{}", 2]) }
29
+ it { expect(meth("method", []).small_signature).to eq(["nil", 0]) }
30
+ end
31
+
32
+ describe "#signature" do
33
+ it { expect(meth("method", [%w(a b)]).signature).to eq("method(a, b)") }
34
+ #it { expect(meth("method", [[], [], []]).signature).to eq("method([],[],[])")}
35
+ # it { expect(meth("method", [{}, []]).signature).to eq("method({})") }
36
+ # it { expect(meth("method", []).signature).to eq("method(nil)") }
37
+ # it { expect(meth("method", [%w(a b), :other, :a => b]).signature).to eq("method(a, b)") }
38
+ end
39
+
40
+ context "sorted_args" do
41
+ it "should not tack on [] to base args" do
42
+ expect(meth('name',['a','b']).sorted_args.to_s).to eq('a, b')
43
+ end
44
+
45
+ it "should handle sub arrays" do
46
+ expect(meth.sorted_hash(['b']).to_s).to eq('["b"]')
47
+ end
48
+
49
+ it "should handle hash" do
50
+ expect(meth.sorted_hash(b:5, a:3).to_s).to eq('{:a=>3, :b=>5}')
51
+ end
52
+ end
53
+
54
+ context "method_type" do
55
+ # a string, a constant - same thing
56
+ it "should handle strings" do
57
+ expect(meth('name',['a']).method_types).to eq(['str'])
58
+ end
59
+ it "should handle arrays and hashes" do
60
+ expect(meth('name',[['a','b'], {'a' => 5}, 'x']).method_types).to eq(['[]','{}','str'])
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def meth(name = "method", args=[], is_yield=false, source = "file1", line = 5)
67
+ CodeWeb::MethodCall.new(source, line, name, args, is_yield)
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe CodeWeb::MethodList do
4
+ let(:four) { described_class.new(nil, [meth("a",%w(a b c)), meth("a",%w(a b)), meth("b",%w(a b)), meth("b",%w(a))]) }
5
+
6
+ describe "#group_by" do
7
+ subject { four.group_by(:name) }
8
+ it { expect(four.group_by(:name).count).to eq(2) }
9
+ it { expect(four.group_by(:name).map { |m| m.f.name }).to eq(%w(a b)) }
10
+ it { expect(four.group_by(:args_size).map { |m| m.args_size }).to eq([1, 2, 3]) }
11
+ end
12
+
13
+ # "#group_by"
14
+ #detect
15
+ #each
16
+ describe "#count" do
17
+ it { expect(ml([meth("a"), meth("b")]).count).to eq(2) }
18
+ end
19
+ describe "#arg_keys" do
20
+ it { expect(ml([meth("method", [{a:5, b:5, c:5}]), meth("method", [{d:5}])]).arg_keys).to eq([:a, :b, :c, :d]) }
21
+ end
22
+
23
+ private
24
+
25
+ def ml(methods, name=nil)
26
+ described_class.new(name, methods)
27
+ end
28
+
29
+ def meth(name = "method", args=[], is_yield=false, source = "file1", line = 5)
30
+ CodeWeb::MethodCall.new(source, line, name, args, is_yield)
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'code_web'
3
+
4
+ begin
5
+ require 'pry'
6
+ rescue LoadError
7
+ end