metric_fu 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -1,3 +1,15 @@
1
+ === MetricFu 2.1.0 / 2011-03-1
2
+
3
+ * Flog gemspec version was >= 2.2.0, which was too early and didn't work. Changed to >= 2.3.0 - Chris Griego
4
+ * RCov generator now uses a regex with begin and end line anchor to avoid splitting on comments with equal signs in source files - Andrew Selder
5
+ * RCov generator now always strips the 3 leading characters from the lines when reconstruction source files so that heredocs and block comments parse successfully - Andrew Selder
6
+ * Dan Mayer ported some specs for the Hotspots code into MetricFu from Caliper's code.
7
+ * Stefan Huber fixed some problems with churn pretending not to support Svn.
8
+ * Kakutani Shintaro added the ability to opt out of opening files with TextMate (config.darwin_txmt_protocol_no_thanks = true).
9
+ * Joel Nimety and Andrew Selder fixed a problem where Saikuro was parsing a dir twice.
10
+ * Dan Sinclair added some awesome 'annotate' functionality to the Hotspots page. Click on it so see the file with problems in-line.
11
+ * Dan Sinclair added a verbose mode (config.verbose = true).
12
+
1
13
  === MetricFu 2.0.1 / 2010-11-13
2
14
 
3
15
  * Delete trailing whitespaces - Delwyn de Villiers
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008,2009,2010 Jake Scruggs
1
+ Copyright (c) 2008,2009,2010,2011 Jake Scruggs
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
@@ -8,7 +8,7 @@ module MetricFu
8
8
  # to actually write out the template. See StandardTemplate for an
9
9
  # example.
10
10
  class Template
11
- attr_accessor :report
11
+ attr_accessor :report, :per_file_data
12
12
 
13
13
  private
14
14
  # Creates a new erb evaluated result from the passed in section.
@@ -137,13 +137,19 @@ module MetricFu
137
137
  def file_url(name, line) # :nodoc:
138
138
  return '' unless name
