code_web 0.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,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