metric_fu 4.2.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.metrics +19 -1
  2. data/.travis.yml +2 -2
  3. data/Gemfile +2 -1
  4. data/HISTORY.md +24 -0
  5. data/README.md +60 -1
  6. data/TODO.md +1 -0
  7. data/bin/mf-saikuro +8 -0
  8. data/checksum/metric_fu-4.3.0.gem.sha512 +1 -0
  9. data/gemfiles/Gemfile.travis +9 -0
  10. data/lib/metric_fu.rb +1 -1
  11. data/lib/metric_fu/cli/client.rb +2 -2
  12. data/lib/metric_fu/cli/helper.rb +3 -3
  13. data/lib/metric_fu/cli/parser.rb +37 -16
  14. data/lib/metric_fu/configuration.rb +9 -1
  15. data/lib/metric_fu/constantize.rb +57 -0
  16. data/lib/metric_fu/data_structures/line_numbers.rb +19 -3
  17. data/lib/metric_fu/data_structures/location.rb +8 -3
  18. data/lib/metric_fu/formatter.rb +24 -0
  19. data/lib/metric_fu/formatter/html.rb +91 -0
  20. data/lib/metric_fu/formatter/yaml.rb +18 -0
  21. data/lib/metric_fu/initial_requires.rb +0 -1
  22. data/lib/metric_fu/io.rb +69 -0
  23. data/lib/metric_fu/load_files.rb +5 -2
  24. data/lib/metric_fu/logging/mf_debugger.rb +23 -0
  25. data/lib/metric_fu/metrics/base_template.rb +10 -4
  26. data/lib/metric_fu/metrics/cane/cane.rb +2 -1
  27. data/lib/metric_fu/metrics/cane/cane_bluff_grapher.rb +10 -9
  28. data/lib/metric_fu/metrics/cane/cane_gchart_grapher.rb +25 -0
  29. data/lib/metric_fu/metrics/flay/flay_bluff_grapher.rb +10 -9
  30. data/lib/metric_fu/metrics/flay/flay_gchart_grapher.rb +14 -11
  31. data/lib/metric_fu/metrics/flog/flog_bluff_grapher.rb +11 -10
  32. data/lib/metric_fu/metrics/flog/flog_gchart_grapher.rb +22 -15
  33. data/lib/metric_fu/metrics/generator.rb +10 -10
  34. data/lib/metric_fu/metrics/graph.rb +2 -2
  35. data/lib/metric_fu/metrics/hotspots/analysis/scoring_strategies.rb +3 -3
  36. data/lib/metric_fu/metrics/hotspots/hotspot.rb +4 -3
  37. data/lib/metric_fu/metrics/hotspots/hotspot_analyzer.rb +19 -10
  38. data/lib/metric_fu/metrics/hotspots/hotspots.rb +1 -1
  39. data/lib/metric_fu/metrics/hotspots/template_awesome/hotspots.html.erb +45 -45
  40. data/lib/metric_fu/metrics/rails_best_practices/rails_best_practices_bluff_grapher.rb +10 -9
  41. data/lib/metric_fu/metrics/rails_best_practices/rails_best_practices_gchart_grapher.rb +21 -15
  42. data/lib/metric_fu/metrics/rcov/rcov.rb +1 -1
  43. data/lib/metric_fu/metrics/rcov/rcov_bluff_grapher.rb +10 -9
  44. data/lib/metric_fu/metrics/rcov/rcov_gchart_grapher.rb +13 -8
  45. data/lib/metric_fu/metrics/reek/reek_bluff_grapher.rb +9 -13
  46. data/lib/metric_fu/metrics/reek/reek_gchart_grapher.rb +22 -17
  47. data/lib/metric_fu/metrics/roodi/roodi_bluff_grapher.rb +10 -9
  48. data/lib/metric_fu/metrics/roodi/roodi_gchart_grapher.rb +14 -11
  49. data/lib/metric_fu/metrics/saikuro/saikuro.rb +5 -34
  50. data/lib/metric_fu/metrics/stats/stats.rb +2 -1
  51. data/lib/metric_fu/metrics/stats/stats_bluff_grapher.rb +11 -10
  52. data/lib/metric_fu/metrics/stats/stats_gchart_grapher.rb +21 -14
  53. data/lib/metric_fu/reporter.rb +37 -0
  54. data/lib/metric_fu/reporting/graphs/engines/bluff.rb +20 -0
  55. data/lib/metric_fu/reporting/graphs/engines/gchart.rb +41 -3
  56. data/lib/metric_fu/reporting/graphs/grapher.rb +9 -2
  57. data/lib/metric_fu/reporting/result.rb +51 -0
  58. data/lib/metric_fu/reporting/templates/awesome/awesome_template.rb +8 -8
  59. data/lib/metric_fu/run.rb +34 -39
  60. data/lib/metric_fu/version.rb +1 -1
  61. data/lib/metric_fu_requires.rb +50 -33
  62. data/metric_fu.gemspec +30 -39
  63. data/spec/cli/helper_spec.rb +15 -0
  64. data/spec/metric_fu/configuration_spec.rb +40 -2
  65. data/spec/metric_fu/formatter/html_spec.rb +134 -0
  66. data/spec/metric_fu/formatter/yaml_spec.rb +59 -0
  67. data/spec/metric_fu/formatter_spec.rb +49 -0
  68. data/spec/metric_fu/metrics/base_template_spec.rb +23 -23
  69. data/spec/metric_fu/metrics/cane/cane_spec.rb +2 -2
  70. data/spec/metric_fu/metrics/churn/churn_spec.rb +1 -1
  71. data/spec/metric_fu/metrics/flay/flay_spec.rb +4 -4
  72. data/spec/metric_fu/metrics/flog/flog_spec.rb +7 -7
  73. data/spec/metric_fu/metrics/generator_spec.rb +21 -21
  74. data/spec/metric_fu/metrics/graph_spec.rb +9 -9
  75. data/spec/metric_fu/metrics/hotspots/hotspots_spec.rb +1 -1
  76. data/spec/metric_fu/metrics/rcov/rcov_spec.rb +8 -8
  77. data/spec/metric_fu/metrics/reek/reek_spec.rb +1 -1
  78. data/spec/metric_fu/metrics/saikuro/saikuro_spec.rb +5 -5
  79. data/spec/metric_fu/metrics/stats/stats_spec.rb +4 -4
  80. data/spec/metric_fu/reporter_spec.rb +41 -0
  81. data/spec/metric_fu/reporting/graphs/engines/gchart_spec.rb +7 -7
  82. data/spec/metric_fu/reporting/result_spec.rb +51 -0
  83. data/spec/run_spec.rb +167 -27
  84. data/spec/spec_helper.rb +1 -0
  85. data/spec/support/matcher_create_file.rb +7 -2
  86. data/spec/support/matcher_create_files.rb +41 -0
  87. data/spec/support/suite.rb +32 -0
  88. metadata +27 -6
  89. data/lib/metric_fu/reporting/report.rb +0 -111
  90. data/spec/metric_fu/reporting/report_spec.rb +0 -148
