danmayer-metric_fu 2.1.2

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 (120) hide show
  1. data/HISTORY +237 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README +29 -0
  4. data/Rakefile +18 -0
  5. data/TODO +6 -0
  6. data/lib/base/base_template.rb +172 -0
  7. data/lib/base/churn_analyzer.rb +38 -0
  8. data/lib/base/code_issue.rb +97 -0
  9. data/lib/base/configuration.rb +199 -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/line_numbers.rb +74 -0
  15. data/lib/base/location.rb +85 -0
  16. data/lib/base/md5_tracker.rb +52 -0
  17. data/lib/base/metric_analyzer.rb +404 -0
  18. data/lib/base/ranking.rb +34 -0
  19. data/lib/base/rcov_analyzer.rb +43 -0
  20. data/lib/base/reek_analyzer.rb +163 -0
  21. data/lib/base/report.rb +108 -0
  22. data/lib/base/roodi_analyzer.rb +37 -0
  23. data/lib/base/saikuro_analyzer.rb +48 -0
  24. data/lib/base/scoring_strategies.rb +29 -0
  25. data/lib/base/stats_analyzer.rb +37 -0
  26. data/lib/base/table.rb +102 -0
  27. data/lib/generators/churn.rb +28 -0
  28. data/lib/generators/flay.rb +31 -0
  29. data/lib/generators/flog.rb +111 -0
  30. data/lib/generators/hotspots.rb +52 -0
  31. data/lib/generators/rails_best_practices.rb +53 -0
  32. data/lib/generators/rcov.rb +122 -0
  33. data/lib/generators/reek.rb +81 -0
  34. data/lib/generators/roodi.rb +35 -0
  35. data/lib/generators/saikuro.rb +256 -0
  36. data/lib/generators/stats.rb +58 -0
  37. data/lib/graphs/engines/bluff.rb +113 -0
  38. data/lib/graphs/engines/gchart.rb +157 -0
  39. data/lib/graphs/flay_grapher.rb +18 -0
  40. data/lib/graphs/flog_grapher.rb +57 -0
  41. data/lib/graphs/grapher.rb +11 -0
  42. data/lib/graphs/rails_best_practices_grapher.rb +19 -0
  43. data/lib/graphs/rcov_grapher.rb +18 -0
  44. data/lib/graphs/reek_grapher.rb +30 -0
  45. data/lib/graphs/roodi_grapher.rb +18 -0
  46. data/lib/graphs/stats_grapher.rb +20 -0
  47. data/lib/metric_fu.rb +40 -0
  48. data/lib/templates/awesome/awesome_template.rb +73 -0
  49. data/lib/templates/awesome/churn.html.erb +58 -0
  50. data/lib/templates/awesome/css/buttons.css +82 -0
  51. data/lib/templates/awesome/css/default.css +91 -0
  52. data/lib/templates/awesome/css/integrity.css +334 -0
  53. data/lib/templates/awesome/css/reset.css +7 -0
  54. data/lib/templates/awesome/css/syntax.css +19 -0
  55. data/lib/templates/awesome/flay.html.erb +34 -0
  56. data/lib/templates/awesome/flog.html.erb +55 -0
  57. data/lib/templates/awesome/hotspots.html.erb +62 -0
  58. data/lib/templates/awesome/index.html.erb +34 -0
  59. data/lib/templates/awesome/layout.html.erb +30 -0
  60. data/lib/templates/awesome/rails_best_practices.html.erb +27 -0
  61. data/lib/templates/awesome/rcov.html.erb +42 -0
  62. data/lib/templates/awesome/reek.html.erb +40 -0
  63. data/lib/templates/awesome/roodi.html.erb +27 -0
  64. data/lib/templates/awesome/saikuro.html.erb +71 -0
  65. data/lib/templates/awesome/stats.html.erb +51 -0
  66. data/lib/templates/javascripts/bluff-min.js +1 -0
  67. data/lib/templates/javascripts/excanvas.js +35 -0
  68. data/lib/templates/javascripts/js-class.js +1 -0
  69. data/lib/templates/standard/churn.html.erb +31 -0
  70. data/lib/templates/standard/default.css +64 -0
  71. data/lib/templates/standard/flay.html.erb +34 -0
  72. data/lib/templates/standard/flog.html.erb +57 -0
  73. data/lib/templates/standard/hotspots.html.erb +54 -0
  74. data/lib/templates/standard/index.html.erb +41 -0
  75. data/lib/templates/standard/rails_best_practices.html.erb +29 -0
  76. data/lib/templates/standard/rcov.html.erb +43 -0
  77. data/lib/templates/standard/reek.html.erb +42 -0
  78. data/lib/templates/standard/roodi.html.erb +29 -0
  79. data/lib/templates/standard/saikuro.html.erb +84 -0
  80. data/lib/templates/standard/standard_template.rb +26 -0
  81. data/lib/templates/standard/stats.html.erb +55 -0
  82. data/spec/base/base_template_spec.rb +177 -0
  83. data/spec/base/configuration_spec.rb +276 -0
  84. data/spec/base/generator_spec.rb +223 -0
  85. data/spec/base/graph_spec.rb +61 -0
  86. data/spec/base/line_numbers_spec.rb +62 -0
  87. data/spec/base/md5_tracker_spec.rb +57 -0
  88. data/spec/base/report_spec.rb +146 -0
  89. data/spec/generators/churn_spec.rb +41 -0
  90. data/spec/generators/flay_spec.rb +105 -0
  91. data/spec/generators/flog_spec.rb +70 -0
  92. data/spec/generators/rails_best_practices_spec.rb +52 -0
  93. data/spec/generators/rcov_spec.rb +180 -0
  94. data/spec/generators/reek_spec.rb +134 -0
  95. data/spec/generators/roodi_spec.rb +24 -0
  96. data/spec/generators/saikuro_spec.rb +74 -0
  97. data/spec/generators/stats_spec.rb +74 -0
  98. data/spec/graphs/engines/bluff_spec.rb +19 -0
  99. data/spec/graphs/engines/gchart_spec.rb +156 -0
  100. data/spec/graphs/flay_grapher_spec.rb +56 -0
  101. data/spec/graphs/flog_grapher_spec.rb +108 -0
  102. data/spec/graphs/rails_best_practices_grapher_spec.rb +61 -0
  103. data/spec/graphs/rcov_grapher_spec.rb +56 -0
  104. data/spec/graphs/reek_grapher_spec.rb +65 -0
  105. data/spec/graphs/roodi_grapher_spec.rb +56 -0
  106. data/spec/graphs/stats_grapher_spec.rb +68 -0
  107. data/spec/resources/line_numbers/foo.rb +33 -0
  108. data/spec/resources/line_numbers/module.rb +11 -0
  109. data/spec/resources/line_numbers/module_surrounds_class.rb +15 -0
  110. data/spec/resources/line_numbers/two_classes.rb +11 -0
  111. data/spec/resources/saikuro/app/controllers/sessions_controller.rb_cyclo.html +10 -0
  112. data/spec/resources/saikuro/app/controllers/users_controller.rb_cyclo.html +16 -0
  113. data/spec/resources/saikuro/index_cyclo.html +155 -0
  114. data/spec/resources/saikuro_sfiles/thing.rb_cyclo.html +11 -0
  115. data/spec/resources/yml/20090630.yml +7922 -0
  116. data/spec/resources/yml/metric_missing.yml +1 -0
  117. data/spec/spec.opts +6 -0
  118. data/spec/spec_helper.rb +7 -0
  119. data/tasks/metric_fu.rake +22 -0
  120. metadata +462 -0
