jscruggs-metric_fu 0.9.0 → 1.0.0

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 (69) hide show
  1. data/HISTORY +10 -0
  2. data/README +1 -154
  3. data/Rakefile +37 -2
  4. data/TODO +2 -1
  5. data/lib/base/base_template.rb +134 -0
  6. data/lib/base/configuration.rb +187 -0
  7. data/lib/base/generator.rb +144 -0
  8. data/lib/{metric_fu → base}/md5_tracker.rb +0 -0
  9. data/lib/base/report.rb +100 -0
  10. data/lib/{metric_fu → generators}/churn.rb +20 -22
  11. data/lib/generators/flay.rb +29 -0
  12. data/lib/generators/flog.rb +130 -0
  13. data/lib/generators/rcov.rb +74 -0
  14. data/lib/generators/reek.rb +31 -0
  15. data/lib/generators/roodi.rb +28 -0
  16. data/lib/generators/saikuro.rb +201 -0
  17. data/lib/generators/stats.rb +43 -0
  18. data/lib/metric_fu.rb +20 -3
  19. data/lib/templates/{churn.html.erb → standard/churn.html.erb} +12 -4
  20. data/lib/templates/{default.css → standard/default.css} +20 -1
  21. data/lib/templates/{flay.html.erb → standard/flay.html.erb} +12 -9
  22. data/lib/templates/standard/flog.html.erb +52 -0
  23. data/lib/templates/standard/index.html.erb +38 -0
  24. data/lib/templates/standard/rcov.html.erb +42 -0
  25. data/lib/templates/standard/reek.html.erb +41 -0
  26. data/lib/templates/{roodi.html.erb → standard/roodi.html.erb} +10 -8
  27. data/lib/templates/standard/saikuro.html.erb +83 -0
  28. data/lib/templates/standard/standard_template.rb +26 -0
  29. data/lib/templates/standard/stats.html.erb +54 -0
  30. data/spec/base/base_template_spec.rb +140 -0
  31. data/spec/base/configuration_spec.rb +303 -0
  32. data/spec/base/generator_spec.rb +159 -0
  33. data/spec/{md5_tracker_spec.rb → base/md5_tracker_spec.rb} +1 -1
  34. data/spec/base/report_spec.rb +139 -0
  35. data/spec/generators/churn_spec.rb +152 -0
  36. data/spec/generators/flay_spec.rb +101 -0
  37. data/spec/generators/flog_spec.rb +189 -0
  38. data/spec/generators/reek_spec.rb +47 -0
  39. data/spec/generators/saikuro_spec.rb +35 -0
  40. data/spec/generators/stats_spec.rb +74 -0
  41. data/spec/spec_helper.rb +24 -7
  42. data/tasks/metric_fu.rake +14 -0
  43. data/{lib/tasks → tasks}/railroad.rake +0 -0
  44. data/{lib/metric_fu → vendor}/saikuro/saikuro.rb +0 -0
  45. metadata +58 -47
  46. data/lib/metric_fu/base.rb +0 -160
  47. data/lib/metric_fu/flay.rb +0 -17
  48. data/lib/metric_fu/flog.rb +0 -129
  49. data/lib/metric_fu/reek.rb +0 -17
  50. data/lib/metric_fu/roodi.rb +0 -17
  51. data/lib/tasks/churn.rake +0 -9
  52. data/lib/tasks/coverage.rake +0 -54
  53. data/lib/tasks/flay.rake +0 -6
  54. data/lib/tasks/flog.rake +0 -69
  55. data/lib/tasks/metric_fu.rake +0 -24
  56. data/lib/tasks/metric_fu.rb +0 -6
  57. data/lib/tasks/reek.rake +0 -6
  58. data/lib/tasks/roodi.rake +0 -7
  59. data/lib/tasks/saikuro.rake +0 -35
  60. data/lib/tasks/stats.rake +0 -14
  61. data/lib/templates/flog.html.erb +0 -38
  62. data/lib/templates/flog_page.html.erb +0 -25
  63. data/lib/templates/reek.html.erb +0 -30
  64. data/spec/base_spec.rb +0 -57
  65. data/spec/churn_spec.rb +0 -117
  66. data/spec/config_spec.rb +0 -110
  67. data/spec/flay_spec.rb +0 -19
  68. data/spec/flog_spec.rb +0 -208
  69. data/spec/reek_spec.rb +0 -26