@@ -2,7 +2,8 @@ module MetricFu
2
2
  class Location
3
3
  include Comparable
4
4
 
5
- attr_accessor :class_name, :method_name, :file_path, :simple_method_name, :hash
5
+ attr_accessor :file_path, :file_name, :line_number,
6
+ :class_name, :method_name, :simple_method_name, :hash
6
7
 
7
8
  def self.get(file_path, class_name, method_name)
8
9
  location = new(file_path, class_name, method_name)
@@ -16,6 +17,7 @@ module MetricFu
16
17
 
17
18
  def initialize(file_path, class_name, method_name)
18
19
  @file_path = file_path
20
+ @file_name, @line_number = file_path.to_s.split(/:/)
19
21
  @class_name = class_name
20
22
  @method_name = method_name
21
23
  @simple_method_name = @method_name.to_s.sub(@class_name.to_s,'')
@@ -24,7 +26,7 @@ module MetricFu
24
26
 
25
27
  # TODO - we need this method (and hash accessor above) as a temporary hack where we're using Location as a hash key
26
28
  def eql?(other)
27
- # REMOVED per https://github.com/jscruggs/metric_fu/pull/67/files
29
+ # REMOVED per https://github.com/jscruggs/metric_fu/pull/67/files
28
30
  # [self.file_path.to_s, self.class_name.to_s, self.method_name.to_s] == [other.file_path.to_s, other.class_name.to_s, other.method_name.to_s]
