kwala 0.9.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 (67) hide show
  1. data/README +62 -0
  2. data/bin/kwala +39 -0
  3. data/lib/kwala.rb +59 -0
  4. data/lib/kwala/actions/code_change.rb +128 -0
  5. data/lib/kwala/actions/code_coverage.rb +303 -0
  6. data/lib/kwala/actions/code_duplication.rb +222 -0
  7. data/lib/kwala/actions/code_formatting.rb +358 -0
  8. data/lib/kwala/actions/cycle_detection.rb +118 -0
  9. data/lib/kwala/actions/cyclomatic_complexity.rb +159 -0
  10. data/lib/kwala/actions/dead_code.rb +68 -0
  11. data/lib/kwala/actions/executable_files_check.rb +111 -0
  12. data/lib/kwala/actions/flagged_comments.rb +128 -0
  13. data/lib/kwala/actions/gem_plugin.rb +31 -0
  14. data/lib/kwala/actions/loc_count.rb +153 -0
  15. data/lib/kwala/actions/multi_ruby_unit_test.rb +62 -0
  16. data/lib/kwala/actions/outside_links.rb +85 -0
  17. data/lib/kwala/actions/rails_migrate.rb +62 -0
  18. data/lib/kwala/actions/strange_requires.rb +106 -0
  19. data/lib/kwala/actions/syntax_check.rb +130 -0
  20. data/lib/kwala/actions/unit_test.rb +368 -0
  21. data/lib/kwala/build_action.rb +88 -0
  22. data/lib/kwala/build_context.rb +49 -0
  23. data/lib/kwala/command_line_runner.rb +184 -0
  24. data/lib/kwala/ext/demos.jar +0 -0
  25. data/lib/kwala/ext/pmd.jar +0 -0
  26. data/lib/kwala/ext/prefuse.jar +0 -0
  27. data/lib/kwala/extensions.rb +36 -0
  28. data/lib/kwala/lib/code_analyzer.rb +678 -0
  29. data/lib/kwala/lib/cycle_detector.rb +793 -0
  30. data/lib/kwala/lib/strange_requires_detector.rb +145 -0
  31. data/lib/kwala/notification.rb +45 -0
  32. data/lib/kwala/notifications/email.rb +54 -0
  33. data/lib/kwala/notifications/rss.rb +151 -0
  34. data/lib/kwala/project_builder_utils.rb +178 -0
  35. data/lib/kwala/templates/build_template.html +33 -0
  36. data/lib/kwala/templates/code_change_summary.html +27 -0
  37. data/lib/kwala/templates/code_coverage_detailed.html +25 -0
  38. data/lib/kwala/templates/code_coverage_summary.html +30 -0
  39. data/lib/kwala/templates/code_duplication_detailed.html +45 -0
  40. data/lib/kwala/templates/code_duplication_summary.html +9 -0
  41. data/lib/kwala/templates/code_formatting_detailed.html +27 -0
  42. data/lib/kwala/templates/code_formatting_summary.html +11 -0
  43. data/lib/kwala/templates/cycle_detection_detailed.html +20 -0
  44. data/lib/kwala/templates/cycle_detection_summary.html +7 -0
  45. data/lib/kwala/templates/cyclomatic_complexity_summary.html +27 -0
  46. data/lib/kwala/templates/executable_files_check_detailed.html +31 -0
  47. data/lib/kwala/templates/executable_files_check_summary.html +20 -0
  48. data/lib/kwala/templates/flagged_comments_detailed.html +22 -0
  49. data/lib/kwala/templates/flagged_comments_summary.html +10 -0
  50. data/lib/kwala/templates/loc_count_detailed.html +24 -0
  51. data/lib/kwala/templates/loc_count_summary.html +14 -0
  52. data/lib/kwala/templates/mdd/1.png +0 -0
  53. data/lib/kwala/templates/mdd/2.png +0 -0
  54. data/lib/kwala/templates/mdd/3.png +0 -0
  55. data/lib/kwala/templates/mdd/4.png +0 -0
  56. data/lib/kwala/templates/mdd/5.png +0 -0
  57. data/lib/kwala/templates/outside_links_summary.html +4 -0
  58. data/lib/kwala/templates/strange_requires_detailed.html +23 -0
  59. data/lib/kwala/templates/strange_requires_summary.html +13 -0
  60. data/lib/kwala/templates/style.css +95 -0
  61. data/lib/kwala/templates/syntax_check_detailed.html +21 -0
  62. data/lib/kwala/templates/syntax_check_summary.html +7 -0
  63. data/lib/kwala/templates/unit_test_detailed.html +66 -0
  64. data/lib/kwala/templates/unit_test_summary.html +70 -0
  65. data/test/tc_build_action.rb +32 -0
  66. data/test/tc_templates.rb +24 -0
  67. metadata +118 -0
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ # Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are
8
+ # met:
9
+ #
10
+ #
11
+ # * Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ #
14
+ # * Redistributions in binary form must reproduce the above
15
+ # copyright notice, this list of conditions and the following
16
+ # disclaimer in the documentation and/or other materials provided
17
+ # with the distribution.
18
+ #
19
+ # * Neither the name of Ubit nor the names of its
20
+ # contributors may be used to endorse or promote products derived
21
+ # from this software without specific prior written permission.
22
+ #
23
+ #
24
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ #
36
+ # == Author
37
+ # Michael Reinsch (mr@ubit.com)
38
+
39
+ require File.dirname(__FILE__) + "/cycle_detector" # for ReqWalker
40
+
41
+ class Dir
42
+ def == (other)
43
+ case other
44
+ when String; return path == other
45
+ when Dir; return path == other.path
46
+ when FakeDir; return path == other.path
47
+ end
48
+ false
49
+ end
50
+
51
+ def is_subdir_of?(dir)
52
+ return path[0..dir.path.length-1] == dir.path
53
+ end
54
+
55
+ def cut(basedir)
56
+ path
57
+ end
58
+ end
59
+
60
+
61
+ class StrangeRequiresDetector
62
+
63
+ attr_writer :print_strategy
64
+
65
+ def initialize(printer)
66
+ @graph = Hash.new
67
+ @printer = printer
68
+ @file_filter = Proc.new do |files|
69
+ files.find_all do |f|
70
+ /tests\// !~ f && /extensions\// !~ f &&
71
+ /tools\// !~ f && /\/ms\// !~ f && /\/site\// !~f
72
+ end
73
+ end
74
+ @req_filter = Proc.new do |reqs|
75
+ reqs.find_all do |r|
76
+
77
+ end
78
+ end
79
+ end
80
+
81
+ def cut_base(filename, basename)
82
+ filename.gsub(/^#{basename}/, '')
83
+ end
84
+
85
+ def check(dir, basedir = "")
86
+ files = ReqWalker.find_ruby_files(dir)
87
+ @file_filter.call(files).each do |f|
88
+ ReqWalker.get_requires(f).each do |r|
89
+ r = ReqWalker.find_file(r)
90
+ if r && /\/lib\/ruby\// !~ r
91
+ f_base = Dir.new(File.dirname(f))
92
+ r_base = Dir.new(File.dirname(r))
93
+ # well, we assume that a directory models our "class category"...
94
+ if f_base != r_base
95
+ if r_base.is_subdir_of?(f_base)
96
+ @printer.subdir_requirement(cut_base(f,basedir), cut_base(r,basedir))
97
+ else
98
+ @printer.parent_dir_requirement(cut_base(f,basedir), cut_base(r,basedir))
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+
109
+ if __FILE__ == $0
110
+ class STDOUTPrintStrategy
111
+ def subdir_requirement(from, to)
112
+ puts "yellow: #{from} -> #{to}"
113
+ end
114
+ def parent_dir_requirement(from, to)
115
+ puts "red : #{from} -> #{to}"
116
+ end
117
+ end
118
+
119
+ require 'getoptlong'
120
+
121
+ srd = StrangeRequiresDetector.new(STDOUTPrintStrategy.new)
122
+ dir = "./"
123
+
124
+ begin
125
+ options = GetoptLong.new(
126
+ ["-d","--dir", GetoptLong::REQUIRED_ARGUMENT]
127
+ )
128
+
129
+ options.each do |opt,arg|
130
+ case opt
131
+ when "-d"
132
+ dir = arg
133
+ else
134
+ STDERR.puts "Unknown command #{ opt }"
135
+ exit 1
136
+ end
137
+ end
138
+ rescue => err
139
+ STDERR.puts "Error invalid command"
140
+ exit 1
141
+ end
142
+
143
+ srd.check(dir)
144
+
145
+ end
@@ -0,0 +1,45 @@
1
+
2
+ class Notification
3
+ @@notifications = []
4
+
5
+ def self.context=(context)
6
+ @context = context
7
+ end
8
+
9
+ def self.context
10
+ @context
11
+ end
12
+
13
+ def self.notify(event, result, data)
14
+ self.notifications.each { |n| n.notify(event, result, data) }
15
+ end
16
+
17
+ def self.add_notification(obj)
18
+ self.notifications << obj
19
+ end
20
+
21
+ def self.notifications
22
+ if @@notifications.empty?
23
+ Dir.glob(NOTIFICATION_DIR + "/*.rb").sort.each do |file|
24
+ begin
25
+ require file
26
+ rescue Exception => err
27
+ $stderr.puts "Notification error loading #{file}", err.class, err.message
28
+ end
29
+ end
30
+ end
31
+ @@notifications
32
+ end
33
+
34
+ def notify(event, result = nil, data = nil)
35
+ if self.respond_to?(event)
36
+ self.send(event, result, data, Notification.context)
37
+ else
38
+ undefined_event(event, result, data, Notification.context)
39
+ end
40
+ end
41
+
42
+ def undefined_event(event, result, data, context)
43
+ end
44
+
45
+ end
@@ -0,0 +1,54 @@
1
+ require 'net/smtp'
2
+
3
+ class EmailNotification < Notification
4
+
5
+ def unit_test(result, data, context)
6
+ # TODO: Probably should move this to a configuration based
7
+ from_addr = ""
8
+ to_addr = [ "" ]
9
+ smtp_server = ""
10
+ smtp_port = 25
11
+
12
+ # Get the time and set the status msg
13
+ now = Time.now
14
+ status_msg = "Unit tests run #{result.to_s} at"
15
+
16
+ # just file name for now
17
+ data ||= {}
18
+ data = Array(data[:filtered_results]).map do |d|
19
+ d[:file_name]
20
+ end
21
+
22
+ if !data.empty?
23
+ failure_msg = <<-MSG
24
+ Test files that reported failures:
25
+ #{data.join("\n")}
26
+ MSG
27
+ else
28
+ failure_msg = ''
29
+ end
30
+
31
+ # Create the SMTP envelope
32
+ msgstr = <<-END_OF_MESSAGE
33
+ From: Kwala Continuous Integration <EMAIL>
34
+ To: Edge Dev Mailing List <EMAIL>
35
+ Subject: [Kwala] edge build #{context.vcs_num} #{result}
36
+ Date: #{now.strftime("%a, %d %b %Y %X +0900")}
37
+ Message-Id: <#{now.strftime("%m%d%Y%H%M%S")}@EMAIL>
38
+
39
+ Result Summary: #{result.to_s}
40
+ See Results at https://BASEURL/kwala/
41
+
42
+ #{failure_msg}
43
+
44
+ Commit Info:
45
+ #{context.vcs_info}
46
+ END_OF_MESSAGE
47
+
48
+ # Send the message
49
+ Net::SMTP.start(smtp_server, smtp_port) do |smtp|
50
+ smtp.send_message(msgstr, from_addr, *to_addr)
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,151 @@
1
+ require 'pstore'
2
+ require 'rss/2.0'
3
+ require 'rss/utils'
4
+
5
+ class RSSNotification < Notification
6
+
7
+ def unit_test(result, data, context)
8
+ @test_results = data[:test_results]
9
+
10
+ update_pstore(context)
11
+ make_rss(context)
12
+ end
13
+
14
+ protected
15
+
16
+ PSTORE_FILE_NAME = 'prev_test_results.pstore'
17
+
18
+ RSS_FILE_NAME = 'unit_test_results.rss'
19
+
20
+ class UnitTestResult
21
+ attr_reader :filename, :test_results, :errors, :warnings, :first_seen
22
+ attr_accessor :last_seen
23
+
24
+ def initialize(result_hash, timestamp)
25
+ @filename = result_hash[:file_name]
26
+ @errors = result_hash[:sys_errors]
27
+ @warnings = result_hash[:warnings]
28
+ @first_seen = @last_seen = timestamp
29
+
30
+ @test_results = Hash.new
31
+ result_hash[:version].each do |r|
32
+ if !r[:test_results].empty?
33
+ @test_results[r[:version_name]] = r[:test_results]
34
+ end
35
+ end
36
+ end
37
+
38
+ def pass?
39
+ @errors.empty? && @warnings.empty? && @test_results.empty?
40
+ end
41
+
42
+ def ==(obj)
43
+ res = obj.kind_of?(UnitTestResult)
44
+ res &= obj.filename == @filename
45
+ res &= obj.errors == @errors
46
+ res &= obj.warnings == @warnings
47
+ res &= obj.test_results == @test_results
48
+ res
49
+ end
50
+
51
+ def to_rss_item(compare_timestamp)
52
+ if @last_seen < compare_timestamp
53
+ defect_status = "Fixed"
54
+ else
55
+ defect_status = "New"
56
+ end
57
+
58
+ item = RSS::Rss::Channel::Item.new
59
+ item.title = "#{@first_seen.strftime("%b %d, %H:%M")} - #{defect_status} defects in #{@filename}"
60
+ item.pubDate = @first_seen
61
+ item.description = defect_details_html
62
+ item
63
+ end
64
+
65
+ protected
66
+
67
+ def system_messages_html(messages, type)
68
+ details = ""
69
+ if !messages.empty?
70
+ details << "<h2>System #{type}s</h2>\n<pre>\n"
71
+ details << RSS::Utils.html_escape(messages.join)
72
+ details << "\n</pre>\n"
73
+ end
74
+ details
75
+ end
76
+
77
+ def test_result_html
78
+ details = ""
79
+ @test_results.each do |ruby_ver, defects|
80
+ details << "<h2>Test Results for #{ruby_ver}</h2>\n"
81
+ defects.each do |d|
82
+ details << "<pre>\n"
83
+ details << RSS::Utils.html_escape(d[:test_info])
84
+ details << "\n</pre>\n"
85
+ end
86
+ end
87
+ details
88
+ end
89
+
90
+ def defect_details_html
91
+ details = ""
92
+ details << test_result_html
93
+ details << system_messages_html(@errors, "Error")
94
+ details << system_messages_html(@warnings, "Warning")
95
+ details
96
+ end
97
+ end
98
+
99
+ def create_pstore(context)
100
+ PStore.new(File.join(context.output_directory, PSTORE_FILE_NAME))
101
+ end
102
+
103
+ def update_pstore(context)
104
+ create_pstore(context).transaction do |store|
105
+ run_timestamp = Time.now
106
+ del_timestamp = run_timestamp - 12*60*60 # 12 hours
107
+ prev_test_results = store.fetch(:prev_test_results, Hash.new)
108
+
109
+ prev_test_results.delete_if do |key, res|
110
+ res.last_seen <= del_timestamp
111
+ end
112
+
113
+ @test_results.each do |cur_res_hash|
114
+ cur_res = UnitTestResult.new(cur_res_hash, run_timestamp)
115
+ next if cur_res.pass?
116
+
117
+ prev_res = prev_test_results[cur_res.filename]
118
+ if prev_res.nil? || cur_res != prev_res
119
+ # new defects
120
+ prev_test_results[cur_res.filename] = cur_res
121
+ else
122
+ # same'ol defect
123
+ prev_res.last_seen = run_timestamp
124
+ end
125
+ end
126
+
127
+ store[:last_run_timestamp] = run_timestamp
128
+ store[:prev_test_results] = prev_test_results
129
+ end
130
+ end
131
+
132
+ def make_rss(context)
133
+ rss = RSS::Rss.new("2.0")
134
+ chan = RSS::Rss::Channel.new
135
+ chan.title = chan.description = "Unit Test Results for #{context.project_name}"
136
+ rss.channel = chan
137
+
138
+ create_pstore(context).transaction(true) do |store|
139
+ last_run_timestamp = store.fetch(:last_run_timestamp, 0)
140
+ test_results = store.fetch(:prev_test_results, Hash.new)
141
+
142
+ test_results.each do |key, res|
143
+ chan.items << res.to_rss_item(last_run_timestamp)
144
+ end
145
+ end
146
+
147
+ File.open(File.join(context.output_directory, RSS_FILE_NAME), "w") {|f| f.puts(rss.to_s) }
148
+ end
149
+
150
+
151
+ end
@@ -0,0 +1,178 @@
1
+ module ProjectBuilderUtils
2
+
3
+ def ProjectBuilderUtils.find_ruby_files(dir)
4
+ ruby_files = Array.new
5
+
6
+ Find.find(dir) do |path|
7
+ if File.directory?(path)
8
+ # Ignore all directories starting with .
9
+ if File.basename(path)[0] == ?. || File.basename(path) =~ /vendor/
10
+ Find.prune
11
+ else
12
+ next
13
+ end
14
+ elsif File.file?(path)
15
+ case path.to_s
16
+ when /\.rb$/
17
+ ruby_files << path
18
+ when /\.rb~$/
19
+ next
20
+ when ProjectBuilderUtils.contains_ruby_execution_command?(path)
21
+ ruby_files << path
22
+ else
23
+ end
24
+ end
25
+ end
26
+
27
+ ruby_files = ruby_files.sort
28
+ end
29
+
30
+ def ProjectBuilderUtils.strip_base_dir(base_dir, file)
31
+ m = /(#{base_dir})(.*)/.match(file)
32
+ m[2][1..-1]
33
+ end
34
+
35
+ def ProjectBuilderUtils.get_ruby_and_unit_test_files(dir=Dir.pwd)
36
+ ruby_files = ProjectBuilderUtils.find_ruby_files(dir)
37
+
38
+ # remove all test case files
39
+ test_files, ruby_files = ruby_files.partition do |f|
40
+ f =~ /\/(tc|ts)_.+\.rb$|.*_test.rb$/
41
+ end
42
+
43
+ # XXX: UGLY HACK TO AVOID SIMULATION
44
+ test_files.delete_if { |f| f =~ /test\/simulation\// }
45
+
46
+ [ruby_files.uniq, test_files.sort.uniq]
47
+ end
48
+
49
+ def ProjectBuilderUtils.clean_trailing_slash(dir)
50
+ if dir[dir.size - 1] == ?/
51
+ if dir.size > 1
52
+ dir = dir[0..dir.size - 2]
53
+ else
54
+ dir = "."
55
+ end
56
+ end
57
+ dir
58
+ end
59
+
60
+
61
+ def ProjectBuilderUtils.expand_template(template, data)
62
+ str = StringIO.new
63
+ template.expand(str, data)
64
+ str.string
65
+ end
66
+
67
+ def ProjectBuilderUtils.load_actions
68
+ Dir.glob(ACTION_DIR + "/*.rb").sort.each do |action_file|
69
+ require(action_file)
70
+ end
71
+ end
72
+
73
+ def ProjectBuilderUtils.contains_ruby_execution_command?(path)
74
+ File.open(path, "r") do |f|
75
+ return (f.read(1024).to_s =~ /^#!.*ruby/)
76
+ end
77
+ end
78
+
79
+
80
+ def ProjectBuilderUtils.expand_summary_template(data, project_name, summaries)
81
+ template = TemplateFile.new("#{TEMPLATE_DIR}/build_template.html")
82
+ data[:action_summaries] = summaries
83
+ ProjectBuilderUtils.expand_template(template, data)
84
+ end
85
+
86
+ def ProjectBuilderUtils.make_summary_report(context, summaries)
87
+ if context.vcs_num
88
+
89
+ context.amrita_data[:vcs] = {
90
+ :vcs_num => context.vcs_num,
91
+ :vcs_info => context.vcs_info
92
+ }
93
+ end
94
+ context.build_complete
95
+
96
+ exp_res = ProjectBuilderUtils.expand_summary_template(context.amrita_data, context.project_name, summaries)
97
+ if context.output_directory
98
+ sum_file = "#{context.output_directory}/index.html"
99
+ File.open(sum_file, "w") do |f|
100
+ f.puts exp_res
101
+ end
102
+ else
103
+ STDOUT.puts exp_res
104
+ end
105
+ end
106
+
107
+ # Pull this into better place
108
+ def ProjectBuilderUtils.calc_score(context, action_instances)
109
+ scores = action_instances.map { |a| a.score }
110
+ scores.compact!
111
+
112
+ # If action doesn't calculate score, just default to 1 for now
113
+ if !scores.empty?
114
+ total = scores.inject(0) { |s,v| s + v }
115
+
116
+ # puts "Total : #{total} SIZE: #{scores.size}"
117
+ score = ((total / (scores.size * 10).to_f) * 10).ceil
118
+ img_num = ProjectBuilderUtils.image_for_score(score)
119
+ # puts "!!!!!!!!!!!!!!!!!! SCORE : #{score} IMG #{img_num} !!!!!!!!!!!"
120
+ else
121
+ img_num = 1
122
+ end
123
+ # Mascot Driven Development!
124
+ context.amrita_data[:mdd] = Amrita.a(:src => "mdd/#{img_num}.png")
125
+ end
126
+
127
+ def ProjectBuilderUtils.image_for_score(score)
128
+ case score
129
+ when 8..10 : 1
130
+ when 6..7 : 2
131
+ when 4..5 : 3
132
+ when 3..2 : 4
133
+ else
134
+ 5
135
+ end
136
+ end
137
+
138
+ def ProjectBuilderUtils.make_detailed_reports(context, action_instances)
139
+ if context.output_directory
140
+ action_instances.each do |action|
141
+ det_file, det_res = action.detailed_display(context)
142
+ if det_file
143
+ File.open(det_file, "w") do |f|
144
+ f.puts det_res
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ ProjectBuilderUtils.calc_score(context, action_instances)
151
+ end
152
+
153
+ # returns an array of the actions that were successfully built
154
+ def ProjectBuilderUtils.build_actions(action_instances, context)
155
+ success = Array.new
156
+
157
+ action_instances.each do |action|
158
+ begin
159
+ action.build_action(context)
160
+ success << action
161
+ rescue KwalaAbortBuildException => err
162
+ # We want to failed action, but stop the others
163
+ success << action
164
+ break
165
+ end
166
+ end
167
+
168
+ success
169
+ end
170
+
171
+ def ProjectBuilderUtils.build_summaries(action_instances, context)
172
+ action_instances.map do |action|
173
+ { :summary => action.summary_display(context) }
174
+ end
175
+ end
176
+
177
+
178
+ end