139
139
  filename = File.expand_path(name.gsub(/^\//, ''))
140
- if MetricFu.configuration.platform.include?('darwin')
140
+ if render_as_txmt_protocol?
141
141
  "txmt://open/?url=file://#{filename}" << (line ? "&line=#{line}" : "")
142
142
  else
143
143
  "file://#{filename}"
144
144
  end
145
145
  end
146
146
 
147
+ def render_as_txmt_protocol? # :nodoc:
148
+ config = MetricFu.configuration
149
+ return false unless config.platform.include?('darwin')
150
+ return !config.darwin_txmt_protocol_no_thanks
151
+ end
152
+
147
153
  # Provides a brain dead way to cycle between two values during
148
154
  # an iteration of some sort. Pass in the first_value, the second_value,
149
155
  # and the cardinality of the iteration.
@@ -162,6 +168,5 @@ module MetricFu
162
168
  return first_value if iteration % 2 == 0
163
169
  return second_value
164
170
  end
165
-
166
171
  end
167
172
  end
@@ -35,18 +35,4 @@ class ChurnAnalyzer
35
35
  end
36
36
  end
37
37
 
38
- private
39
-
40
- def self.update_changes(total, changed)
41
- changed.each do |change|
42
- #should work as has_key(change), but hash == doesn't work on 1.8.6 here for some reason it never matches
43
- if total.has_key?(change.to_a.sort)
44
- total[change.to_a.sort] += 1
45
- else
46
- total[change.to_a.sort] = 1
47
- end
48
- end
49
- total
50
- end
51
-
52
38
  end
@@ -8,9 +8,9 @@ module MetricFu
8
8
  AVAILABLE_METRICS = [:churn, :flog, :flay, :reek,
9
9
  :roodi, :rcov,
10
10
  :hotspots]
11
-
11
+
12
12
  AVAILABLE_METRICS << :saikuro unless RUBY_VERSION == '1.9.2'
13
-
13
+
14
14
  AVAILABLE_GRAPHS = [:flog, :flay, :reek, :roodi, :rcov, :rails_best_practices]
15
15
  AVAILABLE_GRAPH_ENGINES = [:gchart, :bluff]
16
16
 
@@ -145,7 +145,11 @@ module MetricFu
145
145
  @hotspots = {}
146
146
  @file_globs_to_ignore = []
147
147
 
148
+ @verbose = false
149
+
148
150
  @graph_engine = :bluff # can be :bluff or :gchart
151
+
152
+ @darwin_txmt_protocol_no_thanks = false
149
153
  end
150
154
 
151
155
  # Perform a simple check to try and guess if we're running
@@ -121,6 +121,10 @@ module MetricFu
121
121
  # methods to allow extra hooks into the processing methods, and help
122
122
  # to keep the logic of your Generators clean.
123
123
  def generate_report
124
+ if MetricFu.configuration.verbose
125
+ puts "Executing #{self.class.to_s.gsub(/.*::/, '')}"
126
+ end
127
+
124
128
  %w[emit analyze].each do |meth|
125
129
  send("before_#{meth}".to_sym)
126
130
  send("#{meth}".to_sym)
@@ -4,8 +4,8 @@ module MetricFu
4
4
 
5
5
  def initialize(contents)
6
6
  rp = RubyParser.new
7
- file_sexp = rp.parse(contents)
8
7
  @locations = {}
8
+ file_sexp = rp.parse(contents)
9
9
  case file_sexp[0]
10
10
  when :class
11
11
  process_class(file_sexp)
@@ -15,6 +15,9 @@ module MetricFu
15
15
  file_sexp.each_of_type(:class) { |sexp| process_class(sexp) }
16
16
  else
17
17
  end
18
+ rescue Exception
19
+ #catch errors for files ruby_parser fails on
20
+ @locations
18
21
  end
19
22
 
20
23
  def in_method? line_number
@@ -31,6 +34,11 @@ module MetricFu
31
34
  found_method_and_range.first
32
35
  end
33
36
 
37
+ def start_line_for_method(method)
38
+ return nil unless @locations.has_key?(method)
39
+ @locations[method].first
40
+ end
41
+
34
42
  private
35
43
 
36
44
  def process_module(sexp)
@@ -63,4 +71,4 @@ module MetricFu
63
71
  end
64
72
 
65
73
  end
66
- end
74
+ end
@@ -3,23 +3,70 @@
3
3
  class ReekAnalyzer
4
4
  include ScoringStrategies
5
5
 
6
- REEK_ISSUE_INFO = {'Uncommunicative Name' =>
7
- {'link' => 'http://wiki.github.com/kevinrutherford/reek/uncommunicative-name', 'info' => 'An Uncommunicative Name is a name that doesn’t communicate its intent well enough.'},
6
+ REEK_ISSUE_INFO = {
7
+ 'Uncommunicative Name' =>
8
+ {'link' => 'http://wiki.github.com/kevinrutherford/reek/uncommunicative-name',
9
+ 'info' => 'An Uncommunicative Name is a name that doesn’t communicate its intent well enough.'},
8
10
  'Class Variable' =>
9
- {'link' => 'http://wiki.github.com/kevinrutherford/reek/class-variable', 'info' => 'Class variables form part of the global runtime state, and as such make it easy for one part of the system to accidentally or inadvertently depend on another part of the system.'},
10
- 'Duplication' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/duplication', 'info' => 'Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.'},
11
- 'Low Cohesion' => {'link' => 'http://en.wikipedia.org/wiki/Cohesion_(computer_science)', 'info' => 'Low cohesion is associated with undesirable traits such as being difficult to maintain, difficult to test, difficult to reuse, and even difficult to understand.'},
12
- 'Nested Iterators' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/nested-iterators', 'info' => 'Nested Iterator occurs when a block contains another block.'},
13
- 'Control Couple' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/control-couple', 'info' => 'Control coupling occurs when a method or block checks the value of a parameter in order to decide which execution path to take. The offending parameter is often called a “Control Couple”.'},
14
- 'Irresponsible Module' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/irresponsible-module', 'info' => 'Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.'},
15
- 'Long Parameter List' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/long-parameter-list', 'info' => 'A Long Parameter List occurs when a method has more than one or two parameters, or when a method yields more than one or two objects to an associated block.'},
16
- 'Data Clump' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/data-clump', 'info' => 'In general, a Data Clump occurs when the same two or three items frequently appear together in classes and parameter lists, or when a group of instance variable names start or end with similar substrings.'},
17
- 'Simulated Polymorphism' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/simulated-polymorphism', 'info' => 'Simulated Polymorphism occurs when, code uses a case statement (especially on a type field) or code uses instance_of?, kind_of?, is_a?, or === to decide what code to execute'},
18
- 'Large Class' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/large-class', 'info' => 'A Large Class is a class or module that has a large number of instance variables, methods or lines of code in any one piece of its specification.'},
19
- 'Long Method' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/long-method', 'info' => 'Long methods can be hard to read and understand. They often are harder to test and maintain as well, which can lead to buggier code.'},
20
- 'Feature Envy' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/feature-envy', 'info' => 'Feature Envy occurs when a code fragment references another object more often than it references itself, or when several clients do the same series of manipulations on a particular type of object.'},
21
- 'Utility Function' => {'link' =>'http://wiki.github.com/kevinrutherford/reek/utility-function', 'info' => 'A Utility Function is any instance method that has no dependency on the state of the instance. It reduces the code’s ability to communicate intent. Code that “belongs” on one class but which is located in another can be hard to find.'},
22
- 'Attribute' => {'link' => 'http://wiki.github.com/kevinrutherford/reek/attribute', 'info' => 'A class that publishes a getter or setter for an instance variable invites client classes to become too intimate with its inner workings, and in particular with its representation of state.'}
11
+ {'link' => 'http://wiki.github.com/kevinrutherford/reek/class-variable',
12
+ 'info' => 'Class variables form part of the global runtime state, and as such make it ' +
13
+ 'easy for one part of the system to accidentally or inadvertently depend on ' +
14
+ 'another part of the system.'},
15
+ 'Duplication' =>
16
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/duplication',
17
+ 'info' => 'Duplication occurs when two fragments of code look nearly identical, or when ' +
18
+ 'two fragments of code have nearly identical effects at some conceptual level.'},
19
+ 'Low Cohesion' =>
20
+ {'link' => 'http://en.wikipedia.org/wiki/Cohesion_(computer_science)',
21
+ 'info' => 'Low cohesion is associated with undesirable traits such as being difficult to ' +
22
+ 'maintain, difficult to test, difficult to reuse, and even difficult to understand.'},
23
+ 'Nested Iterators' =>
24
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/nested-iterators',
25
+ 'info' => 'Nested Iterator occurs when a block contains another block.'},
26
+ 'Control Couple' =>
27
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/control-couple',
28
+ 'info' => 'Control coupling occurs when a method or block checks the value of a parameter in ' +
29
+ 'order to decide which execution path to take. The offending parameter is often called a “Control Couple”.'},
30
+ 'Irresponsible Module' =>
31
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/irresponsible-module',
32
+ 'info' => 'Classes and modules are the units of reuse and release. It is therefore considered ' +
33
+ 'good practice to annotate every class and module with a brief comment outlining its responsibilities.'},
34
+ 'Long Parameter List' =>
35
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/long-parameter-list',
36
+ 'info' => 'A Long Parameter List occurs when a method has more than one or two parameters, ' +
37
+ 'or when a method yields more than one or two objects to an associated block.'},
38
+ 'Data Clump' =>
39
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/data-clump',
40
+ 'info' => 'In general, a Data Clump occurs when the same two or three items frequently appear ' +
41
+ 'together in classes and parameter lists, or when a group of instance variable names ' +
42
+ 'start or end with similar substrings.'},
43
+ 'Simulated Polymorphism' =>
44
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/simulated-polymorphism',
45
+ 'info' => 'Simulated Polymorphism occurs when, code uses a case statement (especially on a ' +
46
+ 'type field) or code uses instance_of?, kind_of?, is_a?, or === to decide what code to execute'},
47
+ 'Large Class' =>
48
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/large-class',
49
+ 'info' => 'A Large Class is a class or module that has a large number of instance variables, ' +
50
+ 'methods or lines of code in any one piece of its specification.'},
51
+ 'Long Method' =>
52
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/long-method',
53
+ 'info' => 'Long methods can be hard to read and understand. They often are harder to test and ' +
54
+ 'maintain as well, which can lead to buggier code.'},
55
+ 'Feature Envy' =>
56
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/feature-envy',
57
+ 'info' => 'Feature Envy occurs when a code fragment references another object more often than ' +
58
+ 'it references itself, or when several clients do the same series of manipulations ' +
59
+ 'on a particular type of object.'},
60
+ 'Utility Function' =>
61
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/utility-function',
62
+ 'info' => 'A Utility Function is any instance method that has no dependency on the state of the ' +
63
+ 'instance. It reduces the code’s ability to communicate intent. Code that “belongs” on ' +
64
+ 'one class but which is located in another can be hard to find.'},
65
+ 'Attribute' =>
66
+ {'link' => 'http://wiki.github.com/kevinrutherford/reek/attribute',
67
+ 'info' => 'A class that publishes a getter or setter for an instance variable invites client ' +
68
+ 'classes to become too intimate with its inner workings, and in particular with its ' +
69
+ 'representation of state.'}
23
70
  }
