metric_fu 4.4.1 → 4.4.2

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.
Files changed (39) hide show
  1. checksums.yaml +14 -6
  2. data/CONTRIBUTORS +1 -0
  3. data/Gemfile +1 -1
  4. data/HISTORY.md +13 -0
  5. data/checksum/metric_fu-4.4.1.gem.sha512 +1 -0
  6. data/lib/metric_fu/cli/parser.rb +7 -0
  7. data/lib/metric_fu/data_structures/line_numbers.rb +70 -59
  8. data/lib/metric_fu/data_structures/location.rb +36 -20
  9. data/lib/metric_fu/data_structures/sexp_node.rb +89 -0
  10. data/lib/metric_fu/environment.rb +48 -0
  11. data/lib/metric_fu/io.rb +1 -7
  12. data/lib/metric_fu/metrics/base_template.rb +28 -9
  13. data/lib/metric_fu/metrics/hotspots/analysis/analyzed_problems.rb +8 -44
  14. data/lib/metric_fu/metrics/hotspots/analysis/analyzer_tables.rb +3 -0
  15. data/lib/metric_fu/metrics/hotspots/analysis/problems.rb +6 -2
  16. data/lib/metric_fu/metrics/hotspots/analysis/ranked_problem_location.rb +71 -0
  17. data/lib/metric_fu/metrics/hotspots/analysis/rankings.rb +8 -1
  18. data/lib/metric_fu/metrics/hotspots/hotspot_analyzer.rb +0 -1
  19. data/lib/metric_fu/metrics/hotspots/hotspots.rb +8 -3
  20. data/lib/metric_fu/metrics/hotspots/template_awesome/hotspots.html.erb +12 -12
  21. data/lib/metric_fu/metrics/saikuro/init.rb +3 -0
  22. data/lib/metric_fu/metrics/saikuro/parsing_element.rb +39 -0
  23. data/lib/metric_fu/metrics/saikuro/saikuro.rb +4 -145
  24. data/lib/metric_fu/metrics/saikuro/scratch_file.rb +114 -0
  25. data/lib/metric_fu/version.rb +1 -1
  26. data/spec/metric_fu/configuration_spec.rb +0 -7
  27. data/spec/metric_fu/formatter/html_spec.rb +0 -1
  28. data/spec/metric_fu/formatter/yaml_spec.rb +0 -1
  29. data/spec/metric_fu/metrics/base_template_spec.rb +1 -2
  30. data/spec/metric_fu/metrics/hotspots/analysis/analyzed_problems_spec.rb +8 -16
  31. data/spec/metric_fu/metrics/hotspots/hotspots_spec.rb +26 -69
  32. data/spec/metric_fu/metrics/saikuro/saikuro_spec.rb +5 -5
  33. data/spec/metric_fu/reporting/graphs/engines/gchart_spec.rb +1 -0
  34. data/spec/resources/yml/hotspots/generator.yml +47 -0
  35. data/spec/resources/yml/hotspots/generator_analysis.yml +53 -0
  36. data/spec/run_spec.rb +0 -1
  37. data/spec/support/helper_methods.rb +10 -0
  38. data/spec/support/suite.rb +13 -14
  39. metadata +35 -25
@@ -48,13 +48,7 @@ module MetricFu
48
48
 
49
49
  # Add the 'app' directory if we're running within rails.
50
50
  def set_code_dirs(config)
51
- # TODO: Rather than check if we're running against a rails app,
52
- # shouldn't we just check if the directories exist?
53
- if config.rails?
54
- @directories['code_dirs'] = %w(app lib)
55
- else
56
- @directories['code_dirs'] = %w(lib)
57
- end
51
+ @directories['code_dirs'] = %w(app lib).select{|dir| Dir.exists?(dir) }
58
52
  end
59
53
 
60
54
  end
@@ -1,4 +1,3 @@
1
- require 'pathname'
2
1
  require 'erb'
3
2
  module MetricFu
4
3
 
@@ -7,7 +6,7 @@ module MetricFu
7
6
  # methods to make templating a bit easier. However, classes do not
8
7
  # have to inherit from here in order to provide a template. The only
9
8
  # requirement for a template class is that it provides a #write method
10
- # to actually write out the template. See StandardTemplate for an
9
+ # to actually write out the template. See AwesomeTemplate for an
11
10
  # example.
12
11
  class Template
13
12
  attr_accessor :result, :per_file_data, :formatter, :output_directory
