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,164 @@
1
+ # coding: utf-8
2
+
3
+ class ReekAnalyzer
4
+ include ScoringStrategies
5
+
6
+ REEK_ISSUE_INFO = {
7
+ 'Uncommunicative Name' =>
8
+ {'link' => 'http://wiki.github.com/kevinrutherford/reek/uncommunicative-name',
9
+ 'info' => 'An Uncommunicative Name is a name that doesn’t communicate its intent well enough.'},
10
+ 'Class Variable' =>
11
+ {'link' => 'http://wiki.github.com/kevinrutherford/reek/class-variable',
12
+ 'info' => 'Class variables form part of the global runtime state, and as such make it ' +
13
+ 'easy for one part of the system to accidentally or inadvertently depend on ' +
14
+ 'another part of the system.'},
15
+ 'Duplication' =>
16
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/duplication',
17
+ 'info' => 'Duplication occurs when two fragments of code look nearly identical, or when ' +
18
+ 'two fragments of code have nearly identical effects at some conceptual level.'},
19
+ 'Low Cohesion' =>
20
+ {'link' => 'http://en.wikipedia.org/wiki/Cohesion_(computer_science)',
21
+ 'info' => 'Low cohesion is associated with undesirable traits such as being difficult to ' +
22
+ 'maintain, difficult to test, difficult to reuse, and even difficult to understand.'},
23
+ 'Nested Iterators' =>
24
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/nested-iterators',
25
+ 'info' => 'Nested Iterator occurs when a block contains another block.'},
26
+ 'Control Couple' =>
27
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/control-couple',
28
+ 'info' => 'Control coupling occurs when a method or block checks the value of a parameter in ' +
29
+ 'order to decide which execution path to take. The offending parameter is often called a “Control Couple”.'},
30
+ 'Irresponsible Module' =>
31
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/irresponsible-module',
32
+ 'info' => 'Classes and modules are the units of reuse and release. It is therefore considered ' +
33
+ 'good practice to annotate every class and module with a brief comment outlining its responsibilities.'},
34
+ 'Long Parameter List' =>
35
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/long-parameter-list',
36
+ 'info' => 'A Long Parameter List occurs when a method has more than one or two parameters, ' +
37
+ 'or when a method yields more than one or two objects to an associated block.'},
38
+ 'Data Clump' =>
39
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/data-clump',
40
+ 'info' => 'In general, a Data Clump occurs when the same two or three items frequently appear ' +
41
+ 'together in classes and parameter lists, or when a group of instance variable names ' +
42
+ 'start or end with similar substrings.'},
43
+ 'Simulated Polymorphism' =>
44
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/simulated-polymorphism',
45
+ 'info' => 'Simulated Polymorphism occurs when, code uses a case statement (especially on a ' +
46
+ 'type field) or code uses instance_of?, kind_of?, is_a?, or === to decide what code to execute'},
47
+ 'Large Class' =>
48
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/large-class',
49
+ 'info' => 'A Large Class is a class or module that has a large number of instance variables, ' +
50
+ 'methods or lines of code in any one piece of its specification.'},
51
+ 'Long Method' =>
52
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/long-method',
53
+ 'info' => 'Long methods can be hard to read and understand. They often are harder to test and ' +
54
+ 'maintain as well, which can lead to buggier code.'},
55
+ 'Feature Envy' =>
56
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/feature-envy',
57
+ 'info' => 'Feature Envy occurs when a code fragment references another object more often than ' +
58
+ 'it references itself, or when several clients do the same series of manipulations ' +
59
+ 'on a particular type of object.'},
60
+ 'Utility Function' =>
61
+ {'link' =>'http://wiki.github.com/kevinrutherford/reek/utility-function',
62
+ 'info' => 'A Utility Function is any instance method that has no dependency on the state of the ' +
63
+ 'instance. It reduces the code’s ability to communicate intent. Code that “belongs” on ' +
64
+ 'one class but which is located in another can be hard to find.'},
65
+ 'Attribute' =>
66
+ {'link' => 'http://wiki.github.com/kevinrutherford/reek/attribute',
67
+ 'info' => 'A class that publishes a getter or setter for an instance variable invites client ' +
68
+ 'classes to become too intimate with its inner workings, and in particular with its ' +
69
+ 'representation of state.'}
70
+ }
71
+
72
+ # Note that in practice, the prefix reek__ is appended to each one
73
+ # This was a partially implemented idea to avoid column name collisions
74
+ # but it is only done in the ReekAnalyzer
75
+ COLUMNS = %w{type_name message value value_description comparable_message}
76
+
77
+ def self.issue_link(issue)
78
+ REEK_ISSUE_INFO[issue]
79
+ end
80
+
81
+ def columns
82
+ COLUMNS.map{|column| "#{name}__#{column}"}
83
+ end
84
+
85
+ def name
86
+ :reek
87
+ end
88
+
89
+ def map(row)
90
+ ScoringStrategies.present(row)
91
+ end
92
+
93
+ def reduce(scores)
94
+ ScoringStrategies.sum(scores)
95
+ end
96
+
97
+ def score(metric_ranking, item)
98
+ ScoringStrategies.percentile(metric_ranking, item)
99
+ end
100
+
101
+ def generate_records(data, table)
102
+ return if data==nil
103
+ data[:matches].each do |match|
104
+ file_path = match[:file_path]
105
+ match[:code_smells].each do |smell|
106
+ location = MetricFu::Location.for(smell[:method])
107
+ smell_type = smell[:type]
108
+ message = smell[:message]
109
+ table << {
110
+ "metric" => name, # important
111
+ "file_path" => file_path, # important
112
+ # NOTE: ReekAnalyzer is currently different than other analyzers with regard
113
+ # to column name. Note the COLUMNS constant and #columns method
114
+ "reek__message" => message,
115
+ "reek__type_name" => smell_type,
116
+ "reek__value" => parse_value(message),
117
+ "reek__value_description" => build_value_description(smell_type, message),
118
+ "reek__comparable_message" => comparable_message(smell_type, message),
119
+ "class_name" => location.class_name, # important
120
+ "method_name" => location.method_name, # important
121
+ }
122
+ end
123
+ end
124
+ end
125
+
126
+ def self.numeric_smell?(type)
127
+ ["Large Class", "Long Method", "Long Parameter List"].include?(type)
128
+ end
129
+
130
+ private
131
+
132
+ def comparable_message(type_name, message)
133
+ if self.class.numeric_smell?(type_name)
134
+ match = message.match(/\d+/)
135
+ if(match)
136
+ match.pre_match + match.post_match
137
+ else
138
+ message
139
+ end
140
+ else
141
+ message
142
+ end
143
+ end
144
+
145
+ def build_value_description(type_name, message)
146
+ item_type = message.match(/\d+ (.*)$/)
147
+ if(item_type)
148
+ "number of #{item_type[1]} in #{type_name.downcase}"
149
+ else
150
+ nil
151
+ end
152
+ end
153
+
154
+ def parse_value(message)
155
+ # mf_debug "parsing #{message}"
156
+ match = message.match(/\d+/)
157
+ if(match)
158
+ match[0].to_i
159
+ else
160
+ nil
161
+ end
162
+ end
163
+
164
+ end
@@ -0,0 +1,110 @@
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
+ def per_file_data
32
+ @per_file_data ||= {}
33
+ end
34
+
35
+ def report_hash #:nodoc:
36
+ @report_hash ||= {}
37
+ end
38
+
39
+ # Instantiates a new template class based on the configuration set
40
+ # in MetricFu::Configuration, or through the MetricFu.config block
41
+ # in your rake file (defaults to the included AwesomeTemplate),
42
+ # assigns the report_hash to the report_hash in the template, and
43
+ # tells the template to to write itself out.
44
+ def save_templatized_report
45
+ @template = MetricFu.template_class.new
46
+ @template.report = report_hash
47
+ @template.per_file_data = per_file_data
48
+ @template.write
49
+ end
50
+
51
+ # Adds a hash from a passed report, produced by one of the Generator
52
+ # classes to the aggregate report_hash managed by this hash.
53
+ #
54
+ # @param report_type Hash
55
+ # The hash to add to the aggregate report_hash
56
+ def add(report_type)
57
+ mf_debug "report requested #{report_type}"
58
+ clazz = MetricFu.const_get(report_type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
59
+ mf_debug "report class found #{clazz}"
60
+ inst = clazz.new
61
+
62
+ report_hash.merge!(inst.generate_report)
63
+
64
+ inst.per_file_info(per_file_data) if inst.respond_to?(:per_file_info)
65
+ end
66
+
67
+ # Saves the passed in content to the passed in directory. If
68
+ # a filename is passed in it will be used as the name of the
69
+ # file, otherwise it will default to 'index.html'
70
+ #
71
+ # @param content String
72
+ # A string containing the content (usually html) to be written
73
+ # to the file.
74
+ #
75
+ # @param dir String
76
+ # A dir containing the path to the directory to write the file in.
77
+ #
78
+ # @param file String
79
+ # A filename to save the path as. Defaults to 'index.html'.
80
+ #
81
+ def save_output(content, dir, file='index.html')
82
+ open("#{dir}/#{file}", "w") do |f|
83
+ f.puts content
84
+ end
85
+ end
86
+
87
+ # Checks to discover whether we should try and open the results
88
+ # of the report in the browser on this system. We only try and open
89
+ # in the browser if we're on OS X and we're not running in a
90
+ # CruiseControl.rb environment. See MetricFu.configuration for more
91
+ # details about how we make those guesses.
92
+ #
93
+ # @return Boolean
94
+ # Should we open in the browser or not?
95
+ def open_in_browser?
96
+ MetricFu.configuration.platform.include?('darwin') &&
97
+ ! MetricFu.configuration.is_cruise_control_rb?
98
+ end
99
+
100
+ # Shows 'index.html' from the passed directory in the browser
101
+ # if we're able to open the browser on this platform.
102
+ #
103
+ # @param dir String
104
+ # The directory path where the 'index.html' we want to open is
105
+ # stored
106
+ def show_in_browser(dir)
107
+ system("open #{dir}/index.html") if open_in_browser?
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,37 @@
1
+ class RoodiAnalyzer
2
+ include ScoringStrategies
3
+
4
+ COLUMNS = %w{problems}
5
+
6
+ def columns
7
+ COLUMNS
8
+ end
9
+
10
+ def name
11
+ :roodi
12
+ end
13
+
14
+ def map(row)
15
+ ScoringStrategies.present(row)
16
+ end
17
+
18
+ def reduce(scores)
19
+ ScoringStrategies.sum(scores)
20
+ end
21
+
22
+ def score(metric_ranking, item)
23
+ ScoringStrategies.percentile(metric_ranking, item)
24
+ end
25
+
26
+ def generate_records(data, table)
27
+ return if data==nil
28
+ Array(data[:problems]).each do |problem|
29
+ table << {
30
+ "metric" => name,
31
+ "problems" => problem[:problem],
32
+ "file_path" => problem[:file]
33
+ }
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,48 @@
1
+ class SaikuroAnalyzer
2
+ include ScoringStrategies
3
+
4
+ COLUMNS = %w{lines complexity}
5
+
6
+ def columns
7
+ COLUMNS
8
+ end
9
+
10
+ def name
11
+ :saikuro
12
+ end
13
+
14
+ def map(row)
15
+ row.complexity
16
+ end
17
+
18
+ def reduce(scores)
19
+ ScoringStrategies.average(scores)
20
+ end
21
+
22
+ def score(metric_ranking, item)
23
+ ScoringStrategies.identity(metric_ranking, item)
24
+ end
25
+
26
+ def generate_records(data, table)
27
+ return if data == nil
28
+ data[:files].each do |file|
29
+ file_name = file[:filename]
30
+ file[:classes].each do |klass|
31
+ location = MetricFu::Location.for(klass[:class_name])
32
+ offending_class = location.class_name
33
+ klass[:methods].each do |match|
34
+ offending_method = MetricFu::Location.for(match[:name]).method_name
35
+ table << {
36
+ "metric" => name,
37
+ "lines" => match[:lines],
38
+ "complexity" => match[:complexity],
39
+ "class_name" => offending_class,
40
+ "method_name" => offending_method,
41
+ "file_path" => file_name,
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,29 @@
1
+ module ScoringStrategies
2
+
3
+ def percentile(ranking, item)
4
+ ranking.percentile(item) # per project score percentile
5
+ end
6
+
7
+ def identity(ranking, item)
8
+ ranking[item] # Use the score you got (ex flog score of 20 is not bad even if it is the top one in project)
9
+ end
10
+
11
+ def present(row)
12
+ 1 # If present it's a one, not present it's a zero - For things like Reek that don't have a number
13
+ end
14
+
15
+ def sum(scores)
16
+ scores.inject(0) {|s,x| s+x}
17
+ end
18
+
19
+ def average(scores)
20
+ # remove dependency on statarray
21
+ # scores.to_statarray.mean
22
+ score_length = scores.length
23
+ sum = 0
24
+ sum = scores.inject( nil ) { |sum,x| sum ? sum+x : x }
25
+ (sum.to_f / score_length.to_f)
26
+ end
27
+
28
+ extend self
29
+ end
@@ -0,0 +1,37 @@
1
+ class StatsAnalyzer
2
+
3
+ COLUMNS = %w{stat_name stat_value}
4
+
5
+ def columns
6
+ COLUMNS
7
+ end
8
+
9
+ def name
10
+ :stats
11
+ end
12
+
13
+ def map(row)
14
+ 0
15
+ end
16
+
17
+ def reduce(scores)
18
+ 0
19
+ end
20
+
21
+ def score(metric_ranking, item)
22
+ 0
23
+ end
24
+
25
+ def generate_records(data, table)
26
+ return if data == nil
27
+ data.each do |key, value|
28
+ next if value.kind_of?(Array)
29
+ table << {
30
+ "metric" => name,
31
+ "stat_name" => key,
32
+ "stat_value" => value
33
+ }
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,108 @@
1
+ [
2
+ '/base/record'
3
+ ].each do |path|
4
+ require File.expand_path(File.join(MetricFu::LIB_ROOT,path))
5
+ end
6
+
7
+ class Table
8
+
9
+ def initialize(opts = {})
10
+ @rows = []
11
+ @columns = opts.fetch(:column_names)
12
+
13
+ @make_index = opts.fetch(:make_index) {true}
14
+ @metric_index = {}
15
+ end
16
+
17
+ def <<(row)
18
+ record = nil
19
+ if row.is_a?(MetricFu::Record) || row.is_a?(CodeIssue)
20
+ record = row
21
+ else
22
+ record = MetricFu::Record.new(row, @columns)
23
+ end
24
+ @rows << record
25
+ updated_key_index(record) if @make_index
26
+ end
27
+
28
+ def each
29
+ @rows.each do |row|
30
+ yield row
31
+ end
32
+ end
33
+
34
+ def size
35
+ length
36
+ end
37
+
38
+ def length
39
+ @rows.length
40
+ end
41
+
42
+ def [](index)
43
+ @rows[index]
44
+ end
45
+
46
+ def column(column_name)
47
+ arr = []
48
+ @rows.each do |row|
49
+ arr << row[column_name]
50
+ end
51
+ arr
52
+ end
53
+
54
+ def group_by_metric
55
+ @metric_index.to_a
56
+ end
57
+
58
+ def rows_with(conditions)
59
+ if optimized_conditions?(conditions)
60
+ optimized_select(conditions)
61
+ else
62
+ slow_select(conditions)
63
+ end
64
+ end
65
+
66
+ def delete_at(index)
67
+ @rows.delete_at(index)
68
+ end
69
+
70
+ def to_a
71
+ @rows
72
+ end
73
+
74
+ def map
75
+ new_table = Table.new(:column_names => @columns)
76
+ @rows.map do |row|
77
+ new_table << (yield row)
78
+ end
79
+ new_table
80
+ end
81
+
82
+ private
83
+
84
+ def optimized_conditions?(conditions)
85
+ conditions.keys.length == 1 && conditions.keys.first.to_sym == :metric
86
+ end
87
+
88
+ def optimized_select(conditions)
89
+ metric = (conditions['metric'] || conditions[:metric]).to_s
90
+ @metric_index[metric].to_a.clone
91
+ end
92
+
93
+ def slow_select(conditions)
94
+ @rows.select do |row|
95
+ conditions.all? do |key, value|
96
+ row.has_key?(key.to_s) && row[key.to_s] == value
97
+ end
98
+ end
99
+ end
100
+
101
+ def updated_key_index(record)
102
+ if record.has_key?('metric')
103
+ @metric_index[record.metric] ||= Table.new(:column_names => @columns, :make_index => false)
104
+ @metric_index[record.metric] << record
105
+ end
106
+ end
107
+
108
+ end