reconn 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.
@@ -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