@@ -27,8 +26,21 @@ module MetricFu
27
26
  # @return String
28
27
  # The erb evaluated string
29
28
  def erbify(section)
30
- erb_doc = File.read(template(section))
31
- ERB.new(erb_doc).result(binding)
29
+ template_file = template(section)
30
+ erb = erb_template_source(template_file)
31
+ erb.result(binding)
32
+ rescue => e
33
+ message = "Error: #{e.class}; message #{e.message}. "
34
+ message << "Failed evaluating erb template "
35
+ message << "for section #{section} and template #{template_file}."
36
+ raise message
37
+ end
38
+
39
+ def erb_template_source(template_file)
40
+ erb_doc = File.read(template_file)
41
+ erb = ERB.new(erb_doc)
42
+ erb.filename = template_file
43
+ erb
32
44
  end
33
45
 
34
46
  # Copies an instance variable mimicing the name of the section
@@ -64,9 +76,11 @@ module MetricFu
64
76
  File.join(template_directory, section.to_s + ".html.erb")
65
77
  end
66
78
  end
79
+
67
80
  def template_dir(metric)
68
81
  File.join(MetricFu.metrics_dir, metric, metric_template_dir)
69
82
  end
83
+
70
84
  # e.g. template_awesome, template_standard
71
85
  def metric_template_dir
72
86
  template_name = self.class.name.sub('Template', '')[/^([A-Z][a-z]+)+/].downcase
@@ -106,7 +120,8 @@ module MetricFu
106
120
  # @return String
107
121
  # The contents of the css file
108
122
  def inline_css(css)
109
- open(File.join(template_directory, css)) { |f| f.read }
123
+ css_file = File.join(template_directory, css)
124
+ open(css_file) {|f| f.read }
110
125
  end
111
126
 
112
127
  # Provides a link to open a file through the textmate protocol
@@ -121,7 +136,9 @@ module MetricFu
121
136
  # @return String
122
137
  # An anchor link to a textmate reference or a file reference
123
138
  def link_to_filename(name, line = nil, link_content = nil)
124
- "<a href='#{file_url(name, line)}'>#{link_content(name, line, link_content)}</a>"
139
+ href = file_url(name, line)
140
+ link_text = link_content(name, line, link_content)
141
+ "<a href='#{href}'>#{link_text}</a>"
125
142
  end
126
143
 
127
144
  def round_to_tenths(decimal)
@@ -140,9 +157,9 @@ module MetricFu
140
157
  end
141
158
 
142
159
  def display_location(location)
143
- class_name, method_name = location.class_name, location.method_name
160
+ class_name, method_name = location.fetch('class_name'), location.fetch('method_name')
144
161
  str = ""
145
- str += link_to_filename(location.file_name, location.line_number)
162
+ str += link_to_filename(location.fetch('file_name'), location.fetch('line_number'))
146
163
  str += " : " if method_name || class_name
147
164
  if(method_name)
148
165
  str += "#{method_name}"
@@ -177,6 +194,7 @@ module MetricFu
177
194
  def remove_leading_slash(filename)
