reconn 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/reconn +1 -1
- data/lib/reconn/analyzer.rb +35 -10
- data/lib/reconn/analyzer/code_smell.rb +11 -11
- data/lib/reconn/analyzer/project_elements/class.rb +7 -3
- data/lib/reconn/analyzer/project_elements/method.rb +11 -3
- data/lib/reconn/util/project_scanner.rb +22 -20
- data/lib/reconn/version.rb +1 -1
- data/lib/reconn/view/view.rb +7 -5
- data/lib/reconn/visualizer.rb +149 -28
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98a521e90dd569d2e385655e695c2b20b573c72a
|
4
|
+
data.tar.gz: 68dea3d08b90bca2e6790d675970783886bab1d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50359a392132d7e1bfff84fef8afc3fa4bd0dd8c8504a54dd4d7263630c6e38c0b90f0560142325417e18b4eb43511b2e300a4a6bddef5fc3788e085d5c20bc1
|
7
|
+
data.tar.gz: b2de99d4e108c48ecf5f0acc30f1080fd1639dbcf5cd74eb2a49f36ab70346436688c7d57a4c0d06d335bbed63b467b341ad82d86ae4a6cba3c38c3591690b99
|
data/bin/reconn
CHANGED
data/lib/reconn/analyzer.rb
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'ruby_parser'
|
4
4
|
require 'sexp_processor'
|
5
|
-
require_relative 'util/project_scanner'
|
6
|
-
require_relative 'analyzer/project_elements/class.rb'
|
7
|
-
require_relative 'analyzer/project_elements/method.rb'
|
8
5
|
|
6
|
+
module Reconn
|
9
7
|
module Analyzer
|
10
8
|
class Analyzer < MethodBasedSexpProcessor
|
11
9
|
|
@@ -43,9 +41,10 @@ module Analyzer
|
|
43
41
|
klass.complexity = count_complexity_in_class(klass)
|
44
42
|
end
|
45
43
|
prune_dependencies
|
44
|
+
find_external_dependencies(paths)
|
46
45
|
|
47
46
|
# Deletes empty classes
|
48
|
-
|
47
|
+
#@classes.delete(Class.new(:none))
|
49
48
|
|
50
49
|
@smells = find_code_smells
|
51
50
|
|
@@ -58,7 +57,7 @@ module Analyzer
|
|
58
57
|
def process_class(exp)
|
59
58
|
exp.shift
|
60
59
|
in_klass(exp.shift) do
|
61
|
-
@current_class = Class.new(klass_name)
|
60
|
+
@current_class = Class.new(klass_name, [@current_path.to_s])
|
62
61
|
@classes << @current_class
|
63
62
|
process_until_empty exp
|
64
63
|
end
|
@@ -68,10 +67,10 @@ module Analyzer
|
|
68
67
|
end
|
69
68
|
|
70
69
|
def process_defn(exp)
|
71
|
-
exp.shift
|
70
|
+
is_singleton = exp.shift.to_s == "defn" ? false : true
|
72
71
|
method_name = exp.shift.to_s
|
73
72
|
lines = count_lines_in_method(method_name)
|
74
|
-
@current_method = Method.new(method_name, @current_class.name, lines)
|
73
|
+
@current_method = Method.new(method_name, @current_path.to_s, @current_class.name, lines, is_singleton)
|
75
74
|
exp.shift
|
76
75
|
process_until_empty exp
|
77
76
|
|
@@ -163,8 +162,15 @@ module Analyzer
|
|
163
162
|
@classes.each do |klass|
|
164
163
|
klass.dependencies = klass.dependencies.map do |dep|
|
165
164
|
dep_split = dep.split('::')
|
165
|
+
if class_names.include?(dep)
|
166
|
+
next dep
|
167
|
+
end
|
166
168
|
klass_split = klass.name.split('::')
|
167
|
-
klass_split.
|
169
|
+
if klass_split.size != dep_split.size
|
170
|
+
klass_split.pop(dep_split.size)
|
171
|
+
else
|
172
|
+
klass_split.pop(dep_split.size - 1)
|
173
|
+
end
|
168
174
|
(klass_split + dep_split).join('::')
|
169
175
|
end
|
170
176
|
klass.dependencies = klass.dependencies.uniq.keep_if {|dep| dep != klass.name && class_names.include?(dep)}
|
@@ -184,14 +190,32 @@ module Analyzer
|
|
184
190
|
end
|
185
191
|
end
|
186
192
|
|
193
|
+
def find_external_dependencies(paths)
|
194
|
+
@classes.each do |klass|
|
195
|
+
external_deps = []
|
196
|
+
klass.filepaths.each do |path|
|
197
|
+
File.foreach(path) do |line|
|
198
|
+
line.strip!
|
199
|
+
if line =~ /^require .*$/
|
200
|
+
dep = line.split(" ")[1].gsub(/([\"\'])/, "")
|
201
|
+
external_deps << dep if !paths.find {|p| p.to_s =~ /.*#{dep}.*/}
|
202
|
+
else
|
203
|
+
next
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
klass.external_deps = external_deps
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
187
211
|
def find_code_smells
|
188
212
|
code_smells = []
|
189
213
|
@methods.each do |method|
|
190
214
|
if method.lines > MAX_METHOD_LENGTH
|
191
|
-
code_smells << CodeSmell.new(:too_big_method, method
|
215
|
+
code_smells << CodeSmell.new(:too_big_method, method)
|
192
216
|
end
|
193
217
|
if method.complexity > MAX_COMPLEXITY
|
194
|
-
code_smells << CodeSmell.new(:too_complex_method, method
|
218
|
+
code_smells << CodeSmell.new(:too_complex_method, method)
|
195
219
|
end
|
196
220
|
end
|
197
221
|
code_smells
|
@@ -199,3 +223,4 @@ module Analyzer
|
|
199
223
|
|
200
224
|
end
|
201
225
|
end
|
226
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
1
|
+
module Reconn
|
2
|
+
module Analyzer
|
3
|
+
class CodeSmell
|
4
|
+
attr_reader :type, :method
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
6
|
+
def initialize(type, method)
|
7
|
+
@type = type
|
8
|
+
@method = method
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def to_s
|
12
|
+
"Smell: #{@type.to_s} in #{@method.to_s} in file: #{method.filepath}"
|
13
|
+
end
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
module Reconn
|
1
2
|
module Analyzer
|
2
3
|
# Represents a class in the project
|
3
4
|
class Class
|
4
|
-
attr_reader :name, :methods
|
5
|
-
attr_accessor :lines, :complexity, :dependencies
|
5
|
+
attr_reader :name, :methods, :filepaths
|
6
|
+
attr_accessor :lines, :complexity, :dependencies, :external_deps
|
6
7
|
|
7
8
|
include Comparable
|
8
9
|
|
@@ -10,8 +11,9 @@ module Analyzer
|
|
10
11
|
name == other.name
|
11
12
|
end
|
12
13
|
|
13
|
-
def initialize(name)
|
14
|
+
def initialize(name, filepaths = [])
|
14
15
|
@name = name
|
16
|
+
@filepaths = filepaths
|
15
17
|
@dependencies = []
|
16
18
|
@methods = []
|
17
19
|
@lines = 0
|
@@ -37,6 +39,7 @@ module Analyzer
|
|
37
39
|
end
|
38
40
|
end
|
39
41
|
@dependencies += other.dependencies
|
42
|
+
@filepaths += other.filepaths
|
40
43
|
self
|
41
44
|
end
|
42
45
|
|
@@ -46,3 +49,4 @@ module Analyzer
|
|
46
49
|
|
47
50
|
end
|
48
51
|
end
|
52
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
module Reconn
|
1
2
|
module Analyzer
|
2
3
|
# Represents a method in the project
|
3
4
|
class Method
|
4
|
-
attr_reader :name, :class_name, :lines, :complexity
|
5
|
+
attr_reader :name, :class_name, :lines, :complexity, :filepath
|
5
6
|
|
6
7
|
include Comparable
|
7
8
|
|
@@ -9,20 +10,27 @@ module Analyzer
|
|
9
10
|
name == other.name && class_name == other.class_name
|
10
11
|
end
|
11
12
|
|
12
|
-
def initialize(name, class_name = :none, lines = 0)
|
13
|
+
def initialize(name, filepath = "", class_name = :none, lines = 0, is_singleton = false)
|
13
14
|
@name = name
|
15
|
+
@filepath = filepath
|
14
16
|
@class_name = class_name
|
15
17
|
@lines = lines
|
16
18
|
@complexity = 1
|
19
|
+
@is_singleton = is_singleton
|
17
20
|
end
|
18
21
|
|
19
22
|
def to_s
|
20
|
-
name.to_s
|
23
|
+
class_name.to_s + (is_singleton? ? "::" : "#") + name.to_s
|
21
24
|
end
|
22
25
|
|
23
26
|
def incr_complexity
|
24
27
|
@complexity += 1
|
25
28
|
end
|
26
29
|
|
30
|
+
def is_singleton?
|
31
|
+
@is_singleton
|
32
|
+
end
|
33
|
+
|
27
34
|
end
|
28
35
|
end
|
36
|
+
end
|
@@ -1,28 +1,30 @@
|
|
1
1
|
require 'find'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
if
|
15
|
-
|
3
|
+
module Reconn
|
4
|
+
class ProjectScanner
|
5
|
+
# Scans the given directory and all its subdirectories for ruby files
|
6
|
+
#
|
7
|
+
# @param proj_path [String] path to the project directory
|
8
|
+
# @return [Array<String>] paths to the ruby files
|
9
|
+
# @raise [InvalidPathException] if it can't open the directory
|
10
|
+
def self.scan(proj_path)
|
11
|
+
paths = []
|
12
|
+
begin
|
13
|
+
Find.find(proj_path) do |path|
|
14
|
+
if FileTest.directory?(path)
|
15
|
+
if File.basename(path)[0] == '.'
|
16
|
+
Find.prune
|
17
|
+
end
|
18
|
+
end
|
19
|
+
if File.extname(path) == '.rb'
|
20
|
+
paths << path
|
16
21
|
end
|
17
22
|
end
|
18
|
-
|
19
|
-
|
20
|
-
end
|
23
|
+
rescue
|
24
|
+
raise InvalidPathException, "Can't open the directory"
|
21
25
|
end
|
22
|
-
rescue
|
23
|
-
raise InvalidPathException, "Can't open the directory"
|
24
|
-
end
|
25
26
|
|
26
|
-
|
27
|
+
paths
|
28
|
+
end
|
27
29
|
end
|
28
30
|
end
|
data/lib/reconn/version.rb
CHANGED
data/lib/reconn/view/view.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require_relative '../analyzer.rb'
|
2
2
|
require_relative '../visualizer.rb'
|
3
3
|
|
4
|
+
module Reconn
|
4
5
|
class View
|
5
6
|
|
6
7
|
include GladeGUI
|
@@ -195,8 +196,8 @@ class View
|
|
195
196
|
end
|
196
197
|
|
197
198
|
def build_method_diag_view
|
198
|
-
diagrams = [ {title: "Lines of code", parameter: :lines} ]
|
199
|
-
diagrams << {title: "Cyclomatic complexity", parameter: :complexity}
|
199
|
+
diagrams = [ {title: "Lines of code", parameter: :lines, treshold: Analyzer::Analyzer::MAX_METHOD_LENGTH} ]
|
200
|
+
diagrams << {title: "Cyclomatic complexity", parameter: :complexity, treshold: Analyzer::Analyzer::MAX_COMPLEXITY}
|
200
201
|
build_diag_view(@methods, diagrams)
|
201
202
|
end
|
202
203
|
|
@@ -209,7 +210,7 @@ class View
|
|
209
210
|
title = diag[:title]
|
210
211
|
|
211
212
|
pie_chart = build_pie_chart(title, data)
|
212
|
-
bar_chart = build_bar_chart(title, data.first(10))
|
213
|
+
bar_chart = build_bar_chart(title, data.first(10), diag[:treshold])
|
213
214
|
|
214
215
|
container = Gtk::VBox.new(false, 4)
|
215
216
|
container = container.pack_end(pie_chart)
|
@@ -227,8 +228,8 @@ class View
|
|
227
228
|
chart_to_image(binary_chart)
|
228
229
|
end
|
229
230
|
|
230
|
-
def build_bar_chart(title, data)
|
231
|
-
binary_chart = Visualizer.make_bar_chart(title, data)
|
231
|
+
def build_bar_chart(title, data, treshold)
|
232
|
+
binary_chart = Visualizer.make_bar_chart(title, data, treshold)
|
232
233
|
chart_to_image(binary_chart)
|
233
234
|
end
|
234
235
|
|
@@ -275,3 +276,4 @@ class View
|
|
275
276
|
:build_method_stats_view, :build_if_smell_view,
|
276
277
|
:build_method_smell_view, :build_text_view
|
277
278
|
end
|
279
|
+
end
|
data/lib/reconn/visualizer.rb
CHANGED
@@ -1,44 +1,165 @@
|
|
1
1
|
require 'gruff'
|
2
2
|
require 'ruby-graphviz'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
module Reconn
|
5
|
+
class Visualizer
|
6
|
+
def self.make_pie_chart(title, data, items_number)
|
7
|
+
chart = Gruff::Pie.new
|
8
|
+
chart.title = title.to_s
|
9
|
+
data = Array.new(data)
|
10
|
+
data.first(items_number).each do |item|
|
11
|
+
chart.data(item[:label], item[:value])
|
12
|
+
data.delete(item)
|
13
|
+
end
|
14
|
+
|
15
|
+
chart.data("other", data.map {|itm| itm[:value]}.inject(:+))
|
16
|
+
|
17
|
+
chart.to_blob
|
12
18
|
end
|
13
19
|
|
14
|
-
|
20
|
+
def self.make_bar_chart(title, data, additional_line_value = nil)
|
21
|
+
chart = Gruff::Bar.new
|
22
|
+
chart.minimum_value = 0
|
23
|
+
chart.maximum_value = data.first[:value]
|
24
|
+
chart.additional_line_values = [additional_line_value] unless additional_line_value.nil?
|
25
|
+
chart.title = title.to_s
|
26
|
+
data.each do |item|
|
27
|
+
chart.data(item[:label], item[:value])
|
28
|
+
end
|
29
|
+
|
30
|
+
chart.to_blob
|
31
|
+
end
|
15
32
|
|
16
|
-
|
33
|
+
def self.make_dependency_diagram(classes)
|
34
|
+
diagram = GraphViz.new(:G, :type => :digraph)
|
35
|
+
nodes = classes.map { |c| diagram.add_nodes(c.name) }
|
36
|
+
external_nodes = classes.map {|c| c.external_deps}.inject(:+).uniq.map {|n| diagram.add_nodes(n)}
|
37
|
+
classes.each do |klass|
|
38
|
+
classes.each do |other_klass|
|
39
|
+
if !klass.dependencies.index {|d| d == other_klass.name }.nil? || klass.dependencies.find_all {|d| other_klass.name =~ /^.*{0,}::#{d}$/}.size == 1
|
40
|
+
node, other_node = [klass, other_klass].map {|k| nodes.find {|n| n[:label].to_s.gsub('"', '') == k.name}}
|
41
|
+
diagram.add_edges(node, other_node)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
external_nodes.each {|n| n.set {|_n| _n.color = "blue"}}
|
45
|
+
external_nodes.each do |ext_node|
|
46
|
+
if klass.external_deps.include?(ext_node[:label].to_s.gsub('"', ''))
|
47
|
+
node = nodes.find {|n| n[:label].to_s.gsub('"', '') == klass.name}
|
48
|
+
diagram.add_edges(node, ext_node)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
diagram.output(:png => String)
|
53
|
+
end
|
17
54
|
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#Monkeypatching additional line drawing
|
58
|
+
module Gruff
|
59
|
+
class Bar
|
60
|
+
def draw
|
61
|
+
@center_labels_over_point = (@labels.keys.length > @column_count ? true : false)
|
62
|
+
super
|
63
|
+
return unless @has_data
|
18
64
|
|
19
|
-
|
20
|
-
|
21
|
-
chart.minimum_value = 0
|
22
|
-
chart.maximum_value = data.first[:value]
|
23
|
-
chart.title = title.to_s
|
24
|
-
data.each do |item|
|
25
|
-
chart.data(item[:label], item[:value])
|
65
|
+
draw_bars
|
66
|
+
draw_additional_line
|
26
67
|
end
|
68
|
+
def draw_bars
|
69
|
+
# Setup spacing.
|
70
|
+
#
|
71
|
+
# Columns sit side-by-side.
|
72
|
+
@bar_spacing ||= @spacing_factor # space between the bars
|
73
|
+
@bar_width = @graph_width / (@column_count * @data.length).to_f
|
74
|
+
padding = (@bar_width * (1 - @bar_spacing)) / 2
|
27
75
|
|
28
|
-
|
29
|
-
|
76
|
+
@d = @d.stroke_opacity 0.0
|
77
|
+
|
78
|
+
# Setup the BarConversion Object
|
79
|
+
conversion = Gruff::BarConversion.new()
|
80
|
+
conversion.graph_height = @graph_height
|
81
|
+
conversion.graph_top = @graph_top
|
30
82
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
83
|
+
# Set up the right mode [1,2,3] see BarConversion for further explanation
|
84
|
+
if @minimum_value >= 0 then
|
85
|
+
# all bars go from zero to positiv
|
86
|
+
conversion.mode = 1
|
87
|
+
else
|
88
|
+
# all bars go from 0 to negativ
|
89
|
+
if @maximum_value <= 0 then
|
90
|
+
conversion.mode = 2
|
91
|
+
else
|
92
|
+
# bars either go from zero to negativ or to positiv
|
93
|
+
conversion.mode = 3
|
94
|
+
conversion.spread = @spread
|
95
|
+
conversion.minimum_value = @minimum_value
|
96
|
+
conversion.zero = -@minimum_value/@spread
|
39
97
|
end
|
40
98
|
end
|
99
|
+
|
100
|
+
# iterate over all normalised data
|
101
|
+
@norm_data.each_with_index do |data_row, row_index|
|
102
|
+
|
103
|
+
data_row[DATA_VALUES_INDEX].each_with_index do |data_point, point_index|
|
104
|
+
# Use incremented x and scaled y
|
105
|
+
# x
|
106
|
+
left_x = @graph_left + (@bar_width * (row_index + point_index + ((@data.length - 1) * point_index))) + padding
|
107
|
+
right_x = left_x + @bar_width * @bar_spacing
|
108
|
+
# y
|
109
|
+
conv = []
|
110
|
+
conversion.get_left_y_right_y_scaled( data_point, conv )
|
111
|
+
|
112
|
+
# create new bar
|
113
|
+
@d = @d.fill data_row[DATA_COLOR_INDEX]
|
114
|
+
@d = @d.rectangle(left_x, conv[0], right_x, conv[1])
|
115
|
+
|
116
|
+
# Calculate center based on bar_width and current row
|
117
|
+
label_center = @graph_left +
|
118
|
+
(@data.length * @bar_width * point_index) +
|
119
|
+
(@data.length * @bar_width / 2.0)
|
120
|
+
|
121
|
+
# Subtract half a bar width to center left if requested
|
122
|
+
draw_label(label_center - (@center_labels_over_point ? @bar_width / 2.0 : 0.0), point_index)
|
123
|
+
if @show_labels_for_bar_values
|
124
|
+
val = (@label_formatting || '%.2f') % @norm_data[row_index][3][point_index]
|
125
|
+
draw_value_label(left_x + (right_x - left_x)/2, conv[0]-30, val.commify, true)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Draw the last label if requested
|
131
|
+
draw_label(@graph_right, @column_count) if @center_labels_over_point
|
132
|
+
|
133
|
+
draw_additional_line
|
134
|
+
@d.draw(@base_image)
|
135
|
+
end
|
136
|
+
# Draws additional horizontal line
|
137
|
+
def draw_additional_line
|
138
|
+
@additional_line_colors << '#f61100'
|
139
|
+
@d = @d.stroke_opacity 100.0
|
140
|
+
i = 0
|
141
|
+
@additional_line_values.each do |value|
|
142
|
+
@increment_scaled = @graph_height.to_f / (@maximum_value.to_f / value)
|
143
|
+
|
144
|
+
y = @graph_top + @graph_height - @increment_scaled
|
145
|
+
|
146
|
+
@d = @d.stroke(@additional_line_colors[i])
|
147
|
+
@d = @d.line(@graph_left, y, @graph_right, y)
|
148
|
+
|
149
|
+
|
150
|
+
@d.fill = @additional_line_colors[i]
|
151
|
+
@d.font = @font if @font
|
152
|
+
@d.stroke('transparent')
|
153
|
+
@d.pointsize = scale_fontsize(@marker_font_size)
|
154
|
+
@d.gravity = EastGravity
|
155
|
+
@d = @d.annotate_scaled( @base_image,
|
156
|
+
@graph_right - LABEL_MARGIN, 1.0,
|
157
|
+
0.0, y - (@marker_font_size/2.0),
|
158
|
+
label(value, value), @scale)
|
159
|
+
i += 1
|
160
|
+
end
|
161
|
+
|
162
|
+
@d = @d.stroke_antialias true
|
41
163
|
end
|
42
|
-
diagram.output(:png => String)
|
43
164
|
end
|
44
165
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reconn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mateusz Czarnecki
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: vrlib
|