reconn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,277 @@
1
+ require_relative '../analyzer.rb'
2
+ require_relative '../visualizer.rb'
3
+
4
+ class View
5
+
6
+ include GladeGUI
7
+
8
+ def before_show()
9
+ dialog_response, dialog = show_file_chooser_dialog
10
+ if dialog_response == Gtk::Dialog::RESPONSE_ACCEPT
11
+ analyzer = Analyzer::Analyzer.new
12
+ @classes, @methods, @smells = analyzer.analyze(dialog.filename)
13
+
14
+ @general_stats_view = build_general_stats_view
15
+ @builder['dataviewport'].add(@general_stats_view)
16
+ end
17
+ dialog.destroy
18
+ end
19
+
20
+ #########################################
21
+ # On button clicked methods
22
+
23
+ def on_general_stats_button_clicked
24
+ clean_data_view
25
+
26
+ unless @general_stats_view
27
+ @general_stats_view = build_general_stats_view
28
+ end
29
+
30
+ @builder['dataviewport'].add(@general_stats_view)
31
+ end
32
+
33
+ def on_class_stats_button_clicked
34
+ clean_data_view
35
+
36
+ unless @class_stats_view
37
+ @class_stats_view = build_class_stats_view
38
+ end
39
+
40
+ @builder['dataviewport'].add(@class_stats_view)
41
+ end
42
+
43
+ def on_class_diag_button_clicked
44
+ clean_data_view
45
+
46
+ unless @class_diag_view
47
+ @class_diag_view = build_class_diag_view
48
+ end
49
+
50
+ @builder['dataviewport'].add(@class_diag_view)
51
+ end
52
+
53
+ def on_class_dep_button_clicked
54
+ clean_data_view
55
+
56
+ unless @class_dep_view
57
+ @class_dep_view = build_class_dep_view
58
+ end
59
+
60
+ @builder['dataviewport'].add(@class_dep_view)
61
+ end
62
+
63
+ def on_method_stats_button_clicked
64
+ clean_data_view
65
+
66
+ unless @method_stats_view
67
+ @method_stats_view = build_method_stats_view
68
+ end
69
+
70
+ @builder['dataviewport'].add(@method_stats_view)
71
+ end
72
+
73
+ def on_method_diag_button_clicked
74
+ clean_data_view
75
+
76
+ unless @method_diag_view
77
+ @method_diag_view = build_method_diag_view
78
+ end
79
+
80
+ @builder['dataviewport'].add(@method_diag_view)
81
+ end
82
+
83
+ def on_if_smell_button_clicked
84
+ clean_data_view
85
+
86
+ unless @if_smell_view
87
+ @if_smell_view = build_if_smell_view
88
+ end
89
+
90
+ @builder['dataviewport'].add(@if_smell_view)
91
+ end
92
+
93
+ def on_method_smell_button_clicked
94
+ clean_data_view
95
+
96
+ unless @method_smell_view
97
+ @method_smell_view = build_method_smell_view
98
+ end
99
+
100
+ @builder['dataviewport'].add(@method_smell_view)
101
+ end
102
+
103
+ ###########################################
104
+ # View builders
105
+
106
+ def build_general_stats_view
107
+ lines = 0
108
+ @methods.each do |method|
109
+ lines += method.lines
110
+ end
111
+
112
+ text_arr = ["Total number of classes: #{@classes.length} \n"]
113
+ text_arr << "Total number of methods: #{@methods.length} \n"
114
+ text_arr << "Total number of lines of code: #{lines} \n"
115
+
116
+ largest_class = @classes.sort_by {|c| c.lines}.pop
117
+
118
+ class_methodnum = Hash.new(0)
119
+ @classes.each do |klass|
120
+ class_methodnum.store(klass.name, klass.methods.size)
121
+ end
122
+ class_methodnum = class_methodnum.sort_by {|name, methods| methods}
123
+
124
+ text_arr << "\n"
125
+ text_arr << "Largest class: #{largest_class} \n"
126
+ text_arr << "Number of lines: #{largest_class.lines} \n"
127
+ text_arr << "\n"
128
+ text_arr << "Class with largest number of methods: #{class_methodnum.last[0]} \n"
129
+ text_arr << "Number of methods: #{class_methodnum.last[1]} \n"
130
+
131
+ largest_method = @methods.sort_by {|m| m.lines}.pop
132
+ text_arr << "\n"
133
+ text_arr << "Largest method: #{largest_method} \n"
134
+ text_arr << "Number of lines: #{largest_method.lines} \n"
135
+
136
+ build_text_view(text_arr.join)
137
+ end
138
+
139
+ def build_class_stats_view
140
+ features = [ {name: "Number of lines", data: prepare_data(@classes, :lines)} ]
141
+ features << {name: "Cyclomatic complexity", data: prepare_data(@classes, :complexity)}
142
+ features << {name: "Number of methods", data: prepare_data(@classes, :methods_number)}
143
+ text = prepare_text(features)
144
+ build_text_view(text)
145
+ end
146
+
147
+ def build_method_stats_view
148
+ features = [ {name: "Number of lines", data: prepare_data(@methods, :lines)} ]
149
+ features << {name: "Cyclomatic complexity", data: prepare_data(@methods, :complexity)}
150
+ text = prepare_text(features)
151
+ build_text_view(text)
152
+ end
153
+
154
+ def build_if_smell_view
155
+ text_arr = []
156
+ @smells.each {|s| text_arr << s.to_s + "\n" if s.type == :too_complex_method}
157
+ build_text_view(text_arr.join)
158
+ end
159
+
160
+ def build_method_smell_view
161
+ text_arr = []
162
+ @smells.each {|s| text_arr << s.to_s + "\n" if s.type == :too_big_method}
163
+ build_text_view(text_arr.join)
164
+ end
165
+
166
+ def build_text_view(text)
167
+ stats_view = Gtk::TextView.new
168
+ stats_view.editable = false
169
+ stats_view.cursor_visible = false
170
+ stats_view.buffer.text = text
171
+ stats_view.show
172
+ stats_view
173
+ end
174
+
175
+ def prepare_text(features)
176
+ text_arr = []
177
+ features.each do |feature|
178
+ text_arr << "#{feature[:name]}: \n"
179
+ data = feature[:data]
180
+ data.each_index do |i|
181
+ break if i == 30
182
+ item = data[i]
183
+ text_arr << "#{i+1}. #{item[:label]} => #{item[:value]} \n"
184
+ end
185
+ text_arr << "\n"
186
+ end
187
+ text_arr.join
188
+ end
189
+
190
+ def build_class_diag_view
191
+ diagrams = [ {title: "Lines of code", parameter: :lines} ]
192
+ diagrams << {title: "Cyclomatic complexity", parameter: :complexity}
193
+ diagrams << {title: "Number of methods", parameter: "methods_number"}
194
+ build_diag_view(@classes, diagrams)
195
+ end
196
+
197
+ def build_method_diag_view
198
+ diagrams = [ {title: "Lines of code", parameter: :lines} ]
199
+ diagrams << {title: "Cyclomatic complexity", parameter: :complexity}
200
+ build_diag_view(@methods, diagrams)
201
+ end
202
+
203
+ def build_diag_view(raw_data, diagrams)
204
+ tabbed_panel = Gtk::Notebook.new
205
+
206
+ diagrams.each do |diag|
207
+ data = prepare_data(raw_data, diag[:parameter])
208
+
209
+ title = diag[:title]
210
+
211
+ pie_chart = build_pie_chart(title, data)
212
+ bar_chart = build_bar_chart(title, data.first(10))
213
+
214
+ container = Gtk::VBox.new(false, 4)
215
+ container = container.pack_end(pie_chart)
216
+ container = container.pack_end(bar_chart)
217
+ container.show
218
+
219
+ tabbed_panel.append_page(container, Gtk::Label.new(title))
220
+ end
221
+
222
+ tabbed_panel.show
223
+ end
224
+
225
+ def build_pie_chart(title, data)
226
+ binary_chart = Visualizer.make_pie_chart(title, data, 4)
227
+ chart_to_image(binary_chart)
228
+ end
229
+
230
+ def build_bar_chart(title, data)
231
+ binary_chart = Visualizer.make_bar_chart(title, data)
232
+ chart_to_image(binary_chart)
233
+ end
234
+
235
+ def chart_to_image(binary_chart)
236
+ loader = Gdk::PixbufLoader.new("png")
237
+ loader.last_write(binary_chart)
238
+ chart = loader.pixbuf
239
+ Gtk::Image.new(chart).show
240
+ end
241
+ #######
242
+ def build_class_dep_view
243
+ binary_chart = Visualizer.make_dependency_diagram(@classes)
244
+ loader = Gdk::PixbufLoader.new("png")
245
+ loader.last_write(binary_chart)
246
+ chart = loader.pixbuf
247
+ Gtk::Image.new(chart).show
248
+ end
249
+
250
+ ##########################################
251
+
252
+ def show_file_chooser_dialog
253
+ dialog = Gtk::FileChooserDialog.new("Open File",
254
+ nil,
255
+ Gtk::FileChooser::ACTION_SELECT_FOLDER,
256
+ nil,
257
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
258
+ [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT])
259
+ dialog_response = dialog.run
260
+ return dialog_response, dialog
261
+ end
262
+
263
+ def clean_data_view
264
+ @builder['dataviewport'].each do |child|
265
+ @builder['dataviewport'].remove(child)
266
+ end
267
+ end
268
+
269
+ def prepare_data(raw_data, parameter)
270
+ data = raw_data.sort_by {|d| d.send(parameter.to_s)}.reverse
271
+ data.map! {|d| {label: d.to_s, value: d.send(parameter.to_s)}}
272
+ end
273
+
274
+ private :build_general_stats_view, :build_class_stats_view,
275
+ :build_method_stats_view, :build_if_smell_view,
276
+ :build_method_smell_view, :build_text_view
277
+ end
@@ -0,0 +1,44 @@
1
+ require 'gruff'
2
+ require 'ruby-graphviz'
3
+
4
+ class Visualizer
5
+ def self.make_pie_chart(title, data, items_number)
6
+ chart = Gruff::Pie.new
7
+ chart.title = title.to_s
8
+ data = Array.new(data)
9
+ data.first(items_number).each do |item|
10
+ chart.data(item[:label], item[:value])
11
+ data.delete(item)
12
+ end
13
+
14
+ chart.data("other", data.map {|itm| itm[:value]}.inject(:+))
15
+
16
+ chart.to_blob
17
+ end
18
+
19
+ def self.make_bar_chart(title, data)
20
+ chart = Gruff::Bar.new
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])
26
+ end
27
+
28
+ chart.to_blob
29
+ end
30
+
31
+ def self.make_dependency_diagram(classes)
32
+ diagram = GraphViz.new(:G, :type => :digraph)
33
+ nodes = classes.map { |c| diagram.add_nodes(c.name) }
34
+ classes.each do |klass|
35
+ classes.each do |other_klass|
36
+ if !klass.dependencies.index {|d| d == other_klass.name }.nil?
37
+ node, other_node = [klass, other_klass].map {|k| nodes.find {|n| n[:label].to_s.gsub('"', '') == k.name}}
38
+ diagram.add_edges(node, other_node)
39
+ end
40
+ end
41
+ end
42
+ diagram.output(:png => String)
43
+ end
44
+ end
data/reconn.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'reconn/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "reconn"
8
+ spec.version = Reconn::VERSION
9
+ spec.authors = ["Mateusz Czarnecki"]
10
+ spec.email = ["mateusz.czarnecki92@gmail.com"]
11
+ spec.summary = %q{A tool for analysis and visualization of projects written in Ruby}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "vrlib", ">= 1.0.16"
21
+ spec.add_dependency "gtk2", ">= 2.2.0"
22
+ spec.add_dependency "require_all", ">= 1.3.2"
23
+ spec.add_dependency "ruby_parser", ">= 3.6.3"
24
+ spec.add_dependency "sexp_processor", ">= 4.4.4"
25
+ spec.add_dependency "gruff", ">= 0.5.1"
26
+ spec.add_dependency "ruby-graphviz", ">= 1.2.1"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.5"
29
+ spec.add_development_dependency "rake"
30
+ spec.add_development_dependency "rspec"
31
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Analyzer do
4
+ describe '#analyze' do
5
+ before :all do
6
+ @result = Analyzer::Analyzer.new.analyze(File.dirname(__FILE__) + '/../../../lib')
7
+ @classes, @methods, @smells = @result
8
+ end
9
+
10
+ it 'returns an Array' do
11
+ expect( @result.respond_to?(:to_ary) ).to be true
12
+ end
13
+
14
+ it 'returns classes data' do
15
+ expect( @classes ).not_to be_empty
16
+ expect( @classes.map {|x| x.name } ).to include('Analyzer')
17
+ end
18
+
19
+ it 'returns methods data' do
20
+ expect( @methods ).not_to be_empty
21
+ expect( @methods.map {|x| x.name } ).to include('analyze')
22
+ end
23
+
24
+ it 'calculates lines of code in classes' do
25
+ end
26
+
27
+ it 'calculates lines of code in methods' do
28
+ end
29
+
30
+ it 'finds dependencies between classes' do
31
+ end
32
+ end
33
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ module Analizer
4
+ describe Class do
5
+ it 'is not valid without a name' do
6
+ expect { Analizer::Class.new }.to raise_error
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ module Analyzer
4
+ describe Method do
5
+ it 'is not valid without a name' do
6
+ expect { Method.new }.to raise_error
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProjectScanner do
4
+ describe '::scan' do
5
+ it 'should scan the project directory' +
6
+ ' and return paths to ruby source files when the path is correct and the directory has .rb files' do
7
+ paths = ProjectScanner.scan(File.dirname(__FILE__) + '/../../test_projects')
8
+ expect(paths).not_to be_empty
9
+ end
10
+
11
+ it 'should raise an exception when the project directory path is invalid' do
12
+ expect { ProjectScanner.scan('a_directory')}.to raise_error
13
+ end
14
+
15
+ it 'should return empty array when no ruby files were found' do
16
+ paths = ProjectScanner.scan(File.dirname(__FILE__) + '/../../test_projects/empty_project')
17
+ expect(paths).to be_empty
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'reconn'
File without changes