178
195
  filename.gsub(/^\//, '')
179
196
  end
197
+
180
198
  def render_as_txmt_protocol? # :nodoc:
181
199
  if MetricFu.configuration.osx?
182
200
  !MetricFu::Formatter::Templates.option('darwin_txmt_protocol_no_thanks')
@@ -184,6 +202,7 @@ module MetricFu
184
202
  false
185
203
  end
186
204
  end
205
+
187
206
  def render_as_mvim_protocol? # :nodoc:
188
207
  if MetricFu.configuration.osx?
189
208
  !MetricFu::Formatter::Templates.option('darwin_mvim_protocol_no_thanks')
@@ -227,7 +246,7 @@ module MetricFu
227
246
  end
228
247
 
229
248
  def snake_case_to_title_case(string)
230
- string.split('_').collect{|word| word[0] = word[0..0].upcase; word}.join(" ")
249
+ string.split('_').collect{|word| word[0] = word[0..0].upcase; word}.join(" ")
231
250
  end
232
251
 
233
252
  # belive me, I tried to meta program this with an inherited hook
@@ -1,11 +1,12 @@
1
1
  module MetricFu
2
2
  class HotspotAnalyzedProblems
3
-
3
+ MetricFu.metrics_require { "hotspots/analysis/ranked_problem_location" }
4
4
 
5
5
  def initialize(hotspot_rankings, analyzer_tables)
6
6
  @hotspot_rankings = hotspot_rankings
7
7
  @analyzer_tables = analyzer_tables
8
8
  end
9
+
9
10
  def worst_items
10
11
  worst_items = {}
11
12
  worst_items[:files] = worst(@hotspot_rankings.worst_files, :file)
@@ -18,53 +19,16 @@ module MetricFu
18
19
 
19
20
  # @param rankings [Array<MetricFu::HotspotRankings>]
20
21
  # @param granularity [Symbol] one of :class, :method, :file
21
- def worst(rankings,granularity)
22
+ def worst(rankings, granularity)
22
23
  rankings.map do |ranked_item_name|
23
- location = location(granularity, ranked_item_name)
24
- details = problems_with(granularity, ranked_item_name)
25
- {:location => location, :details => details}
26
- end
27
- end
28
-
29
- # @todo redo as item,value, options = {}
30
- # Note that the other option for 'details' is :detailed (this isn't
31
- # at all clear from this method itself
32
- def problems_with(item, value)
33
- sub_table = get_sub_table(item, value)
34
- #grouping = Ruport::Data::Grouping.new(sub_table, :by => 'metric')
35
- grouping = get_grouping(sub_table, :by => 'metric')
36
- MetricFu::HotspotProblems.new(grouping).problems
37
- end
38
-
39
- def location(item, value)
40
- sub_table = get_sub_table(item, value)
41
- assert_sub_table_has_data(item, sub_table, value)
42
- first_row = sub_table[0]
43
- case item
44
- when :class
45
- MetricFu::Location.get(first_row.file_path, first_row.class_name, nil)
46
- when :method
47
- MetricFu::Location.get(first_row.file_path, first_row.class_name, first_row.method_name)
48
- when :file
49
- MetricFu::Location.get(first_row.file_path, nil, nil)
50
- else
51
- raise ArgumentError, "Item must be :class, :method, or :file"
52
- end
53
- end
54
-
55
- def assert_sub_table_has_data(item, sub_table, value)
56
- if (sub_table.length==0)
57
- raise MetricFu::AnalysisError, "The #{item.to_s} '#{value.to_s}' does not have any rows in the analysis table"
24
+ sub_table = get_sub_table(granularity, ranked_item_name)
25
+ MetricFu::HotspotRankedProblemLocation.new(sub_table, granularity)
58
26
  end
59
27
  end
60
28
 
61
- def get_sub_table(item, value)
62
- tables = @analyzer_tables.tables_for(item)
63
- tables[value]
64
- end
65
-
66
- def get_grouping(table, opts)
67
- MetricFu::HotspotGroupings.new(table, opts).get_grouping
29
+ def get_sub_table(granularity, ranked_item_name)
30
+ tables = @analyzer_tables.tables_for(granularity)
31
+ tables[ranked_item_name]
68
32
  end
69
33
 
70
34
  end
@@ -44,6 +44,7 @@ module MetricFu
44
44
  }
45
45
  end
46
46
 
47
+ # COLLECT AND PROCESS RAW HOTSPOT METRICS
47
48
  def build_lookups!
48
49
  @class_and_method_to_file ||= {}
49
50
  # Build a mapping from [class,method] => filename
@@ -58,6 +59,7 @@ module MetricFu
58
59
  end
59
60
  end
60
61
 
62
+ # COLLECT AND PROCESS RAW HOTSPOT METRICS
61
63
  def process_rows!
62
64
  # Correct incorrect rows in the table
63
65
  table.each do |row|
@@ -74,6 +76,7 @@ module MetricFu
74
76
  end
75
77
 
76
78
 
79
+ # COLLECT AND PROCESS RAW HOTSPOT METRICS
77
80
  def fix_row_file_path!(row)
78
81
  # We know that Saikuro rows are broken
79
82
  # next unless row['metric'] == :saikuro
@@ -1,8 +1,8 @@
1
1
  module MetricFu
2
2
  class HotspotProblems
3
3
 
4
- def initialize(grouping)
5
- @grouping = grouping
4
+ def initialize(sub_table)
5
+ @grouping = group_by(sub_table, 'metric')
6
6
  end
7
7
 
8
8
  def problems
@@ -13,5 +13,9 @@ module MetricFu
13
13
  problems
14
14
  end
15
15
 
16
+ def group_by(sub_table, by = 'metric')
17
+ MetricFu::HotspotGroupings.new(sub_table, :by => by).get_grouping
18
+ end
19
+
16
20
  end