29
31
  @hash == other.hash
30
32
  end
@@ -38,6 +40,7 @@ module MetricFu
38
40
  rescue => error
39
41
  #new error during port to metric_fu occasionally a unintialized
40
42
  #MatchData object shows up here. Not expected.
43
+ mf_debug "ERROR on getting location for #{class_or_method_name} #{error.inspect}"
41
44
  match = nil
42
45
  end
43
46
 
@@ -68,6 +71,8 @@ module MetricFu
68
71
 
69
72
  def finalize
70
73
  @file_path &&= @file_path.clone
74
+ @file_name &&= @file_name.clone
75
+ @line_number &&= @line_number.clone
71
76
  @class_name &&= @class_name.clone
72
77
  @method_name &&= @method_name.clone
73
78
  freeze # we cache a lot of method call results, so we want location to be immutable
@@ -76,7 +81,7 @@ module MetricFu
76
81
  private
77
82
 
78
83
  def self.strip_modules(class_or_method_name)
79
- # reek reports the method with :: not # on modules like
84
+ # reek reports the method with :: not # on modules like
80
85
  # module ApplicationHelper \n def signed_in?, convert it so it records correctly
81
86
  # but classes have to start with a capital letter... HACK for REEK bug, reported underlying issue to REEK
82
87
  if(class_or_method_name=~/\:\:[A-Z]/)
@@ -0,0 +1,24 @@
1
+ require 'metric_fu/constantize'
2
+ module MetricFu
3
+ module Formatter
4
+ BUILTIN_FORMATS = {
5
+ 'html' => ['MetricFu::Formatter::HTML', 'Generates a templated HTML report using the configured template class and graph engine.'],
6
+ 'yaml' => ['MetricFu::Formatter::YAML', 'Generates the raw output as yaml']
7
+ }
8
+ DEFAULT = [[:html]]
9
+
10
+ class << self
11
+ include MetricFu::Constantize
12
+
13
+ def class_for(format)
14
+ if (builtin = BUILTIN_FORMATS[format.to_s])
15
+ constantize(builtin[0])
16
+ else
17
+ constantize(format.to_s)
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,91 @@
1
+ module MetricFu
2
+ module Formatter
3
+ class HTML
4
+ include MetricFu::Io
5
+
6
+ def initialize(opts={})
7
+ @options = opts
8
+ end
9
+
10
+ def finish
11
+ mf_log "** SAVING REPORTS"
12
+ mf_debug "** SAVING REPORT YAML OUTPUT TO #{MetricFu.base_directory}"
13
+ MetricFu::Formatter::YAML.new.finish
14
+
15
+ mf_debug "** SAVING REPORT DATA OUTPUT TO #{MetricFu.data_directory}"
16
+ MetricFu::Formatter::YAML.new(
17
+ output: Pathname.pwd.join("#{MetricFu.data_directory}/#{Time.now.strftime("%Y%m%d")}.yml")
18
+ ).finish
19
+
20
+ mf_debug "** SAVING TEMPLATIZED REPORT"
21
+ save_templatized_result
22
+ save_graphs
23
+ end
24
+
25
+ def write_template(output, file)
26
+ write_output(output, "#{self.output_directory}/#{file}")
27
+ end
28
+
29
+ def display_results
30
+ if self.open_in_browser?
31
+ mf_debug "** OPENING IN BROWSER FROM #{self.output_directory}"
32
+ self.show_in_browser(self.output_directory)
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def output_directory
39
+ @output ||= dir_for(@options[:output]) || Pathname.pwd.join(MetricFu.output_directory)
40
+ end
41
+
42
+ # Instantiates a new template class based on the configuration set
43
+ # in MetricFu::Configuration, or through the MetricFu.config block
44
+ # in your rake file (defaults to the included AwesomeTemplate),
45
+ # assigns the result_hash to the result_hash in the template, and
46
+ # tells the template to to write itself out.
47
+ def save_templatized_result
48
+ @template = MetricFu.template_class.new
49
+ @template.output_directory = self.output_directory
50
+ @template.result = MetricFu.result.result_hash
51
+ @template.per_file_data = MetricFu.result.per_file_data
52
+ @template.formatter = self
53
+ @template.write
54
+ end
55
+
56
+ def save_graphs
57
+ mf_log "** GENERATING GRAPHS"
58
+ mf_debug "** PREPARING TO GRAPH"
59
+ MetricFu.graphs.each {|graph|
60
+ mf_debug "** Graphing #{graph} with #{MetricFu.graph_engine}"
61
+ MetricFu.graph.add(graph, MetricFu.graph_engine, self.output_directory)
62
+ }
63
+ mf_debug "** GENERATING GRAPH"
64
+ MetricFu.graph.generate
65
+ end
66
+
67
+ # Checks to discover whether we should try and open the results
68
+ # of the report in the browser on this system. We only try and open
69
+ # in the browser if we're on OS X and we're not running in a
70
+ # CruiseControl.rb environment. See MetricFu.configuration for more
71
+ # details about how we make those guesses.
72
+ #
73
+ # @return Boolean
74
+ # Should we open in the browser or not?
75
+ def open_in_browser?
76
+ MetricFu.configuration.platform.include?('darwin') &&
77
+ ! MetricFu.configuration.is_cruise_control_rb?
78
+ end
79
+
80
+ # Shows 'index.html' from the passed directory in the browser
81
+ # if we're able to open the browser on this platform.
82
+ #
83
+ # @param dir String
84
+ # The directory path where the 'index.html' we want to open is
85
+ # stored
86
+ def show_in_browser(dir)
87
+ system("open #{dir}/index.html") if open_in_browser?
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,18 @@
1
+ module MetricFu
2
+ module Formatter
3
+ class YAML
4
+ include MetricFu::Io
5
+
6
+ DEFAULT_PATH = "report.yml"
7
+
8
+ def initialize(opts={})
9
+ @options = opts
10
+ @path_or_io = @options[:output] || DEFAULT_PATH
11
+ end
12
+
13
+ def finish
14
+ write_output(MetricFu.result.as_yaml, @path_or_io)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,4 @@
1
1
  # rake is required for
