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.
Files changed (127) hide show
  1. data/HISTORY +252 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.md +49 -0
  4. data/Rakefile +22 -0
  5. data/TODO +6 -0
  6. data/lib/base/base_template.rb +182 -0
  7. data/lib/base/churn_analyzer.rb +38 -0
  8. data/lib/base/code_issue.rb +100 -0
  9. data/lib/base/configuration.rb +219 -0
  10. data/lib/base/flay_analyzer.rb +50 -0
  11. data/lib/base/flog_analyzer.rb +43 -0
  12. data/lib/base/generator.rb +166 -0
  13. data/lib/base/graph.rb +44 -0
  14. data/lib/base/grouping.rb +42 -0
  15. data/lib/base/line_numbers.rb +79 -0
  16. data/lib/base/location.rb +87 -0
  17. data/lib/base/md5_tracker.rb +52 -0
  18. data/lib/base/metric_analyzer.rb +331 -0
  19. data/lib/base/ranking.rb +34 -0
  20. data/lib/base/rcov_analyzer.rb +43 -0
  21. data/lib/base/record.rb +43 -0
  22. data/lib/base/reek_analyzer.rb +164 -0
  23. data/lib/base/report.rb +110 -0
  24. data/lib/base/roodi_analyzer.rb +37 -0
  25. data/lib/base/saikuro_analyzer.rb +48 -0
  26. data/lib/base/scoring_strategies.rb +29 -0
  27. data/lib/base/stats_analyzer.rb +37 -0
  28. data/lib/base/table.rb +108 -0
  29. data/lib/generators/churn.rb +28 -0
  30. data/lib/generators/flay.rb +31 -0
  31. data/lib/generators/flog.rb +113 -0
  32. data/lib/generators/hotspots.rb +52 -0
  33. data/lib/generators/rails_best_practices.rb +53 -0
  34. data/lib/generators/rcov.rb +124 -0
  35. data/lib/generators/reek.rb +81 -0
  36. data/lib/generators/roodi.rb +35 -0
  37. data/lib/generators/saikuro.rb +259 -0
  38. data/lib/generators/stats.rb +58 -0
  39. data/lib/graphs/engines/bluff.rb +113 -0
  40. data/lib/graphs/engines/gchart.rb +157 -0
  41. data/lib/graphs/flay_grapher.rb +18 -0
  42. data/lib/graphs/flog_grapher.rb +57 -0
  43. data/lib/graphs/grapher.rb +11 -0
  44. data/lib/graphs/rails_best_practices_grapher.rb +19 -0
  45. data/lib/graphs/rcov_grapher.rb +18 -0
  46. data/lib/graphs/reek_grapher.rb +30 -0
  47. data/lib/graphs/roodi_grapher.rb +18 -0
  48. data/lib/graphs/stats_grapher.rb +20 -0
  49. data/lib/metric_fu.rb +80 -0
  50. data/lib/tasks/metric_fu.rake +36 -0
  51. data/lib/templates/awesome/awesome_template.rb +92 -0
  52. data/lib/templates/awesome/churn.html.erb +58 -0
  53. data/lib/templates/awesome/css/buttons.css +82 -0
  54. data/lib/templates/awesome/css/default.css +91 -0
  55. data/lib/templates/awesome/css/integrity.css +334 -0
  56. data/lib/templates/awesome/css/reset.css +7 -0
  57. data/lib/templates/awesome/css/syntax.css +19 -0
  58. data/lib/templates/awesome/flay.html.erb +34 -0
  59. data/lib/templates/awesome/flog.html.erb +55 -0
  60. data/lib/templates/awesome/hotspots.html.erb +62 -0
  61. data/lib/templates/awesome/index.html.erb +34 -0
  62. data/lib/templates/awesome/layout.html.erb +30 -0
  63. data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
  64. data/lib/templates/awesome/rcov.html.erb +42 -0
  65. data/lib/templates/awesome/reek.html.erb +40 -0
  66. data/lib/templates/awesome/roodi.html.erb +27 -0
  67. data/lib/templates/awesome/saikuro.html.erb +71 -0
  68. data/lib/templates/awesome/stats.html.erb +51 -0
  69. data/lib/templates/javascripts/bluff-min.js +1 -0
  70. data/lib/templates/javascripts/excanvas.js +35 -0
  71. data/lib/templates/javascripts/js-class.js +1 -0
  72. data/lib/templates/standard/churn.html.erb +31 -0
  73. data/lib/templates/standard/default.css +64 -0
  74. data/lib/templates/standard/flay.html.erb +34 -0
  75. data/lib/templates/standard/flog.html.erb +57 -0
  76. data/lib/templates/standard/hotspots.html.erb +54 -0
  77. data/lib/templates/standard/index.html.erb +41 -0
  78. data/lib/templates/standard/rails_best_practices.html.erb +29 -0
  79. data/lib/templates/standard/rcov.html.erb +43 -0
  80. data/lib/templates/standard/reek.html.erb +42 -0
  81. data/lib/templates/standard/roodi.html.erb +29 -0
  82. data/lib/templates/standard/saikuro.html.erb +84 -0
  83. data/lib/templates/standard/standard_template.rb +27 -0
  84. data/lib/templates/standard/stats.html.erb +55 -0
  85. data/spec/base/base_template_spec.rb +194 -0
  86. data/spec/base/configuration_spec.rb +277 -0
  87. data/spec/base/generator_spec.rb +223 -0
  88. data/spec/base/graph_spec.rb +61 -0
  89. data/spec/base/line_numbers_spec.rb +62 -0
  90. data/spec/base/location_spec.rb +127 -0
  91. data/spec/base/md5_tracker_spec.rb +57 -0
  92. data/spec/base/metric_analyzer_spec.rb +452 -0
  93. data/spec/base/ranking_spec.rb +42 -0
  94. data/spec/base/report_spec.rb +146 -0
  95. data/spec/base/table_spec.rb +36 -0
  96. data/spec/generators/churn_spec.rb +41 -0
  97. data/spec/generators/flay_spec.rb +105 -0
  98. data/spec/generators/flog_spec.rb +70 -0
  99. data/spec/generators/hotspots_spec.rb +88 -0
  100. data/spec/generators/rails_best_practices_spec.rb +52 -0
  101. data/spec/generators/rcov_spec.rb +180 -0
  102. data/spec/generators/reek_spec.rb +134 -0
  103. data/spec/generators/roodi_spec.rb +24 -0
  104. data/spec/generators/saikuro_spec.rb +74 -0
  105. data/spec/generators/stats_spec.rb +74 -0
  106. data/spec/graphs/engines/bluff_spec.rb +19 -0
  107. data/spec/graphs/engines/gchart_spec.rb +156 -0
  108. data/spec/graphs/flay_grapher_spec.rb +56 -0
  109. data/spec/graphs/flog_grapher_spec.rb +108 -0
  110. data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
  111. data/spec/graphs/rcov_grapher_spec.rb +56 -0
  112. data/spec/graphs/reek_grapher_spec.rb +65 -0
  113. data/spec/graphs/roodi_grapher_spec.rb +56 -0
  114. data/spec/graphs/stats_grapher_spec.rb +68 -0
  115. data/spec/resources/line_numbers/foo.rb +33 -0
  116. data/spec/resources/line_numbers/module.rb +11 -0
  117. data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
  118. data/spec/resources/line_numbers/two_classes.rb +11 -0
  119. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
  120. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
  121. data/spec/resources/saikuro/index_cyclo.html +155 -0
  122. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
  123. data/spec/resources/yml/20090630.yml +7922 -0
  124. data/spec/resources/yml/metric_missing.yml +1 -0
  125. data/spec/spec.opts +6 -0
  126. data/spec/spec_helper.rb +7 -0
  127. 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
@@ -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