17
21
  end
@@ -0,0 +1,71 @@
1
+ module MetricFu
2
+ class HotspotRankedProblemLocation
3
+ MetricFu.data_structures_require { 'location' }
4
+ attr_reader :sub_table, :granularity
5
+ def initialize(sub_table, granularity)
6
+ @sub_table = sub_table
7
+ @granularity = granularity
8
+ end
9
+
10
+ def to_hash
11
+ {
12
+ 'location' => location.to_hash,
13
+ 'details' => stringify_keys(problems),
14
+ }
15
+ end
16
+
17
+ def stringify_keys(hash)
18
+ result = {}
19
+ hash.each do |key, value|
20
+ result[key.to_s] = value
21
+ end
22
+ result
23
+ end
24
+
25
+
26
+ # @todo redo as item,value, options = {}
27
+ # Note that the other option for 'details' is :detailed (this isn't
28
+ # at all clear from this method itself
29
+ def problems
30
+ @problems ||= MetricFu::HotspotProblems.new(sub_table).problems
31
+ end
32
+
33
+ def location
34
+ @location ||= case granularity
35
+ when :class then class_location
36
+ when :method then method_location
37
+ when :file then file_location
38
+ else raise ArgumentError, "Item must be :class, :method, or :file"
39
+ end
40
+ end
41
+
42
+ def file_path
43
+ first_row.file_path
44
+ end
45
+ def class_name
46
+ first_row.class_name
47
+ end
48
+ def method_name
49
+ first_row.method_name
50
+ end
51
+ def file_location
52
+ MetricFu::Location.get(file_path, nil, nil)
53
+ end
54
+ def method_location
55
+ MetricFu::Location.get(file_path, class_name, method_name)
56
+ end
57
+ def class_location
58
+ MetricFu::Location.get(file_path, class_name, nil)
59
+ end
60
+ def first_row
61
+ assert_sub_table_has_data
62
+ @first_row ||= sub_table[0]
63
+ end
64
+ def assert_sub_table_has_data
65
+ if (sub_table.length==0)
66
+ raise MetricFu::AnalysisError, "The #{item.to_s} '#{value.to_s}' does not have any rows in the analysis table"
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -39,8 +39,15 @@ module MetricFu
39
39
 
40
40
  def calculate_score_for_granularity(analyzer, granularity)
41
41
  metric_ranking = calculate_metric_scores(granularity, analyzer)
42
- add_to_master_ranking(ranking(granularity), metric_ranking, analyzer)
42
+
43
+ add_to_master_ranking(
44
+ ranking(granularity),
45
+ metric_ranking,
46
+ analyzer
47
+ )
43
48
  end
49
+
50
+ # CALCULATES METRIC HOTSPOT SCORES / RANKINGS PER map/reduce in HOTSPOT subclasses
44
51
  def calculate_metric_scores(granularity, analyzer)
45
52
  metric_ranking = MetricFu::Ranking.new
46
53
  metric_violations = @tool_tables[analyzer.name]
@@ -1,5 +1,4 @@
1
1
  require File.expand_path('analysis_error', MetricFu.errors_dir)
2
- MetricFu.data_structures_require { 'location' }
3
2
  %w(table record grouping ranking problems).each do |path|
4
3
  MetricFu.metrics_require { "hotspots/analysis/#{path}" }
5
4
  end
@@ -12,15 +12,20 @@ module MetricFu
12
12
  end
13
13
 
14
14
  def emit
15
- @analyzer = MetricFu::HotspotAnalyzer.new(MetricFu.result.result_hash)
15
+ # no-op
16
16
  end
17
17
 
18
18
  def analyze
19
- @hotspots = @analyzer && @analyzer.hotspots || {}
19
+ analyzer = MetricFu::HotspotAnalyzer.new(MetricFu.result.result_hash)
20
+ @hotspots = analyzer.hotspots
20
21
  end
21
22
 
22
23
  def to_h
23
- {:hotspots => @hotspots}
24
+ result = {:hotspots => {}}
25
+ @hotspots.each do |granularity, hotspots|
26
+ result[:hotspots][granularity.to_s] = hotspots.map(&:to_hash)
27
+ end
28
+ result
24
29
  end
25
30
  end
26
31
 
@@ -2,11 +2,11 @@
2
2
  <p>Meta analysis of your metrics to find hotspots in your code.</p>
3
3
  <br/>