@@ -0,0 +1,100 @@
1
+ module MetricFu
2
+
3
+ # MetricFu.report memoizes access to a Report object, that will be
4
+ # used throughout the lifecycle of the MetricFu app.
5
+ def self.report
6
+ @report ||= Report.new
7
+ end
8
+
9
+ # = Report
10
+ #
11
+ # The Report class is responsible two things:
12
+ #
13
+ # It adds information to the yaml report, produced by the system
14
+ # as a whole, for each of the generators used in this test run.
15
+ #
16
+ # It also handles passing the information from each generator used
17
+ # in this test run out to the template class set in
18
+ # MetricFu::Configuration.
19
+ class Report
20
+
21
+ # Renders the result of the report_hash into a yaml serialization
22
+ # ready for writing out to a file.
23
+ #
24
+ # @return YAML
25
+ # A YAML object containing the results of the report generation
26
+ # process
27
+ def to_yaml
28
+ report_hash.to_yaml
29
+ end
30
+
31
+
32
+ def report_hash #:nodoc:
33
+ @report_hash ||= {}
34
+ end
35
+
36
+ # Instantiates a new template class based on the configuration set
37
+ # in MetricFu::Configuration, or through the MetricFu.config block
38
+ # in your rake file (defaults to the included StandardTemplate) and
39
+ # assigns the report_hash to the report_hash to the template and
40
+ # asks itself to write itself out.
41
+ def save_templatized_report
42
+ @template = MetricFu.template_class.new
43
+ @template.report = report_hash
44
+ @template.write
45
+ end
46
+
47
+ # Adds a hash from a passed report, produced by one of the Generator
48
+ # classes to the aggregate report_hash managed by this hash.
49
+ #
50
+ # @param report_type Hash
51
+ # The hash to add to the aggregate report_hash
52
+ def add(report_type)
53
+ clazz = MetricFu.const_get(report_type.to_s.capitalize)
54
+ report_hash.merge!(clazz.generate_report)
55
+ end
56
+
57
+ # Saves the passed in content to the passed in directory. If
58
+ # a filename is passed in it will be used as the name of the
59
+ # file, otherwise it will default to 'index.html'
60
+ #
61
+ # @param content String
62
+ # A string containing the content (usually html) to be written
63
+ # to the file.
64
+ #
65
+ # @param dir String
66
+ # A dir containing the path to the directory to write the file in.
67
+ #
68
+ # @param file String
69
+ # A filename to save the path as. Defaults to 'index.html'.
70
+ #
71
+ def save_output(content, dir, file='index.html')
72
+ open("#{dir}/#{file}", "w") do |f|
73
+ f.puts content
74
+ end
75
+ end
76
+
77
+ # Checks to discover whether we should try and open the results
78
+ # of the report in the browser on this system. We only try and open
79
+ # in the browser if we're on OS X and we're not running in a
80
+ # CruiseControl.rb environment. See MetricFu.configuration for more
81
+ # details about how we make those guesses.
82
+ #
83
+ # @return Boolean
84
+ # Should we open in the browser or not?
85
+ def open_in_browser?
86
+ MetricFu.configuration.platform.include?('darwin') &&
87
+ ! MetricFu.configuration.is_cruise_control_rb?
88
+ end
89
+
90
+ # Shows 'index.html' from the passed directory in the browser
91
+ # if we're able to open the browser on this platform.
92
+ #
93
+ # @param dir String
94
+ # The directory path where the 'index.html' we want to open is
95
+ # stored
96
+ def show_in_browser(dir)
97
+ system("open #{dir}/index.html") if open_in_browser?
98
+ end
99
+ end
100
+ end
@@ -1,27 +1,32 @@
1
+ require 'chronic'
2
+ require 'generator'
1
3
  module MetricFu
