kwala 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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,88 @@
1
+ class BuildAction
2
+ extend InheritanceTracker
3
+
4
+ def build_action(context)
5
+ raise "Must be implemented by subclass."
6
+ end
7
+
8
+ def summary_display(context)
9
+ raise "Must be implemented by subclass."
10
+ end
11
+
12
+ def detailed_display(context)
13
+ [nil, nil]
14
+ end
15
+
16
+ # a number from 1 - 10 that represents some type of rating that will help
17
+ # determine if the koala's expression.
18
+ # For now if nil, then not scorable. This might be better as an additional method
19
+ # called scoreable?
20
+ # Default is unscoreable
21
+ def score
22
+ nil
23
+ end
24
+
25
+ def self.command_line_action_name
26
+ self.name.gsub(/BuildAction|Action/, '').kwala_underscore
27
+ end
28
+
29
+ def self.summary_template_file
30
+ "#{TEMPLATE_DIR}/#{command_line_action_name}_summary.html"
31
+ end
32
+
33
+ def self.detailed_template_file
34
+ "#{TEMPLATE_DIR}/#{command_line_action_name}_detailed.html"
35
+ end
36
+
37
+ def BuildAction.create_action_from_command_line_name(name)
38
+ imp = BuildAction.get_implementors.find do |imp|
39
+ imp.command_line_action_name == name
40
+ end
41
+
42
+ if imp
43
+ imp.new
44
+ else
45
+ nil
46
+ end
47
+ end
48
+
49
+ def BuildAction.command_line_action_names
50
+ BuildAction.get_implementors.map do |imp|
51
+ imp.command_line_action_name
52
+ end.sort
53
+ end
54
+
55
+
56
+ protected
57
+
58
+ def time_out_readlines(io, timeout_period = 300)
59
+ res = nil
60
+ begin
61
+ timeout(timeout_period) do
62
+ res = io.readlines
63
+ end
64
+ rescue TimeoutError => err
65
+ res = ["IO readlines timed out."]
66
+ end
67
+ res
68
+ end
69
+
70
+ def summary_expand(template, context)
71
+ res = ProjectBuilderUtils.expand_template(template, context.amrita_data)
72
+ Amrita::SanitizedString.new(res)
73
+ end
74
+
75
+ # These are bad param names, need to make them better
76
+ # Extremely naive function to put the errors to some scale around 10.
77
+ def score_scaling(errors, bounds)
78
+ 10 - ((errors.to_f / bounds.to_f) * 10).ceil
79
+ end
80
+
81
+ def notify(result, data = nil)
82
+ Notification.notify(self.class.command_line_action_name, result, data)
83
+ end
84
+
85
+ end
86
+
87
+ class KwalaAbortBuildException < StandardError
88
+ end
@@ -0,0 +1,49 @@
1
+ class BuildContext
2
+ attr_accessor :project_directory, :ruby_files, :test_files
3
+ attr_accessor :amrita_data, :ri_dir_1, :ri_dir_2, :pre_requires, :require
4
+ attr_accessor :output_directory
5
+ attr_accessor :vcs_num
6
+ attr_reader :project_name
7
+
8
+ def initialize
9
+ @project_directory = @project_name = @ri_dir_1 = @ri_dir_2 =
10
+ @output_directory = @require = nil
11
+ @ruby_files = Array.new
12
+ @test_files = Array.new
13
+ @pre_requires = nil
14
+ self.initialize_build_data
15
+ end
16
+
17
+
18
+ def initialize_build_data()
19
+ @amrita_data = Hash.new
20
+ @amrita_data[:project_name] = self.project_name
21
+ @amrita_data[:build_date] = Time.now
22
+ end
23
+
24
+ def project_name=(project_name)
25
+ @project_name = project_name
26
+ @amrita_data[:project_name] = @project_name
27
+ end
28
+
29
+ def build_complete
30
+ @amrita_data[:complete_time] = Time.now
31
+ @amrita_data[:total_build_time] = ( (@amrita_data[:complete_time] - @amrita_data[:build_date]) / 60.0 )
32
+ end
33
+
34
+ def vcs_info
35
+ if @vcs_info.nil?
36
+ # For now svn
37
+ cur = Dir.pwd
38
+ begin
39
+ Dir.chdir(self.project_directory)
40
+ @vcs_info = `svn log -r #{self.vcs_num} --verbose`
41
+ ensure
42
+ Dir.chdir(cur)
43
+ end
44
+ end
45
+
46
+ @vcs_info
47
+ end
48
+
49
+ end
@@ -0,0 +1,184 @@
1
+ #! /usr/bin/ruby1.8
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
+ # Zev Blut (zb@ubit.com)
38
+
39
+ puts "Starting #{Time.now}"
40
+
41
+ require 'getoptlong'
42
+ require 'kwala'
43
+
44
+ def usage
45
+ puts "Usage: #{$0} [-d -p -a -y] -o"
46
+ puts " --project_name, -p (project name): The name of the project"
47
+ puts " --directory, -d (project base directory): The base directory of the project."
48
+ puts " if not set then current directory is assumed."
49
+ puts " --action, -a (action name): The name of an action to execute."
50
+ puts " if no action is set then all actions are assumed."
51
+ puts " --yaml, -y (yaml debug file name): The name of a file to save yaml debug information to."
52
+ puts " --output_dir, -o (output directory): The directory to output results to."
53
+ puts " --require,-r (file to require): A file to require for use when testing unit tests."
54
+ puts " --ri_cmp_1,-1 (old ri directory): The old ri output directory to compare against."
55
+ puts " --ri_cmp_2,-2 (new ri directory): The new ri output directory to compare against."
56
+ puts " --list_actions, -l: Lists the action names available."
57
+ puts " --notifications, -n: Activates notifications for action results."
58
+ puts " --vcs_num, -v: A string with the vcs version numbers to check"
59
+ exit 1
60
+ end
61
+
62
+ def list_actions
63
+ puts "Available actions are: "
64
+ BuildAction.command_line_action_names.each do |name|
65
+ puts " #{ name }"
66
+ end
67
+ end
68
+
69
+ ProjectBuilderUtils.load_actions
70
+
71
+ actions = Array.new
72
+ context = BuildContext.new
73
+
74
+ context.project_directory = Dir.pwd
75
+ context.project_name = ""
76
+ debug_results = nil
77
+ begin
78
+ options = GetoptLong.new(
79
+ ["-p", "--project_name", GetoptLong::REQUIRED_ARGUMENT],
80
+ ["-d", "--dir", GetoptLong::REQUIRED_ARGUMENT],
81
+ ["-a", "--action", GetoptLong::REQUIRED_ARGUMENT],
82
+ ["-y", "--yaml", GetoptLong::REQUIRED_ARGUMENT],
83
+ ["-r", "--require", GetoptLong::REQUIRED_ARGUMENT],
84
+ ["-1", "--ri_cmp_1", GetoptLong::REQUIRED_ARGUMENT],
85
+ ["-2", "--ri_cmp_2", GetoptLong::REQUIRED_ARGUMENT],
86
+ ["-e", "--extensions", GetoptLong::REQUIRED_ARGUMENT],
87
+ ["-o", "--output_dir", GetoptLong::REQUIRED_ARGUMENT],
88
+ ["-l", "--list_actions", GetoptLong::NO_ARGUMENT],
89
+ ["-n", "--notifications", GetoptLong::NO_ARGUMENT],
90
+ ["-v", "--vcs_num", GetoptLong::REQUIRED_ARGUMENT] )
91
+
92
+ options.each do |opt, arg|
93
+ case opt
94
+ when "-p"
95
+ context.project_name = arg
96
+ when "-a"
97
+ actions << arg
98
+ when "-d"
99
+ context.project_directory = arg
100
+ when "-y"
101
+ debug_results = arg
102
+ when "-o"
103
+ context.output_directory = arg
104
+ when "-r"
105
+ context.pre_requires = arg
106
+ when "-1"
107
+ context.ri_dir_1 = ProjectBuilderUtils.clean_trailing_slash(arg)
108
+ when "-2"
109
+ context.ri_dir_2 = ProjectBuilderUtils.clean_trailing_slash(arg)
110
+ when "-e"
111
+ require arg
112
+ # required file should define get extensions
113
+ extensions = get_extensions
114
+ when "-l"
115
+ list_actions
116
+ exit 0
117
+ when "-n"
118
+ Notification.context = context
119
+ Notification.notifications
120
+ Notification.add_notification(EmailNotification.new)
121
+ Notification.add_notification(JabberNotification.new)
122
+ when "-v"
123
+ context.vcs_num = arg
124
+ else
125
+ STDERR.puts "Unknown argument #{opt}"
126
+ usage
127
+ end
128
+ end
129
+ rescue => err
130
+ STDERR.puts "Error: #{err}", err.backtrace
131
+ usage
132
+ end
133
+
134
+ usage if context.output_directory.nil?
135
+
136
+ # Set default actions if not passed
137
+ if actions.empty?
138
+ actions = %w(loc_count syntax_check unit_test flagged_comments)
139
+ end
140
+
141
+ if actions.include?("code_change")
142
+ usage if (context.ri_dir_1 && !context.ri_dir_2) || (!context.ri_dir_1 && context.ri_dir_2)
143
+ end
144
+
145
+ # Set up necessary info before actions
146
+
147
+ ruby_files, test_files = ProjectBuilderUtils.get_ruby_and_unit_test_files(context.project_directory)
148
+ context.ruby_files = ruby_files
149
+ context.test_files = test_files
150
+
151
+ action_instances = Array.new
152
+ actions.each do |action|
153
+ action_obj = BuildAction.create_action_from_command_line_name(action)
154
+ if action_obj
155
+ action_instances << action_obj
156
+ else
157
+ STDERR.puts "Unknown action : #{action}"
158
+ list_actions
159
+ end
160
+ end
161
+
162
+ # Run the build stage
163
+ action_instances = ProjectBuilderUtils.build_actions(action_instances, context)
164
+
165
+ # Run the summary stage
166
+ summaries = ProjectBuilderUtils.build_summaries(action_instances, context)
167
+
168
+ if debug_results
169
+ #Check the syncro of templates
170
+ File.open(debug_results, "w") do |f|
171
+ f.puts context.amrita_data.to_yaml
172
+ end
173
+ end
174
+
175
+ ProjectBuilderUtils.make_detailed_reports(context, action_instances)
176
+
177
+ ProjectBuilderUtils.make_summary_report(context, summaries)
178
+
179
+
180
+ # Copy the stylesheet to the output directory
181
+ FileUtils.cp(TEMPLATE_DIR + "style.css", context.output_directory)
182
+ FileUtils.cp_r(TEMPLATE_DIR + "mdd", context.output_directory)
183
+
184
+ puts "Done #{Time.now}"
Binary file
Binary file
Binary file
@@ -0,0 +1,36 @@
1
+ # Provides a method that gives instances of all of the includer's subclasses.
2
+ module InheritanceTracker
3
+
4
+ # Returns all subclasses of calling Class.
5
+ def get_implementors
6
+ imps = []
7
+
8
+ ObjectSpace.each_object(Class) do |klass|
9
+ if klass.ancestors.include?(self)
10
+ imps << klass
11
+ end
12
+ end
13
+
14
+ # remove self from the list
15
+ imps - [self]
16
+ end
17
+
18
+ end
19
+
20
+ class DeepCopy
21
+ extend Marshal
22
+
23
+ def DeepCopy.deep_copy(obj)
24
+ load(dump(obj))
25
+ end
26
+
27
+ end
28
+
29
+ class String
30
+
31
+ # Very Simple underscore implementation.
32
+ def kwala_underscore
33
+ self.gsub(/([A-Z])/, '_\1').downcase.gsub(/^_/, '')
34
+ end
35
+
36
+ end
@@ -0,0 +1,678 @@
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
+ # Zev Blut (zb@ubit.com)
38
+
39
+ require 'irb/ruby-lex'
40
+ require 'pp'
41
+
42
+ # Add indent to token
43
+ module RubyToken
44
+ class Token
45
+ attr_accessor :indent
46
+ end
47
+ end
48
+
49
+ class FileAnalyzer
50
+ include RubyToken
51
+
52
+ attr_reader :file , :requires , :classes , :modules , :constants ,
53
+ :methods , :g_vars , :i_vars , :c_vars ,
54
+ :symbols , :blocks , :yields , :raises , :rescues , :cases , :aliases ,
55
+ :undefs , :ensures , :evals , :big_begins , :big_ends , :exits , :defines ,
56
+ :retries , :redoes , :nexts , :breaks , :ensures , :assignments ,
57
+ :is_a_kind_ofs , :method_missing , :static_methods
58
+
59
+ def initialize(file)
60
+ @file = file
61
+ @requires = []
62
+ @classes = []
63
+ @modules = []
64
+ @constants = []
65
+ @methods = []
66
+ @g_vars = []
67
+ @i_vars = []
68
+ @c_vars = []
69
+ @symbols = []
70
+ @blocks = 0
71
+ @yields = 0
72
+ @raises = 0
73
+ @rescues = 0
74
+ @cases = 0
75
+ @aliases = []
76
+ @undefs = []
77
+ @ensures = 0
78
+ @evals = 0
79
+ @big_begins = 0
80
+ @big_ends = 0
81
+ @exits = 0
82
+ @defines = 0
83
+ @retries = 0
84
+ @redoes = 0
85
+ @nexts = 0
86
+ @breaks = 0
87
+ @ensures = 0
88
+ @assignments = 0
89
+ @is_a_kind_ofs = 0
90
+ @method_missing = 0
91
+ @static_methods = []
92
+ @tl_ref = Hash.new
93
+ end
94
+
95
+ def analyze
96
+ #puts "Lexing File: #{ @file }"
97
+
98
+ lexer = RubyLex.new
99
+ lexer.exception_on_syntax_error = false
100
+ lexer.set_input(File.new(@file,"r"))
101
+
102
+ tokens = Array.new
103
+ while (tok = lexer.token)
104
+ tok.indent = lexer.indent
105
+ tokens<< tok
106
+ @tl_ref[tok.object_id] = lexer.line_no
107
+ end
108
+
109
+ i = 0
110
+ while i < tokens.size
111
+ ni = check_token(tokens[i], tokens[i..-1])
112
+ i += ni
113
+ end
114
+
115
+
116
+ # types, rem = BaseType.create_types_from_tokens(tokens)
117
+
118
+
119
+ # puts "TYPES ----------------"
120
+ # pp types
121
+ # exit 1
122
+
123
+ self
124
+ rescue RubyLex::SyntaxError => synerr
125
+ $stderr.puts synerr
126
+ ensure
127
+ self
128
+ end
129
+
130
+ def summarize
131
+ puts "-- Summary for #{ @file } --",""
132
+ puts "Requires".ljust(15) + ": " + @requires.size.to_s
133
+ puts "Modules".ljust(15) + ": " + @modules.size.to_s
134
+ puts "Classes".ljust(15) + ": " + @classes.size.to_s
135
+ puts "Constants".ljust(15) + ": " + @constants.size.to_s
136
+ puts "Methods".ljust(15) + ": " + @methods.size.to_s
137
+ puts "Static Methods".ljust(15) + ": " + @static_methods.size.to_s
138
+ puts "G Vars".ljust(15) + ": " + @g_vars.size.to_s
139
+ puts "C Vars".ljust(15) + ": " + @c_vars.size.to_s
140
+ puts "I Vars".ljust(15) + ": " + @i_vars.size.to_s
141
+ puts "Symbols".ljust(15) + ": " + @symbols.size.to_s
142
+ puts "Blocks".ljust(15) + ": " + @blocks.to_s
143
+ puts "Yields".ljust(15) + ": " + @yields.to_s
144
+ puts "Raises".ljust(15) + ": " + @raises.to_s
145
+ puts "Rescues".ljust(15) + ": " + @rescues.to_s
146
+ puts "Retries".ljust(15) + ": " + @retries.to_s
147
+ puts "Redoes".ljust(15) + ": " + @redoes.to_s
148
+ puts "Ensures".ljust(15) + ": " + @ensures.to_s
149
+ puts "Undefs".ljust(15) + ": " + @undefs.size.to_s
150
+ puts "Evals".ljust(15) + ": " + @evals.to_s
151
+ puts "Aliases".ljust(15) + ": " + @aliases.size.to_s
152
+ puts "BEGINs".ljust(15) + ": " + @big_begins.to_s
153
+ puts "ENDs".ljust(15) + ": " + @big_ends.to_s
154
+ puts "Exits".ljust(15) + ": " + @exits.to_s
155
+ puts "Defined?".ljust(15) + ": " + @defines.to_s
156
+ puts "Is_a? ".ljust(15) + ": " + @is_a_kind_ofs.to_s
157
+ puts "M missing".ljust(15) + ": " + @method_missing.to_s
158
+ puts "Nexts".ljust(15) + ": " + @nexts.to_s
159
+ puts "Breaks".ljust(15) + ": " + @breaks.to_s
160
+ puts "Cases".ljust(15) + ": " + @cases.to_s
161
+ puts "Assignments".ljust(15) + ": " + @assignments.to_s
162
+ puts "","",""
163
+
164
+ if @constants.size > 0
165
+ puts "","",""
166
+ puts "Constants ->", @constants.sort.uniq
167
+ end
168
+
169
+ if @static_methods.size > 0
170
+ puts "","",""
171
+ puts "Static methods ->", @static_methods.sort.uniq
172
+ end
173
+
174
+
175
+ puts "","----------------------------"
176
+ end
177
+
178
+ def check_token(tok, stack)
179
+ i = 1
180
+ case tok
181
+ when TkCLASS
182
+ name, i = find_identifier_name(stack)
183
+ @classes<< name
184
+ when TkMODULE
185
+ name, i = find_identifier_name(stack)
186
+ @modules<< name
187
+ when TkCONSTANT
188
+ #@contants<< tok.name
189
+ i = find_static_method_call(tok, stack)
190
+ when TkDEF
191
+ name, i = find_identifier_name(stack)
192
+ @methods<< name
193
+ when TkIDENTIFIER, TkFID
194
+ #@
195
+ case tok.name
196
+ when "require"
197
+ @requires<< nil
198
+ when "raise"
199
+ @raises += 1
200
+ when "eval", "instance_eval", "class_eval", "module_eval"
201
+ @evals += 1
202
+ when "is_a?", "kind_of?"
203
+ @is_a_kind_ofs += 1
204
+ when "method_missing"
205
+ @method_missing += 1
206
+ when /^exit!?$/
207
+ @exits += 1
208
+ end
209
+
210
+ when TkSYMBEG
211
+ name, i = find_identifier_name(stack)
212
+ @symbols<< name
213
+ when TkCVAR
214
+ @c_vars<< tok.name
215
+ when TkIVAR
216
+ @i_vars<< tok.name
217
+ when TkGVAR
218
+ @g_vars << tok.name
219
+ when TkDO,TkfLBRACE
220
+ # do
221
+ @blocks += 1
222
+ when TkYIELD
223
+ @yields += 1
224
+ when TkEND
225
+ when TkRESCUE
226
+ @rescues += 1
227
+ when TklBEGIN
228
+ @big_begins += 1
229
+ when TklEND
230
+ @big_ends += 1
231
+ when TkDEFINED
232
+ @defines += 1
233
+ when TkRETRY
234
+ @retries += 1
235
+ when TkREDO
236
+ @redoes += 1
237
+ when TkNEXT
238
+ @nexts += 1
239
+ when TkBREAK
240
+ @breaks += 1
241
+ when TkENSURE
242
+ @ensures += 1
243
+ when TkUNDEF
244
+ @undefs << nil
245
+ when TkCASE
246
+ @cases += 1
247
+ when TkALIAS
248
+ name, i = find_identifier_name(stack)
249
+ @aliases << name
250
+ when TkASSIGN, TkOPASGN
251
+ @assignments += 1
252
+ else
253
+
254
+ end
255
+ i
256
+ end
257
+
258
+ def find_identifier_name(stream, check_types = [TkIDENTIFIER, TkFID, TkCONSTANT])
259
+ i = 1
260
+ name = ""
261
+ found = false
262
+
263
+ while !found && stream[i]
264
+ tok = stream[i]
265
+ if check_types.include?(tok.class)
266
+ found = true
267
+ name = tok.name
268
+ else
269
+ i += 1
270
+ end
271
+ end
272
+
273
+ [name, i]
274
+ end
275
+
276
+ def find_static_method_call(tok, stream)
277
+ sname = tok.name.dup
278
+ searching = true
279
+ i = 1
280
+ while searching
281
+ val = stream[i]
282
+ case val
283
+ when TkDOT
284
+ sname<< "."
285
+ when TkCOLON2
286
+ sname<< "::"
287
+ when TkCONSTANT, TkIDENTIFIER, TkFID
288
+ sname << val.name
289
+ when TkNL, TkSPACE
290
+ searching = false
291
+ when TkLPAREN
292
+ sname<< "()"
293
+ searching = false
294
+ when TkfLBRACK
295
+ sname<< "[]"
296
+ searching = false
297
+ when TkfLBRACE
298
+ searching = false
299
+ when TkRPAREN, TkRBRACK, TkRBRACE, TkDOT2, TkDOT3, TkBITOR, TkBITAND,
300
+ TkCOMMA, TkSEMICOLON, TkPLUS, TkMINUS, TkMULT, TkDIV
301
+ # close of a method call
302
+ searching = false
303
+ when TkASSIGN, TkMOD
304
+ # could be class = ? most likely a constant
305
+ searching = false
306
+ when TkDSTRING, TkSTRING
307
+ # ignore the constant as it is wrapped in a string
308
+ searching = false
309
+ else
310
+ puts "NEW CASE #{ val.class } for #{self.file}"
311
+ puts "Line: #{ @tl_ref[val.object_id] }"
312
+ searching = false
313
+ end
314
+ i += 1
315
+ end
316
+ if tok.name != sname
317
+ # Make sure we don't get things like Array[] or Array()
318
+ if /:/ =~ sname && /^[A-Z]/ =~ sname.split(".").last.split(":").last
319
+ @constants << sname
320
+ else
321
+ @static_methods<< sname
322
+ end
323
+ else
324
+ @constants << sname
325
+ #puts "JUST A CONST :#{ sname } ----"
326
+ #puts "Line: #{ @tl_ref[tok.object_id] }"
327
+ end
328
+ i
329
+ end
330
+
331
+ def group_methods(tokens)
332
+ # meth_groups = []
333
+ #
334
+ # cur_group_toks = []
335
+ # indent = 0
336
+ # indef = false
337
+ #
338
+ # tokens.each do |tok|
339
+ # cur_group_toks<< tok
340
+ #
341
+ # if tok.class == TkDEF
342
+ # indent = tok.indent
343
+ # indef = true
344
+ # cur_group_toks = [ tok ]
345
+ # elsif tok.class == TkEND
346
+ # if indef && (tok.indent == (indent - 1))
347
+ # indef = false
348
+ # indent = tok.indent
349
+ #
350
+ # meth_groups<< cur_group_toks
351
+ # cur_group_toks = []
352
+ # end
353
+ # end
354
+ # end
355
+ #
356
+ # puts "Meth group #{ meth_groups.size }"
357
+ # pp meth_groups
358
+ # exit
359
+ #
360
+ groups, idx = group_tokens(tokens, [TkDEF])
361
+ puts "Meth groups cnt #{ groups.size }"
362
+ pp groups
363
+ exit
364
+ end
365
+
366
+
367
+
368
+ end
369
+
370
+
371
+
372
+
373
+ class FileAnalyzerGroup
374
+ def initialize(files)
375
+ @fa_files = []
376
+ files.each do |file|
377
+ @fa_files<< FileAnalyzer.new(file).analyze
378
+ end
379
+ end
380
+
381
+ def summarize
382
+ fst = @fa_files.first
383
+ vals = fst.instance_variables.map { |iv| iv[1..-1] }
384
+ # remove the instance variables we set that are not related to data tracking.
385
+ vals = vals - ["file", "tl_ref"]
386
+ t_v = vals.map do |v|
387
+ res = fst.send(v)
388
+
389
+ if res.is_a?(Array)
390
+ p = Proc.new { |v| v.size }
391
+ else
392
+ p = Proc.new { |v| v.to_i }
393
+ end
394
+ [v, p]
395
+ end
396
+
397
+ res = Hash.new { |h,k| h[k] = [] }
398
+ @fa_files.each do |f|
399
+ t_v.each do |method, calc|
400
+ res[method]<< calc.call( f.send(method) )
401
+ end
402
+ end
403
+
404
+
405
+ base_sum = Hash.new(0)
406
+
407
+ res.sort.each do |key, val|
408
+ r = val.inject(0) { |t, v| t + v }
409
+ base_sum[key] = r
410
+ end
411
+
412
+ print_summary_header(base_sum)
413
+
414
+ extra_summary( [ ["static_methods", "static methods"],
415
+ ["g_vars", "global variables"],
416
+ ["c_vars", "class variables"],
417
+ ["i_vars", "instance variables"],
418
+ ["constants", "constants"],
419
+ ["methods", "methods"],
420
+ ["classes", "classes"],
421
+ ["modules", "modules"],
422
+ ["symbols", "symbols"],
423
+ ["aliases", "aliases"],
424
+ ["undefs", "undefs"]
425
+ ] )
426
+
427
+ end
428
+
429
+ def print_summary_header(summary)
430
+ puts "Summary"
431
+ summary.sort.each do |key, res|
432
+ puts "#{ key.ljust(15) } : #{res}"
433
+ end
434
+ end
435
+
436
+ def extra_summary(methods)
437
+ ttl_files = @fa_files.size
438
+
439
+ methods.each do |mcall, mname|
440
+ cnt = @fa_files.inject(0) { |cnt, f| cnt += (f.send(mcall).size > 0 ? 1 : 0) }
441
+ if cnt == 0
442
+ next
443
+ end
444
+ msg = "#{ cnt } out of #{ ttl_files }" +
445
+ " (#{sprintf("%.2f",(cnt.to_f / ttl_files) * 100)} %) use #{ mname}"
446
+
447
+
448
+ uniqs = Hash.new { |h,k| h[k] = [] }
449
+ @fa_files.each do |f|
450
+ f.send(mcall).uniq.each do |sm|
451
+ uniqs[sm] << f.file
452
+ end
453
+ end
454
+
455
+ print_extra_summary(mname, msg, uniqs)
456
+ end
457
+ end
458
+
459
+ def print_extra_summary(name, msg, uniqs)
460
+ puts "" , "---" + msg + "---"
461
+ puts "#{name} used by files:", ""
462
+ uniqs.sort.each do |k, m|
463
+ puts k.to_s.ljust(30) + " -> ", " " + m.join("\n "), ""
464
+ end
465
+ end
466
+
467
+ end
468
+
469
+ class BaseType
470
+ include RubyToken
471
+
472
+ attr_accessor :name, :start_line, :end_line, :children, :tokens
473
+
474
+ def initialize(tokens = Array.new)
475
+ @children = Array.new
476
+
477
+ # These may be computed by tokens?
478
+ @start_line = 0
479
+ @end_line = 0
480
+ @tokens = tokens
481
+ @rem_tokens = []
482
+
483
+ create_child_groupings
484
+ analyse_self
485
+ end
486
+
487
+ def sub_types
488
+ @children.each { |c| yield c }
489
+ end
490
+
491
+
492
+ def self.create_types_from_tokens(tokens)
493
+ groups, idx = group_tokens(tokens, [TkCLASS, TkMODULE, TkDEF])
494
+
495
+
496
+ remainder_tokens = []
497
+ types = []
498
+
499
+ groups.each do |sub_tokens|
500
+
501
+ case sub_tokens.first
502
+ when TkCLASS
503
+ types<< ClassType.new(sub_tokens)
504
+ when TkMODULE
505
+ types<< ModuleType.new(sub_tokens)
506
+ when TkDEF
507
+ types<< DefType.new(sub_tokens)
508
+ else
509
+ remainder_tokens<< sub_tokens
510
+ end
511
+
512
+ end
513
+
514
+ [types, remainder_tokens]
515
+ end
516
+
517
+ private
518
+ def self.group_tokens(tokens, trigger, indent_target = nil, idx = 0)
519
+ groups = []
520
+
521
+ while idx < tokens.size
522
+ tok = tokens[idx]
523
+ idx += 1
524
+
525
+ if trigger.include?(tok.class)
526
+ indent = tok.indent
527
+
528
+ if indent_target.nil?
529
+ indent_target = indent - 1
530
+ end
531
+
532
+
533
+ sub_group, idx = collect_into_group(tokens, indent - 1, idx)
534
+
535
+ groups << [tok] + sub_group
536
+ end
537
+
538
+ end
539
+
540
+ [groups, idx]
541
+ end
542
+
543
+
544
+ def self.collect_into_group(tokens, indent_target, idx)
545
+ group = []
546
+
547
+ while idx < tokens.size
548
+ tok = tokens[idx]
549
+ idx += 1
550
+
551
+ group << tok
552
+ if tok.class == TkEND
553
+ if tok.indent == indent_target
554
+ return [group, idx]
555
+ elsif tok.indent < indent_target
556
+ puts "ERROR INDENT lower than TARGET"
557
+ return [group, idx]
558
+ else
559
+ # puts "NO CARE"
560
+ end
561
+ end
562
+ end
563
+
564
+ puts "ERROR FELL out while looking for END!"
565
+ return [group, idx]
566
+ end
567
+
568
+
569
+ def create_child_groupings
570
+ # ignore first so that we do not get stuck on self and last end
571
+ @children, rem = BaseType.create_types_from_tokens(@tokens[1..-2])
572
+ @rem_tokens = rem
573
+ end
574
+
575
+ def analyse_self
576
+ end
577
+
578
+ end
579
+
580
+
581
+ class ClassType < BaseType
582
+ attr_accessor :super_class, :includes
583
+
584
+ def analyse_self
585
+
586
+
587
+ @name = find_c_m_name
588
+ @super_class = find_super_class
589
+ @includes = find_includes
590
+ end
591
+
592
+ def find_c_m_name
593
+ ""
594
+ end
595
+
596
+ def find_super_class
597
+ nil
598
+ end
599
+
600
+ def find_includes
601
+ []
602
+ end
603
+ end
604
+
605
+ class ModuleType < BaseType
606
+ attr_accessor :includes
607
+ end
608
+
609
+ class DefType < BaseType
610
+ attr_accessor :arguments, :raises
611
+
612
+ def analyse_self
613
+ find_name
614
+ find_arguments
615
+ find_raises
616
+ end
617
+
618
+ def find_name
619
+ end
620
+
621
+ def find_arguments
622
+ end
623
+
624
+ def find_raises
625
+ end
626
+
627
+ end
628
+
629
+ if __FILE__ == $0
630
+ FileAnalyzerGroup.new(ARGV).summarize
631
+ end
632
+
633
+ __END__
634
+
635
+ # LOOK for initialize calls with calling super
636
+ #
637
+ # More things to look for
638
+ # Misspelled local/variables
639
+ # No method comments
640
+ # Unused globals and locals (module or variable)
641
+ # Unused function/method arguments (can ignore self)
642
+
643
+ #LOOK for EMPTY RESCUE blocks.
644
+ # I.e. a rescue that just catches the exception and does nothing.
645
+
646
+ # Count # of parameters per method (over 7 warn!)
647
+ # check for similar sequence of parameters in methods
648
+ # Warn if inconsistent?
649
+ # i.e. foo(a, b, c) and moo(c, a , b)
650
+
651
+ # Unused method parameters! unless it is a overriding subclass?
652
+
653
+ # Have view to show all those who subclass/include/extend a class or module (Fan in)
654
+ # Also view for normal fan out?
655
+
656
+ # Check block parameters!
657
+
658
+ # Variable bad things (smalltalk best practice p.103)
659
+ # long extent (how long its value lasts )
660
+ # wide scope (how long it can be accessed)
661
+ # large type (How many messages it can respond to)
662
+
663
+
664
+
665
+ # Method call
666
+ Line: 63 17 Token #<RubyToken::TkIDENTIFIER:0xb7d33790>: interface
667
+ Line: 63 18 Token #<RubyToken::TkDOT:0xb7d335d8>
668
+ Line: 63 31 Token #<RubyToken::TkIDENTIFIER:0xb7d33024>: billing_steps
669
+
670
+ Line: 64 13 Token #<RubyToken::TkFID:0xb7d30504>: nil?
671
+
672
+ Line: 68 2513 Token #<RubyToken::TkNIL:0xb7d27c24>: nil
673
+
674
+
675
+ Line: 78 12 Token #<RubyToken::TkCONSTANT:0xb7d1b7bc>: Registry
676
+ Line: 78 13 Token #<RubyToken::TkDOT:0xb7d1b5dc>
677
+ Line: 78 26 Token #<RubyToken::TkIDENTIFIER:0xb7d1b104>: each_behavior
678
+