4
4
 
5
- <% if !@hotspots || @hotspots.size == 0 %>
5
+ <% if @hotspots.nil? || @hotspots.size.zero? %>
6
6
  No Hotspots were found.
7
- <% elsif @hotspots && @hotspots.size > 0 %>
7
+ <% else %>
8
8
 
9
- <% granularities = [:files, :classes, :methods] %>
9
+ <% granularities = %w[files classes methods] %>
10
10
  <table>
11
11
  <tr valign="top">
12
12
  <% granularities.each do |granularity| %>
@@ -15,21 +15,21 @@
15
15
  <% end %>
16
16
  </tr>
17
17
 
18
- <% items = [] %>
18
+ <% hotspots = [] %>
19
19
  <% granularities.each_index do |index| %>
20
20
  <% granularity = granularities[index] %>
21
- <% items << @hotspots[granularity] %>
21
+ <% hotspots << @hotspots[granularity] %>
22
22
  <% end %>
23
23
 
24
- <% items_length = 0 %>
24
+ <% hotspots_length = 0 %>
25
25
  <% columns = [0, 1, 2] %>
26
- <% while (items_length < items[0].length || items_length < items[1].length || items_length < items[2].length) do %>
26
+ <% while (hotspots_length < hotspots[0].length || hotspots_length < hotspots[1].length || hotspots_length < hotspots[2].length) do %>
27
27
  <tr valign="top">
28
28
  <% columns.each do |column| %>
29
- <% item = items[column].length >= items_length ? items[column][items_length] : nil %>
29
+ <% item = hotspots[column].length >= hotspots_length ? hotspots[column][hotspots_length] : nil %>
30
30
  <% if item %>
31
- <% location = item[:location] %>
32
- <% file, line = location.file_name, location.line_number %>
31
+ <% location = item.fetch('location') %>
32
+ <% file, line = location.fetch('file_name'), location.fetch('line_number') %>
33
33
  <td>
34
34
  <b>
35
35
  <%= display_location(location) %>
@@ -43,7 +43,7 @@
43
43
  <% end %>
44
44
  <br/><br/>
45
45
  <!-- TODO HOTSPOTS for metric fu nice metric_link method -->
46
- <% item[:details].each do |metric, info| %>
46
+ <% item.fetch('details').each do |metric, info| %>
47
47
  <%#= metric_link(@stat, metric, h(metric.to_s.capitalize)) + ": " + h(info)%><br/>
48
48
  <%= "#{metric.to_s.capitalize}: #{info}" %><br/>
49
49
  <% end %>
@@ -52,7 +52,7 @@
52
52
  <td> &nbsp; </td>
53
53
  <% end %>
54
54
  <% end %>
55
- <% items_length += 1 %>
55
+ <% hotspots_length += 1 %>
56
56
  </tr>
57
57
  <% end %>
58
58
 
@@ -23,5 +23,8 @@ module MetricFu
23
23
  super
24
24
  end
25
25
 
26
+ def activate
27
+ super
28
+ end
26
29
  end
27
30
  end
@@ -0,0 +1,39 @@
1
+ module MetricFu
2
+ class SaikuroParsingElement
3
+
4
+ TYPE_REGEX=/Type:(.*) Name/
5
+ NAME_REGEX=/Name:(.*) Complexity/
6
+ COMPLEXITY_REGEX=/Complexity:(.*) Lines/
7
+ LINES_REGEX=/Lines:(.*)/
8
+
9
+ attr_reader :complexity, :lines, :defs, :element_type
10
+ attr_accessor :name
11
+
12
+ def initialize(line)
13
+ @line = line
14
+ @element_type = line.match(TYPE_REGEX)[1].strip
15
+ @name = line.match(NAME_REGEX)[1].strip
16
+ @complexity = line.match(COMPLEXITY_REGEX)[1].strip
17
+ @lines = line.match(LINES_REGEX)[1].strip
18
+ @defs = []
19
+ end
20
+
21
+ def <<(line)
22
+ @defs << MetricFu::SaikuroParsingElement.new(line)
23
+ end
24
+
25
+ def to_h
26
+ base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
27
+ unless @defs.empty?
28
+ defs = @defs.map do |my_def|
29
+ my_def = my_def.to_h
30
+ my_def.delete(:defs)
31
+ my_def
32
+ end
33
+ base[:defs] = defs
34
+ end
35
+ return base
36
+ end
37
+
38
+ end
39
+ end