sandi_meter 0.0.6 → 1.0.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MjEzMjdjMjMzYWE4NzkwMWQyYmNiOTE0ZjY0N2NkZDdmNTM3NWIwMQ==
4
+ MjRmZDFmOTBlY2IxMzJlMjBhNTQwMDIwMjMzMmNlNWQyZDM2ODEyYQ==
5
5
  data.tar.gz: !binary |-
6
- NjU1M2UzMTIwNjNhMzkyYzZhMGYyNGJlNjVmOTZhYTI3YjcwNzM2Yg==
6
+ ZjQyYWJiNDNlY2RlNjk0YTIzYTQ2MGU2YTUwNDAwMWU2OWQ3M2ZjZQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NWJhODU3OWQ4ZmNiYzg1ZWIzMDc5MTBkYTE1MTc3MWEyNWNhMDFkNjZjZDIw
10
- NzIzOGFmMmMzMmQ2MjZlMDQ1ZjFlM2I4MmYwMjExMDI1M2E1Yzc5NzFiNjc1
11
- M2JmOTllMmI0ZDBkZDM2ZDlmZDZjYzE3MzQxZmJhOWE5YzY1NDM=
9
+ MzgxY2NjZGZjNTJhYmVhNmZhMDUxYmQ5MWQ2ZWEzNGMwZjA1ZTEyYTdjMjUy
10
+ MmQ1ZTZiOGE5OTAyMzBmOTA3MGFmNmRlZjkzNjA1ZWI2MzJlMWUzMDdmNDNi
11
+ MGJjZGFiMzNiNTI2OWIyOTI1YzBkMDNkZGQ0ZDg3ZTc1N2U2Zjc=
12
12
  data.tar.gz: !binary |-
