khall-metric_fu 1.0.2.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 (47) hide show
  1. data/HISTORY +119 -0
  2. data/MIT-LICENSE +22 -0
  3. data/Manifest.txt +25 -0
  4. data/README +1 -0
  5. data/Rakefile +46 -0
  6. data/TODO +14 -0
  7. data/lib/base/base_template.rb +134 -0
  8. data/lib/base/configuration.rb +187 -0
  9. data/lib/base/generator.rb +147 -0
  10. data/lib/base/md5_tracker.rb +52 -0
  11. data/lib/base/report.rb +100 -0
  12. data/lib/generators/churn.rb +86 -0
  13. data/lib/generators/flay.rb +29 -0
  14. data/lib/generators/flog.rb +127 -0
  15. data/lib/generators/rcov.rb +77 -0
  16. data/lib/generators/reek.rb +32 -0
  17. data/lib/generators/roodi.rb +24 -0
  18. data/lib/generators/saikuro.rb +212 -0
  19. data/lib/generators/stats.rb +43 -0
  20. data/lib/metric_fu.rb +20 -0
  21. data/lib/templates/standard/churn.html.erb +30 -0
  22. data/lib/templates/standard/default.css +64 -0
  23. data/lib/templates/standard/flay.html.erb +33 -0
  24. data/lib/templates/standard/flog.html.erb +52 -0
  25. data/lib/templates/standard/index.html.erb +38 -0
  26. data/lib/templates/standard/rcov.html.erb +42 -0
  27. data/lib/templates/standard/reek.html.erb +41 -0
  28. data/lib/templates/standard/roodi.html.erb +28 -0
  29. data/lib/templates/standard/saikuro.html.erb +83 -0
  30. data/lib/templates/standard/standard_template.rb +26 -0
  31. data/lib/templates/standard/stats.html.erb +54 -0
  32. data/spec/base/base_template_spec.rb +140 -0
  33. data/spec/base/configuration_spec.rb +303 -0
  34. data/spec/base/generator_spec.rb +159 -0
  35. data/spec/base/md5_tracker_spec.rb +57 -0
  36. data/spec/base/report_spec.rb +139 -0
  37. data/spec/generators/churn_spec.rb +152 -0
  38. data/spec/generators/flay_spec.rb +101 -0
  39. data/spec/generators/flog_spec.rb +200 -0
  40. data/spec/generators/reek_spec.rb +59 -0
  41. data/spec/generators/saikuro_spec.rb +53 -0
  42. data/spec/generators/stats_spec.rb +74 -0
  43. data/spec/spec_helper.rb +28 -0
  44. data/tasks/metric_fu.rake +15 -0
  45. data/tasks/railroad.rake +39 -0
  46. data/vendor/saikuro/saikuro.rb +1214 -0
  47. metadata +163 -0