2
- # Saikuro : sh
3
2
  # Rcov : FileList
4
3
  # loading metric_fu.rake
5
4
  require 'rake'
@@ -0,0 +1,69 @@
1
+ module MetricFu
2
+ module Io
3
+ # Writes the output to a file or io stream.
4
+ # @param output [String, #to_s] the content to write.
5
+ # @param path_or_io [String, #to_s, IO, #write] a file path
6
+ # or an io stream that responds to write.
7
+ # @return [nil]
8
+ def write_output(output, path_or_io)
9
+ io_for(path_or_io) do |io|
10
+ io.write(output)
11
+ end
12
+ end
13
+
14
+ # Yields an io object for writing output.
15
+ # @example
16
+ # io_for('path/to/file') do |io|
17
+ # io.write(output)
18
+ # end
19
+ #
20
+ # io_for(STDOUT) do |io|
21
+ # io.write(output)
22
+ # end
23
+ #
24
+ # stream = StringIO.new
25
+ # io_for(stream) do |io|
26
+ # io.write(output)
27
+ # end
28
+ #
29
+ # @param path_or_io [String, #to_s, IO, #write] a file path
30
+ # or an io stream that responds to write.
31
+ #
32
+ # @yield [IO] an open stream for writing.
33
+ #
34
+ # @note Given a path to a file, an open file will
35
+ # be yielded and closed after the block completes.
36
+ # Given an existing io stream, the stream will not
37
+ # be automatically closed. Cleanup, if necessary, is
38
+ # the responsibility of the caller.
39
+ def io_for(path_or_io)
40
+ raise ArgumentError, "No path or io provided." if path_or_io.nil?
41
+ raise ArgumentError, "No block given. Cannot yield io stream." unless block_given?
42
+
43
+ if path_or_io.respond_to?(:write)
44
+ # We have an existing open stream...
45
+ yield path_or_io
46
+ else # Otherwise, we assume its a file path...
47
+ file_for(path_or_io) {|io| yield io }
48
+ end
49
+ end
50
+
51
+ def file_for(path)
52
+ File.open(path_relative_to_base(path), 'w') do |file|
53
+ yield file
54
+ end
55
+ end
56
+
57
+ def dir_for(path)
58
+ return nil if path.nil?
59
+ pathname = path_relative_to_base(path)
60
+ FileUtils.mkdir_p(pathname) unless File.directory?(pathname)
61
+ pathname
62
+ end
63
+
64
+ def path_relative_to_base(path)
65
+ pathname = Pathname.pwd.join(MetricFu.base_directory) # make full path relative to base directory
66
+ pathname.join(path)
67
+ end
68
+ end
69
+ end
@@ -1,12 +1,12 @@
1
1
  # require these first because others depend on them