24
71
 
25
72
  # Note that in practice, the prefix reek__ is appended to each one
@@ -28,6 +28,9 @@ module MetricFu
28
28
  report_hash.to_yaml
29
29
  end
30
30
 
31
+ def per_file_data
32
+ @per_file_data ||= {}
33
+ end
31
34
 
32
35
  def report_hash #:nodoc:
33
36
  @report_hash ||= {}
@@ -41,6 +44,7 @@ module MetricFu
41
44
  def save_templatized_report
42
45
  @template = MetricFu.template_class.new
43
46
  @template.report = report_hash
47
+ @template.per_file_data = per_file_data
44
48
  @template.write
45
49
  end
46
50
 
@@ -51,7 +55,11 @@ module MetricFu
51
55
  # The hash to add to the aggregate report_hash
52
56
  def add(report_type)
53
57
  clazz = MetricFu.const_get(report_type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
54
- report_hash.merge!(clazz.generate_report)
58
+ inst = clazz.new
59
+
60
+ report_hash.merge!(inst.generate_report)
61
+
62
+ inst.per_file_info(per_file_data) if inst.respond_to?(:per_file_info)
55
63
  end
56
64
 
57
65
  # Saves the passed in content to the passed in directory. If
@@ -13,7 +13,7 @@ module MetricFu
13
13
  end
14
14
 
15
15
  def analyze
16
- if @output.match(/fatal: Not a git repository/)
16
+ if @output.match(/Churning requires a subversion or git repo/)
17
17
  @churn = [:churn => {}]
18
18
  else
19
19
  @churn = YAML::load(@output)
@@ -48,6 +48,19 @@ module MetricFu
48
48
  :method_containers => sorted_containers.map {|method_container| method_container.to_h}}}
