jscruggs-metric_fu 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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