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,128 @@
1
+ # $Id: flagged_comments.rb 32 2008-04-02 07:50:18Z zev $
2
+ #
3
+ # This file implements the logic to scan for special comments in
4
+ # a source base.
5
+
6
+ # Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
7
+ # All rights reserved.
8
+ #
9
+ # Redistribution and use in source and binary forms, with or without
10
+ # modification, are permitted provided that the following conditions are
11
+ # met:
12
+ #
13
+ #
14
+ # * Redistributions of source code must retain the above copyright
15
+ # notice, this list of conditions and the following disclaimer.
16
+ #
17
+ # * Redistributions in binary form must reproduce the above
18
+ # copyright notice, this list of conditions and the following
19
+ # disclaimer in the documentation and/or other materials provided
20
+ # with the distribution.
21
+ #
22
+ # * Neither the name of Ubit nor the names of its
23
+ # contributors may be used to endorse or promote products derived
24
+ # from this software without specific prior written permission.
25
+ #
26
+ #
27
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+ #
39
+ # == Author
40
+ # Zev Blut (zb@ubit.com)
41
+
42
+
43
+ class FlaggedCommentsAction < BuildAction
44
+ COMMENTFLAGS = [ "BUG", "FIXME", "???", "TODO", "XXX" ]
45
+ COMMENTREGEXP = Regexp.new("\#\s*(" +
46
+ COMMENTFLAGS.map { |i| Regexp.escape(i)}.join("|") +
47
+ ")\s*:?(.*)")
48
+
49
+ def initialize
50
+ @comment_count = Hash.new { |h,k| h[k] = Array.new }
51
+ end
52
+
53
+ def build_action(context)
54
+ projbase = context.project_directory.split("/").last
55
+ (context.ruby_files + context.test_files).each do |file|
56
+ fcount = collect_comments_from_file(file)
57
+ fcount.each do |cflag, val|
58
+ fnamemap = val.map do |idx, txt|
59
+ nfile = file.split(context.project_directory).last
60
+ ["#{ projbase }/#{ nfile }\##{ idx }" , txt]
61
+ end
62
+ @comment_count[cflag].concat( fnamemap )
63
+ end
64
+ end
65
+ end
66
+
67
+ def summary_display(context)
68
+ template = TemplateFile.new(self.class.summary_template_file)
69
+
70
+ ares = COMMENTFLAGS.map do |ctype|
71
+ { :type => ctype,
72
+ :count => @comment_count[ctype].size
73
+ }
74
+ end
75
+
76
+ context.amrita_data[:com_results] = { :entry => ares }
77
+ loc_base = "#{context.project_name}_com.html"
78
+ context.amrita_data[:com_details] =
79
+ (Amrita::e(:a, :href => loc_base) { "Flagged Comments Details" } )
80
+
81
+ summary_expand(template, context)
82
+ end
83
+
84
+ def detailed_display(context)
85
+ template = TemplateFile.new(self.class.detailed_template_file)
86
+
87
+
88
+ ares = COMMENTFLAGS.map do |ctype|
89
+ { :type => ctype,
90
+ :count => @comment_count[ctype].size,
91
+ :entries => @comment_count[ctype].map do |file, txt|
92
+ {
93
+ :file => Amrita::e(:a, :href => link_to_file(file)) { file },
94
+ :txt => txt
95
+ }
96
+ end
97
+ }
98
+ end
99
+
100
+ context.amrita_data[:com_details] = { :entry => ares }
101
+ det_res = ProjectBuilderUtils.expand_template(template, context.amrita_data)
102
+ det_file = "#{context.output_directory}/#{context.project_name}_com.html"
103
+ [det_file, det_res]
104
+ end
105
+
106
+
107
+ def collect_comments_from_file(file)
108
+ res = Hash.new { |h,k| h[k] = Array.new }
109
+ IO.readlines(file).each_with_index do |line, idx|
110
+ if m = COMMENTREGEXP.match(line)
111
+ res[m[1]]<< [idx + 1 , m[2]]
112
+ end
113
+ end
114
+ res
115
+ end
116
+
117
+ #annotate does the line numbering and pointing
118
+ def link_to_file(file)
119
+ file, line = file.split("#")
120
+ view_url = annotate_url +
121
+ file.to_s + "?annotate=HEAD\#" + line.to_s
122
+ end
123
+
124
+ def annotate_url
125
+ "http://localhost/cgi-bin/viewcvs/cgi/viewcvs.cgi/"
126
+ end
127
+
128
+ end
@@ -0,0 +1,31 @@
1
+ # This is a base action that all external plugins (i.e. gems)
2
+ # should subclass
3
+
4
+ # How would I test this?
5
+ class GemPluginAction < BuildAction
6
+
7
+
8
+ # For now I assume that the gem's templates are in the same directory as the action file
9
+ def self.summary_template_file
10
+ "#{File.dirname(__FILE__)}/#{command_line_action_name}_summary.html"
11
+ end
12
+
13
+ def self.detailed_template_file
14
+ "#{File.dirname(__FILE__)}/#{command_line_action_name}_detailed.html"
15
+ end
16
+
17
+ def self.load_external_gems
18
+ require 'rubygems'
19
+ # Here we do stuff with the gem api to find all external gems that require kwala.
20
+ # We will assume that they are actions and require them
21
+ # Thanks to Chad Fowler for this bit of code
22
+ Gem.cache.search(//).select do |specification|
23
+ specification.dependencies.map{ |dep| dep.name }.include('kwala')
24
+ end.each do |depname|
25
+ require depname.name
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ GemPluginAction.load_external_gems
@@ -0,0 +1,153 @@
1
+ # $Id: loc_count.rb 29 2008-04-02 07:32:44Z zev $
2
+ #
3
+ # This file implements the logic to count the lines of code in
4
+ # a source base.
5
+
6
+ # Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
7
+ # All rights reserved.
8
+ #
9
+ # Redistribution and use in source and binary forms, with or without
10
+ # modification, are permitted provided that the following conditions are
11
+ # met:
12
+ #
13
+ #
14
+ # * Redistributions of source code must retain the above copyright
15
+ # notice, this list of conditions and the following disclaimer.
16
+ #
17
+ # * Redistributions in binary form must reproduce the above
18
+ # copyright notice, this list of conditions and the following
19
+ # disclaimer in the documentation and/or other materials provided
20
+ # with the distribution.
21
+ #
22
+ # * Neither the name of Ubit nor the names of its
23
+ # contributors may be used to endorse or promote products derived
24
+ # from this software without specific prior written permission.
25
+ #
26
+ #
27
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+ #
39
+ # == Author
40
+ # Zev Blut (zb@ubit.com)
41
+
42
+ module LOCCountModule
43
+ def count_loc(file)
44
+ code = comments = blank = 0
45
+ inside_embedded_doc = false
46
+
47
+ IO.readlines(file).each do |line|
48
+ if line =~ /\s*#/
49
+ #ignore comments
50
+ comments += 1
51
+ elsif line =~ /^=begin/
52
+ inside_embedded_doc = true
53
+ comments += 1
54
+ elsif line =~ /^=end/
55
+ inside_embedded_doc = false
56
+ comments += 1
57
+ elsif inside_embedded_doc
58
+ comments += 1
59
+ elsif line =~ /^\s*$/
60
+ #blank space to ignore
61
+ blank += 1
62
+ else
63
+ code+= 1
64
+ end
65
+ end
66
+
67
+ [code, comments, blank]
68
+ end
69
+ end
70
+
71
+
72
+ class LocCountAction < BuildAction
73
+ include LOCCountModule
74
+
75
+ TOTAL = "TOTAL_LOC"
76
+ PROD_CODE = "TOTAL_PRODUCTION_LOC"
77
+ TEST_CODE = "TOTAL_TEST_LOC"
78
+
79
+ def initialize
80
+ @file_loc = Hash.new { |h, k| h[k] = Array.new }
81
+ end
82
+
83
+ def build_action(context)
84
+
85
+ (context.ruby_files + context.test_files).each do |file|
86
+ @file_loc[file] = count_loc(file)
87
+ end
88
+
89
+ @file_loc[TOTAL] = sum_count(@file_loc.keys)
90
+ @file_loc[PROD_CODE] = sum_count(context.ruby_files)
91
+ @file_loc[TEST_CODE] = sum_count(context.test_files)
92
+ end
93
+
94
+ def summary_display(context)
95
+ template = TemplateFile.new(self.class.summary_template_file)
96
+
97
+ ares = [ ["Total", @file_loc[TOTAL]],
98
+ ["Production Code", @file_loc[PROD_CODE]],
99
+ ["Test Code", @file_loc[TEST_CODE]] ].map do |ctype, cdata|
100
+ code, comments, blank, files = *cdata
101
+ ttl = code + comments + blank
102
+ { :type => ctype, :total => ttl,
103
+ :code => code, :code_per => percent(code, ttl),
104
+ :comments => comments, :comments_per => percent(comments, ttl),
105
+ :blank => blank, :blank_per => percent(blank, ttl) ,
106
+ :files => files
107
+ }
108
+ end
109
+
110
+ context.amrita_data[:loc_results] = { :entry => ares }
111
+ loc_base = "#{context.project_name}_loc.html"
112
+ context.amrita_data[:loc_details] =
113
+ (Amrita::e(:a, :href => loc_base) { "Lines of Code Details" } )
114
+
115
+ summary_expand(template, context)
116
+ end
117
+
118
+ def detailed_display(context)
119
+ template = TemplateFile.new(self.class.detailed_template_file)
120
+
121
+ ares = (@file_loc.keys - [TOTAL, PROD_CODE, TEST_CODE]).sort.map do |file|
122
+ code, comments, blank = *(@file_loc[file])
123
+ ttl = code + comments + blank
124
+ { :file => file, :total => ttl,
125
+ :code => code, :code_per => percent(code, ttl),
126
+ :comments => comments, :comments_per => percent(comments, ttl),
127
+ :blank => blank, :blank_per => percent(blank, ttl) }
128
+ end
129
+
130
+ context.amrita_data[:loc_details] = { :entry => ares }
131
+ det_res = ProjectBuilderUtils.expand_template(template, context.amrita_data)
132
+ det_file = "#{context.output_directory}/#{context.project_name}_loc.html"
133
+ [det_file, det_res]
134
+ end
135
+
136
+ def percent(val, total)
137
+ sprintf("%.2f%", (val.to_f / total) * 100)
138
+ end
139
+
140
+ def sum_count(files)
141
+ code = comments = blank = 0
142
+
143
+ files.each do |file|
144
+ fcode, fcomments, fblank = *( @file_loc[file] )
145
+ code += fcode
146
+ comments += fcomments
147
+ blank += fblank
148
+ end
149
+
150
+ [code, comments, blank, files.size]
151
+ end
152
+
153
+ end
@@ -0,0 +1,62 @@
1
+ require "#{File.dirname(__FILE__)}/unit_test"
2
+
3
+ # requires multiruby, part of ZenTest: http://www.zenspider.com/ZSS/Products/ZenTest/
4
+ class MultiRubyUnitTestBuildAction < UnitTestBuildAction
5
+
6
+ MULTIRUBY = '/usr/local/bin/multiruby'
7
+ SITE_RUBY = '/usr/local/lib/ruby/site_ruby/1.8'
8
+
9
+ def self.summary_template_file
10
+ superclass.summary_template_file
11
+ end
12
+
13
+ def self.detailed_template_file
14
+ superclass.detailed_template_file
15
+ end
16
+
17
+ #################
18
+ # Protected
19
+
20
+ def ruby_version
21
+ return @ruby_version if defined? @ruby_version
22
+ version = `#{MULTIRUBY} -v`
23
+ @ruby_version = version.scan(/VERSION = ([\.0-9]*)/).join(', ')
24
+ end
25
+
26
+ def ruby_command_line(pre_requires, file)
27
+ req_cmd = "-I#{SITE_RUBY} -I#{SITE_RUBY}/i686-linux/"
28
+ if pre_requires
29
+ req_cmd << " -r #{pre_requires}"
30
+ end
31
+ "#{MULTIRUBY} #{req_cmd} #{file}"
32
+ end
33
+
34
+ def parse_test_results(data, file)
35
+ version_reg = /([0-9\.]*)#{RES_REGEXP}/im
36
+ version_array = data.split('VERSION = ').map{|s| s.match(version_reg)}.compact
37
+ if version_array.empty?
38
+ [no_match_result(data, file)]
39
+ else
40
+ version_array.map do |m|
41
+ {
42
+ :file_name => file,
43
+ :version_name => m[1],
44
+ :time => m[3],
45
+ :tests => m[5],
46
+ :assertions => m[6],
47
+ :failures => template_error_num(m[7]),
48
+ :errors => template_error_num(m[8]),
49
+ :test_results => parse_out_test_info(m[4]).map {|i| {:test_info => i}}
50
+ }
51
+ end
52
+ end
53
+ end
54
+
55
+ alias :old_time_out_readlines :time_out_readlines
56
+
57
+ def time_out_readlines(io)
58
+ # We give it a minute and a half now
59
+ old_time_out_readlines(io, 90)
60
+ end
61
+
62
+ end
@@ -0,0 +1,85 @@
1
+ # $Id: outside_links.rb 34 2008-04-03 06:01:19Z zev $
2
+ #
3
+ # This file implements an action that creates links to reports
4
+ # not generated by the project builder
5
+
6
+ # Copyright (c) 2006, Ubiquitous Business Technology (http://ubit.com)
7
+ # All rights reserved.
8
+ #
9
+ # Redistribution and use in source and binary forms, with or without
10
+ # modification, are permitted provided that the following conditions are
11
+ # met:
12
+ #
13
+ #
14
+ # * Redistributions of source code must retain the above copyright
15
+ # notice, this list of conditions and the following disclaimer.
16
+ #
17
+ # * Redistributions in binary form must reproduce the above
18
+ # copyright notice, this list of conditions and the following
19
+ # disclaimer in the documentation and/or other materials provided
20
+ # with the distribution.
21
+ #
22
+ # * Neither the name of Ubit nor the names of its
23
+ # contributors may be used to endorse or promote products derived
24
+ # from this software without specific prior written permission.
25
+ #
26
+ #
27
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+ #
39
+ # == Author
40
+ # Zev Blut (zb@ubit.com)
41
+
42
+ class OutsideLinksAction < BuildAction
43
+ BASE_URL = ""
44
+
45
+ def initialize
46
+ no_http = Proc.new do |link, project_name|
47
+ "#{BASE_URL}/#{link}/#{project_name}/"
48
+ end
49
+ http = Proc.new do |link, project_name|
50
+ "#{link}/#{project_name}/"
51
+ end
52
+
53
+ @links = [
54
+ ["rdocs", "Documentation", no_http],
55
+ ["statcvs_output", "StatCVS", no_http],
56
+ ["http://localhost/viewvc", "ViewVC", http],
57
+ ["http://localhost/pragprog/data/rss", "CVS Commit RSS",
58
+ Proc.new { |link, project_name| "#{link}/#{project_name}.rss" }
59
+ ]
60
+ ]
61
+ end
62
+
63
+ def build_action(context)
64
+ #Do nothing
65
+ end
66
+
67
+ def summary_display(context)
68
+ template = TemplateFile.new(self.class.summary_template_file)
69
+ context.amrita_data[:outside_links] = @links.map do |link, lname, action|
70
+ {
71
+ :item => Amrita::e(:h3) do
72
+ url = action.call(link, context.project_name)
73
+ Amrita::e(:a, :href => url) { lname }
74
+ end
75
+ }
76
+ end
77
+
78
+ summary_expand(template, context)
79
+ end
80
+
81
+ def self.detailed_template_file
82
+ nil
83
+ end
84
+
85
+ end