2
4
 
3
- def self.generate_churn_report
4
- Churn.generate_report(MetricFu.churn)
5
- system("open #{Churn.metric_dir}/index.html") if open_in_browser?
6
- end
7
-
8
- class Churn < Base::Generator
5
+ class Churn < Generator
9
6
 
10
7
  def initialize(options={})
11
- @base_dir = File.join(MetricFu::BASE_DIRECTORY, template_name)
8
+ super
12
9
  if File.exist?(".git")
13
- @source_control = Git.new(options[:start_date])
10
+ @source_control = Git.new(MetricFu.churn[:start_date])
14
11
  elsif File.exist?(".svn")
15
- @source_control = Svn.new(options[:start_date])
12
+ @source_control = Svn.new(MetricFu.churn[:start_date])
16
13
  else
17
14
  raise "Churning requires a subversion or git repo"
18
15
  end
16
+ @minimum_churn_count = MetricFu.churn[:minimum_churn_count] || 5
17
+ end
19
18
 
20
- @minimum_churn_count = options[:minimum_churn_count] || 5
19
+ def emit
20
+ @changes = parse_log_for_changes.reject {|file, change_count| change_count < @minimum_churn_count}
21
21
  end
22
22
 
23
23
  def analyze
24
- @changes = parse_log_for_changes.reject! {|file, change_count| change_count < @minimum_churn_count}
24
+ @changes = @changes.to_a.sort {|x,y| y[1] <=> x[1]}
25
+ @changes = @changes.map {|change| {:file_path => change[0], :times_changed => change[1] }}
26
+ end
27
+
28
+ def to_h
29
+ {:churn => {:changes => @changes}}
25
30
  end
26
31
 
27
32
  private
@@ -41,13 +46,6 @@ module MetricFu
41
46
  def initialize(start_date=nil)
42
47
  @start_date = start_date
43
48
  end
44
-
45
- private
46
- def require_rails_env
47
- # not sure if the following works because active_support might only be in vendor/rails
48
- # require 'activesupport'
49
- require RAILS_ROOT + '/config/environment'
50
- end
51
49
  end
52
50
 
53
51
  class Git < SourceControl
@@ -58,8 +56,8 @@ module MetricFu
58
56
  private
59
57
  def date_range
60
58
  if @start_date
61
- require_rails_env
62
- "--after=#{@start_date.call.strftime('%Y-%m-%d')}"
59
+ date = Chronic.parse(@start_date)
60
+ "--after=#{date.strftime('%Y-%m-%d')}"
63
61
  end
64
62
  end
65
63
 
@@ -73,8 +71,8 @@ module MetricFu
73
71
  private
74
72
  def date_range
75
73
  if @start_date
76
- require_rails_env
77
- "--revision {#{@start_date.call.strftime('%Y-%m-%d')}}:{#{Time.now.strftime('%Y-%m-%d')}}"
74
+ date = Chronic.parse(@start_date)
75
+ "--revision {#{date.strftime('%Y-%m-%d')}}:{#{Time.now.strftime('%Y-%m-%d')}}"
78
76
  end
79
77
  end
80
78
 
