reconn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ # @author Mateusz Czarnecki <mateusz.czarnecki92@gmail.com>
2
+
3
+ require 'ruby_parser'
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
+
9
+ module Analyzer
10
+ class Analyzer < MethodBasedSexpProcessor
11
+ def initialize
12
+ super()
13
+ self.auto_shift_type = false
14
+ @classes = []
15
+ @methods = []
16
+ @smells = []
17
+ @current_class = Class.new(:none)
18
+ end
19
+
20
+ # Analyzes all the ruby files in the given directory and its subdirectories
21
+ # @param dir [String] path to the directory
22
+ # @return [Array(Array<Class>, Array<Method>, Array<CodeSmell>)] found classes,
23
+ # methods and code smells
24
+ def analyze(dir)
25
+ parser = RubyParser.new
26
+ paths = ProjectScanner.scan(dir)
27
+ paths.each do |path|
28
+ @current_path = path
29
+ ast = parser.process(File.binread(path), path)
30
+ process ast
31
+ @current_class = Class.new(:none)
32
+ end
33
+
34
+ @classes.each {|klass| klass.lines = count_lines_in_class(klass)}
35
+ prune_dependencies
36
+
37
+ return @classes, @methods, @smells
38
+ end
39
+
40
+ #########################################
41
+ # Process methods:
42
+
43
+ def process_class(exp)
44
+ exp.shift
45
+ class_name = exp.shift.to_s
46
+ @current_class = Class.new(class_name)
47
+ @classes << @current_class
48
+ process_until_empty exp
49
+ s()
50
+ end
51
+
52
+ def process_defn(exp)
53
+ exp.shift
54
+ method_name = exp.shift.to_s
55
+ lines = count_lines_in_method(method_name)
56
+ method = Method.new(method_name, @current_class.name, lines)
57
+ @methods << method
58
+ @current_class.add_method(method)
59
+ exp.shift
60
+ process_until_empty exp
61
+ s()
62
+ end
63
+
64
+ def process_const(exp)
65
+ exp.shift
66
+ name = exp.shift.to_s
67
+ is_class = !(Object.const_get(name) rescue nil).nil?
68
+ @current_class.add_dependency(name) if is_class
69
+
70
+ exp.shift
71
+ process_until_empty exp
72
+ s()
73
+ end
74
+
75
+ ########################################
76
+
77
+ #Counts lines of code in a method
78
+ #
79
+ #@param method_name [String] the name of the method
80
+ #@return [Integer] lines of code count
81
+ def count_lines_in_method(method_name)
82
+ flag = false
83
+ lines = []
84
+ File.foreach(@current_path) do |line|
85
+ break if line =~ /def/ && flag
86
+ lines << line if flag && line.strip != '' && line.strip[0] != '#'
87
+ flag = true if line =~ /def #{method_name}/
88
+ end
89
+
90
+ lines.size
91
+ end
92
+
93
+ #Counts lines of code in a class (sums LOC of methods)
94
+ #
95
+ #@param klass [Class] the class
96
+ #@return [Integer] lines of code count
97
+ def count_lines_in_class(klass)
98
+ lines = klass.methods.map {|method| method.lines}.inject(:+)
99
+ lines.nil? ? 0 : lines
100
+ end
101
+
102
+ #Deletes dependencies which are not classes within analyzed project
103
+ def prune_dependencies
104
+ class_names = @classes.map {|klass| klass.name}
105
+ @classes.each do |klass|
106
+ klass.dependencies = klass.dependencies.uniq.keep_if {|dep| class_names.include?(dep)}
107
+ end
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,189 @@
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
+ end
58
+
59
+ @builder['dataviewport'].add(Gtk::TextView.new)
60
+ end
61
+
62
+ def on_method_stats_button_clicked
63
+ clean_data_view
64
+
65
+ unless @method_stats_view
66
+ @method_stats_view = build_method_stats_view
67
+ end
68
+
69
+ @builder['dataviewport'].add(@method_stats_view)
70
+ end
71
+
72
+ def on_method_diag_button_clicked
73
+ clean_data_view
74
+
75
+ unless @method_diag_view
76
+ end
77
+
78
+ @builder['dataviewport'].add(Gtk::TextView.new)
79
+ end
80
+
81
+ def on_if_smell_button_clicked
82
+ clean_data_view
83
+
84
+ unless @if_smell_view
85
+ @if_smell_view = build_if_smell_view
86
+ end
87
+
88
+ @builder['dataviewport'].add(@if_smell_view)
89
+ end
90
+
91
+ def on_method_smell_button_clicked
92
+ clean_data_view
93
+
94
+ unless @method_smell_view
95
+ @method_smell_view = build_method_smell_view
96
+ end
97
+
98
+ @builder['dataviewport'].add(@method_smell_view)
99
+ end
100
+
101
+ ###########################################
102
+ # View builders
103
+
104
+ def build_general_stats_view
105
+ lines = 0
106
+ @methods.each do |method|
107
+ lines += method.lines
108
+ end
109
+
110
+ text_arr = ["Total number of classes: #{@classes.length} \n"]
111
+ text_arr << "Total number of methods: #{@methods.length} \n"
112
+ text_arr << "Total number of lines of code: #{lines}"
113
+ build_text_view(text_arr.join)
114
+ end
115
+
116
+ def build_class_stats_view
117
+ largest_class = @classes.sort.pop
118
+
119
+ class_methodnum = Hash.new(0)
120
+ @classes.each do |klass|
121
+ class_methodnum.store(klass.name, klass.methods.size)
122
+ end
123
+ class_methodnum = class_methodnum.sort_by {|name, methods| methods}
124
+
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
+ build_text_view(text_arr.join)
131
+ end
132
+
133
+ def build_method_stats_view
134
+ largest_method = @methods.sort.pop
135
+ text_arr = ["Largest method: #{largest_method} \n"]
136
+ text_arr << "Number of lines: #{largest_method.lines}"
137
+ build_text_view(text_arr.join)
138
+ end
139
+
140
+ def build_if_smell_view
141
+ text_arr = []
142
+ build_text_view(text_arr.join)
143
+ end
144
+
145
+ def build_method_smell_view
146
+ text_arr = []
147
+ build_text_view(text_arr.join)
148
+ end
149
+
150
+ def build_text_view(text)
151
+ stats_view = Gtk::TextView.new
152
+ stats_view.editable = false
153
+ stats_view.cursor_visible = false
154
+ stats_view.buffer.text = text
155
+ stats_view.show
156
+ stats_view
157
+ end
158
+
159
+ def build_class_diag_view
160
+ binary_chart = Visualizer.make_class_pie_chart(@classes)
161
+ loader = Gdk::PixbufLoader.new("png")
162
+ loader.last_write(binary_chart)
163
+ chart = loader.pixbuf
164
+ Gtk::Image.new(chart).show
165
+ end
166
+
167
+ ##########################################
168
+
169
+ def show_file_chooser_dialog
170
+ dialog = Gtk::FileChooserDialog.new("Open File",
171
+ nil,
172
+ Gtk::FileChooser::ACTION_SELECT_FOLDER,
173
+ nil,
174
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
175
+ [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT])
176
+ dialog_response = dialog.run
177
+ return dialog_response, dialog
178
+ end
179
+
180
+ def clean_data_view
181
+ @builder['dataviewport'].each do |child|
182
+ @builder['dataviewport'].remove(child)
183
+ end
184
+ end
185
+
186
+ private :build_general_stats_view, :build_class_stats_view,
187
+ :build_method_stats_view, :build_if_smell_view,
188
+ :build_method_smell_view, :build_text_view
189
+ end
@@ -0,0 +1,111 @@
1
+ # @author Mateusz Czarnecki <mateusz.czarnecki92@gmail.com>
2
+
3
+ require 'ruby_parser'
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
+
9
+ module Analyzer
10
+ class Analyzer < MethodBasedSexpProcessor
11
+ def initialize
12
+ super()
13
+ self.auto_shift_type = false
14
+ @classes = []
15
+ @methods = []
16
+ @smells = []
17
+ @current_class = Class.new(:none)
18
+ end
19
+
20
+ # Analyzes all the ruby files in the given directory and its subdirectories
21
+ # @param dir [String] path to the directory
22
+ # @return [Array(Array<Class>, Array<Method>, Array<CodeSmell>)] found classes,
23
+ # methods and code smells
24
+ def analyze(dir)
25
+ parser = RubyParser.new
26
+ paths = ProjectScanner.scan(dir)
27
+ paths.each do |path|
28
+ @current_path = path
29
+ ast = parser.process(File.binread(path), path)
30
+ process ast
31
+ @current_class = Class.new(:none)
32
+ end
33
+
34
+ @classes.each {|klass| klass.lines = count_lines_in_class(klass)}
35
+ prune_dependencies
36
+
37
+ return @classes, @methods, @smells
38
+ end
39
+
40
+ #########################################
41
+ # Process methods:
42
+
43
+ def process_class(exp)
44
+ exp.shift
45
+ class_name = exp.shift.to_s
46
+ @current_class = Class.new(class_name)
47
+ @classes << @current_class
48
+ process_until_empty exp
49
+ s()
50
+ end
51
+
52
+ def process_defn(exp)
53
+ exp.shift
54
+ method_name = exp.shift.to_s
55
+ lines = count_lines_in_method(method_name)
56
+ method = Method.new(method_name, @current_class.name, lines)
57
+ @methods << method
58
+ @current_class.add_method(method)
59
+ exp.shift
60
+ process_until_empty exp
61
+ s()
62
+ end
63
+
64
+ def process_const(exp)
65
+ exp.shift
66
+ name = exp.shift.to_s
67
+ is_class = !(Object.const_get(name) rescue nil).nil?
68
+ @current_class.add_dependency(name) if is_class
69
+
70
+ exp.shift
71
+ process_until_empty exp
72
+ s()
73
+ end
74
+
75
+ ########################################
76
+
77
+ #Counts lines of code in a method
78
+ #
79
+ #@param method_name [String] the name of the method
80
+ #@return [Integer] lines of code count
81
+ def count_lines_in_method(method_name)
82
+ flag = false
83
+ lines = []
84
+ File.foreach(@current_path) do |line|
85
+ break if line =~ /def/ && flag
86
+ lines << line if flag && line.strip != '' && line.strip[0] != '#'
87
+ flag = true if line =~ /def #{method_name}/
88
+ end
89
+
90
+ lines.size
91
+ end
92
+
93
+ #Counts lines of code in a class (sums LOC of methods)
94
+ #
95
+ #@param klass [Class] the class
96
+ #@return [Integer] lines of code count
97
+ def count_lines_in_class(klass)
98
+ lines = klass.methods.map {|method| method.lines}.inject(:+)
99
+ lines.nil? ? 0 : lines
100
+ end
101
+
102
+ #Deletes dependencies which are not classes within analyzed project
103
+ def prune_dependencies
104
+ class_names = @classes.map {|klass| klass.name}
105
+ @classes.each do |klass|
106
+ klass.dependencies = klass.dependencies.uniq.keep_if {|dep| class_names.include?(dep)}
107
+ end
108
+ end
109
+
110
+ end
111
+ end
metadata ADDED
@@ -0,0 +1,226 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reconn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mateusz Czarnecki
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: vrlib
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.16
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.16
27
+ - !ruby/object:Gem::Dependency
28
+ name: gtk2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: require_all
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: ruby_parser
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 3.6.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 3.6.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: sexp_processor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 4.4.4
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 4.4.4
83
+ - !ruby/object:Gem::Dependency
84
+ name: gruff
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.5.1
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.5.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: ruby-graphviz
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.2.1
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.2.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: bundler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '1.5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '1.5'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description:
154
+ email:
155
+ - mateusz.czarnecki92@gmail.com
156
+ executables:
157
+ - reconn
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - .gitignore
162
+ - Gemfile
163
+ - LICENSE.txt
164
+ - README.md
165
+ - Rakefile
166
+ - bin/reconn
167
+ - lib/reconn.rb
168
+ - lib/reconn/analyzer.rb
169
+ - lib/reconn/analyzer/code_smell.rb
170
+ - lib/reconn/analyzer/project_elements/class.rb
171
+ - lib/reconn/analyzer/project_elements/method.rb
172
+ - lib/reconn/util/project_scanner.rb
173
+ - lib/reconn/version.rb
174
+ - lib/reconn/view/glade/View.glade
175
+ - lib/reconn/view/view.rb
176
+ - lib/reconn/visualizer.rb
177
+ - reconn.gemspec
178
+ - spec/reconn/analyzer/analyzer_spec.rb
179
+ - spec/reconn/analyzer/code_smells/code_smell_spec.rb
180
+ - spec/reconn/analyzer/code_smells/if_smell_spec.rb
181
+ - spec/reconn/analyzer/code_smells/too_big_method_smell_spec.rb
182
+ - spec/reconn/analyzer/project_elements/class_spec.rb
183
+ - spec/reconn/analyzer/project_elements/method_spec.rb
184
+ - spec/reconn/util/project_scanner_spec.rb
185
+ - spec/spec_helper.rb
186
+ - spec/test_projects/empty_project/not_rb_file
187
+ - spec/test_projects/multiple_files_project/analyzer.rb
188
+ - spec/test_projects/multiple_files_project/view.rb
189
+ - spec/test_projects/one_file_project/analyzer.rb
190
+ homepage: ''
191
+ licenses:
192
+ - MIT
193
+ metadata: {}
194
+ post_install_message:
195
+ rdoc_options: []
196
+ require_paths:
197
+ - lib
198
+ required_ruby_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - '>='
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ required_rubygems_version: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - '>='
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ requirements: []
209
+ rubyforge_project:
210
+ rubygems_version: 2.2.2
211
+ signing_key:
212
+ specification_version: 4
213
+ summary: A tool for analysis and visualization of projects written in Ruby
214
+ test_files:
215
+ - spec/reconn/analyzer/analyzer_spec.rb
216
+ - spec/reconn/analyzer/code_smells/code_smell_spec.rb
217
+ - spec/reconn/analyzer/code_smells/if_smell_spec.rb
218
+ - spec/reconn/analyzer/code_smells/too_big_method_smell_spec.rb
219
+ - spec/reconn/analyzer/project_elements/class_spec.rb
220
+ - spec/reconn/analyzer/project_elements/method_spec.rb
221
+ - spec/reconn/util/project_scanner_spec.rb
222
+ - spec/spec_helper.rb
223
+ - spec/test_projects/empty_project/not_rb_file
224
+ - spec/test_projects/multiple_files_project/analyzer.rb
225
+ - spec/test_projects/multiple_files_project/view.rb
226
+ - spec/test_projects/one_file_project/analyzer.rb