2
- MetricFu.reporting_require { 'report' }
2
+ MetricFu.reporting_require { 'result' }
3
3
  MetricFu.metrics_require { 'hotspots/hotspot' }
4
4
  MetricFu.metrics_require { 'generator' }
5
5
  MetricFu.metrics_require { 'graph' }
6
6
  MetricFu.reporting_require { 'graphs/grapher' }
7
7
  MetricFu.metrics_require { 'hotspots/analysis/scoring_strategies' }
8
8
 
9
- Dir.glob(File.join(MetricFu.lib_dir, '*.rb')).each do |file|
9
+ Dir.glob(File.join(MetricFu.lib_dir, '*.rb')).reject{|file| file =~ /#{__FILE__}/}.each do |file|
10
10
  require file
11
11
  end
12
12
  # prevent the task from being run multiple times.
@@ -29,3 +29,6 @@ end
29
29
  Dir.glob(File.join(MetricFu.reporting_dir, '**/*.rb')).each do |file|
30
30
  require file
31
31
  end
32
+ Dir.glob(File.join(MetricFu.formatter_dir, '**/*.rb')).each do |file|
33
+ require file
34
+ end
@@ -21,6 +21,29 @@ module MfDebugger
21
21
  end
22
22
  end
23
23
  end
24
+ # From episode 029 of Ruby Tapas by Avdi
25
+ # https://rubytapas.dpdcart.com/subscriber/post?id=88
26
+ def self.capture_output(&block)
27
+ old_stdout = STDOUT.clone
28
+ pipe_r, pipe_w = IO.pipe
29
+ pipe_r.sync = true
30
+ output = ""
31
+ reader = Thread.new do
32
+ begin
33
+ loop do
34
+ output << pipe_r.readpartial(1024)
35
+ end
36
+ rescue EOFError
37
+ end
38
+ end
39
+ STDOUT.reopen(pipe_w)
40
+ yield
41
+ ensure
42
+ STDOUT.reopen(old_stdout)
43
+ pipe_w.close
44
+ reader.join
45
+ return output
46
+ end
24
47
  end
25
48
 
26
49
  def mf_debug(msg,&block)
@@ -10,7 +10,13 @@ module MetricFu
10
10
  # to actually write out the template. See StandardTemplate for an
11
11
  # example.
12
12
  class Template
13
- attr_accessor :report, :per_file_data
13
+ attr_accessor :result, :per_file_data, :formatter, :output_directory
14
+
15
+
16
+ def output_directory
17
+ @output_directory || MetricFu.output_directory
18
+ end
19
+
14
20
 
15
21
  private
16
22
  # Creates a new erb evaluated result from the passed in section.
@@ -131,10 +137,10 @@ module MetricFu
131
137
  end
132
138
  end
133
139
 
134
- def display_location(location, stat)
135
- file_path, class_name, method_name = location.file_path, location.class_name, location.method_name
140
+ def display_location(location)
141
+ class_name, method_name = location.class_name, location.method_name
136
142
  str = ""
137
- str += link_to_filename(file_path)
143
+ str += link_to_filename(location.file_name, location.line_number)
138
144
  str += " : " if method_name || class_name
139
145
  if(method_name)
140
146
  str += "#{method_name}"
@@ -4,7 +4,7 @@ module MetricFu
4
4
 
5
5
  def emit
6
6
  command = %Q{mf-cane#{abc_max_param}#{style_measure_param}#{no_doc_param}#{no_readme_param}}
7
- mf_debug = "** #{command}"
7
+ mf_debug "** #{command}"
8
8
  @output = `#{command}`
9
9
  end
10
10
 
@@ -51,6 +51,7 @@ module MetricFu
51
51
  :documentation => /documentation/
52
52
  }
53
53
  category, desc_matcher = category_descriptions.find {|k,v| description =~ v}
54
+ mf_debug desc_matcher.inspect
54
55
  category
55
56
  end
56
57
 
@@ -1,15 +1,16 @@
1
1
  MetricFu.metrics_require { 'cane/cane_grapher' }
2
2
  module MetricFu
3
3
  class CaneBluffGrapher < CaneGrapher
4
- def graph!
5
- content = <<-EOS
6
- #{BLUFF_DEFAULT_OPTIONS}
7
- g.title = 'Cane: code quality threshold violations';
8
- g.data('cane', [#{@cane_violations.join(',')}]);
9
- g.labels = #{MultiJson.dump(@labels)};
10
- g.draw();
11
- EOS
12
- File.open(File.join(MetricFu.output_directory, 'cane.js'), 'w') {|f| f << content }
4
+ def title
5
+ 'Cane: code quality threshold violations'
6
+ end
7
+ def data
8
+ [
9
+ ['cane', @cane_violations.join(',')]
10
+ ]
11
+ end
12
+ def output_filename
13
+ 'cane.js'
13
14
  end
14
15
  end
15
16
  end
@@ -0,0 +1,25 @@
1
+ MetricFu.metrics_require { 'cane/cane_grapher' }
2
+ module MetricFu
3
+ class CaneGchartGrapher < CaneGrapher
4
+ def title
5
+ 'Cane: code quality threshold violations'
6
+ end
7
+ def legend
8
+ ['violations']
9
+ end
10
+ def data
11
+ @cane_violations
12
+ end
13
+ def output_filename
14
+ 'cane.png'
15
+ end
16
+ def gchart_line_options
17
+ super.merge({
18
+ :legend => legend,
19
+ })
20
+ end
21
+ def y_axis_scale_argument
22
+ @cane_violations
23
+ end
24
+ end
25
+ end
@@ -1,15 +1,16 @@
1
1
  MetricFu.metrics_require { 'flay/flay_grapher' }
2
2
  module MetricFu
3
3
  class FlayBluffGrapher < FlayGrapher
4
- def graph!
5
- content = <<-EOS
6
- #{BLUFF_DEFAULT_OPTIONS}
7
- g.title = 'Flay: duplication';
8
- g.data('flay', [#{@flay_score.join(',')}]);
9
- g.labels = #{MultiJson.dump(@labels)};
10
- g.draw();
11
- EOS
12
- File.open(File.join(MetricFu.output_directory, 'flay.js'), 'w') {|f| f << content }
4
+ def title
5
+ 'Flay: duplication'
6
+ end
7
+ def data
8
+ [
9
+ ['flay', @flay_score.join(',')]
10
+ ]
11
+ end
12
+ def output_filename
13
+ 'flay.js'
13
14
  end
14
15
  end
15
16
  end
@@ -1,17 +1,20 @@
1
1
  MetricFu.metrics_require { 'flay/flay_grapher' }
2
2
  module MetricFu
3
3
  class FlayGchartGrapher < FlayGrapher
4
- def graph!
5
- determine_y_axis_scale(@flay_score)
6
- url = Gchart.line(
7
- :size => GCHART_GRAPH_SIZE,
8
- :title => URI.escape("Flay: duplication"),
9
- :data => @flay_score,
10
- :max_value => @max_value,
11
- :axis_with_labels => 'x,y',
12
- :axis_labels => [@labels.values, @yaxis],
13
- :format => 'file',
14
- :filename => File.join(MetricFu.output_directory, 'flay.png'))
4
+ def title
5
+ "Flay: duplication"
6
+ end
7
+ def data
8
+ @flay_score
9
+ end
10
+ def output_filename
11
+ 'flay.png'
12
+ end
13
+ def gchart_line_options
14
+ super
15
+ end
16
+ def y_axis_scale_argument
17
+ @flay_score
15
18
  end
16
19
  end
17
20
  end