bf4-metric_fu 2.1.3.1
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.
- data/HISTORY +252 -0
- data/MIT-LICENSE +22 -0
- data/README.md +49 -0
- data/Rakefile +22 -0
- data/TODO +6 -0
- data/lib/base/base_template.rb +182 -0
- data/lib/base/churn_analyzer.rb +38 -0
- data/lib/base/code_issue.rb +100 -0
- data/lib/base/configuration.rb +219 -0
- data/lib/base/flay_analyzer.rb +50 -0
- data/lib/base/flog_analyzer.rb +43 -0
- data/lib/base/generator.rb +166 -0
- data/lib/base/graph.rb +44 -0
- data/lib/base/grouping.rb +42 -0
- data/lib/base/line_numbers.rb +79 -0
- data/lib/base/location.rb +87 -0
- data/lib/base/md5_tracker.rb +52 -0
- data/lib/base/metric_analyzer.rb +331 -0
- data/lib/base/ranking.rb +34 -0
- data/lib/base/rcov_analyzer.rb +43 -0
- data/lib/base/record.rb +43 -0
- data/lib/base/reek_analyzer.rb +164 -0
- data/lib/base/report.rb +110 -0
- data/lib/base/roodi_analyzer.rb +37 -0
- data/lib/base/saikuro_analyzer.rb +48 -0
- data/lib/base/scoring_strategies.rb +29 -0
- data/lib/base/stats_analyzer.rb +37 -0
- data/lib/base/table.rb +108 -0
- data/lib/generators/churn.rb +28 -0
- data/lib/generators/flay.rb +31 -0
- data/lib/generators/flog.rb +113 -0
- data/lib/generators/hotspots.rb +52 -0
- data/lib/generators/rails_best_practices.rb +53 -0
- data/lib/generators/rcov.rb +124 -0
- data/lib/generators/reek.rb +81 -0
- data/lib/generators/roodi.rb +35 -0
- data/lib/generators/saikuro.rb +259 -0
- data/lib/generators/stats.rb +58 -0
- data/lib/graphs/engines/bluff.rb +113 -0
- data/lib/graphs/engines/gchart.rb +157 -0
- data/lib/graphs/flay_grapher.rb +18 -0
- data/lib/graphs/flog_grapher.rb +57 -0
- data/lib/graphs/grapher.rb +11 -0
- data/lib/graphs/rails_best_practices_grapher.rb +19 -0
- data/lib/graphs/rcov_grapher.rb +18 -0
- data/lib/graphs/reek_grapher.rb +30 -0
- data/lib/graphs/roodi_grapher.rb +18 -0
- data/lib/graphs/stats_grapher.rb +20 -0
- data/lib/metric_fu.rb +80 -0
- data/lib/tasks/metric_fu.rake +36 -0
- data/lib/templates/awesome/awesome_template.rb +92 -0
- data/lib/templates/awesome/churn.html.erb +58 -0
- data/lib/templates/awesome/css/buttons.css +82 -0
- data/lib/templates/awesome/css/default.css +91 -0
- data/lib/templates/awesome/css/integrity.css +334 -0
- data/lib/templates/awesome/css/reset.css +7 -0
- data/lib/templates/awesome/css/syntax.css +19 -0
- data/lib/templates/awesome/flay.html.erb +34 -0
- data/lib/templates/awesome/flog.html.erb +55 -0
- data/lib/templates/awesome/hotspots.html.erb +62 -0
- data/lib/templates/awesome/index.html.erb +34 -0
- data/lib/templates/awesome/layout.html.erb +30 -0
- data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
- data/lib/templates/awesome/rcov.html.erb +42 -0
- data/lib/templates/awesome/reek.html.erb +40 -0
- data/lib/templates/awesome/roodi.html.erb +27 -0
- data/lib/templates/awesome/saikuro.html.erb +71 -0
- data/lib/templates/awesome/stats.html.erb +51 -0
- data/lib/templates/javascripts/bluff-min.js +1 -0
- data/lib/templates/javascripts/excanvas.js +35 -0
- data/lib/templates/javascripts/js-class.js +1 -0
- data/lib/templates/standard/churn.html.erb +31 -0
- data/lib/templates/standard/default.css +64 -0
- data/lib/templates/standard/flay.html.erb +34 -0
- data/lib/templates/standard/flog.html.erb +57 -0
- data/lib/templates/standard/hotspots.html.erb +54 -0
- data/lib/templates/standard/index.html.erb +41 -0
- data/lib/templates/standard/rails_best_practices.html.erb +29 -0
- data/lib/templates/standard/rcov.html.erb +43 -0
- data/lib/templates/standard/reek.html.erb +42 -0
- data/lib/templates/standard/roodi.html.erb +29 -0
- data/lib/templates/standard/saikuro.html.erb +84 -0
- data/lib/templates/standard/standard_template.rb +27 -0
- data/lib/templates/standard/stats.html.erb +55 -0
- data/spec/base/base_template_spec.rb +194 -0
- data/spec/base/configuration_spec.rb +277 -0
- data/spec/base/generator_spec.rb +223 -0
- data/spec/base/graph_spec.rb +61 -0
- data/spec/base/line_numbers_spec.rb +62 -0
- data/spec/base/location_spec.rb +127 -0
- data/spec/base/md5_tracker_spec.rb +57 -0
- data/spec/base/metric_analyzer_spec.rb +452 -0
- data/spec/base/ranking_spec.rb +42 -0
- data/spec/base/report_spec.rb +146 -0
- data/spec/base/table_spec.rb +36 -0
- data/spec/generators/churn_spec.rb +41 -0
- data/spec/generators/flay_spec.rb +105 -0
- data/spec/generators/flog_spec.rb +70 -0
- data/spec/generators/hotspots_spec.rb +88 -0
- data/spec/generators/rails_best_practices_spec.rb +52 -0
- data/spec/generators/rcov_spec.rb +180 -0
- data/spec/generators/reek_spec.rb +134 -0
- data/spec/generators/roodi_spec.rb +24 -0
- data/spec/generators/saikuro_spec.rb +74 -0
- data/spec/generators/stats_spec.rb +74 -0
- data/spec/graphs/engines/bluff_spec.rb +19 -0
- data/spec/graphs/engines/gchart_spec.rb +156 -0
- data/spec/graphs/flay_grapher_spec.rb +56 -0
- data/spec/graphs/flog_grapher_spec.rb +108 -0
- data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
- data/spec/graphs/rcov_grapher_spec.rb +56 -0
- data/spec/graphs/reek_grapher_spec.rb +65 -0
- data/spec/graphs/roodi_grapher_spec.rb +56 -0
- data/spec/graphs/stats_grapher_spec.rb +68 -0
- data/spec/resources/line_numbers/foo.rb +33 -0
- data/spec/resources/line_numbers/module.rb +11 -0
- data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
- data/spec/resources/line_numbers/two_classes.rb +11 -0
- data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
- data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
- data/spec/resources/saikuro/index_cyclo.html +155 -0
- data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
- data/spec/resources/yml/20090630.yml +7922 -0
- data/spec/resources/yml/metric_missing.yml +1 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +7 -0
- metadata +560 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
module MetricFu
|
|
2
|
+
|
|
3
|
+
# = Generator
|
|
4
|
+
#
|
|
5
|
+
# The Generator class is an abstract class that provides the
|
|
6
|
+
# skeleton for producing different types of metrics.
|
|
7
|
+
#
|
|
8
|
+
# It drives the production of the metrics through a template
|
|
9
|
+
# method - #generate_report(options={}). This method calls
|
|
10
|
+
# #emit, #analyze and #to_h in order to produce the metrics.
|
|
11
|
+
#
|
|
12
|
+
# To implement a concrete class to generate a metric, therefore,
|
|
13
|
+
# the class must implement those three methods.
|
|
14
|
+
#
|
|
15
|
+
# * #emit should take care of running the metric tool and
|
|
16
|
+
# gathering its output.
|
|
17
|
+
# * #analyze should take care of manipulating the output from
|
|
18
|
+
# #emit and making it possible to store it in a programmatic way.
|
|
19
|
+
# * #to_h should provide a hash representation of the output from
|
|
20
|
+
# #analyze ready to be serialized into yaml at some point.
|
|
21
|
+
#
|
|
22
|
+
# == Pre-conditions
|
|
23
|
+
#
|
|
24
|
+
# Based on the class name of the concrete class implementing a
|
|
25
|
+
# Generator, the Generator class will create a 'metric_directory'
|
|
26
|
+
# named after the class under the MetricFu.scratch_directory, where
|
|
27
|
+
# any output from the #emit method should go.
|
|
28
|
+
#
|
|
29
|
+
# It will also create the MetricFu.output_directory if neccessary, and
|
|
30
|
+
# in general setup the directory structure that the MetricFu system
|
|
31
|
+
# expects.
|
|
32
|
+
class Generator
|
|
33
|
+
attr_reader :report, :template
|
|
34
|
+
|
|
35
|
+
def initialize(options={})
|
|
36
|
+
create_metric_dir_if_missing
|
|
37
|
+
create_output_dir_if_missing
|
|
38
|
+
create_data_dir_if_missing
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Creates a new generator and returns the output of the
|
|
42
|
+
# #generate_report method. This is the typical way to
|
|
43
|
+
# generate a new MetricFu report. For more information see
|
|
44
|
+
# the #generate_report instance method.
|
|
45
|
+
#
|
|
46
|
+
# @params options Hash
|
|
47
|
+
# A currently unused hash to configure the Generator
|
|
48
|
+
#
|
|
49
|
+
# @see generate_report
|
|
50
|
+
def self.generate_report(options={})
|
|
51
|
+
generator = self.new(options)
|
|
52
|
+
generator.generate_report
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Provides the unqualified class name of an implemented concrete
|
|
56
|
+
# class, as a string. For example:
|
|
57
|
+
#
|
|
58
|
+
# class Flay < Generator; end
|
|
59
|
+
# klass = Flay.new
|
|
60
|
+
# klass.class_name
|
|
61
|
+
# > "flay"
|
|
62
|
+
#
|
|
63
|
+
# @return String
|
|
64
|
+
# The unqualified class name of this concrete class, returned
|
|
65
|
+
# as a string.
|
|
66
|
+
def self.class_name
|
|
67
|
+
self.to_s.split('::').last.downcase
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns the directory where the Generator will write any output
|
|
71
|
+
def self.metric_directory
|
|
72
|
+
File.join(MetricFu.scratch_directory, class_name)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def create_metric_dir_if_missing #:nodoc:
|
|
76
|
+
unless File.directory?(metric_directory)
|
|
77
|
+
FileUtils.mkdir_p(metric_directory, :verbose => false)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create_output_dir_if_missing #:nodoc:
|
|
82
|
+
unless File.directory?(MetricFu.output_directory)
|
|
83
|
+
FileUtils.mkdir_p(MetricFu.output_directory, :verbose => false)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_data_dir_if_missing #:nodoc:
|
|
88
|
+
unless File.directory?(MetricFu.data_directory)
|
|
89
|
+
FileUtils.mkdir_p(MetricFu.data_directory, :verbose => false)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @return String
|
|
94
|
+
# The path of the metric directory this class is using.
|
|
95
|
+
def metric_directory
|
|
96
|
+
self.class.metric_directory
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def remove_excluded_files(paths, globs_to_remove = MetricFu.file_globs_to_ignore)
|
|
100
|
+
files_to_remove = []
|
|
101
|
+
globs_to_remove.each do |glob|
|
|
102
|
+
files_to_remove.concat(Dir[glob])
|
|
103
|
+
end
|
|
104
|
+
paths - files_to_remove
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Defines some hook methods for the concrete classes to hook into.
|
|
108
|
+
%w[emit analyze].each do |meth|
|
|
109
|
+
define_method("before_#{meth}".to_sym) {}
|
|
110
|
+
define_method("after_#{meth}".to_sym) {}
|
|
111
|
+
end
|
|
112
|
+
define_method("before_to_h".to_sym) {}
|
|
113
|
+
|
|
114
|
+
# Provides a template method to drive the production of a metric
|
|
115
|
+
# from a concrete implementation of this class. Each concrete
|
|
116
|
+
# class must implement the three methods that this template method
|
|
117
|
+
# calls: #emit, #analyze and #to_h. For more details, see the
|
|
118
|
+
# class documentation.
|
|
119
|
+
#
|
|
120
|
+
# This template method also calls before_emit, after_emit... etc.
|
|
121
|
+
# methods to allow extra hooks into the processing methods, and help
|
|
122
|
+
# to keep the logic of your Generators clean.
|
|
123
|
+
def generate_report
|
|
124
|
+
if MetricFu.configuration.verbose
|
|
125
|
+
puts "Executing #{self.class.to_s.gsub(/.*::/, '')}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
%w[emit analyze].each do |meth|
|
|
129
|
+
send("before_#{meth}".to_sym)
|
|
130
|
+
send("#{meth}".to_sym)
|
|
131
|
+
send("after_#{meth}".to_sym)
|
|
132
|
+
end
|
|
133
|
+
before_to_h()
|
|
134
|
+
to_h()
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def round_to_tenths(decimal)
|
|
138
|
+
decimal = 0.0 if decimal.to_s.eql?('NaN')
|
|
139
|
+
(decimal * 10).round / 10.0
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def emit #:nodoc:
|
|
143
|
+
raise <<-EOF
|
|
144
|
+
This method must be implemented by a concrete class descending
|
|
145
|
+
from Generator. See generator class documentation for more
|
|
146
|
+
information.
|
|
147
|
+
EOF
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def analyze #:nodoc:
|
|
151
|
+
raise <<-EOF
|
|
152
|
+
This method must be implemented by a concrete class descending
|
|
153
|
+
from Generator. See generator class documentation for more
|
|
154
|
+
information.
|
|
155
|
+
EOF
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def to_graph #:nodoc:
|
|
159
|
+
raise <<-EOF
|
|
160
|
+
This method must be implemented by a concrete class descending
|
|
161
|
+
from Generator. See generator class documentation for more
|
|
162
|
+
information.
|
|
163
|
+
EOF
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
data/lib/base/graph.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module MetricFu
|
|
2
|
+
|
|
3
|
+
def self.graph
|
|
4
|
+
@graph ||= Graph.new
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class Graph
|
|
8
|
+
|
|
9
|
+
attr_accessor :clazz
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
self.clazz = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add(graph_type, graph_engine)
|
|
16
|
+
grapher_name = graph_type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + graph_engine.to_s.capitalize + "Grapher"
|
|
17
|
+
self.clazz.push MetricFu.const_get(grapher_name).new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def generate
|
|
22
|
+
return if self.clazz.empty?
|
|
23
|
+
puts "Generating graphs"
|
|
24
|
+
Dir[File.join(MetricFu.data_directory, '*.yml')].sort.each do |metric_file|
|
|
25
|
+
puts "Generating graphs for #{metric_file}"
|
|
26
|
+
date_parts = year_month_day_from_filename(metric_file)
|
|
27
|
+
metrics = YAML::load(File.open(metric_file))
|
|
28
|
+
|
|
29
|
+
self.clazz.each do |grapher|
|
|
30
|
+
grapher.get_metrics(metrics, "#{date_parts[:m]}/#{date_parts[:d]}")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
self.clazz.each do |grapher|
|
|
34
|
+
grapher.graph!
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def year_month_day_from_filename(path_to_file_with_date)
|
|
40
|
+
date = path_to_file_with_date.match(/\/(\d+).yml$/)[1]
|
|
41
|
+
{:y => date[0..3].to_i, :m => date[4..5].to_i, :d => date[6..7].to_i}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[
|
|
2
|
+
'/base/table'
|
|
3
|
+
].each do |path|
|
|
4
|
+
require File.expand_path(File.join(MetricFu::LIB_ROOT,path))
|
|
5
|
+
end
|
|
6
|
+
module MetricFu
|
|
7
|
+
class Grouping
|
|
8
|
+
|
|
9
|
+
def initialize(table, opts)
|
|
10
|
+
column_name = opts.fetch(:by)
|
|
11
|
+
order = opts.fetch(:order) { nil }
|
|
12
|
+
hash = {}
|
|
13
|
+
if column_name.to_sym == :metric # special optimized case
|
|
14
|
+
hash = table.group_by_metric
|
|
15
|
+
else
|
|
16
|
+
table.each do |row|
|
|
17
|
+
hash[row[column_name]] ||= Table.new(:column_names => row.attributes)
|
|
18
|
+
hash[row[column_name]] << row
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
if order
|
|
22
|
+
@arr = hash.sort_by &order
|
|
23
|
+
else
|
|
24
|
+
@arr = hash.to_a
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def [](key)
|
|
29
|
+
@arr.each do |group_key, table|
|
|
30
|
+
return table if group_key == key
|
|
31
|
+
end
|
|
32
|
+
return nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def each
|
|
36
|
+
@arr.each do |value, rows|
|
|
37
|
+
yield value, rows
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'ruby_parser'
|
|
2
|
+
module MetricFu
|
|
3
|
+
class LineNumbers
|
|
4
|
+
|
|
5
|
+
def initialize(contents)
|
|
6
|
+
rp = RubyParser.new
|
|
7
|
+
@locations = {}
|
|
8
|
+
file_sexp = rp.parse(contents)
|
|
9
|
+
case file_sexp[0]
|
|
10
|
+
when :class
|
|
11
|
+
process_class(file_sexp)
|
|
12
|
+
when :module
|
|
13
|
+
process_module(file_sexp)
|
|
14
|
+
when :block
|
|
15
|
+
file_sexp.each_of_type(:class) { |sexp| process_class(sexp) }
|
|
16
|
+
else
|
|
17
|
+
puts "Unexpected sexp_type #{file_sexp[0].inspect}"
|
|
18
|
+
end
|
|
19
|
+
rescue Exception => e
|
|
20
|
+
#catch errors for files ruby_parser fails on
|
|
21
|
+
puts "#{e.class}\t#{e.message}\t#{class_name.inspect}\t#{sexp.inspect}\t#{e.backtrace}"
|
|
22
|
+
@locations
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def in_method? line_number
|
|
26
|
+
!!@locations.detect do |method_name, line_number_range|
|
|
27
|
+
line_number_range.include?(line_number)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def method_at_line line_number
|
|
32
|
+
found_method_and_range = @locations.detect do |method_name, line_number_range|
|
|
33
|
+
line_number_range.include?(line_number)
|
|
34
|
+
end
|
|
35
|
+
if found_method_and_range
|
|
36
|
+
found_method_and_range.first
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def start_line_for_method(method)
|
|
43
|
+
return nil unless @locations.has_key?(method)
|
|
44
|
+
@locations[method].first
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def process_module(sexp)
|
|
50
|
+
module_name = sexp[1]
|
|
51
|
+
sexp.each_of_type(:class) do |sexp|
|
|
52
|
+
process_class(sexp, module_name)
|
|
53
|
+
hide_methods_from_next_round(sexp)
|
|
54
|
+
end
|
|
55
|
+
process_class(sexp)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def process_class(sexp, module_name=nil)
|
|
59
|
+
class_name = sexp[1]
|
|
60
|
+
process_class_self_blocks(sexp, class_name)
|
|
61
|
+
module_name_string = module_name ? "#{module_name}::" : nil
|
|
62
|
+
sexp.each_of_type(:defn) { |s| @locations["#{module_name_string}#{class_name}##{s[1]}"] = (s.line)..(s.last.line) }
|
|
63
|
+
sexp.each_of_type(:defs) { |s| @locations["#{module_name_string}#{class_name}::#{s[2]}"] = (s.line)..(s.last.line) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def process_class_self_blocks(sexp, class_name)
|
|
67
|
+
sexp.each_of_type(:sclass) do |sexp_in_class_self_block|
|
|
68
|
+
sexp_in_class_self_block.each_of_type(:defn) { |s| @locations["#{class_name}::#{s[1]}"] = (s.line)..(s.last.line) }
|
|
69
|
+
hide_methods_from_next_round(sexp_in_class_self_block)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def hide_methods_from_next_round(sexp)
|
|
74
|
+
sexp.find_and_replace_all(:defn, :ignore_me)
|
|
75
|
+
sexp.find_and_replace_all(:defs, :ignore_me)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module MetricFu
|
|
2
|
+
class Location
|
|
3
|
+
include Comparable
|
|
4
|
+
|
|
5
|
+
attr_accessor :class_name, :method_name, :file_path, :simple_method_name, :hash
|
|
6
|
+
|
|
7
|
+
def self.get(file_path, class_name, method_name)
|
|
8
|
+
# This could be more 'confident' using Maybe, but we want it to be as fast as possible
|
|
9
|
+
file_path_copy = file_path == nil ? nil : file_path.clone
|
|
10
|
+
class_name_copy = class_name == nil ? nil : class_name.clone
|
|
11
|
+
method_name_copy = method_name == nil ? nil : method_name.clone
|
|
12
|
+
key = [file_path_copy, class_name_copy, method_name_copy]
|
|
13
|
+
@@locations ||= {}
|
|
14
|
+
if @@locations.has_key?(key)
|
|
15
|
+
@@locations[key]
|
|
16
|
+
else
|
|
17
|
+
location = self.new(file_path_copy, class_name_copy, method_name_copy)
|
|
18
|
+
@@locations[key] = location
|
|
19
|
+
location.freeze # we cache a lot of method call results, so we want location to be immutable
|
|
20
|
+
location
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(file_path, class_name, method_name)
|
|
25
|
+
@file_path = file_path
|
|
26
|
+
@class_name = class_name
|
|
27
|
+
@method_name = method_name
|
|
28
|
+
@simple_method_name = @method_name.sub(@class_name,'') unless @method_name == nil
|
|
29
|
+
@hash = [@file_path, @class_name, @method_name].hash
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# TODO - we need this method (and hash accessor above) as a temporary hack where we're using Location as a hash key
|
|
33
|
+
def eql?(other)
|
|
34
|
+
# REMOVED per https://github.com/jscruggs/metric_fu/pull/67/files
|
|
35
|
+
# [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]
|
|
36
|
+
@hash == other.hash
|
|
37
|
+
end
|
|
38
|
+
# END we need these methods as a temporary hack where we're using Location as a hash key
|
|
39
|
+
|
|
40
|
+
def self.for(class_or_method_name)
|
|
41
|
+
class_or_method_name = strip_modules(class_or_method_name)
|
|
42
|
+
if(class_or_method_name)
|
|
43
|
+
begin
|
|
44
|
+
match = class_or_method_name.match(/(.*)((\.|\#|\:\:[a-z])(.+))/)
|
|
45
|
+
rescue => error
|
|
46
|
+
#new error during port to metric_fu occasionally a unintialized
|
|
47
|
+
#MatchData object shows up here. Not expected.
|
|
48
|
+
match = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# reek reports the method with :: not # on modules like
|
|
52
|
+
# module ApplicationHelper \n def signed_in?, convert it so it records correctly
|
|
53
|
+
# but classes have to start with a capital letter... HACK for REEK bug, reported underlying issue to REEK
|
|
54
|
+
if(match)
|
|
55
|
+
class_name = strip_modules(match[1])
|
|
56
|
+
method_name = class_or_method_name.gsub(/\:\:/,"#")
|
|
57
|
+
else
|
|
58
|
+
class_name = strip_modules(class_or_method_name)
|
|
59
|
+
method_name = nil
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
class_name = nil
|
|
63
|
+
method_name = nil
|
|
64
|
+
end
|
|
65
|
+
self.get(nil, class_name, method_name)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def <=>(other)
|
|
69
|
+
[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]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def self.strip_modules(class_or_method_name)
|
|
75
|
+
# reek reports the method with :: not # on modules like
|
|
76
|
+
# module ApplicationHelper \n def signed_in?, convert it so it records correctly
|
|
77
|
+
# but classes have to start with a capital letter... HACK for REEK bug, reported underlying issue to REEK
|
|
78
|
+
if(class_or_method_name=~/\:\:[A-Z]/)
|
|
79
|
+
class_or_method_name.split("::").last
|
|
80
|
+
else
|
|
81
|
+
class_or_method_name
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module MetricFu
|
|
5
|
+
class MD5Tracker
|
|
6
|
+
|
|
7
|
+
@@unchanged_md5s = []
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def md5_dir(path_to_file, base_dir)
|
|
11
|
+
File.join(base_dir,
|
|
12
|
+
path_to_file.split('/')[0..-2].join('/'))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def md5_file(path_to_file, base_dir)
|
|
16
|
+
File.join(md5_dir(path_to_file, base_dir),
|
|
17
|
+
path_to_file.split('/').last.sub(/\.[a-z]+/, '.md5'))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def track(path_to_file, base_dir)
|
|
21
|
+
md5 = Digest::MD5.hexdigest(File.read(path_to_file))
|
|
22
|
+
FileUtils.mkdir_p(md5_dir(path_to_file, base_dir), :verbose => false)
|
|
23
|
+
f = File.new(md5_file(path_to_file, base_dir), "w")
|
|
24
|
+
f.puts(md5)
|
|
25
|
+
f.close
|
|
26
|
+
md5
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def file_changed?(path_to_file, base_dir)
|
|
30
|
+
orig_md5_file = md5_file(path_to_file, base_dir)
|
|
31
|
+
return !!track(path_to_file, base_dir) unless File.exist?(orig_md5_file)
|
|
32
|
+
|
|
33
|
+
current_md5 = ""
|
|
34
|
+
file = File.open(orig_md5_file, 'r')
|
|
35
|
+
file.each_line { |line| current_md5 << line }
|
|
36
|
+
file.close
|
|
37
|
+
current_md5.chomp!
|
|
38
|
+
|
|
39
|
+
new_md5 = Digest::MD5.hexdigest(File.read(path_to_file))
|
|
40
|
+
new_md5.chomp!
|
|
41
|
+
|
|
42
|
+
@@unchanged_md5s << path_to_file if new_md5 == current_md5
|
|
43
|
+
|
|
44
|
+
return new_md5 != current_md5
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def file_already_counted?(path_to_file)
|
|
48
|
+
return @@unchanged_md5s.include?(path_to_file)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|