@@ -0,0 +1,29 @@
1
+ require 'generator'
2
+ module MetricFu
3
+
4
+ class Flay < Generator
5
+
6
+ def emit
7
+ files_to_flay = MetricFu.flay[:dirs_to_flay].map{|dir| Dir[File.join(dir, "**/*.rb")] }
8
+ @output = `flay #{files_to_flay.join(" ")}`
9
+ end
10
+
11
+ def analyze
12
+ @matches = @output.chomp.split("\n\n").map{|m| m.split("\n ") }
13
+ end
14
+
15
+ def to_h
16
+ target = []
17
+ total_score = @matches.shift.first.split('=').last.strip
18
+ @matches.each do |problem|
19
+ reason = problem.shift.strip
20
+ lines_info = problem.map do |full_line|
21
+ name, line = full_line.split(":")
22
+ {:name => name.strip, :line => line.strip}
23
+ end
24
+ target << [:reason => reason, :matches => lines_info]
25
+ end
26
+ {:flay => {:total_score => total_score, :matches => target.flatten}}
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,130 @@
1
+ module MetricFu
2
+
3
+ class Flog < Generator
4
+ attr_reader :pages
5
+
6
+ SCORE_FORMAT = "%0.2f"
7
+ METHOD_LINE_REGEX = /(\d+\.\d+):\s+([A-Za-z]+#.*)/
8
+ OPERATOR_LINE_REGEX = /\s*(\d+\.\d+):\s(.*)$/
9
+
10
+ def emit
11
+ metric_dir = MetricFu::Flog.metric_directory
12
+ MetricFu.flog[:dirs_to_flog].each do |directory|
13
+ Dir.glob("#{directory}/**/*.rb").each do |filename|
14
+ output_dir = "#{metric_dir}/#{filename.split("/")[0..-2].join("/")}"
15
+ mkdir_p(output_dir, :verbose => false) unless File.directory?(output_dir)
16
+ if MetricFu::MD5Tracker.file_changed?(filename, metric_dir)
17
+ `flog -ad #{filename} > #{metric_dir}/#{filename.split('.')[0]}.txt`
18
+ end
19
+ end
20
+ end
21
+ rescue LoadError
22
+ if RUBY_PLATFORM =~ /java/
23
+ puts 'running in jruby - flog tasks not available'
24
+ else
25
+ puts 'sudo gem install flog # if you want the flog tasks'
26
+ end
27
+ end
28
+
29
+ def parse(text)
30
+ summary, methods_summary = text.split "\n\n"
31
+ score, average = summary.split("\n").map {|line| line[OPERATOR_LINE_REGEX, 1]}
32
+ return nil unless score && methods_summary
33
+ page = Flog::Page.new(score, average)
34
+ methods_summary.each_line do |method_line|
35
+ if match = method_line.match(METHOD_LINE_REGEX)
36
+ page.scanned_methods << ScannedMethod.new(match[2], match[1])
37
+ elsif match = method_line.match(OPERATOR_LINE_REGEX)
38
+ return if page.scanned_methods.empty?
39
+ page.scanned_methods.last.operators << Operator.new(match[1], match[2])
40
+ end
41
+ end
42
+ page
43
+ end
44
+
45
+ def analyze
46
+ @pages = []
47
+ flog_results.each do |path|
48
+ page = parse(open(path, "r") { |f| f.read })
49
+ if page
50
+ page.path = path.sub(metric_directory, "").sub(".txt", ".rb")
51
+ @pages << page
52
+ end
53
+ end
54
+ end
55
+
56
+ def to_h
57
+ number_of_methods = @pages.inject(0) {|count, page| count += page.scanned_methods.size}
58
+ total_flog_score = @pages.inject(0) {|total, page| total += page.score}
59
+ sorted_pages = @pages.sort_by {|page| page.score }.reverse
60
+ {:flog => { :total => total_flog_score,
61
+ :average => round_to_tenths(total_flog_score/number_of_methods),
62
+ :pages => sorted_pages.map {|page| page.to_h}}}
63
+ end
64
+
65
+ def round_to_tenths(decimal)
66
+ (decimal * 10).round / 10.0
67
+ end
68
+ def flog_results
69
+ Dir.glob("#{metric_directory}/**/*.txt")
70
+ end
71
+
72
+ class Operator
73
+ attr_accessor :score, :operator
74
+
75
+ def initialize(score, operator)
76
+ @score = score.to_f
77
+ @operator = operator
78
+ end
79
+
80
+ def to_h
81
+ {:score => @score, :operator => @operator}
82
+ end
83
+ end
84
+
85
+ class ScannedMethod
86
+ attr_accessor :name, :score, :operators
87
+
88
+ def initialize(name, score, operators = [])
89
+ @name = name
90
+ @score = score.to_f
91
+ @operators = operators
92
+ end
93
+
94
+ def to_h
95
+ {:name => @name,
96
+ :score => @score,
97
+ :operators => @operators.map {|o| o.to_h}}
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ class Flog::Page < MetricFu::Generator
104
+ attr_accessor :path, :score, :scanned_methods, :average_score
105
+
106
+ def initialize(score, average_score, scanned_methods = [])
107
+ @score = score.to_f
108
+ @scanned_methods = scanned_methods
109
+ @average_score = average_score.to_f
110
+ end
111
+
112
+ def filename
113
+ File.basename(path, ".txt")
114
+ end
115
+
116
+ def to_h
117
+ {:score => @score,
118
+ :scanned_methods => @scanned_methods.map {|sm| sm.to_h},
119
+ :highest_score => highest_score,
120
+ :average_score => average_score,
121
+ :path => path}
122
+ end
123
+
124
+ def highest_score
125
+ scanned_methods.inject(0) do |highest, m|
126
+ m.score > highest ? m.score : highest
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,74 @@
1
+ require 'enumerator'
2
+
3
+ module MetricFu
4
+
5
+ class Rcov < Generator
6
+ NEW_FILE_MARKER = ("=" * 80) + "\n"
7
+
8
+
9
+ class Line
10
+ attr_accessor :content, :was_run
11
+
12
+ def initialize(content, was_run)
13
+ @content = content
14
+ @was_run = was_run
15
+ end
16
+
17
+ def to_h
18
+ {:content => @content, :was_run => @was_run}
19
+ end
20
+ end
21
+
22
+ def emit
23
+ begin
24
+ FileUtils.rm_rf(MetricFu::Rcov.metric_directory, :verbose => false)
25
+ Dir.mkdir(MetricFu::Rcov.metric_directory)
26
+ test_files = FileList[*MetricFu.rcov[:test_files]].join(' ')
27
+ rcov_opts = MetricFu.rcov[:rcov_opts].join(' ')
28
+ output = ">> #{MetricFu::Rcov.metric_directory}/rcov.txt"
29
+ `rcov --include-file #{test_files} #{rcov_opts} #{output}`
30
+ rescue LoadError
31
+ if RUBY_PLATFORM =~ /java/
32
+ puts 'running in jruby - rcov tasks not available'
33
+ else
34
+ puts 'sudo gem install rcov # if you want the rcov tasks'
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ def analyze
41
+ output = File.open(MetricFu::Rcov.metric_directory + '/rcov.txt').read
42
+ output = output.split(NEW_FILE_MARKER)
43
+ # Throw away the first entry - it's the execution time etc.
44
+ output.shift
45
+ files = {}
46
+ output.each_slice(2) {|out| files[out.first.strip] = out.last}
47
+ files.each_pair {|fname, content| files[fname] = content.split("\n") }
48
+ files.each_pair do |fname, content|
49
+ content.map! do |raw_line|
50
+ if raw_line.match(/^!!/)
51
+ line = Line.new(raw_line.gsub('!!', ' '), false).to_h
52
+ else
53
+ line = Line.new(raw_line, true).to_h
54
+ end
55
+ end
56
+ files[fname] = {:lines => content}
57
+ end
58
+
59
+ # Calculate the percentage of lines run in each file
60
+ files.each_pair do |fname, content|
61
+ lines = content[:lines]
62
+ lines_run = lines.find_all {|line| line[:was_run] == true }.length
63
+ total_lines = lines.length
64
+ percent_run = ((lines_run.to_f / total_lines.to_f) * 100).round
65
+ files[fname][:percent_run] = percent_run
66
+ end
67
+ @rcov = files
68
+ end
69
+
70
+ def to_h
71
+ {:rcov => @rcov}
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,31 @@
1
+ module MetricFu
2
+
3
+ class Reek < Generator
4
+ REEK_REGEX = /^(\S+) (.*) \((.*)\)$/
5
+
6
+ def emit
7
+ files_to_reek = MetricFu.reek[:dirs_to_reek].map{|dir| Dir[File.join(dir, "**/*.rb")] }
8
+ @output = `reek #{files_to_reek.join(" ")}`
9
+ end
10
+
11
+ def analyze
12
+ @matches = @output.chomp.split("\n\n").map{|m| m.split("\n") }
13
+ @matches = @matches.map do |match|
14
+ file_path = match.shift.split('--').first
15
+ file_path = file_path.gsub('"', ' ').strip
16
+ code_smells = match.map do |smell|
17
+ match_object = smell.match(REEK_REGEX)
18
+ {:method => match_object[1].strip,
19
+ :message => match_object[2].strip,
20
+ :type => match_object[3].strip}
21
+ end
22
+ {:file_path => file_path, :code_smells => code_smells}
23
+ end
24
+ end
25
+
26
+ def to_h
27
+ {:reek => {:matches => @matches}}
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ module MetricFu
2
+
3
+
4
+ class Roodi < Generator
5
+
6
+ def emit
7
+ files_to_analyze = MetricFu.roodi[:dirs_to_roodi].map{|dir| Dir[File.join(dir, "**/*.rb")] }
8
+ @output = `roodi #{files_to_analyze.join(" ")}`
9
+
10
+ end
11
+
12
+ def analyze
13
+ @matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
14
+ total = @matches.pop
15
+ @matches.reject! {|array| array.empty? }
16
+ @matches.map! do |match|
17
+ file, line = match[0].split(':')
18
+ problem = match[1]
19
+ {:file => file, :line => line, :problem => problem}
20
+ end
21
+ @roodi_results = {:total => total, :problems => @matches}
22
+ end
23
+
24
+ def to_h
25
+ {:roodi => @roodi_results}
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,201 @@
1
+ module MetricFu
2
+
3
+ class Saikuro < Generator
4
+
5
+
6
+ def emit
7
+ relative_path = [File.dirname(__FILE__), '..', '..',
8
+ 'vendor', 'saikuro', 'saikuro.rb']
9
+ saikuro = File.expand_path(File.join(relative_path))
10
+ options_string = MetricFu.saikuro.inject("") do |o, h|
11
+ o + "--#{h.join(' ')} "
12
+ end
13
+ sh %{ruby "#{saikuro}" #{options_string}} do |ok, response|
14
+ unless ok
15
+ puts "Saikuro failed with exit status: #{response.exitstatus}"
16
+ exit 1
17
+ end
18
+ end
19
+ end
20
+
21
+ def analyze
22
+ @files = []
23
+ saikuro_results.each do |path|
24
+ if Saikuro::SFile.is_valid_text_file?(path)
25
+ file = Saikuro::SFile.new(path)
26
+ if file
27
+ @files << file
28
+ end
29
+ end
30
+ end
31
+ @files = @files.sort_by do |file|
32
+ file.elements.
33
+ max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
34
+ complexity.to_i
35
+ end
36
+ @files.reverse!
37
+ klasses = []
38
+ @files.each {|f| klasses << f.elements}
39
+ klasses.flatten!
40
+ @classes = klasses.sort_by {|k| k.complexity.to_i}
41
+ @classes.reverse!
42
+ meths = []
43
+ @files.each {|f|
44
+ f.elements.each {|el|
45
+ el.defs.each {|defn|
46
+ defn.name = "#{el.name}##{defn.name}"
47
+ meths << defn}
48
+ }
49
+ }
50
+ meths = meths.sort_by {|meth| meth.complexity.to_i}
51
+ @meths = meths.reverse
52
+
53
+ end
54
+
55
+ def to_h
56
+ files = @files.map do |file|
57
+ my_file = file.to_h
58
+ my_file[:filename] = file.filename
59
+ my_file
60
+ end
61
+ {:saikuro => {:files => files,
62
+ :classes => @classes.map {|c| c.to_h},
63
+ :methods => @meths.map {|m| m.to_h}
64
+ }
65
+ }
66
+ end
67
+
68
+ def saikuro_results
69
+ Dir.glob("#{metric_directory}/**/*.html")
70
+ end
71
+
72
+ private
73
+
74
+
75
+ end
76
+
77
+ class Saikuro::SFile
78
+
79
+ attr_reader :elements
80
+
81
+ def initialize(path)
82
+ @path = path
83
+ @file_handle = File.open(@path, "r")
84
+ @elements = []
85
+ get_elements
86
+ end
87
+
88
+ def self.is_valid_text_file?(path)
89
+ File.open(path, "r") do |f|
90
+ unless f.readline.match /--/
91
+ return false
92
+ else
93
+ return true
94
+ end
95
+ end
96
+ end
97
+
98
+ def filename
99
+ File.basename(@path, '_cyclo.html')
100
+ end
101
+
102
+ def to_h
103
+ merge_classes
104
+ {:classes => @elements}
105
+ end
106
+
107
+ def get_elements
108
+ begin
109
+ while ( line = @file_handle.readline) do
110
+ if line.match /START/
111
+ line = @file_handle.readline
112
+ element = Saikuro::ParsingElement.new(line)
113
+ elsif line.match /END/
114
+ @elements << element
115
+ element = nil
116
+ else
117
+ element << line
118
+ end
119
+ end
120
+ rescue EOFError
121
+ nil
122
+ end
123
+ end
124
+
125
+
126
+ def merge_classes
127
+ new_elements = []
128
+ get_class_names.each do |target_class|
129
+ elements = @elements.find_all {|el| el.name == target_class }
130
+ complexity = 0
131
+ lines = 0
132
+ defns = []
133
+ elements.each do |el|
134
+ complexity += el.complexity.to_i
135
+ lines += el.lines.to_i
136
+ defns << el.defs
137
+ end
138
+
139
+ new_element = {:class_name => target_class,
140
+ :complexity => complexity,
141
+ :lines => lines,
142
+ :methods => defns.flatten.map {|d| d.to_h}}
143
+ new_element[:methods] = new_element[:methods].
144
+ sort_by {|x| x[:complexity] }.
145
+ reverse
146
+
147
+ new_elements << new_element
148
+ end
149
+ @elements = new_elements if new_elements
150
+ end
151
+
152
+ def get_class_names
153
+ class_names = []
154
+ @elements.each do |element|
155
+ unless class_names.include?(element.name)
156
+ class_names << element.name
157
+ end
158
+ end
159
+ class_names
160
+ end
161
+
162
+ end
163
+
164
+ class Saikuro::ParsingElement
165
+ TYPE_REGEX=/Type:(.*) Name/
166
+ NAME_REGEX=/Name:(.*) Complexity/
167
+ COMPLEXITY_REGEX=/Complexity:(.*) Lines/
168
+ LINES_REGEX=/Lines:(.*)/
169
+
170
+ attr_reader :complexity, :lines, :defs, :element_type
171
+ attr_accessor :name
172
+
173
+ def initialize(line)
174
+ @line = line
175
+ @element_type = line.match(TYPE_REGEX)[1].strip
176
+ @name = line.match(NAME_REGEX)[1].strip
177
+ @complexity = line.match(COMPLEXITY_REGEX)[1].strip
178
+ @lines = line.match(LINES_REGEX)[1].strip
179
+ @defs = []
180
+ end
181
+
182
+ def <<(line)
183
+ @defs << Saikuro::ParsingElement.new(line)
184
+ end
185
+
186
+ def to_h
187
+ base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
188
+ unless @defs.empty?
189
+ defs = @defs.map do |my_def|
190
+ my_def = my_def.to_h
191
+ my_def.delete(:defs)
192
+ my_def
193
+ end
194
+ base[:defs] = defs
195
+ end
196
+ return base
197
+ end
198
+ end
199
+
200
+
201
+ end