49
49
  end
50
50
 
51
+ def per_file_info(out)
52
+ @method_containers.each_pair do |klass, container|
53
+ container.methods.each_pair do |method_name, data|
54
+ next if data[:path].nil?
55
+
56
+ file, line = data[:path].split(':')
57
+
58
+ out[file] ||= {}
59
+ out[file][line] ||= []
60
+ out[file][line] << {:type => :flog, :description => "Score of %.2f" % data[:score]}
61
+ end
62
+ end
63
+ end
51
64
  end
52
65
 
53
66
  class MethodContainer
@@ -2,24 +2,52 @@ module MetricFu
2
2
  class RailsBestPractices < Generator
3
3
 
4
4
  def emit
5
- @output = `rails_best_practices .`
5
+ @output = `rails_best_practices --without-color .`
6
6
  end
7
7
 
8
8
  def analyze
9
9
  @matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
10
10
  total = @matches.pop
11
+ cleanup_color_switches(total.first)
12
+
11
13
  2.times { @matches.pop } # ignore wiki link
12
14
  @matches.reject! {|array| array.empty? }
13
15
  @matches.map! do |match|
14
16
  file, line = match[0].split(':')
15
17
  problem = match[1]
18
+
19
+ cleanup_color_switches(file)
20
+ cleanup_color_switches(problem)
21
+
22
+ file.gsub!(/^\.\//, '')
23
+
16
24
  {:file => file, :line => line, :problem => problem}
17
25
  end
18
26
  @rails_best_practices_results = {:total => total, :problems => @matches}
19
27
  end
20
28
 
29
+ def cleanup_color_switches(str)
30
+ return if str.nil?
31
+ str.gsub!(%r{^\e\[3[1|2]m}, '')
32
+ str.gsub!(%r{\e\[0m$}, '')
33
+ end
34
+
21
35
  def to_h
22
36
  {:rails_best_practices => @rails_best_practices_results}
23
37
  end
38
+
39
+ def per_file_info(out)
40
+ @rails_best_practices_results[:problems].each do |problem|
41
+ next if problem[:file] == '' || problem[:problem].nil?
42
+
43
+ out[problem[:file]] ||= {}
44
+
45
+ lines = problem[:line].split(/\s*,\s*/)
46
+ lines.each do |line|
47
+ out[problem[:file]][line] ||= []
48
+ out[problem[:file]][line] << {:type => :rails_best_practices, :description => problem[:problem]}
49
+ end
50
+ end
51
+ end
24
52
  end
25
53
  end
@@ -3,7 +3,7 @@ require 'enumerator'
3
3
  module MetricFu
4
4
 
5
5
  class Rcov < Generator
6
- NEW_FILE_MARKER = ("=" * 80) + "\n"
6
+ NEW_FILE_MARKER = /^={80}$/.freeze
7
7
 
8
8
  class Line
9
9
  attr_accessor :content, :was_run
@@ -100,11 +100,7 @@ module MetricFu
100
100
  files.each_pair {|fname, content| files[fname] = content.split("\n") }
101
101
  files.each_pair do |fname, content|
102
102
  content.map! do |raw_line|
103
- if raw_line.match(/^!!/)
104
- line = Line.new(raw_line.gsub('!!', ' '), false).to_h
105
- else
106
- line = Line.new(raw_line, true).to_h
107
- end
103
+ line = Line.new(raw_line[3..-1], !raw_line.match(/^!!/)).to_h
108
104
  end
109
105
  content.reject! {|line| line[:content].blank? }
110
106
  files[fname] = {:lines => content}
@@ -56,5 +56,26 @@ module MetricFu
56
56
  {:reek => {:matches => @matches}}
57
57
  end
58
58
 
59
+ def per_file_info(out)
60
+ @matches.each do |file_data|
61
+ next if File.extname(file_data[:file_path]) == '.erb'
62
+ begin
63
+ line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:file_path], 'r').read)
64
+ rescue StandardError => e
65
+ raise e unless e.message =~ /you shouldn't be able to get here/
66
+ puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level reek information for this file."
67
+ next
68
+ end
69
+
70
+ out[file_data[:file_path]] ||= {}
71
+ file_data[:code_smells].each do |smell_data|
72
+ line = line_numbers.start_line_for_method(smell_data[:method])
73
+ out[file_data[:file_path]][line.to_s] ||= []
74
+ out[file_data[:file_path]][line.to_s] << {:type => :reek,
75
+ :description => "#{smell_data[:type]} - #{smell_data[:message]}"}
76
+ end
77
+ end
78
+ end
79
+
59
80
  end
60
81
  end
@@ -23,5 +23,13 @@ module MetricFu
23
23
  def to_h
24
24
  {:roodi => @roodi_results}
25
25
  end
