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,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
+