13
- ZDI5OTYwNjAyZDQyZGQ1YzhkN2FhMWY0ZTllNjc2NmJmZDA5OTgzNzE1MTlk
14
- OWRjM2Q4MDMyOTdlZjAxZjI1ZDU5OGE4Yjk3YjhlOTNjMmU2Y2YxNzhkZjk3
15
- MThhZDE4ZjNhMWNiMDViNDM2NjNiNTYyY2QxZDJiOGQ0YzAzNDQ=
13
+ ZDMwMTA4NTA1ZTI3ZjU4OGVmYjI3MDMzMzQ2MGQ5Zjc3OGY0ZGZhYzA2Yjlm
14
+ NzQ1ODg5NmYzMzc0NjZhMmU5ZjFiYmY1YmEyM2FjMWRmNjg5MTRjY2FmNjU4
15
+ MDg1NmJmYjE1Mjc0NzU0YmQ1Y2M5ZDY0ZGRkMjM2YjlkMzMxZGY=
data/README.md CHANGED
@@ -15,25 +15,58 @@ Static analysis tool for checking your Ruby code for [Sandi Metz' four rules](ht
15
15
  gem install sandi_meter
16
16
 
17
17
  sandi_meter --help
18
- -g, --graph Create folder and log data to graph
19
- -l, --log Show syntax error and indentation log output
20
- -p, --path PATH Path to folder or file to analyze
21
- -r, --rules Show rules
22
- -h, --help Help
23
-
24
- sandi_meter -p ~/your/ruby/or/rails/project
25
-
26
- 1. 94% of classes are under 100 lines.
27
- 2. 53% of methods are under 5 lines.
28
- 3. 98% of methods calls accepts are less than 4 parameters.
29
- 4. 21% of controllers have one instance variable per action.
18
+ -d, --details CLI mode. Show details (path, line number)
19
+ -g, --graph HTML mode. Create folder, log data and output stats to HTML file.
20
+ -l, --log Show syntax error and indentation log output
21
+ -p, --path PATH Path to folder or file to analyze (default is ".")
22
+ -r, --rules Show rules
23
+ -h, --help Help
24
+
25
+ cd ~/your/ruby/or/rails/project
26
+ sandi_meter -d
27
+
28
+ 1. 85% of classes are under 100 lines.
29
+ 2. 45% of methods are under 5 lines.
30
+ 3. 99% of method calls accepted are less than 4 parameters.
31
+ 4. 66% of controllers have one instance variable per action.
32
+
33
+ Classes with 100+ lines
34
+ Class name | Size | Path
35
+ SandiMeter::Analyzer | 219 | ./lib/sandi_meter/analyzer.rb:7
36
+ SandiMeter::Calculator | 172 | ./lib/sandi_meter/calculator.rb:2
37
+ SandiMeter::HtmlGenerator | 135 | ./lib/sandi_meter/html_generator.rb:5
38
+ Valera | 109 | ./spec/test_classes/12.rb:1
39
+
40
+ Missindented classes
41
+ Class name | Path
42
+ MyApp::TestClass | ./spec/test_classes/1.rb:2
43
+ OneLinerClass | ./spec/test_classes/5.rb:1
44
+
45
+ Methods with 5+ lines
46
+ Class name | Method name | Size | Path
47
+ SandiMeter::Analyzer | initialize | 10 | ./lib/sandi_meter/analyzer.rb:10
48
+ SandiMeter::Analyzer | analyze | 13 | ./lib/sandi_meter/analyzer.rb:22
49
+
50
+ Missindented methods
51
+ Class name | Method name | Path
52
+ MyApp::TestClass | blah | ./spec/test_classes/1.rb:3
53
+
54
+ Method calls with 4+ arguments
55
+ # of arguments | Path
56
+ 5 | ./lib/sandi_meter/html_generator.rb:55
57
+ 5 | ./lib/sandi_meter/html_generator.rb:71
58
+
59
+ Controllers with 1+ instance variables
60
+ Controller name | Action name | Instance variables
61
+ AnotherUsersController | index | @users, @excess_variable
30
62
  ~~~
31
63
 
32
64
  ## HTML mode
33
65
 
34
66
  Try using gem with `-g (--graph)` option, so it will create a folder with beautiful html output and log file with results of any scan.
35
67
 
36
- ![SandiMeter HTML mode](http://img545.imageshack.us/img545/5601/t8qk.png)
68
+ ![SandiMeter HTML mode pie charts](http://imageshack.us/a/img823/7653/hns3.png)
69
+ ![SandiMeter HTML mode details](http://imageshack.us/a/img820/8711/bygo.png)
37
70
 
38
71
  ## Ruby script mode
39
72
 
@@ -47,11 +80,11 @@ pp data
47
80
  # {:first_rule=>
48
81
  # {:small_classes_amount=>916,
49
82
  # :total_classes_amount=>937,
50
- # :missindented_classes_amount=>1},
83
+ # :misindented_classes_amount=>1},
51
84
  # :second_rule=>
52
85
  # {:small_methods_amount=>1144,
53
86
  # :total_methods_amount=>1833,
54
- # :missindented_methods_amount=>0},
87
+ # :misindented_methods_amount=>0},
55
88
  # :third_rule=>{:proper_method_calls=>5857, :total_method_calls=>5894},
56
89
  # :fourth_rule=>{:proper_controllers_amount=>17, :total_controllers_amount=>94}}
57
90
  ~~~
@@ -0,0 +1,8 @@
1
+ <table class="details-stats">
2
+ <thead>
3
+ <% head %>
4
+ </thead>
5
+ <tbody>
6
+ <% rows %>
7
+ </tbody>
8
+ </table>
data/html/forkme.png ADDED
Binary file
data/html/index.html CHANGED
@@ -13,7 +13,7 @@
13
13
  </script>
14
14
  </head>
15
15
  <body>
16
- <a href="https://github.com/makaroni4/sandi_meter"><img style="position: absolute; top: 0; left: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_left_green_007200.png" alt="Fork me on GitHub"></a>
16
+ <a href="https://github.com/makaroni4/sandi_meter"><img style="position: absolute; top: 0; left: 0; border: 0;" src="assets/forkme.png" alt="Fork me on GitHub"></a>
17
17
  <div class="container">
18
18
  <header>
19
19
  <h1 class="main-title">Sandi Metz rules</h1>
@@ -26,7 +26,10 @@
26
26
  <div id="pie4" class="pie-charts-item"></div>
27
27
  <div class="clearfix"></div>
28
28
  </section>
29
- <section class="plot-charts">
29
+ <nav class="main-menu">
30
+ <a href="#" class="main-menu-active js-menu-item" data-rel=".js-charts">Charts</a><a href="#" class="js-menu-item" data-rel=".js-details">Details</a>
31
+ </nav>
32
+ <section class="plot-charts js-charts js-tab-item">
30
33
  <div class="plot-charts-item">
31
34
  <div class="plot-charts-item-caption">Number of 100 line classes vs time</div>
32
35
  <div class="plot-charts-item-graph" id="plot1"></div>
@@ -46,10 +49,13 @@
46
49
  </div>
47
50
  <div class="clearfix"></div>
48
51
  </section>
52
+ <section class="details js-details js-tab-item">
53
+ <% details %>
54
+ </section>
49
55
  <footer class="main-footer">
50
- Scanned with love by <a href="https://github.com/makaroni4/sandi_meter">sandi_meter</a> gem.
51
- Please, <a href="https://github.com/makaroni4/sandi_meter/issues/new">leave an issue</a> or
52
- <a href="email:makaroni4@gmail.com">email</a> some feedback!
56
+ Scanned with love by <a href="https://github.com/makaroni4/sandi_meter">sandi_meter</a> gem.
57
+ Please, <a href="https://github.com/makaroni4/sandi_meter/issues/new">leave an issue</a> or
58
+ <a href="mailto:makaroni4@gmail.com">email</a> some feedback!
53
59
  </footer>
54
60
  </div>
55
61
  </body>
data/html/script.js CHANGED
@@ -61,4 +61,15 @@ $(document).ready(function(){
61
61
  plotLine('plot2', data, ['r20', 'r21'], ['under 5 lines', 'more than 5 lines']);
62
62
  plotLine('plot3', data, ['r30', 'r31'], ['less than 4 params', 'more than 4 params']);
63
63
  plotLine('plot4', data, ['r40', 'r41'], ['one instance variable', 'many instance variables']);
64
+
65
+ var $tabs = $(".js-tab-item");
66
+ var $menuItems = $(".js-menu-item")
67
+ $menuItems.on("click", function(e){
68
+ var rel = $(this).data("rel");
69
+ $tabs.hide();
70
+ $menuItems.removeClass("main-menu-active");
71
+ $(rel).show();
72
+ $(this).addClass("main-menu-active");
73
+ e.preventDefault();
74
+ });
64
75
  })
data/html/style.css CHANGED
@@ -51,3 +51,78 @@ body {
51
51
  .plot-charts-item-graph {
52
52
  height: 280px;
53
53
  }
54
+
55
+ .toggle_report.current_tab {
56
+ text-decoration: none;
57
+ cursor: default;
58
+ pointer-events: none;
59
+ }
60
+
61
+ .details {
62
+ display: none;
63
+ }
64
+
65
+ .details h2 {
66
+ margin: 0 0 14px;
67
+ }
68
+
69
+ .details-stats {
70
+ font-family: monospace;
71
+ margin-bottom: 40px;
72
+ }
73
+
74
+ .details-stats td {
75
+ padding-right: 20px;
76
+ line-height: 18px;
77
+ }
78
+
79
+ .details-stats td.warning {
80
+ color: red;
81
+ }
82
+
83
+ .main-menu {
84
+ margin-bottom: 30px;
85
+ }
86
+
87
+ .main-menu {
88
+ text-align: center;
89
+ }
90
+
91
+ .main-menu a {
92
+ display: inline-block;
93
+ padding: 4px 16px;
94
+ font-size: 18px;
95
+ text-decoration: none;
96
+ color: black;
97
+ border: 1px solid rgb(158, 158, 158);
98
+ }
99
+
100
+ .main-menu a:hover {
101
+ color: rgb(65, 131, 196);;
102
+ }
103
+
104
+ .main-menu a:first-child {
105
+ border-right: none;
106
+ border-top-left-radius: 4px;
107
+ border-bottom-left-radius: 4px;
108
+ }
109
+
110
+ .main-menu a:last-child {
111
+ border-top-right-radius: 4px;
112
+ border-bottom-right-radius: 4px;
113
+ }
114
+
115
+ .main-menu-active {
116
+ background: rgb(240, 240, 240);
117
+ }
118
+
119
+ .main-menu .main-menu-active:hover {
120
+ color: black;
121
+ }
122
+
123
+ .hint {
124
+ margin: -40px 0 40px 0;
125
+ font-family: monospace;
126
+ font-style: italic;
127
+ font-weight: normal;
128
+ }
@@ -5,15 +5,18 @@ require_relative 'method_arguments_counter'
5
5
 
6
6
  module SandiMeter
7
7
  class Analyzer
8
- attr_reader :classes, :missindented_classes, :methods, :missindented_methods, :method_calls, :instance_variables
8
+ attr_reader :classes, :misindented_classes, :methods, :misindented_methods, :method_calls, :instance_variables
9
9
 
10
10
  def initialize
11
11
  @classes = []
12
- @missindented_classes = []
13
- @missindented_methods = {}
12
+ @misindented_classes = []
13
+ @misindented_methods = {}
14
14
  @methods = {}
15
15
  @method_calls = []
16
16
  @instance_variables = {}
17
+
18
+ @parent_token = nil
19
+ @private_or_protected = false
17
20
  end
18
21
 
19
22
  def analyze(file_path)
@@ -37,19 +40,31 @@ module SandiMeter
37
40
 
38
41
  @classes.map! do |klass_params|
39
42
  klass_params << loc_checker.check(klass_params, 'class')
43
+ klass_params << "#{@file_path}:#{klass_params[1]}"
44
+ end
45
+
46
+ @misindented_classes.map! do |klass_params|
47
+ klass_params << "#{@file_path}:#{klass_params[1]}"
40
48
  end
41
49
 
42
50
  @methods.each_pair do |klass, methods|
43
51
  methods.each do |method_params|
44
52
  method_params << loc_checker.check(method_params, 'def')
53
+ method_params << "#{@file_path}:#{method_params[1]}"
54
+ end
55
+ end
56
+
57
+ @misindented_methods.each_pair do |klass, methods|
58
+ methods.each do |method_params|
59
+ method_params << "#{@file_path}:#{method_params[1]}"
45
60
  end
46
61
  end
47
62
 
48
63
  {
49
64
  classes: @classes,
50
- missindented_classes: @missindented_classes,
65
+ misindented_classes: @misindented_classes,
51
66
  methods: @methods,
52
- missindented_methods: @missindented_methods,
67
+ misindented_methods: @misindented_methods,
53
68
  method_calls: @method_calls,
54
69
  instance_variables: @instance_variables
55
70
  }
@@ -104,12 +119,12 @@ module SandiMeter
104
119
 
105
120
  if @indentation_warnings['class'] && @indentation_warnings['class'].any? { |first_line, last_line| first_line == class_params.last }
106
121
  class_params << nil
107
- @missindented_classes << class_params
122
+ @misindented_classes << class_params
108
123
  else
109
124
  class_params += [find_last_line(class_params)]
110
125
 
111
126
  # in case of one liner class last line will be nil
112
- (class_params.last == nil ? @missindented_classes : @classes) << class_params
127
+ (class_params.last == nil ? @misindented_classes : @classes) << class_params
113
128
  end
114
129
 
115
130
  current_namespace = class_params.first
@@ -127,7 +142,7 @@ module SandiMeter
127
142
  counter = SandiMeter::MethodArgumentsCounter.new
128
143
  arguments_count, line = counter.count(sexp)
129
144
 
130
- @method_calls << [arguments_count, line]
145
+ @method_calls << [arguments_count, "#{@file_path}:#{line}"]
131
146
 
132
147
  find_args_add_block(sexp)
133
148
  else
@@ -145,7 +160,7 @@ module SandiMeter
145
160
  if sexp.first == :assign
146
161
  @instance_variables[current_namespace] ||= {}
147
162
  @instance_variables[current_namespace][method_name] ||= []
148
- @instance_variables[current_namespace][method_name] << sexp[1][1][1]
163
+ @instance_variables[current_namespace][method_name] << sexp[1][1][1] if sexp[1][1][0] == :@ivar
149
164
  else
150
165
  scan_def_for_ivars(current_namespace, method_name, sexp)
151
166
  end
@@ -156,24 +171,41 @@ module SandiMeter
156
171
  sexp.each do |element|
157
172
  next unless element.kind_of?(Array)
158
173
 
174
+ @parent_token = element.first
159
175
  case element.first
160
176
  when :def
161
177
  method_params = find_method_params(element)
162
178
  if @indentation_warnings['def'] && @indentation_warnings['def'].any? { |first_line, last_line| first_line == method_params.last }
163
179
  method_params << nil
164
180
  method_params << number_of_arguments(element)
165
- @missindented_methods[current_namespace] ||= []
166
- @missindented_methods[current_namespace] << method_params
181
+ @misindented_methods[current_namespace] ||= []
182
+ @misindented_methods[current_namespace] << method_params
167
183
  else
168
184
  method_params += [find_last_line(method_params, 'def')]
169
185
  method_params << number_of_arguments(element)
170
186
  @methods[current_namespace] ||= []
171
187
  @methods[current_namespace] << method_params
172
188
  end
173
- scan_def_for_ivars(current_namespace, method_params.first, element) if @scan_instance_variables
189
+ if @scan_instance_variables && !@private_or_protected
190
+ scan_def_for_ivars(current_namespace, method_params.first, element)
191
+ end
192
+
174
193
  find_args_add_block(element)
175
194
  when :module, :class
176
195
  scan_class_sexp(element, current_namespace)
196
+ when :vcall
197
+ if element[1].first == :@ident
198
+ case element[1][1]
199
+ when "private"
200
+ @private_or_protected = true
201
+ when "public"
202
+ @private_or_protected = false
203
+ else
204
+ scan_sexp(element, current_namespace)
205
+ end
206
+ else
207
+ scan_sexp(element, current_namespace)
208
+ end
177
209
  else
178
210
  scan_sexp(element, current_namespace)
179
211
  end
@@ -17,7 +17,9 @@ module SandiMeter
17
17
  end
18
18
  end
19
19
 
20
- def calculate!
20
+ def calculate!(store_details = false)
21
+ @store_details = store_details
22
+
21
23
  check_first_rule
22
24
  check_second_rule
23
25
  check_third_rule
@@ -27,18 +29,90 @@ module SandiMeter
27
29
  end
28
30
 
29
31
  private
32
+ def log_first_rule
33
+ @output[:first_rule][:log] ||= {}
34
+ @output[:first_rule][:log][:classes] = @data[:classes].inject([]) do |log, class_params|
35
+ # TODO
36
+ # wrap each class params into class and get params with
37
+ # verbose name instead of array keys (class_params[2] should be klass.line_count)
38
+ log << [class_params.first, class_params[2], class_params.last] if class_params[-2] == false
39
+ log
40
+ end
41
+
42
+ @output[:first_rule][:log][:misindented_classes] = @data[:misindented_classes].inject([]) do |log, class_params|
43
+ log << [class_params.first, nil, class_params.last]
44
+ log
45
+ end
46
+ end
47
+
48
+ def log_second_rule
49
+ @output[:second_rule][:log] ||= {}
50
+ @output[:second_rule][:log][:methods] ||= []
51
+ @output[:second_rule][:log][:misindented_methods] ||= []
52
+
53
+ @data[:methods].each_pair do |klass, methods|
54
+ methods.select { |m| m[-2] == false }.each do |method_params|
55
+ params = [klass, method_params.first]
56
+ # TODO
57
+ # wrap method param to method class so method_params[1] becomes method.first_line
58
+ # and method_params[2] method.last_line
59
+ params << method_params[2] - method_params[1]
60
+ params << method_params.last
61
+
62
+ @output[:second_rule][:log][:methods] << params
63
+ end
64
+ end
65
+
66
+ @data[:misindented_methods].each_pair do |klass, methods|
67
+ methods.each do |method_params|
68
+ params = [klass, method_params.first]
69
+ params << nil
70
+ params << method_params.last
71
+
72
+ @output[:second_rule][:log][:misindented_methods] << params
73
+ end
74
+ end
75
+ end
76
+
77
+ def log_third_rule
78
+ @output[:third_rule][:log] ||={}
79
+ @output[:third_rule][:log][:method_calls] ||= []
80
+
81
+ # TODO
82
+ # add name of method being called
83
+ proper_method_calls = @data[:method_calls].inject(0) do |sum, params|
84
+ @output[:third_rule][:log][:method_calls] << params if params.first > 4
85
+ end
86
+ end
87
+
88
+ def log_fourth_rule
89
+ @output[:fourth_rule][:log] ||={}
90
+ @output[:fourth_rule][:log][:controllers] ||= []
91
+
92
+ @data[:instance_variables].each_pair do |controller, methods|
93
+ methods.each_pair do |method, instance_variables|
94
+ if instance_variables.size > 1
95
+ @output[:fourth_rule][:log][:controllers] << [controller, method, instance_variables]
96
+ end
97
+ end
98
+ end
99
+ end
100
+
30
101
  def check_first_rule
31
102
  total_classes_amount = @data[:classes].size
32
103
  small_classes_amount = @data[:classes].inject(0) do |sum, class_params|
33
- sum += 1 if class_params.last == true
104
+ sum += 1 if class_params[-2] == true
34
105
  sum
35
106
  end
36
- missindented_classes_amount = @data[:missindented_classes].size
107
+
108
+ misindented_classes_amount = @data[:misindented_classes].size
37
109
 
38
110
  @output[:first_rule] ||= {}
39
111
  @output[:first_rule][:small_classes_amount] = small_classes_amount
40
112
  @output[:first_rule][:total_classes_amount] = total_classes_amount
41
- @output[:first_rule][:missindented_classes_amount] = missindented_classes_amount
113
+ @output[:first_rule][:misindented_classes_amount] = misindented_classes_amount
114
+
115
+ log_first_rule if @store_details
42
116
  end
43
117
 
44
118
  def check_second_rule
@@ -46,19 +120,21 @@ module SandiMeter
46
120
  small_methods_amount = 0
47
121
 
48
122
  @data[:methods].each_pair do |klass, methods|
49
- small_methods_amount += methods.select { |m| m.last == true }.size
123
+ small_methods_amount += methods.select { |m| m[-2] == true }.size
50
124
  total_methods_amount += methods.size
51
125
  end
52
126
 
53
- missindented_methods_amount = 0
54
- @data[:missindented_methods].each_pair do |klass, methods|
55
- missindented_methods_amount += methods.size
127
+ misindented_methods_amount = 0
128
+ @data[:misindented_methods].each_pair do |klass, methods|
129
+ misindented_methods_amount += methods.size
56
130
  end
57
131
 
58
132
  @output[:second_rule] ||= {}
59
133
  @output[:second_rule][:small_methods_amount] = small_methods_amount
60
134
  @output[:second_rule][:total_methods_amount] = total_methods_amount
61
- @output[:second_rule][:missindented_methods_amount] = missindented_methods_amount
135
+ @output[:second_rule][:misindented_methods_amount] = misindented_methods_amount
136
+
137
+ log_second_rule if @store_details
62
138
  end
63
139
 
64
140
  # TODO
@@ -74,6 +150,8 @@ module SandiMeter
74
150
  @output[:third_rule] ||= {}
75
151
  @output[:third_rule][:proper_method_calls] = proper_method_calls
76
152
  @output[:third_rule][:total_method_calls] = total_method_calls
153
+
154
+ log_third_rule if @store_details
77
155
  end
78
156
 
79
157
  def check_fourth_rule
@@ -88,6 +166,8 @@ module SandiMeter
88
166
  @output[:fourth_rule] ||= {}
89
167
  @output[:fourth_rule][:proper_controllers_amount] = proper_controllers_amount
90
168
  @output[:fourth_rule][:total_controllers_amount] = total_controllers_amount
169
+
170
+ log_fourth_rule if @store_details
91
171
  end
92
172
  end
93
173
  end
@@ -20,10 +20,16 @@ module SandiMeter
20
20
  description: "Show syntax error and indentation log output",
21
21
  boolean: true
22
22
 
23
+ option :details,
24
+ short: "-d",
25
+ long: "--details",
26
+ description: "CLI mode. Show details (path, line number)",
27
+ boolean: true
28
+
23
29
  option :graph,
24
30
  short: "-g",
25
31
  long: "--graph",
26
- description: "Create folder and log data to graph",
32
+ description: "HTML mode. Create folder, log data and output stats to HTML file.",
27
33
  boolean: true
28
34
 
29
35
  option :help,
@@ -53,18 +59,26 @@ module SandiMeter
53
59
  end
54
60
 
55
61
  scanner = SandiMeter::FileScanner.new(cli.config[:log])
56
- data = scanner.scan(cli.config[:path])
62
+ data = scanner.scan(cli.config[:path], cli.config[:details] || cli.config[:graph])
63
+
57
64
  formatter = SandiMeter::Formatter.new
58
65
 
59
66
  formatter.print_data(data)
60
67
 
61
68
  if cli.config[:graph]
62
- logger = SandiMeter::Logger.new
63
- logger.log!(cli.config[:path], data)
69
+ if File.directory?(cli.config[:path])
70
+ logger = SandiMeter::Logger.new
71
+ logger.log!(cli.config[:path], data)
72
+
73
+ html_generator = SandiMeter::HtmlGenerator.new
74
+ html_generator.copy_assets!(cli.config[:path])
75
+ html_generator.generate_data!(cli.config[:path])
76
+ html_generator.generate_details!(cli.config[:path], data)
64
77
 
65
- html_generator = SandiMeter::HtmlGenerator.new
66
- html_generator.copy_assets!(cli.config[:path])
67
- html_generator.generate_data!(cli.config[:path])
78
+ system "open sandi_meter/index.html"
79
+ else
80
+ puts "WARNING!!! HTML mode works only if you scan folder."
81
+ end
68
82
  end
69
83
  end
70
84
 
@@ -77,4 +91,4 @@ module SandiMeter
77
91
  )
78
92
  end
79
93
  end
80
- end
94
+ end
@@ -8,14 +8,14 @@ module SandiMeter
8
8
  @calculator = SandiMeter::Calculator.new
9
9
  end
10
10
 
11
- def scan(path)
11
+ def scan(path, store_details = false)
12
12
  if File.directory?(path)
13
13
  scan_dir(path)
14
14
  else
15
15
  scan_file(path)
16
16
  end
17
17
 
18
- @calculator.calculate!
18
+ @calculator.calculate!(store_details)
19
19
  end
20
20
 
21
21
  private
@@ -32,6 +32,8 @@ module SandiMeter
32
32
  @calculator.push(data)
33
33
  rescue Exception => e
34
34
  if @log_errors
35
+ # TODO
36
+ # add backtrace
35
37
  puts "Checkout #{path} for:"
36
38
  puts "\t#{e.message}"
37
39
  end
@@ -4,25 +4,81 @@ module SandiMeter
4
4
  if data[:first_rule][:total_classes_amount] > 0
5
5
  puts "1. #{data[:first_rule][:small_classes_amount] * 100 / data[:first_rule][:total_classes_amount]}% of classes are under 100 lines."
6
6
  else
7
- puts "1. No classes to analize."
7
+ puts "1. No classes to analyze."
8
8
  end
9
9
 
10
10
  if data[:second_rule][:total_methods_amount] > 0
11
11
  puts "2. #{data[:second_rule][:small_methods_amount] * 100 / data[:second_rule][:total_methods_amount]}% of methods are under 5 lines."
12
12
  else
13
- puts "2. No methods to analize."
13
+ puts "2. No methods to analyze."
14
14
  end
15
15
 
16
16
  if data[:third_rule][:total_method_calls] > 0
17
- puts "3. #{data[:third_rule][:proper_method_calls] * 100 / data[:third_rule][:total_method_calls]}% of methods calls accepts are less than 4 parameters."
17
+ puts "3. #{data[:third_rule][:proper_method_calls] * 100 / data[:third_rule][:total_method_calls]}% of method calls accepted are less than 4 parameters."
18
18
  else
19
- puts "3. No method calls to analize."
19
+ puts "3. No method calls to analyze."
20
20
  end
21
21
 
22
22
  if data[:fourth_rule][:total_controllers_amount] > 0
23
23
  puts "4. #{data[:fourth_rule][:proper_controllers_amount] * 100 / data[:fourth_rule][:total_controllers_amount]}% of controllers have one instance variable per action."
24
24
  else
25
- puts "4. No controllers to analize."
25
+ puts "4. No controllers to analyze."
26
+ end
27
+
28
+ print_log(data)
29
+ end
30
+
31
+ def print_log(data)
32
+ return unless data[:first_rule][:log] || data[:second_rule][:log] || data[:fourth_rule][:log]
33
+
34
+ if data[:first_rule][:log][:classes].any?
35
+ puts "\nClasses with 100+ lines"
36
+ print_array_of_arrays [["Class name", "Size", "Path"]] + data[:first_rule][:log][:classes]
37
+ end
38
+
39
+ if data[:first_rule][:log][:misindented_classes].any?
40
+ puts "\nMissindented classes"
41
+ print_array_of_arrays [["Class name", "Path"]] + data[:first_rule][:log][:misindented_classes].map { |row| row.delete_at(1); row } # 1 – size, which nil for misindented_classes
42
+ end
43
+
44
+ if data[:second_rule][:log][:methods].any?
45
+ puts "\nMethods with 5+ lines"
46
+ print_array_of_arrays [["Class name", "Method name", "Size", "Path"]] + data[:second_rule][:log][:methods]
47
+ end
48
+
49
+ if data[:second_rule][:log][:misindented_methods].any?
50
+ puts "\nMissindented methods"
51
+ print_array_of_arrays [["Class name", "Method name", "Path"]] + data[:second_rule][:log][:misindented_methods].map { |row| row.delete_at(2); row } # 2 – size, which nil for misindented_methods
52
+ end
53
+
54
+ if data[:third_rule][:log][:method_calls].any?
55
+ puts "\nMethod calls with 4+ arguments"
56
+ print_array_of_arrays [["# of arguments", "Path"]] + data[:third_rule][:log][:method_calls]
57
+ end
58
+
59
+ if data[:fourth_rule][:log][:controllers].any?
60
+ puts "\nControllers with 1+ instance variables"
61
+ print_array_of_arrays [["Controller name", "Action name", "Instance variables"]] + data[:fourth_rule][:log][:controllers]
62
+ end
63
+ end
64
+
65
+ private
66
+ # TODO
67
+ # sort output by number of lines or any param
68
+ def print_array_of_arrays(nested_array)
69
+ nested_sizes = nested_array.map do |row|
70
+ row.map { |element| element.to_s.size }
71
+ end
72
+
73
+ sizes = nested_sizes.transpose.map { |row| row.max }
74
+
75
+ nested_array.each do |row|
76
+ line_elements = row.each_with_index.map do |element, index|
77
+ element_string = element.kind_of?(Array) ? element.join(', ') : element.to_s
78
+ element_string.ljust(sizes[index] + 1, ' ')
79
+ end
80
+
81
+ puts line_elements.join(' | ').prepend(" ")
26
82
  end
27
83
  end
28
84
  end
@@ -8,7 +8,7 @@ module SandiMeter
8
8
  FileUtils.mkdir(asset_dir_path) unless Dir.exists?(asset_dir_path)
9
9
 
10
10
 
11
- Dir[File.join(File.dirname(__FILE__), "../../html/*.{js,css}")].each do |file|
11
+ Dir[File.join(File.dirname(__FILE__), "../../html/*.{js,css,png}")].each do |file|
12
12
  FileUtils.cp file, File.join(asset_dir_path, File.basename(file))
13
13
  end
14
14
 
@@ -39,5 +39,98 @@ module SandiMeter
39
39
  file.write(index)
40
40
  end
41
41
  end
42
+
43
+ def generate_details!(path, data)
44
+ details = ""
45
+
46
+ if data[:first_rule][:log][:classes].any?
47
+ data[:first_rule][:log][:misindented_classes] ||= []
48
+ data[:first_rule][:log][:misindented_classes].each do |class_params|
49
+ class_params.insert(1, nil)
50
+ end
51
+
52
+ details << string_to_h2("Classes with 100+ lines")
53
+ details << generate_details_block(
54
+ ["Class name", "Size", "Path"],
55
+ proper_data: data[:first_rule][:log][:classes],
56
+ warning_data: data[:first_rule][:log][:misindented_classes],
57
+ hint: "NOTE: Red classes are misindented. Start improving your project by fixing them.",
58
+ warning_message: 'Misindented classes'
59
+ )
60
+ end
61
+
62
+ if data[:second_rule][:log][:methods].any?
63
+ data[:second_rule][:log][:misindented_methods] ||= []
64
+ data[:second_rule][:log][:misindented_methods].each do |method_params|
65
+ method_params.insert(2, nil)
66
+ end
67
+
68
+ details << string_to_h2("Methods with 5+ lines")
69
+ details << generate_details_block(
70
+ ["Class name", "Method name", "Size", "Path"],
71
+ proper_data: data[:second_rule][:log][:methods].sort_by { |a| -a[2].to_i },
72
+ warning_data: data[:second_rule][:log][:misindented_methods].sort_by { |a| -a[1].to_i },
73
+ hint: "NOTE: Red methods are misindented. Continue your way to perfect code by fixing them.",
74
+ warning_message: 'Misindented methods'
75
+ )
76
+ end
77
+
78
+ if data[:third_rule][:log][:method_calls].any?
79
+ details << string_to_h2("Method calls with 4+ arguments")
80
+ details << generate_details_block(
81
+ ["# of arguments", "Path"],
82
+ proper_data: data[:third_rule][:log][:method_calls]
83
+ )
84
+ end
85
+
86
+ if data[:fourth_rule][:log][:controllers].any?
87
+ details << string_to_h2("Controllers with 1+ instance variables")
88
+ details << generate_details_block(
89
+ ["Controller name", "Action name", "Instance variables"],
90
+ proper_data: data[:fourth_rule][:log][:controllers]
91
+ )
92
+ end
93
+
94
+ index_file = File.join(path, 'sandi_meter', 'index.html')
95
+ index = File.read(index_file)
96
+ index.gsub!('<% details %>', details)
97
+
98
+ File.open(index_file, 'w') do |file|
99
+ file.write(index)
100
+ end
101
+ end
102
+
103
+ private
104
+ def generate_details_block(head_row, data)
105
+ block_partial = File.read File.join(File.dirname(__FILE__), "../../html", "_detail_block.html")
106
+ block_partial.gsub!('<% head %>', array_to_tr(head_row, cell: "th"))
107
+
108
+ table_rows = data[:proper_data].map { |row| array_to_tr(row) }.join('')
109
+
110
+ if data[:warning_data]
111
+ table_rows << data[:warning_data].map { |row| array_to_tr(row, css_class: 'warning', tip: data[:warning_message]) }.join('')
112
+ end
113
+
114
+ block_partial.gsub!('<% rows %>', table_rows)
115
+ block_partial << hint(data[:hint]) if data[:hint]
116
+ block_partial
117
+ end
118
+
119
+ def hint(string)
120
+ %(<div class="hint">#{string}</div>)
121
+ end
122
+
123
+ def string_to_h2(string)
124
+ "<h2>#{string}</h2>\n"
125
+ end
126
+
127
+ def cell_to_s(element)
128
+ element.kind_of?(Array) ? element.join(', ') : element.to_s
129
+ end
130
+
131
+ def array_to_tr(array, params = {})
132
+ cell = params[:cell] || "td"
133
+ array.map { |element| "<#{cell} class=\"#{params[:css_class]}\" title=\"#{params[:tip]}\">#{cell_to_s(element)}</#{cell}>\n" }.join('').prepend("<tr>\n").concat("</tr>\n")
134
+ end
42
135
  end
43
136
  end
@@ -1,3 +1,3 @@
1
1
  module SandiMeter
2
- VERSION = "0.0.6"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -12,21 +12,21 @@ describe SandiMeter::Analyzer do
12
12
  end
13
13
 
14
14
  it 'finds indentation warnings for method' do
15
- analyzer.classes.should eq([["TestClass", 1, 5, true]])
16
- analyzer.missindented_classes.should be_empty
15
+ analyzer.classes.should eq([["TestClass", 1, 5, true, "#{test_file_path(3)}:1"]])
16
+ analyzer.misindented_classes.should be_empty
17
17
  end
18
18
 
19
19
  it 'finds methods' do
20
- analyzer.methods.should eq({"TestClass"=>[["blah", 2, 4, 0, true]]})
21
- analyzer.missindented_methods.should be_empty
20
+ analyzer.methods.should eq({"TestClass"=>[["blah", 2, 4, 0, true, "#{test_file_path(3)}:2"]]})
21
+ analyzer.misindented_methods.should be_empty
22
22
  end
23
23
 
24
24
  it 'finds method calls that brakes third rule' do
25
- analyzer.method_calls.should eq([[5,3]])
25
+ analyzer.method_calls.should eq([[5, "#{test_file_path(3)}:3"]])
26
26
  end
27
27
  end
28
28
 
29
- describe 'finds missindented classes without last line' do
29
+ describe 'finds misindented classes without last line' do
30
30
  let(:test_class) { test_file_path(1) }
31
31
 
32
32
  before do
@@ -35,12 +35,12 @@ describe SandiMeter::Analyzer do
35
35
 
36
36
  it 'finds indentation warnings for method' do
37
37
  analyzer.classes.should be_empty
38
- analyzer.missindented_classes.should eq([["MyApp::TestClass", 2, nil]])
38
+ analyzer.misindented_classes.should eq([["MyApp::TestClass", 2, nil, "#{test_file_path(1)}:2"]])
39
39
  end
40
40
 
41
41
  it 'finds methods' do
42
42
  analyzer.methods.should be_empty
43
- analyzer.missindented_methods.should eq({"MyApp::TestClass"=>[["blah", 3, nil, 0]]})
43
+ analyzer.misindented_methods.should eq({"MyApp::TestClass"=>[["blah", 3, nil, 0, "#{test_file_path(1)}:3"]]})
44
44
  end
45
45
  end
46
46
 
@@ -52,15 +52,15 @@ describe SandiMeter::Analyzer do
52
52
  end
53
53
 
54
54
  it 'finds classes' do
55
- analyzer.classes.should include(["FirstTestClass", 1, 4, true])
56
- analyzer.classes.should include(["SecondTestClass", 6, 9, true])
57
- analyzer.missindented_classes.should be_empty
55
+ analyzer.classes.should include(["FirstTestClass", 1, 4, true, "#{test_file_path(4)}:1"])
56
+ analyzer.classes.should include(["SecondTestClass", 6, 9, true, "#{test_file_path(4)}:6"])
57
+ analyzer.misindented_classes.should be_empty
58
58
  end
59
59
 
60
60
  it 'finds methods' do
61
- analyzer.methods["FirstTestClass"].should eq([["first_meth", 2, 3, 1, true]])
62
- analyzer.methods["SecondTestClass"].should eq([["second_meth", 7, 8, 1, true]])
63
- analyzer.missindented_methods.should be_empty
61
+ analyzer.methods["FirstTestClass"].should eq([["first_meth", 2, 3, 1, true, "#{test_file_path(4)}:2"]])
62
+ analyzer.methods["SecondTestClass"].should eq([["second_meth", 7, 8, 1, true, "#{test_file_path(4)}:7"]])
63
+ analyzer.misindented_methods.should be_empty
64
64
  end
65
65
  end
66
66
 
@@ -72,13 +72,13 @@ describe SandiMeter::Analyzer do
72
72
  end
73
73
 
74
74
  it 'finds classes' do
75
- analyzer.missindented_classes.should eq([["OneLinerClass", 1, nil]])
75
+ analyzer.misindented_classes.should eq([["OneLinerClass", 1, nil, "#{test_file_path(5)}:1"]])
76
76
  analyzer.classes.should be_empty
77
77
  end
78
78
 
79
79
  it 'finds methods' do
80
80
  analyzer.methods.should be_empty
81
- analyzer.missindented_methods.should be_empty
81
+ analyzer.misindented_methods.should be_empty
82
82
  end
83
83
  end
84
84
 
@@ -90,16 +90,16 @@ describe SandiMeter::Analyzer do
90
90
  end
91
91
 
92
92
  it 'finds class and subclass' do
93
- analyzer.classes.should include(["MyApp::Blah::User", 5, 13, true])
94
- analyzer.classes.should include(["MyApp::Blah::User::SubUser", 9, 12, true])
95
- analyzer.missindented_classes.should be_empty
93
+ analyzer.classes.should include(["MyApp::Blah::User", 5, 13, true, "#{test_file_path(7)}:5"])
94
+ analyzer.classes.should include(["MyApp::Blah::User::SubUser", 9, 12, true, "#{test_file_path(7)}:9"])
95
+ analyzer.misindented_classes.should be_empty
96
96
  end
97
97
 
98
98
  it 'finds methods' do
99
- analyzer.methods["MyApp::Blah"].should eq([["module_meth", 2, 3, 0, true]])
100
- analyzer.methods["MyApp::Blah::User"].should eq([["class_meth", 6, 7, 0, true]])
101
- analyzer.methods["MyApp::Blah::User::SubUser"].should eq([["sub_meth", 10, 11, 0, true]])
102
- analyzer.missindented_methods.should be_empty
99
+ analyzer.methods["MyApp::Blah"].should eq([["module_meth", 2, 3, 0, true, "#{test_file_path(7)}:2"]])
100
+ analyzer.methods["MyApp::Blah::User"].should eq([["class_meth", 6, 7, 0, true, "#{test_file_path(7)}:6"]])
101
+ analyzer.methods["MyApp::Blah::User::SubUser"].should eq([["sub_meth", 10, 11, 0, true, "#{test_file_path(7)}:10"]])
102
+ analyzer.misindented_methods.should be_empty
103
103
  end
104
104
  end
105
105
 
@@ -111,15 +111,15 @@ describe SandiMeter::Analyzer do
111
111
  end
112
112
 
113
113
  it 'finds class and subclass' do
114
- analyzer.classes.should include(["RailsController", 1, 12, true])
115
- analyzer.missindented_classes.should be_empty
114
+ analyzer.classes.should include(["RailsController", 1, 12, true, "#{test_file_path(8)}:1"])
115
+ analyzer.misindented_classes.should be_empty
116
116
  end
117
117
 
118
118
  it 'finds methods' do
119
- analyzer.methods["RailsController"].should include(["index", 2, 3, 0, true])
120
- analyzer.methods["RailsController"].should include(["destroy", 5, 6, 0, true])
121
- analyzer.methods["RailsController"].should include(["private_meth", 9, 10, 0, true])
122
- analyzer.missindented_methods.should be_empty
119
+ analyzer.methods["RailsController"].should include(["index", 2, 3, 0, true, "#{test_file_path(8)}:2"])
120
+ analyzer.methods["RailsController"].should include(["destroy", 5, 6, 0, true, "#{test_file_path(8)}:5"])
121
+ analyzer.methods["RailsController"].should include(["private_meth", 9, 10, 0, true, "#{test_file_path(8)}:9"])
122
+ analyzer.misindented_methods.should be_empty
123
123
  end
124
124
  end
125
125
 
@@ -136,6 +136,18 @@ describe SandiMeter::Analyzer do
136
136
  end
137
137
  end
138
138
 
139
+ context 'in controller class with non instance variables' do
140
+ let(:test_class) { test_file_path('14_controller') }
141
+
142
+ before do
143
+ analyzer.analyze(test_class)
144
+ end
145
+
146
+ it 'does not find instance variables' do
147
+ analyzer.instance_variables.should eq({"GuestController"=>{"create_guest_user"=>[]}})
148
+ end
149
+ end
150
+
139
151
  context 'not in controller class' do
140
152
  let(:test_class) { test_file_path(10) }
141
153
 
@@ -147,6 +159,32 @@ describe SandiMeter::Analyzer do
147
159
  analyzer.instance_variables.should be_empty
148
160
  end
149
161
  end
162
+
163
+ context 'in controller class with private method' do
164
+ let(:test_class) { test_file_path("15_controller") }
165
+
166
+ before do
167
+ analyzer.analyze(test_class)
168
+ end
169
+
170
+ it 'finds method defined after public keyword' do
171
+ analyzer.instance_variables["UsersController"].should have_key("create")
172
+ end
173
+
174
+ it 'omits actions without instance variables' do
175
+ analyzer.instance_variables["UsersController"].should_not have_key("show")
176
+ end
177
+
178
+ it 'omits private methods' do
179
+ analyzer.instance_variables["UsersController"].should_not have_key("find_user")
180
+ end
181
+
182
+ it 'omits protected methods' do
183
+ analyzer.instance_variables["UsersController"].should_not have_key("protected_find_user")
184
+ end
185
+
186
+
187
+ end
150
188
  end
151
189
 
152
190
  describe 'hash method arguments' do
@@ -157,7 +195,7 @@ describe SandiMeter::Analyzer do
157
195
  end
158
196
 
159
197
  it 'counts arguments' do
160
- analyzer.method_calls.should eq([[5, 3]])
198
+ analyzer.method_calls.should eq([[5, "#{test_file_path(11)}:3"]])
161
199
  end
162
200
  end
163
201
 
@@ -169,11 +207,11 @@ describe SandiMeter::Analyzer do
169
207
  end
170
208
 
171
209
  it 'are count for class definition' do
172
- analyzer.classes.should eq([["Valera", 1, 109, false]])
210
+ analyzer.classes.should eq([["Valera", 1, 109, false, "#{test_file_path(12)}:1"]])
173
211
  end
174
212
 
175
213
  it 'are count for method definition' do
176
- analyzer.methods.should eq({"Valera"=>[["doodle", 2, 9, 0, false]]})
214
+ analyzer.methods.should eq({"Valera"=>[["doodle", 2, 9, 0, false, "#{test_file_path(12)}:2"]]})
177
215
  end
178
216
  end
179
217
 
@@ -0,0 +1,6 @@
1
+ class AnotherUsersController
2
+ def index
3
+ @users = User.page(params[:page])
4
+ @excess_variable = 'blah blah'
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ class GuestController
2
+ def create_guest_user
3
+ u = User.new_guest
4
+ u.save
5
+ session[:guest_user_id] = u.id
6
+ u
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ class UsersController
2
+ before_filter :find_user
3
+
4
+ def show
5
+ end
6
+
7
+ private
8
+ def find_user
9
+ @user = User.find(params[:id])
10
+ end
11
+
12
+ protected
13
+ def protected_find_user
14
+ @user = User.find(params[:id])
15
+ end
16
+
17
+ public
18
+ def create
19
+ @user = User.new(params[:user])
20
+ end
21
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sandi_meter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anatoli Makarevich
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-26 00:00:00.000000000 Z
11
+ date: 2013-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -64,7 +64,9 @@ files:
64
64
  - README.md
65
65
  - Rakefile
66
66
  - sandi_meter.gemspec
67
+ - html/_detail_block.html
67
68
  - html/chart.js
69
+ - html/forkme.png
68
70
  - html/index.html
69
71
  - html/jquery.js
70
72
  - html/morris.css
@@ -88,9 +90,12 @@ files:
88
90
  - spec/method_arguments_counter_spec.rb
89
91
  - spec/test_classes/1.rb
90
92
  - spec/test_classes/10.rb
93
+ - spec/test_classes/10_controller.rb
91
94
  - spec/test_classes/11.rb
92
95
  - spec/test_classes/12.rb
93
96
  - spec/test_classes/13.rb
97
+ - spec/test_classes/14_controller.rb
98
+ - spec/test_classes/15_controller.rb
94
99
  - spec/test_classes/2.rb
95
100
  - spec/test_classes/3.rb
96
101
  - spec/test_classes/4.rb