26
+
27
+ def per_file_info(out)
28
+ @matches.each do |match|
29
+ out[match[:file]] ||= {}
30
+ out[match[:file]][match[:line]] ||= []
31
+ out[match[:file]][match[:line]] << {:type => :roodi, :description => match[:problem]}
32
+ end
33
+ end
26
34
  end
27
35
  end
@@ -4,7 +4,7 @@ module MetricFu
4
4
 
5
5
  def emit
6
6
  options_string = MetricFu.saikuro.inject("") do |options, option|
7
- options + "--#{option.join(' ')} " unless option == :input_directory
7
+ option[0] == :input_directory ? options : options + "--#{option.join(' ')} "
8
8
  end
9
9
 
10
10
  MetricFu.saikuro[:input_directory].each do |input_dir|
@@ -32,14 +32,42 @@ module MetricFu
32
32
  def to_h
33
33
  files = @files.map do |file|
34
34
  my_file = file.to_h
35
- my_file[:filename] = file.filename
35
+
36
+ f = file.filepath
37
+ f.gsub!(%r{^#{metric_directory}/}, '')
38
+ f << "/#{file.filename}"
39
+
40
+ my_file[:filename] = f
36
41
  my_file
37
42
  end
38
- {:saikuro => {:files => files,
39
- :classes => @classes.map {|c| c.to_h},
40
- :methods => @meths.map {|m| m.to_h}
41
- }
42
- }
43
+ @saikuro_data = {:files => files,
44
+ :classes => @classes.map {|c| c.to_h},
45
+ :methods => @meths.map {|m| m.to_h}
46
+ }
47
+ {:saikuro => @saikuro_data}
48
+ end
49
+
50
+ def per_file_info(out)
51
+ @saikuro_data[:files].each do |file_data|
52
+ next if File.extname(file_data[:filename]) == '.erb' || !File.exists?(file_data[:filename])
53
+ begin
54
+ line_numbers = MetricFu::LineNumbers.new(File.open(file_data[:filename], 'r').read)
55
+ rescue StandardError => e
56
+ raise e unless e.message =~ /you shouldn't be able to get here/
57
+ puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Saikuro information for this file."
58
+ next
59
+ end
60
+
61
+ out[file_data[:filename]] ||= {}
62
+ file_data[:classes].each do |class_data|
63
+ class_data[:methods].each do |method_data|
64
+ line = line_numbers.start_line_for_method(method_data[:name])
65
+ out[file_data[:filename]][line.to_s] ||= []
66
+ out[file_data[:filename]][line.to_s] << {:type => :saikuro,
67
+ :description => "Complexity #{method_data[:complexity]}"}
68
+ end
69
+ end
70
+ end
43
71
  end
44
72
 
45
73
  private
@@ -118,6 +146,10 @@ module MetricFu
118
146
  File.basename(@path, '_cyclo.html')
119
147
  end
120
148
 
149
+ def filepath
150
+ File.dirname(@path)
151
+ end
152
+
121
153
  def to_h
122
154
  merge_classes
123
155
  {:classes => @elements}
@@ -26,8 +26,7 @@ module MetricFu
26
26
 
27
27
  def remove_noise(output)
28
28
  lines = output.split("\n")
29
- lines = lines.find_all {|line| line[0].chr != "+" }
30
- lines = lines.find_all {|line| line[0].chr != "(" }
29
+ lines = lines.find_all {|line| line =~ /^\s*[C|]/ }
31
30
  lines.shift
32
31
  lines
33
32
  end
@@ -3,6 +3,7 @@ require 'yaml'
3
3
  begin
4
4
  require 'active_support/core_ext/object/to_json'
5
5
  require 'active_support/core_ext/object/blank'
6
+ require 'active_support/inflector'
6
7
  rescue LoadError
7
8
  require 'activesupport'
8
9
  end
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'syntax/convertors/html'
2
3
 
3
4
  class AwesomeTemplate < MetricFu::Template
4
5
 
@@ -14,6 +15,7 @@ class AwesomeTemplate < MetricFu::Template
14
15
  report.each_pair do |section, contents|
15
16
  if template_exists?(section)
16
17
  create_instance_var(section, contents)
18
+ create_instance_var(:per_file_data, per_file_data)
17
19
  @html = erbify(section)
18
20
  html = erbify('layout')
19
21
  fn = output_filename(section)
@@ -28,6 +30,39 @@ class AwesomeTemplate < MetricFu::Template
28
30
  fn = output_filename('index')
29
31
  MetricFu.report.save_output(html, MetricFu.output_directory, fn)
30
32
  end
33
+
34
+ write_file_data
35
+ end
36
+
37
+ def write_file_data
38
+ convertor = Syntax::Convertors::HTML.for_syntax('ruby')
39
+
40
+ per_file_data.each_pair do |file, lines|
41
+ data = File.open(file, 'r').readlines
42
+ fn = "#{file.gsub(%r{/}, '_')}.html"
43
+
44
+ out = "<html><head><style>#{inline_css('css/syntax.css')}</style></head><body>"
45
+ out << "<table cellpadding='0' cellspacing='0' class='ruby'>"
46
+ data.each_with_index do |line, idx|
47
+ out << "<tr><td valign='top'><small>#{idx + 1}</small></td>"
48
+ out << "<td valign='top'>"
49
+ if lines.has_key?((idx + 1).to_s)
50
+ out << "<ul>"
51
+ lines[(idx + 1).to_s].each do |problem|
52
+ out << "<li>#{problem[:description]} &raquo; #{problem[:type]}</li>"
53
+ end
54
+ out << "</ul>"
55
+ else
56
+ out << "&nbsp;"
57
+ end
58
+ out << "</td>"
59
+ out << "<td valign='top'><a name='line#{idx + 1}'>#{convertor.convert(line)}</a></td>"
60
+ out << "</tr>"
61
+ end
62
+ out << "<table></body></html>"
63
+
64
+ MetricFu.report.save_output(out, MetricFu.output_directory, fn)
65
+ end
31
66
  end
32
67
 
33
68
  def this_directory
@@ -0,0 +1,19 @@
1
+ table { background: #fff; color: #000; }
2
+ .ruby .normal { color: #000; }
3
+ .ruby .comment { color: #005; font-style: italic; }
4
+ .ruby .keyword { color: #A44; font-weight: bold; }
5
+ .ruby .method { color: #44f; }
6
+ .ruby .class { color: #b1713d; }
7
+ .ruby .module { color: #050; }
8
+ .ruby .punct { color: #668; font-weight: bold; }
9
+ .ruby .symbol { color: #00f; }
10
+ .ruby .string { color: #4a4; }
11
+ .ruby .char { color: #F07; }
12
+ .ruby .ident { color: #000; }
13
+ .ruby .constant { color: #b1713d; }
14
+ .ruby .regex { color: #B66; background: #FEF; }
15
+ .ruby .number { color: #F99; }
16
+ .ruby .attribute { color: #f84; }
17
+ .ruby .global { color: #7FB; }
18
+ .ruby .expr { color: #227; }
19
+ .ruby .escape { color: #277; }
@@ -32,6 +32,14 @@ No Hotspots were found.
32
32
  <b>
33
33
  <%= display_location(item[:location], nil) %>
34
34
  </b>
35
+ <% if item[:location].file_path %>
36
+ <% file, line = item[:location].file_path.split(/:/) %>
37
+ <% if per_file_data[file] %>
38
+ <small>&laquo;
39
+ <b><a href="<%= file.gsub(%r{/}, '_') %>.html<%= (line.nil? ? '' : "#line#{line}") %>">annotate</a></b>
40
+ &raquo;</small>
41
+ <% end %>
42
+ <% end %>
35
43
  <br/><br/>
36
44
  <!-- TODO HOTSPOTS for metric fu nice metric_link method -->
37
45
  <% item[:details].each do |metric, info| %>
@@ -1,6 +1,6 @@
1
1
  <h3>Rails Best Practices Results</h3>
2
2
 
3
- <p><a href="http://github.com/flyerhzm/rails_best_practices">rails_best_practices</a> checks quality of rails app files according to ihower’s presentation from Kungfu RailsConf in Shanghai China.</p>
3
+ <p><a href="http://github.com/flyerhzm/rails_best_practices">rails_best_practices</a> is a code metric tool for rails projects.</p>
4
4
 
5
5
  <% graph_name = 'rails_best_practices' %>
6
6
  <% if MetricFu.configuration.graph_engine == :gchart %>
@@ -88,6 +88,7 @@ describe MetricFu::Template do
88
88
  before(:each) do
89
89
  config = mock("configuration")
90
90
  config.stub!(:platform).and_return('universal-darwin-9.0')
91
+ config.stub!(:darwin_txmt_protocol_no_thanks).and_return(false)
91
92
  MetricFu.stub!(:configuration).and_return(config)
92
93
  end
93
94
 
@@ -112,6 +113,22 @@ describe MetricFu::Template do
112
113
  + "/expanded/filename&line=6'>filename:6</a>")
113
114
  end
114
115
 
116
+ describe "but no thanks for txtmt" do
117
+ before(:each) do
118
+ config = mock("configuration")
119
+ config.stub!(:platform).and_return('universal-darwin-9.0')
120
+ config.stub!(:darwin_txmt_protocol_no_thanks).and_return(true)
121
+ MetricFu.stub!(:configuration).and_return(config)
122
+ File.should_receive(:expand_path).and_return('filename')
123
+ end
124
+
125
+ it "should return a file protocol link" do
126
+ name = "filename"
127
+ result = @template.send(:link_to_filename, name)
128
+ result.should == "<a href='file://filename'>filename</a>"
129
+ end
130
+ end
131
+
115
132
  describe "and given link text" do
116
133
  it "should use the submitted link text" do
117
134
  File.stub!(:expand_path).with('filename').and_return('/expanded/filename')
@@ -126,6 +143,7 @@ describe MetricFu::Template do
126
143
  before(:each) do
127
144
  config = mock("configuration")
128
145
  config.should_receive(:platform).and_return('other')
146
+ config.stub!(:darwin_txmt_protocol_no_thanks).and_return(false)
129
147
  MetricFu.stub!(:configuration).and_return(config)
130
148
  File.should_receive(:expand_path).and_return('filename')
131
149
  end
@@ -157,5 +175,3 @@ describe MetricFu::Template do
157
175
  end
158
176
 
159
177
  end
160
-
161
-
@@ -34,6 +34,7 @@ describe MetricFu::Report do
34
34
  template_class.should_receive(:new).and_return(template_class)
35
35
  MetricFu.should_receive(:template_class).and_return(template_class)
36
36
  template_class.should_receive(:report=)
37
+ template_class.should_receive(:per_file_data=)
37
38
  template_class.should_receive(:write)
38
39
  @report.save_templatized_report
39
40
  end
@@ -43,7 +44,13 @@ describe MetricFu::Report do
43
44
  it 'should add a passed hash to the report_hash instance variable' do
44
45
  report_type = mock('report_type')
45
46
  report_type.should_receive(:to_s).and_return('type')
46
- report_type.should_receive(:generate_report).and_return({:a => 'b'})
47
+
48
+ report_inst = mock('report_inst')
49
+ report_type.should_receive(:new).and_return(report_inst)
50
+
51
+ report_inst.should_receive(:generate_report).and_return({:a => 'b'})
52
+ report_inst.should_receive(:respond_to?).and_return(false)
53
+
47
54
  MetricFu.should_receive(:const_get).
48
55
  with('Type').and_return(report_type)
49
56
  report_hash = mock('report_hash')
@@ -11,7 +11,7 @@ describe Churn do
11
11
 
12
12
  it "should be empty on error" do
13
13
  churn = MetricFu::Churn.new
14
- churn.instance_variable_set(:@output, "fatal: Not a git repository")
14
+ churn.instance_variable_set(:@output, "Churning requires a subversion or git repo")
15
15
  result = churn.analyze
16
16
  result.should == [:churn => {}]
17
17
  end
@@ -5,7 +5,7 @@ describe RailsBestPractices do
5
5
  it "should gather the raw data" do
6
6
  MetricFu::Configuration.run {}
7
7
  practices = MetricFu::RailsBestPractices.new
8
- practices.should_receive(:`).with("rails_best_practices .")
8
+ practices.should_receive(:`).with("rails_best_practices --without-color .")
9
9
  practices.emit
10
10
  end
11
11
  end
@@ -34,10 +34,10 @@ describe RailsBestPractices do
34
34
  @results[:problems].size.should == 2
35
35
  @results[:problems].first.should == { :line => "17",
36
36
  :problem => "replace instance variable with local variable",
37
- :file => "./app/views/admin/testimonials/_form.html.erb" }
37
+ :file => "app/views/admin/testimonials/_form.html.erb" }
38
38
  @results[:problems][1].should == { :line => "24,45,68,85",
39
39
  :problem => "use before_filter for show,edit,update,destroy",
40
- :file => "./app/controllers/admin/campaigns_controller.rb" }
40
+ :file => "app/controllers/admin/campaigns_controller.rb" }
41
41
  end
42
42
  end
43
43
 
@@ -47,12 +47,12 @@ describe MetricFu::Rcov do
47
47
 
48
48
  it "should know which lines were run" do
49
49
  @files["lib/templates/awesome/awesome_template.rb"][:lines].
50
- should include({:content=>" require 'fileutils'", :was_run=>true})
50
+ should include({:content=>"require 'fileutils'", :was_run=>true})
51
51
  end
52
52
 
53
53
  it "should know which lines NOT were run" do
54
54
  @files["lib/templates/awesome/awesome_template.rb"][:lines].
55
- should include({:content=>" if template_exists?(section)", :was_run=>false})
55
+ should include({:content=>" if template_exists?(section)", :was_run=>false})
56
56
  end
57
57
  end
58
58
 
@@ -12,7 +12,7 @@ describe Saikuro do
12
12
  end
13
13
 
14
14
  it "should find the filename of a file" do
15
- @output[:saikuro][:files].first[:filename].should == 'users_controller.rb'
15
+ @output[:saikuro][:files].first[:filename].should == 'app/controllers/users_controller.rb'
16
16
  end
17
17
 
18
18
  it "should find the name of the classes" do
@@ -33,6 +33,22 @@ describe Saikuro do
33
33
  @output[:saikuro][:methods].first[:lines].should == 15
34
34
  end
35
35
  end
36
+
37
+ describe "per_file_info method" do
38
+ before :all do
39
+ MetricFu::Configuration.run {}
40
+ File.stub!(:directory?).and_return(true)
41
+ @saikuro = MetricFu::Saikuro.new
42
+ @saikuro.stub!(:metric_directory).and_return(File.join(File.dirname(__FILE__), "..", "resources", "saikuro"))
43
+ @saikuro.analyze
44
+ @output = @saikuro.to_h
45
+ end
46
+
47
+ it "doesn't try to get information if the file does not exist" do
48
+ File.should_receive(:exists?).at_least(:once).and_return(false)
49
+ @saikuro.per_file_info('ignore_me')
50
+ end
51
+ end
36
52
 
37
53
  describe "format_directories method" do
38
54
  it "should format the directories" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metric_fu
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 0
9
8
  - 1
10
- version: 2.0.1
9
+ - 0
10
+ version: 2.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jake Scruggs
@@ -24,7 +24,7 @@ autorequire:
24
24
  bindir: bin
25
25
  cert_chain: []
26
26
 
27
- date: 2010-11-13 00:00:00 -06:00
27
+ date: 2011-03-01 00:00:00 -06:00
28
28
  default_executable:
29
29
  dependencies:
30
30
  - !ruby/object:Gem::Dependency
@@ -51,12 +51,12 @@ dependencies:
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- hash: 7
54
+ hash: 3
55
55
  segments:
56
56
  - 2
57
- - 2
57
+ - 3
58
58
  - 0
59
- version: 2.2.0
59
+ version: 2.3.0
60
60
  type: :runtime
61
61
  version_requirements: *id002
62
62
  - !ruby/object:Gem::Dependency
@@ -116,12 +116,12 @@ dependencies:
116
116
  requirements:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
- hash: 51
119
+ hash: 15
120
120
  segments:
121
121
  - 0
122
- - 3
123
- - 16
124
- version: 0.3.16
122
+ - 6
123
+ - 4
124
+ version: 0.6.4
125
125
  type: :runtime
126
126
  version_requirements: *id006
127
127
  - !ruby/object:Gem::Dependency
@@ -132,12 +132,12 @@ dependencies:
132
132
  requirements:
133
133
  - - ~>
134
134
  - !ruby/object:Gem::Version
135
- hash: 17
135
+ hash: 19
136
136
  segments:
137
137
  - 0
138
- - 2
139
138
  - 3
140
- version: 0.2.3
139
+ - 0
140
+ version: 0.3.0
141
141
  type: :runtime
142
142
  version_requirements: *id007
143
143
  - !ruby/object:Gem::Dependency
@@ -189,9 +189,23 @@ dependencies:
189
189
  type: :runtime
190
190
  version_requirements: *id010
191
191
  - !ruby/object:Gem::Dependency
192
- name: rspec
192
+ name: syntax
193
193
  prerelease: false
194
194
  requirement: &id011 !ruby/object:Gem::Requirement
195
+ none: false
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ hash: 3
200
+ segments:
201
+ - 0
202
+ version: "0"
203
+ type: :runtime
204
+ version_requirements: *id011
205
+ - !ruby/object:Gem::Dependency
206
+ name: rspec
207
+ prerelease: false
208
+ requirement: &id012 !ruby/object:Gem::Requirement
195
209
  none: false
196
210
  requirements:
197
211
  - - "="
@@ -203,11 +217,11 @@ dependencies:
203
217
  - 0
204
218
  version: 1.3.0
205
219
  type: :development
206
- version_requirements: *id011
220
+ version_requirements: *id012
207
221
  - !ruby/object:Gem::Dependency
208
222
  name: test-construct
209
223
  prerelease: false
210
- requirement: &id012 !ruby/object:Gem::Requirement
224
+ requirement: &id013 !ruby/object:Gem::Requirement
211
225
  none: false
212
226
  requirements:
213
227
  - - ">="
@@ -219,11 +233,11 @@ dependencies:
219
233
  - 0
220
234
  version: 1.2.0
221
235
  type: :development
222
- version_requirements: *id012
236
+ version_requirements: *id013
223
237
  - !ruby/object:Gem::Dependency
224
238
  name: googlecharts
225
239
  prerelease: false
226
- requirement: &id013 !ruby/object:Gem::Requirement
240
+ requirement: &id014 !ruby/object:Gem::Requirement
227
241
  none: false
228
242
  requirements:
229
243
  - - ">="
@@ -233,7 +247,7 @@ dependencies:
233
247
  - 0
234
248
  version: "0"
235
249
  type: :development
236
- version_requirements: *id013
250
+ version_requirements: *id014
237
251
  description: Code metrics from Flog, Flay, RCov, Saikuro, Churn, Reek, Roodi, Rails' stats task and Rails Best Practices
238
252
  email: jake.scruggs@gmail.com
239
253
  executables: []
@@ -296,6 +310,7 @@ files:
296
310
  - lib/templates/awesome/css/default.css
297
311
  - lib/templates/awesome/css/integrity.css
298
312
  - lib/templates/awesome/css/reset.css
313
+ - lib/templates/awesome/css/syntax.css
299
314
  - lib/templates/awesome/flay.html.erb
300
315
  - lib/templates/awesome/flog.html.erb
301
316
  - lib/templates/awesome/hotspots.html.erb