@@ -0,0 +1,212 @@
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
+
11
+ format_directories
12
+
13
+ options_string = MetricFu.saikuro.inject("") do |options, option|
14
+ options + "--#{option.join(' ')} "
15
+ end
16
+
17
+ sh %{ruby "#{saikuro}" #{options_string}} do |ok, response|
18
+ unless ok
19
+ puts "Saikuro failed with exit status: #{response.exitstatus}"
20
+ exit 1
21
+ end
22
+ end
23
+ end
24
+
25
+ def format_directories
26
+ dirs = MetricFu.saikuro[:input_directory].join(" | ")
27
+ dirs = "\"#{dirs}\""
28
+ MetricFu.saikuro[:input_directory] = dirs
29
+ end
30
+
31
+ def analyze
32
+ @files = []
33
+ saikuro_results.each do |path|
34
+ if Saikuro::SFile.is_valid_text_file?(path)
35
+ file = Saikuro::SFile.new(path)
36
+ if file
37
+ @files << file
38
+ end
39
+ end
40
+ end
41
+ @files = @files.sort_by do |file|
42
+ file.elements.
43
+ max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
44
+ complexity.to_i
45
+ end
46
+ @files.reverse!
47
+ klasses = []
48
+ @files.each {|f| klasses << f.elements}
49
+ klasses.flatten!
50
+ @classes = klasses.sort_by {|k| k.complexity.to_i}
51
+ @classes.reverse!
52
+ meths = []
53
+ @files.each {|f|
54
+ f.elements.each {|el|
55
+ el.defs.each {|defn|
56
+ defn.name = "#{el.name}##{defn.name}"
57
+ meths << defn}
58
+ }
59
+ }
60
+ meths = meths.sort_by {|meth| meth.complexity.to_i}
61
+ @meths = meths.reverse
62
+
63
+ end
64
+
65
+ def to_h
66
+ files = @files.map do |file|
67
+ my_file = file.to_h
68
+ my_file[:filename] = file.filename
69
+ my_file
70
+ end
71
+ {:saikuro => {:files => files,
72
+ :classes => @classes.map {|c| c.to_h},
73
+ :methods => @meths.map {|m| m.to_h}
74
+ }
75
+ }
76
+ end
77
+
78
+ def saikuro_results
79
+ Dir.glob("#{metric_directory}/**/*.html")
80
+ end
81
+
82
+ private
83
+
84
+
85
+ end
86
+
87
+ class Saikuro::SFile
88
+
89
+ attr_reader :elements
90
+
91
+ def initialize(path)
92
+ @path = path
93
+ @file_handle = File.open(@path, "r")
94
+ @elements = []
95
+ get_elements
96
+ end
97
+
98
+ def self.is_valid_text_file?(path)
99
+ File.open(path, "r") do |f|
100
+ if f.eof? || !f.readline.match /--/
101
+ return false
102
+ else
103
+ return true
104
+ end
105
+ end
106
+ end
107
+
108
+ def filename
109
+ File.basename(@path, '_cyclo.html')
110
+ end
111
+
112
+ def to_h
113
+ merge_classes
114
+ {:classes => @elements}
115
+ end
116
+
117
+ def get_elements
118
+ begin
119
+ while (line = @file_handle.readline) do
120
+ return [] if line.blank?
121
+ if line.match /START/
122
+ line = @file_handle.readline
123
+ element = Saikuro::ParsingElement.new(line)
124
+ elsif line.match /END/
125
+ @elements << element
126
+ element = nil
127
+ else
128
+ element << line
129
+ end
130
+ end
131
+ rescue EOFError
132
+ nil
133
+ end
134
+ end
135
+
136
+
137
+ def merge_classes
138
+ new_elements = []
139
+ get_class_names.each do |target_class|
140
+ elements = @elements.find_all {|el| el.name == target_class }
141
+ complexity = 0
142
+ lines = 0
143
+ defns = []
144
+ elements.each do |el|
145
+ complexity += el.complexity.to_i
146
+ lines += el.lines.to_i
147
+ defns << el.defs
148
+ end
149
+
150
+ new_element = {:class_name => target_class,
151
+ :complexity => complexity,
152
+ :lines => lines,
153
+ :methods => defns.flatten.map {|d| d.to_h}}
154
+ new_element[:methods] = new_element[:methods].
155
+ sort_by {|x| x[:complexity] }.
156
+ reverse
157
+
158
+ new_elements << new_element
159
+ end
160
+ @elements = new_elements if new_elements
161
+ end
162
+
163
+ def get_class_names
164
+ class_names = []
165
+ @elements.each do |element|
166
+ unless class_names.include?(element.name)
167
+ class_names << element.name
168
+ end
169
+ end
170
+ class_names
171
+ end
172
+
173
+ end
174
+
175
+ class Saikuro::ParsingElement
176
+ TYPE_REGEX=/Type:(.*) Name/
177
+ NAME_REGEX=/Name:(.*) Complexity/
178
+ COMPLEXITY_REGEX=/Complexity:(.*) Lines/
179
+ LINES_REGEX=/Lines:(.*)/
180
+
181
+ attr_reader :complexity, :lines, :defs, :element_type
182
+ attr_accessor :name
183
+
184
+ def initialize(line)
185
+ @line = line
186
+ @element_type = line.match(TYPE_REGEX)[1].strip
187
+ @name = line.match(NAME_REGEX)[1].strip
188
+ @complexity = line.match(COMPLEXITY_REGEX)[1].strip
189
+ @lines = line.match(LINES_REGEX)[1].strip
190
+ @defs = []
191
+ end
192
+
193
+ def <<(line)
194
+ @defs << Saikuro::ParsingElement.new(line)
195
+ end
196
+
197
+ def to_h
198
+ base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
199
+ unless @defs.empty?
200
+ defs = @defs.map do |my_def|
201
+ my_def = my_def.to_h
202
+ my_def.delete(:defs)
203
+ my_def
204
+ end
205
+ base[:defs] = defs
206
+ end
207
+ return base
208
+ end
209
+ end
210
+
211
+
212
+ end
@@ -0,0 +1,43 @@
1
+ module MetricFu
2
+
3
+ class Stats < Generator
4
+
5
+ def emit
6
+ `rake stats > #{metric_directory + '/stats.txt'}`
7
+ end
8
+
9
+ def analyze
10
+ output = File.open(metric_directory + '/stats.txt').read
11
+ output = output.split("\n")
12
+ output = output.find_all {|line| line[0].chr != "+" }
13
+ output = output.find_all {|line| line[0].chr != "(" }
14
+ output.shift
15
+ totals = output.pop
16
+ totals = totals.split(" ").find_all {|el| ! el.empty? }
17
+ @stats = {}
18
+ @stats[:codeLOC] = totals[0].match(/\d.*/)[0].to_i
19
+ @stats[:testLOC] = totals[1].match(/\d.*/)[0].to_i
20
+ @stats[:code_to_test_ratio] = totals[2].match(/1\:(\d.*)/)[1].to_f
21
+
22
+ @stats[:lines] = output.map do |line|
23
+ elements = line.split("|")
24
+ elements.map! {|el| el.strip }
25
+ elements = elements.find_all {|el| ! el.empty? }
26
+ info_line = {}
27
+ info_line[:name] = elements.shift
28
+ elements.map! {|el| el.to_i }
29
+ [:lines, :loc, :classes, :methods,
30
+ :methods_per_class, :loc_per_method].each do |sym|
31
+ info_line[sym] = elements.shift
32
+ end
33
+ info_line
34
+ end
35
+ @stats
36
+ end
37
+
38
+ def to_h
39
+ {:stats => @stats}
40
+ end
41
+
42
+ end
43
+ end
data/lib/metric_fu.rb ADDED
@@ -0,0 +1,20 @@
1
+ # Load a few things to make our lives easier elsewhere.
2
+ module MetricFu
3
+ LIB_ROOT = File.dirname(__FILE__)
4
+ end
5
+ base_dir = File.join(MetricFu::LIB_ROOT, 'base')
6
+ generator_dir = File.join(MetricFu::LIB_ROOT, 'generators')
7
+ template_dir = File.join(MetricFu::LIB_ROOT, 'templates')
8
+
9
+ # We need to require these two things first because our other classes
10
+ # depend on them.
11
+ require File.join(base_dir, 'report')
12
+ require File.join(base_dir, 'generator')
13
+
14
+ # Load the rakefile so users of the gem get the default metric_fu task
15
+ load File.join(MetricFu::LIB_ROOT, '..', 'tasks', 'metric_fu.rake')
16
+
17
+ # Now load everything else that's in the directory
18
+ Dir[File.join(base_dir, '*.rb')].each{|l| require l }
19
+ Dir[File.join(generator_dir, '*.rb')].each {|l| require l }
20
+ Dir[File.join(template_dir, 'standard/*.rb')].each {|l| require l}
@@ -0,0 +1,30 @@
1
+ <html>
2
+ <head>
3
+ <title>Source Control Churn Results</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Source Control Churn Results</h1>
11
+ <p>Files that change a lot in your project may be bad a sign.
12
+ This task uses your source control log to identify those files.
13
+ </p>
14
+ <table>
15
+ <tr>
16
+ <th>File Path</th>
17
+ <th>Times Changed</th>
18
+ </tr>
19
+ <% count = 0 %>
20
+ <% @churn[:changes].each do |change| %>
21
+ <tr>
22
+ <td><%= change[:file_path] %></td>
23
+ <td><%= change[:times_changed] %></td>
24
+ </tr>
25
+ <% count += 1 %>
26
+ <% end %>
27
+ </table>
28
+ <p>Generated on <%= Time.now.localtime %></p>
29
+ </body>
30
+ </html>
@@ -0,0 +1,64 @@
1
+ body {
2
+ background-color: #efefef;
3
+ margin: 20px;
4
+ padding: 0;
5
+ font: 12px verdana, arial, helvetica;
6
+ }
7
+
8
+ table {
9
+ border-collapse: collapse;
10
+ border: 1px solid #666;
11
+ background: #fff;
12
+ margin-bottom: 20px;
13
+ }
14
+
15
+ table tr.light {
16
+ background: #fff;
17
+ }
18
+
19
+ table tr.dark {
20
+ background: #f9f9f9;
21
+ }
22
+
23
+ table td, table th {
24
+ padding: 4px 10px;
25
+ font-size: 13px;
26
+ }
27
+ table th {
28
+ text-align: center;
29
+ color: #fc0;
30
+ background: #336;
31
+ font-weight: bold;
32
+ border: #d0d0d0 1px solid;
33
+ }
34
+
35
+ table td {
36
+ border: #d0d0d0 1px solid;
37
+ }
38
+
39
+ table td.score {
40
+ text-align: right;
41
+ }
42
+
43
+ .warning {
44
+ background: yellow;
45
+ }
46
+ .rcov_code td {
47
+ border-bottom: 1px solid #ddd ;
48
+ padding: 0;
49
+ margin: 0;
50
+ }
51
+ .rcov_code tr {
52
+ border: 0px;
53
+ padding:0px;
54
+ margin: 0px;
55
+ }
56
+ .rcov_code pre {
57
+ border: 0px;
58
+ padding: 0px;
59
+ margin: 0px;
60
+ }
61
+ .rcov_run {}
62
+ .rcov_not_run {
63
+ background-color: #d88;
64
+ }
@@ -0,0 +1,33 @@
1
+ <html>
2
+ <head>
3
+ <title>Flay Results</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Flay Results</h1>
11
+ <h2>Total Score (lower is better): <%= @flay[:total_score] %></h2>
12
+ <p><a href='http://ruby.sadi.st/Flay.html'>Flay</a> analyzes ruby code for structural similarities.</p>
13
+ <table>
14
+ <tr>
15
+ <th>Files</th>
16
+ <th>Matches</th>
17
+ </tr>
18
+ <% count = 0 %>
19
+ <% @flay[:matches].each do |match| %>
20
+ <tr class='<%= cycle("light", "dark", count) %>'>
21
+ <td>
22
+ <% match[:matches].each do |file| %>
23
+ <%= file[:name] %>:<%= file[:line] %><br />
24
+ <% end %>
25
+ </td>
26
+ <td><%= match[:reason] %></td>
27
+ </tr>
28
+ <% count += 1 %>
29
+ <% end %>
30
+ </table>
31
+ <p>Generated on <%= Time.now.localtime %></p>
32
+ </body>
33
+ </html>
@@ -0,0 +1,52 @@
1
+ <html>
2
+ <head>
3
+ <title>Flog Reporter</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+ <body>
9
+ <h1>Flog Results</h1>
10
+ <p><a href='http://ruby.sadi.st/Flog.html'>Flog</a> measures code complexity.</p>
11
+ <h2>Total Flog score for all methods: <%= @flog[:total]%></h2>
12
+ <h2>Average Flog score for all methods: <%= @flog[:average]%></h2>
13
+ <table>
14
+ <tr>
15
+ <th>File</th>
16
+ <th>Total score</th>
17
+ <th>Methods</th>
18
+ <th>Average score</th>
19
+ <th>Highest score</th>
20
+ </tr>
21
+ <% @flog[:pages].each do |page| %>
22
+ <tr>
23
+ <td><%= page[:path] %></td>
24
+ <td><%= page[:score].round %></td>
25
+ <td><%= page[:scanned_methods].length %></td>
26
+ <td><%= page[:average_score].round %></td>
27
+ <td><%= page[:highest_score].round %></td>
28
+ </tr>
29
+ <% end %>
30
+ </table>
31
+
32
+ <% @flog[:pages].each do |page| %>
33
+ <h2><%= page[:path] %></h2>
34
+ <% page[:scanned_methods].each do |sm| %>
35
+ <p><%= sm[:name] %></p>
36
+ <table>
37
+ <tr>
38
+ <th>Score</th>
39
+ <th>Operator</th>
40
+ </tr>
41
+ <% sm[:operators].each do |operator| %>
42
+ <tr>
43
+ <td><%= operator[:score] %></td>
44
+ <td><%= operator[:operator] %></td>
45
+ </tr>
46
+ <% end %>
47
+ </table>
48
+ <% end %>
49
+ <% end %>
50
+ <p>Generated on <%= Time.now.localtime %></p>
51
+ </body>
52
+ </html>
@@ -0,0 +1,38 @@
1
+ <html>
2
+ <head>
3
+ <title>Metric Fu Results</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Metric Fu Results</h1>
11
+ <% if @churn %>
12
+ <p><a href="churn.html">Churn report</a></p>
13
+ <% end %>
14
+ <% if @flay %>
15
+ <p><a href="flay.html">Flay report</a></p>
16
+ <% end %>
17
+ <% if @flog %>
18
+ <p><a href="flog.html">Flog report</a></p>
19
+ <% end %>
20
+ <% if @rcov %>
21
+ <p><a href="rcov.html">Rcov report</a></p>
22
+ <% end %>
23
+ <% if @reek %>
24
+ <p><a href="reek.html">Reek report</a></p>
25
+ <% end %>
26
+ <% if @roodi %>
27
+ <p><a href="roodi.html">Roodi report</a></p>
28
+ <% end %>
29
+ <% if @saikuro %>
30
+ <p><a href="saikuro.html">Saikuro report</a></p>
31
+ <% end %>
32
+ <% if @stats %>
33
+ <p><a href="stats.html">Stats report</a></p>
34
+ <% end %>
35
+ <p>Generated on <%= Time.now.localtime %></p>
36
+ </body>
37
+ </html>
38
+
@@ -0,0 +1,42 @@
1
+ <html>
2
+ <head>
3
+ <title>Rcov Code Coverage Results</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Rcov Code Coverage Results</h1>
11
+ <p>C0 code coverage information.</p>
12
+ <p>Total Coverage: <%= @rcov.delete(:global_percent_run) %>% </p>
13
+ <table>
14
+ <tr>
15
+ <th>File Path</th>
16
+ <th>Percent run</th>
17
+ </tr>
18
+ <% count = 0 %>
19
+ <% @rcov.each_pair do |fname, file| %>
20
+ <tr>
21
+ <td><%= fname %></td>
22
+ <td><%= file[:percent_run] %></td>
23
+ </tr>
24
+ <% count += 1 %>
25
+ <% end %>
26
+ </table>
27
+
28
+ <% @rcov.each_pair do |fname, file| %>
29
+ <h2> <%= fname %></h2>
30
+ <table class="rcov_code">
31
+ <% file[:lines].each do |line| %>
32
+ <tr>
33
+ <% css_class = line[:was_run] ? "rcov_run" : "rcov_not_run" %>
34
+ <td class="<%= css_class %>"><pre><%= line[:content] %></pre></td>
35
+ </tr>
36
+ <% end %>
37
+ </table>
38
+ <% end %>
39
+ <p>Generated on <%= Time.now.localtime %></p>
40
+ </body>
41
+ </html>
42
+
@@ -0,0 +1,41 @@
1
+ <html>
2
+ <head>
3
+ <title>Reek Results</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Reek Results</h1>
11
+ <p><a href="http://reek.rubyforge.org/">Reek</a> detects common code smells in ruby code.</p>
12
+ <table>
13
+ <tr>
14
+ <th>File Path</th>
15
+ <th>Method</th>
16
+ <th>Description</th>
17
+ <th>Type</th>
18
+ </tr>
19
+ <% count = 0 %>
20
+ <% @reek[:matches].each do |match| %>
21
+ <% match[:code_smells].each do |smell| %>
22
+ <tr class='<%= cycle("light", "dark", count) %>'>
23
+ <td><%= match[:file_path] %></td>
24
+ <td>
25
+ <%= smell[:method] %>
26
+ </td>
27
+ <td>
28
+ <%= smell[:message] %>
29
+ </td>
30
+ <td>
31
+ <%= smell[:type] %>
32
+ </td>
33
+ </tr>
34
+ <% count += 1 %>
35
+ <% end %>
36
+ <% end %>
37
+
38
+ </table>
39
+ <p>Generated on <%= Time.now.localtime %></p>
40
+ </body>
41
+ </html>
@@ -0,0 +1,28 @@
1
+ <html>
2
+ <head>
3
+ <title>Roodi Results</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Roodi Results</h1>
11
+ <p><a href="http://roodi.rubyforge.org/">Roodi</a> parses your Ruby code and warns you about design issues you have based on the checks that is has configured.</p>
12
+ <table>
13
+ <tr>
14
+ <th>File Path</th>
15
+ <th>Warning</th>
16
+ </tr>
17
+ <% count = 0 %>
18
+ <% @roodi[:problems].each do |problem| %>
19
+ <tr class='<%= cycle("light", "dark", count) %>'>
20
+ <td><%= problem[:file] %>:<%= problem[:line] %></td>
21
+ <td><%= problem[:problem] %></td>
22
+ </tr>
23
+ <% count += 1 %>
24
+ <% end %>
25
+ </table>
26
+ <p>Generated on <%= Time.now.localtime %></p>
27
+ </body>
28
+ </html>
@@ -0,0 +1,83 @@
1
+ <html>
2
+ <head>
3
+ <title>Saikuro Results</title>
4
+ <style>
5
+ <%= inline_css("default.css") %>
6
+ </style>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Saikuro Results</h1>
11
+ <p><a href='http://saikuro.rubyforge.org/'>Saikuro</a> analyzes ruby code for cyclomatic complexity.</p>
12
+
13
+ <h2>Analyzed Methods</h2>
14
+ <table>
15
+ <tr>
16
+ <th>Method Name</th>
17
+ <th>Complexity</th>
18
+ <th># Lines</th>
19
+ </tr>
20
+ <% @saikuro[:methods].each do |method| %>
21
+ <tr>
22
+ <td><%= method[:name] %></td>
23
+ <td><%= method[:complexity] %></td>
24
+ <td><%= method[:lines] %></td>
25
+ </tr>
26
+ <% end %>
27
+ </table>
28
+
29
+
30
+
31
+ <h2>Analyzed Classes</h2>
32
+ <table>
33
+ <tr>
34
+ <th>Class Name</th>
35
+ <th>Complexity</th>
36
+ <th># Lines</th>
37
+ </tr>
38
+ <% @saikuro[:classes].each do |klass| %>
39
+ <tr>
40
+ <td><%= klass[:name] %></td>
41
+ <td><%= klass[:complexity] %></td>
42
+ <td><%= klass[:lines] %></td>
43
+ </tr>
44
+ <% end %>
45
+ </table>
46
+
47
+
48
+ <h2>Analyzed Files</h2>
49
+ <% @saikuro[:files].each do |file| %>
50
+ <% file[:classes].each do |klass| %>
51
+ <% if !klass[:methods].empty? %>
52
+ <h3><%= file[:filename] %></h3>
53
+ <h4>Class : <%= klass[:class_name] %></h4>
54
+ <h5>Total complexity : <%= klass[:complexity] %></h5>
55
+ <h5>Total lines : <%= klass[:lines] %></h5>
56
+ <table>
57
+ <tr>
58
+ <th>Method</th>
59
+ <th>Complexity</th>
60
+ <th># Lines</th>
61
+ </tr>
62
+ <% klass[:methods].each do |method| %>
63
+ <tr>
64
+ <td>
65
+ <%= method[:name] %>
66
+ </td>
67
+ <td>
68
+ <%= method[:complexity] %>
69
+ </td>
70
+ <td>
71
+ <%= method[:lines] %>
72
+ </td>
73
+ </tr>
74
+ <% end %>
75
+ </table>
76
+ <% end %>
77
+ <% end %>
78
+ <% end %>
79
+
80
+ <p>Generated on <%= Time.now.localtime %></p>
81
+ </body>
82
+ </html>
83
+