@@ -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,102 @@
1
+ class Table
2
+
3
+ def initialize(opts = {})
4
+ @rows = []
5
+ @columns = opts.fetch(:column_names)
6
+
7
+ @make_index = opts.fetch(:make_index) {true}
8
+ @metric_index = {}
9
+ end
10
+
11
+ def <<(row)
12
+ record = nil
13
+ if row.is_a?(Record) || row.is_a?(CodeIssue)
14
+ record = row
15
+ else
16
+ record = Record.new(row, @columns)
17
+ end
18
+ @rows << record
19
+ updated_key_index(record) if @make_index
20
+ end
21
+
22
+ def each
23
+ @rows.each do |row|
24
+ yield row
25
+ end
26
+ end
27
+
28
+ def size
29
+ length
30
+ end
31
+
32
+ def length
33
+ @rows.length
34
+ end
35
+
36
+ def [](index)
37
+ @rows[index]
38
+ end
39
+
40
+ def column(column_name)
41
+ arr = []
42
+ @rows.each do |row|
43
+ arr << row[column_name]
44
+ end
45
+ arr
46
+ end
47
+
48
+ def group_by_metric
49
+ @metric_index.to_a
50
+ end
51
+
52
+ def rows_with(conditions)
53
+ if optimized_conditions?(conditions)
54
+ optimized_select(conditions)
55
+ else
56
+ slow_select(conditions)
57
+ end
58
+ end
59
+
60
+ def delete_at(index)
61
+ @rows.delete_at(index)
62
+ end
63
+
64
+ def to_a
65
+ @rows
66
+ end
67
+
68
+ def map
69
+ new_table = Table.new(:column_names => @columns)
70
+ @rows.map do |row|
71
+ new_table << (yield row)
72
+ end
73
+ new_table
74
+ end
75
+
76
+ private
77
+
78
+ def optimized_conditions?(conditions)
79
+ conditions.keys.length == 1 && conditions.keys.first.to_sym == :metric
80
+ end
81
+
82
+ def optimized_select(conditions)
83
+ metric = (conditions['metric'] || conditions[:metric]).to_s
84
+ @metric_index[metric].to_a.clone
85
+ end
86
+
87
+ def slow_select(conditions)
88
+ @rows.select do |row|
89
+ conditions.all? do |key, value|
90
+ row.has_key?(key.to_s) && row[key.to_s] == value
91
+ end
92
+ end
93
+ end
94
+
95
+ def updated_key_index(record)
96
+ if record.has_key?('metric')
97
+ @metric_index[record.metric] ||= Table.new(:column_names => @columns, :make_index => false)
98
+ @metric_index[record.metric] << record
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,28 @@
1
+ module MetricFu
2
+
3
+ class Churn < Generator
4
+
5
+ def initialize(options={})
6
+ super
7
+ end
8
+
9
+ def emit
10
+ @output = `churn --yaml`
11
+ yaml_start = @output.index("---")
12
+ @output = @output[yaml_start...@output.length] if yaml_start
13
+ end
14
+
15
+ def analyze
16
+ if @output.match(/Churning requires a subversion or git repo/)
17
+ @churn = [:churn => {}]
18
+ else
19
+ @churn = YAML::load(@output)
20
+ end
21
+ end
22
+
23
+ def to_h
24
+ {:churn => @churn[:churn]}
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,31 @@
1
+ require 'flay'
2
+
3
+ module MetricFu
4
+
5
+ class Flay < Generator
6
+
7
+ def emit
8
+ mimimum_score_parameter = MetricFu.flay[:minimum_score] ? "--mass #{MetricFu.flay[:minimum_score]} " : ""
9
+
10
+ @output = `flay #{mimimum_score_parameter} #{MetricFu.flay[:dirs_to_flay].join(" ")}`
11
+ end
12
+
13
+ def analyze
14
+ @matches = @output.chomp.split("\n\n").map{|m| m.split("\n ") }
15
+ end
16
+
17
+ def to_h
18
+ target = []
19
+ total_score = @matches.shift.first.split('=').last.strip
20
+ @matches.each do |problem|
21
+ reason = problem.shift.strip
22
+ lines_info = problem.map do |full_line|
23
+ name, line = full_line.split(":")
24
+ {:name => name.strip, :line => line.strip}
25
+ end
26
+ target << [:reason => reason, :matches => lines_info]
27
+ end
28
+ {:flay => {:total_score => total_score, :matches => target.flatten}}
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,111 @@
1
+ require 'pathname'
2
+ require 'optparse'
3
+ require 'flog'
4
+
5
+ module MetricFu
6
+
7
+ class Flog < Generator
8
+
9
+ def emit
10
+ files = []
11
+ MetricFu.flog[:dirs_to_flog].each do |directory|
12
+ directory = "." if directory=='./'
13
+ dir_files = Dir.glob("#{directory}/**/*.rb")
14
+ dir_files = remove_excluded_files(dir_files)
15
+ files += dir_files
16
+ end
17
+ options = ::Flog.parse_options ["--all", "--details"]
18
+
19
+ @flogger = ::Flog.new options
20
+ @flogger.flog files
21
+
22
+ rescue LoadError
23
+ if RUBY_PLATFORM =~ /java/
24
+ puts 'running in jruby - flog tasks not available'
25
+ end
26
+ end
27
+
28
+ def analyze
29
+ @method_containers = {}
30
+ @flogger.calls.each do |full_method_name, operators|
31
+ container_name = full_method_name.split('#').first
32
+ path = @flogger.method_locations[full_method_name]
33
+ if @method_containers[container_name]
34
+ @method_containers[container_name].add_method(full_method_name, operators, @flogger.totals[full_method_name], path)
35
+ @method_containers[container_name].add_path(path)
36
+ else
37
+ mc = MethodContainer.new(container_name, path)
38
+ mc.add_method(full_method_name, operators, @flogger.totals[full_method_name], path)
39
+ @method_containers[container_name] = mc
40
+ end
41
+ end
42
+ end
43
+
44
+ def to_h
45
+ sorted_containers = @method_containers.values.sort_by {|c| c.highest_score}.reverse
46
+ {:flog => { :total => @flogger.total,
47
+ :average => @flogger.average,
48
+ :method_containers => sorted_containers.map {|method_container| method_container.to_h}}}
49
+ end
50
+
51
+ def per_file_info(out)
52
+ @method_containers.each_pair do |klass, container|
53
+ container.methods.each_pair do |method_name, data|
54
+ next if data[:path].nil?
55
+
56
+ file, line = data[:path].split(':')
57
+
58
+ out[file] ||= {}
59
+ out[file][line] ||= []
60
+ out[file][line] << {:type => :flog, :description => "Score of %.2f" % data[:score]}
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ class MethodContainer
67
+ attr_reader :methods
68
+
69
+ def initialize(name, path)
70
+ @name = name
71
+ add_path path
72
+ @methods = {}
73
+ end
74
+
75
+ def add_path path
76
+ return unless path
77
+ @path ||= path.split(':').first
78
+ end
79
+
80
+ def add_method(full_method_name, operators, score, path)
81
+ @methods[full_method_name] = {:operators => operators, :score => score, :path => path}
82
+ end
83
+
84
+ def to_h
85
+ { :name => @name,
86
+ :path => @path || '',
87
+ :total_score => total_score,
88
+ :highest_score => highest_score,
89
+ :average_score => average_score,
90
+ :methods => @methods}
91
+ end
92
+
93
+ def highest_score
94
+ method_scores.max
95
+ end
96
+
97
+ private
98
+
99
+ def method_scores
100
+ @method_scores ||= @methods.values.map {|v| v[:score] }
101
+ end
102
+
103
+ def total_score
104
+ @total_score ||= method_scores.inject(0) {|sum, score| sum += score}
105
+ end
106
+
107
+ def average_score
108
+ total_score / method_scores.size.to_f
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,52 @@
1
+ module MetricFu
2
+
3
+ class Hotspots < Generator
4
+
5
+ def initialize(options={})
6
+ super
7
+ end
8
+
9
+ def self.verify_dependencies!
10
+ true
11
+ end
12
+
13
+ def emit
14
+ @analyzer = MetricAnalyzer.new(MetricFu.report.report_hash)
15
+ end
16
+
17
+ def analyze
18
+ num = nil
19
+ worst_items = {}
20
+ if @analyzer
21
+ worst_items[:files] =
22
+ @analyzer.worst_files(num).inject([]) do |array, worst_file|
23
+ array <<
24
+ {:location => @analyzer.location(:file, worst_file),
25
+ :details => @analyzer.problems_with(:file, worst_file)}
26
+ array
27
+ end
28
+ worst_items[:classes] = @analyzer.worst_classes(num).inject([]) do |array, class_name|
29
+ location = @analyzer.location(:class, class_name)
30
+ array <<
31
+ {:location => location,
32
+ :details => @analyzer.problems_with(:class, class_name)}
33
+ array
34
+ end
35
+ worst_items[:methods] = @analyzer.worst_methods(num).inject([]) do |array, method_name|
36
+ location = @analyzer.location(:method, method_name)
37
+ array <<
38
+ {:location => location,
39
+ :details => @analyzer.problems_with(:method, method_name)}
40
+ array
41
+ end
42
+ end
43
+
44
+ @hotspots = worst_items
45
+ end
46
+
47
+ def to_h
48
+ {:hotspots => @hotspots}
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,53 @@
1
+ module MetricFu
2
+ class RailsBestPractices < Generator
3
+
4
+ def emit
5
+ @output = `rails_best_practices --without-color .`
6
+ end
7
+
8
+ def analyze
9
+ @matches = @output.chomp.split("\n").map{|m| m.split(" - ") }
10
+ total = @matches.pop
11
+ cleanup_color_switches(total.first)
12
+
13
+ 2.times { @matches.pop } # ignore wiki link
14
+ @matches.reject! {|array| array.empty? }
15
+ @matches.map! do |match|
16
+ file, line = match[0].split(':')
17
+ problem = match[1]
18
+
19
+ cleanup_color_switches(file)
20
+ cleanup_color_switches(problem)
21
+
22
+ file.gsub!(/^\.\//, '')
23
+
24
+ {:file => file, :line => line, :problem => problem}
25
+ end
26
+ @rails_best_practices_results = {:total => total, :problems => @matches}
27
+ end
28
+
29
+ def cleanup_color_switches(str)
30
+ return if str.nil?
31
+ str.gsub!(%r{^\e\[3[1|2]m}, '')
32
+ str.gsub!(%r{\e\[0m$}, '')
33
+ end
34
+
35
+ def to_h
36
+ {:rails_best_practices => @rails_best_practices_results}
37
+ end
38
+
39
+ def per_file_info(out)
40
+ @rails_best_practices_results[:problems].each do |problem|
41
+ next if problem[:file] == '' || problem[:problem].nil?
42
+
43
+ out[problem[:file]] ||= {}
44
+
45
+ lines = problem[:line].split(/\s*,\s*/)
46
+ lines.each do |line|
47
+ out[problem[:file]][line] ||= []
48
+ out[problem[:file]][line] << {:type => :rails_best_practices, :description => problem[:problem]}
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,122 @@
1
+ require 'enumerator'
2
+
3
+ module MetricFu
4
+
5
+ class Rcov < Generator
6
+ NEW_FILE_MARKER = /^={80}$/.freeze
7
+
8
+ class Line
9
+ attr_accessor :content, :was_run
10
+
11
+ def initialize(content, was_run)
12
+ @content = content
13
+ @was_run = was_run
14
+ end
15
+
16
+ def to_h
17
+ {:content => @content, :was_run => @was_run}
18
+ end
19
+ end
20
+
21
+ def emit
22
+ unless MetricFu.rcov[:external]
23
+ FileUtils.rm_rf(MetricFu::Rcov.metric_directory, :verbose => false)
24
+ Dir.mkdir(MetricFu::Rcov.metric_directory)
25
+ test_files = FileList[*MetricFu.rcov[:test_files]].join(' ')
26
+ rcov_opts = MetricFu.rcov[:rcov_opts].join(' ')
27
+ output = ">> #{MetricFu::Rcov.metric_directory}/rcov.txt"
28
+ puts "** Running the specs/tests in the [#{MetricFu.rcov[:environment]}] environment"
29
+ `RAILS_ENV=#{MetricFu.rcov[:environment]} rcov #{test_files} #{rcov_opts} #{output}`
30
+ end
31
+ end
32
+
33
+
34
+ def analyze
35
+ output_file = MetricFu.rcov[:external] ? MetricFu.rcov[:external] : MetricFu::Rcov.metric_directory + '/rcov.txt'
36
+ output = File.open(output_file).read
37
+ output = output.split(NEW_FILE_MARKER)
38
+
39
+ output.shift # Throw away the first entry - it's the execution time etc.
40
+
41
+ files = assemble_files(output)
42
+
43
+ @global_total_lines = 0
44
+ @global_total_lines_run = 0
45
+
46
+ @rcov = add_coverage_percentage(files)
47
+ end
48
+
49
+ def to_h
50
+ global_percent_run = ((@global_total_lines_run.to_f / @global_total_lines.to_f) * 100)
51
+ add_method_data
52
+ {:rcov => @rcov.merge({:global_percent_run => round_to_tenths(global_percent_run) })}
53
+ end
54
+
55
+ private
56
+
57
+ def add_method_data
58
+ @rcov.each_pair do |file_path, info|
59
+ file_contents = ""
60
+ coverage = []
61
+
62
+ info[:lines].each_with_index do |line, index|
63
+ file_contents << "#{line[:content]}\n"
64
+ coverage << line[:was_run]
65
+ end
66
+
67
+ begin
68
+ line_numbers = MetricFu::LineNumbers.new(file_contents)
69
+ rescue StandardError => e
70
+ raise e unless e.message =~ /you shouldn't be able to get here/
71
+ puts "ruby_parser blew up while trying to parse #{file_path}. You won't have method level Rcov information for this file."
72
+ next
73
+ end
74
+
75
+ method_coverage_map = {}
76
+ coverage.each_with_index do |covered, index|
77
+ line_number = index + 1
78
+ if line_numbers.in_method?(line_number)
79
+ method_name = line_numbers.method_at_line(line_number)
80
+ method_coverage_map[method_name] ||= {}
81
+ method_coverage_map[method_name][:total] ||= 0
82
+ method_coverage_map[method_name][:total] += 1
83
+ method_coverage_map[method_name][:uncovered] ||= 0
84
+ method_coverage_map[method_name][:uncovered] += 1 if !covered
85
+ end
86
+ end
87
+
88
+ @rcov[file_path][:methods] = {}
89
+
90
+ method_coverage_map.each do |method_name, coverage_data|
91
+ @rcov[file_path][:methods][method_name] = (coverage_data[:uncovered] / coverage_data[:total].to_f) * 100.0
92
+ end
93
+
94
+ end
95
+ end
96
+
97
+ def assemble_files(output)
98
+ files = {}
99
+ output.each_slice(2) {|out| files[out.first.strip] = out.last}
100
+ files.each_pair {|fname, content| files[fname] = content.split("\n") }
101
+ files.each_pair do |fname, content|
102
+ content.map! do |raw_line|
103
+ line = Line.new(raw_line[3..-1], !raw_line.match(/^!!/)).to_h
104
+ end
105
+ content.reject! {|line| line[:content].blank? }
106
+ files[fname] = {:lines => content}
107
+ end
108
+ files
109
+ end
110
+
111
+ def add_coverage_percentage(files)
112
+ files.each_pair do |fname, content|
113
+ lines = content[:lines]
114
+ @global_total_lines_run += lines_run = lines.find_all {|line| line[:was_run] == true }.length
115
+ @global_total_lines += total_lines = lines.length
116
+ percent_run = ((lines_run.to_f / total_lines.to_f) * 100).round
117
+ files[fname][:percent_run] = percent_run
118
+ end
119
+ end
120
+
121
+ end
122
+ end