code_web 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 348410cac5f689ca660f4ee7da0d02186f648d2f
4
+ data.tar.gz: 9e4579dc7394db036a31bea93ebf50573daf6200
5
+ SHA512:
6
+ metadata.gz: 5008e3f84eaede9442dcda953b4e341132ffd0d70f02b74a406583d8b89c45870cd8c62a34eb5d4e9e8052189b3d00a4be9d4c23c6537890ee909d6917a68a81
7
+ data.tar.gz: d42b1b115616040c5a772cb5e60733e99626c8fe6cd205e7955dcde0cbee94f4d38e325f65a4d52d6ea07a18de452e63df515afefb294c4291416fc1243ebc2a
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in code_web.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # CodeWeb
2
+
3
+ This uses ruby parser to read code and find references.
4
+ Works best when looking for finding static methods that possibly span multiple lines
5
+
6
+ It generates an html file with the list of each method and the invocations.
7
+ Each reference has a url to the place in code where it is found.
8
+
9
+ The urls use textmate url format, which also works with sublime.
10
+ I use lincastor on my machine to wire the urls to the actual editor, so any program
11
+ will do.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'code_web'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install code_web
26
+
27
+ ## Usage
28
+
29
+ # if you find the reference in a file named miq_queue.rb, then color the url with #999
30
+ # if you find the reference in a file with the director tools, then color the url with #ccc
31
+ # look for the class name MiqQueue
32
+ # look in the directories app, tools, and lib
33
+ # output the report to miq_queue.html (in html format)
34
+
35
+ $ code_web -p 'miq_queue.rb$=#999' -p 'tools/=#ccc' 'MiqQueue\b' app tools lib -o miq_queue.html
36
+
37
+ ## Url handline
38
+
39
+ Currently, this generates urls using the textmate file protocol.
40
+
41
+ I use [LinCastor] to map the url to my editor of choice, sublime.
42
+ But there are many tools that will do this for you.
43
+ LinCastor has worked for many many years, including my current version, 10.12.3
44
+
45
+ I use the following script to wire it together:
46
+
47
+ [LinCastor]: https://onflapp.wordpress.com/lincastor/
48
+
49
+ ```ruby
50
+ #!/usr/bin/ruby
51
+
52
+ # Parse a url according to
53
+ # http://blog.macromates.com/2007/the-textmate-url-scheme/
54
+ # opens the file
55
+
56
+ SUBL_PATH="/Applications/Sublime Text.app"
57
+ SUBL_BIN_PATH="#{SUBL_PATH}/Contents/SharedSupport/bin/subl"
58
+
59
+ #require 'logger'
60
+ require 'uri'
61
+ require 'cgi'
62
+
63
+ #DEBUG = Logger.new(File.open("#{ENV['HOME']}/sublime_cmd.txt", File::WRONLY | File::APPEND|File::CREAT))
64
+
65
+ subl_url=ENV['URL']
66
+ p=CGI.parse(URI.parse(subl_url).query)
67
+ subl_file="#{p["url"].first[7..-1]}:#{p["line"].first}"
68
+
69
+ #DEBUG.info(subl_file)
70
+
71
+ ret=`"#{SUBL_BIN_PATH}" "#{subl_file}"`
72
+ #DEBUG.info("#{SUBL_BIN_PATH} #{subl_file}")
73
+ #DEBUG.info("/handle_url")
74
+
75
+ exit 0 # the handler has finished successfully
76
+ ```
77
+
78
+
79
+ ## Contributing
80
+
81
+ 1. Fork it
82
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
83
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
84
+ 4. Push to the branch (`git push origin my-new-feature`)
85
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/code_web.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'code_web/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "code_web"
8
+ spec.version = CodeWeb::VERSION
9
+ spec.authors = ["Keenan Brock"]
10
+ spec.email = ["keenan@thebrocks.net"]
11
+ spec.description = %q{Display the web of method calls in an app}
12
+ spec.summary = %q{Display methods}
13
+ spec.homepage = "http://github.com/kbrock/code_web"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "ruby_parser"
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ end
data/exe/code_web ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'code_web'
4
+ require 'code_web/cli'
5
+
6
+ CodeWeb::CLI.parse(ARGV)
@@ -0,0 +1,101 @@
1
+ require 'optparse'
2
+
3
+ module CodeWeb
4
+ class CLI
5
+ def self.parse(args)
6
+ new(args).run
7
+ end
8
+
9
+ attr_accessor :code_parser
10
+ def method_calls ; code_parser.method_calls ; end
11
+ def files_parsed ; code_parser.file_count ; end
12
+ def arg_regex ; code_parser.arg_regex ; end
13
+ def method_regex=(val) ; code_parser.method_regex = val ; end
14
+ def arg_regex=(val) ; code_parser.arg_regex = val ; end
15
+ def exit_on_error=(val) ; code_parser.exit_on_error = val ; end
16
+ def verbose=(val) ; code_parser.verbose = val ; end
17
+ def debug=(val) ; code_parser.debug = val ; end
18
+ def debug? ; code_parser.debug? ; end
19
+
20
+ # @attribute report_generator [rw]
21
+ # @return class that runs the report (i.e.: TextReport, HtmlReport)
22
+ attr_accessor :report_generator
23
+
24
+ # @attribute class_map
25
+ # @return [Map<Regexp,html_class>] files/directories with specal emphasis
26
+ attr_accessor :class_map
27
+
28
+ # @attribute arguments [r]
29
+ # @return [Array<String>] command line arguments
30
+ attr_accessor :arguments
31
+
32
+ # @attribute filenames [rw]
33
+ # @return [Array<String>] regular expression filenames
34
+ attr_accessor :filenames
35
+
36
+ attr_accessor :output
37
+
38
+ def initialize(arguments)
39
+ @arguments = arguments
40
+ @code_parser = CodeWeb::CodeParser.new
41
+ @class_map = {}
42
+ end
43
+
44
+ def run
45
+ parse_arguments
46
+ parse_files
47
+ display_results
48
+ end
49
+
50
+ def parse_arguments
51
+ #defaults
52
+ self.report_generator = ::CodeWeb::HtmlReport
53
+ self.output = STDOUT
54
+
55
+ #parsing the command line
56
+ OptionParser.new do |opt|
57
+ opt.banner = "Usage: code_web regex [file_name ...]"
58
+ # opt.on('-n', '--requests=count', Integer, "Number of requests (default: #{requests})") { |v| options[:requests] = v }
59
+ opt.on('-t', '--text', 'Use text reports') { |v| self.report_generator = ::CodeWeb::TextReport }
60
+ opt.on('-a', '--arg ARG_REGEX', 'Only files with hash argument') { |v| self.arg_regex = Regexp.new(v) }
61
+ opt.on('-o', '--output FILENAME', 'Output filename') { |v| self.output = (v == '-') ? STDOUT : File.new(v,'w') }
62
+ opt.on('-e', '--error-out', 'exit on unknown tags') { |v| self.exit_on_error = true}
63
+ opt.on('-V', '--verbose', 'verbose parsing') { |v| self.verbose = true}
64
+ opt.on('-D', '--debug', 'debug parsing') { |v| self.debug = true}
65
+ opt.on('-p', '--pattern FILENAME_REGEX=COLOR','color to emphasize a file') { |v| v = v.split('=') ; self.class_map[Regexp.new(v.first)] = v.last }
66
+ opt.on('--byebug') { require "byebug" }
67
+ opt.on('--pry') { require "pry" }
68
+ opt.on_tail("-h", "--help", "Show this message") { puts opt ; exit }
69
+ opt.on_tail("-v", "--version", "Show version_information") { puts "Code Web version #{CodeWeb::VERSION}" ; exit }
70
+ opt.parse!(arguments)
71
+
72
+ if arguments.length == 0
73
+ puts opt
74
+ exit
75
+ end
76
+ end
77
+ self.method_regex = Regexp.new(arguments[0])
78
+ self.filenames = arguments[1..-1] || "."
79
+ end
80
+
81
+ def parse_files
82
+ filenames.each do |arg|
83
+ arg = "#{arg}/**/*.rb" if Dir.exists?(arg)
84
+ if File.exist?(arg)
85
+ puts arg if debug?
86
+ code_parser.parse arg
87
+ else
88
+ Dir[arg].each do |file_name|
89
+ puts arg if debug?
90
+ code_parser.parse(file_name)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def display_results
97
+ STDOUT.puts "parsed #{files_parsed} files"
98
+ report_generator.new(method_calls, class_map, arg_regex, output).report
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,229 @@
1
+ require 'ruby_parser'
2
+
3
+ module CodeWeb
4
+ class CodeParser
5
+ extend Forwardable
6
+ SPACES = Hash.new {|h, n| h[n] = " " * n.to_i }
7
+
8
+ attr_accessor :method_cache
9
+ attr_accessor :file_count
10
+ attr_accessor :exit_on_error
11
+ attr_accessor :debug
12
+ attr_accessor :verbose
13
+ def verbose? ; @verbose ; end
14
+ def debug? ; @debug ; end
15
+ def_delegators :@method_cache, :method_regex=, :arg_regex=, :arg_regex, :method_calls
16
+
17
+ def initialize
18
+ @cur_method=[]
19
+ @indent = 0
20
+ @file_count = 0
21
+ @exit_on_error = false
22
+ @method_cache = CodeWeb::MethodCache.new
23
+ end
24
+
25
+ def traverse(ast, has_yield=false)
26
+ puts "#{spaces}||#{collapse_ast(ast,1)}||" if verbose?
27
+ puts src if ast.nil?
28
+ case ast.node_type
29
+ #dstr = define string ("abc#{here}"),
30
+ #evstr evaluate string (#{HERE})
31
+ #attrasgn = attribute assignment
32
+ when :block, :if, :ensure, :rescue, :case, :when, :begin,
33
+ :while, :until, :defined, :resbody, :match2, :match3, :dot2, :dot3,
34
+ :dstr, :evstr, :dsym, :dregx, :hash, :array, :return, :and, :or,
35
+ :next, :to_ary, :splat, :block_pass, :until, :yield,
36
+ /asgn/, :ivar, :arglist, :args, :kwarg, :kwargs, :kwsplat, :zsuper, :not, #statements[]
37
+ :super, :xstr, :for, :until, :dxstr,
38
+ #these end up being no-ops:
39
+ :lit, :lvar, :const, :str, :nil, :gvar, :back_ref,
40
+ :true, :false, :colon2, :colon3, :next, :alias,
41
+ :nth_ref, :sclass, :cvdecl, :break, :retry, :undef,
42
+ #random
43
+ :svalue, :cvar
44
+ traverse_nodes(ast, 1..-1)
45
+ when :self
46
+ traverse_nodes(ast, 1..-1)
47
+ when :module, #name, statements[]
48
+ :class #name, parent, statements[]
49
+ in_context ast[1], true, true do
50
+ traverse_nodes(ast, 2..-1)
51
+ end
52
+ when :cdecl, #name, statements[]
53
+ :defn #name, args[], call[]
54
+ in_context ast[1], true do
55
+ traverse_nodes(ast, 2..-1)
56
+ end
57
+ when :defs #self[], name, args[], call[] # static method
58
+ in_context ast[2], :static do
59
+ traverse_nodes(ast, 2..-1)
60
+ end
61
+ when :iter #call[], yield_args[], yield_{block|call}[]
62
+ traverse(ast[1], :has_yield)
63
+ in_context 'yield', true do
64
+ traverse_nodes(ast, 2..-1)
65
+ end
66
+ when :call # object, statement? || const symbol, args
67
+ handle_method_call(ast, has_yield)
68
+ traverse_nodes(ast, 1..-1)
69
+ else
70
+ STDERR.puts "#{src}\n unknown node: #{ast.node_type} #{collapse_ast(ast,1)}"
71
+ if exit_on_error
72
+ if defined?(Pry)
73
+ binding.pry
74
+ elsif defined?(Byebug)
75
+ byebug
76
+ end
77
+ raise "error"
78
+ end
79
+ traverse_nodes(ast, 1..-1)
80
+ end
81
+ end
82
+
83
+ def traverse_nodes(ast, *ranges)
84
+ ranges = [0..-1] if ranges.empty?
85
+ ranges.each do |range|
86
+ ast[range].each do |node|
87
+ should_call = node.is_a?(Sexp)
88
+ traverse(node) if should_call
89
+ end
90
+ end
91
+ end
92
+
93
+ def handle_method_call(ast, is_yield=false)
94
+ method_name = method_name_from_ast(ast[1..2])
95
+ args = ast[3..-1].map {|arg| collapse_ast(arg,1)}
96
+
97
+ mc = MethodCall.new(ast.file, ast.line, method_name, args, is_yield)
98
+ method_cache << mc
99
+ puts mc.to_s(spaces) if debug? # && method_cache.detect?(mc)
100
+ end
101
+
102
+ def method_name_from_ast(ast)
103
+ ast.map { |node|
104
+ collapse_ast(node)
105
+ }.compact
106
+ end
107
+
108
+ #TODO: add collapse_ast
109
+ # this one creates the true classes, not the string versions
110
+ # (so don't add double quotes, or do 'nil')
111
+ def collapse_ast(ast, max=20)
112
+ if ast.is_a?(Sexp)
113
+ if static?
114
+ ast = ast.gsub(Sexp.new(:self), Sexp.new(:const, self_name))
115
+ else
116
+ ast = ast.gsub(Sexp.new(:call, Sexp.new(:self),:class), Sexp.new(:const, self_name))
117
+ end
118
+ case ast.node_type
119
+ when :hash #name, value, name, value, ...
120
+ if ast[1].is_a?(Sexp) && ast[1].node_type == :kwsplat
121
+ ast[1..-1].map { |i| collapse_ast(i) }
122
+ else
123
+ Hash[*ast[1..-1].map { |i| collapse_ast(i) }]
124
+ end
125
+ when :array
126
+ ast[1..-1].map {|node| collapse_ast(node)}
127
+ when :lit, :lvar, :const, :str, :ivar, :cvar
128
+ ast[1]
129
+ when :true
130
+ true
131
+ when :false
132
+ false
133
+ when :nil
134
+ nil
135
+ when :self
136
+ ast[0]
137
+ when :call
138
+ if ast[2] == :[]
139
+ "#{method_name_from_ast(ast[1..1]).join('.')}[#{collapse_ast(ast[3])}]"
140
+ else
141
+ "#{method_name_from_ast(ast[1..2]).join('.')}#{'(...)' if ast.length > 3}"
142
+ end
143
+ when :evstr
144
+ "#"+"{#{collapse_asts(ast[1..-1]).join}}"
145
+ when :colon2
146
+ "#{method_name_from_ast(ast[1..-1]).join('::')}"
147
+ when :dot2
148
+ "#{collapse_ast(ast[1])}..#{collapse_ast(ast[2])}"
149
+ when :colon3
150
+ "::#{collapse_asts(ast[1..-1]).join}"
151
+ when :[]
152
+ "[#{collapse_asts(ast[1..-1]).join}]"
153
+ when :dstr
154
+ "#{collapse_asts(ast[1..-1]).join}"
155
+ #backref?
156
+ else
157
+ if max > 0
158
+ ast.map {|node| collapse_ast(node, max-1)}
159
+ else
160
+ "#{ast.node_type}[]"
161
+ end
162
+ end
163
+ elsif ast.nil?
164
+ nil
165
+ else
166
+ ast
167
+ end
168
+ end
169
+
170
+ def collapse_asts(ast, max=20)
171
+ ast.map {|node| collapse_ast(node)}
172
+ end
173
+
174
+ def parse(file_name, file_data=nil, required_string=nil)
175
+ #may make more sense to get this into cli (and an option for absolute path)
176
+ file_name = File.realpath(file_name)
177
+ file_data ||= File.binread(file_name)
178
+ begin
179
+ if required_string.nil? || file_data.include?(required_string)
180
+ in_context file_name do
181
+ traverse RubyParser.new.process(file_data, file_name)
182
+ end
183
+ end
184
+ @file_count += 1
185
+ rescue => e
186
+ STDERR.puts("#{e}: [#{file_data.size}] #{file_name}")
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ # where in the source are we?
193
+ def src
194
+ "#{@cur_method.first.first} | #{@cur_method.map(&:first)[1..-1].join('.')}"
195
+ end
196
+
197
+ # return nil if we haven't hit a class yet
198
+ def self_name
199
+ #cm[1] == true for module/class definitions
200
+ node=@cur_method.select {|cm| cm[1] == true}.last
201
+ node.first unless node.nil?
202
+ end
203
+
204
+ def static?
205
+ @cur_method.last.last
206
+ end
207
+
208
+ # mark the context of the method call.
209
+ # optionally indents output as well
210
+ # @param name [String] name of the block - file, module, class, method, 'yield'
211
+ # @param indent [boolean] (false) indent this block (pass :static if this is a static method)
212
+ # @param class_def [boolean] (false) true if this is a class definition
213
+ def in_context name, indent=false, class_def=false
214
+ name = collapse_ast(name) #split("::").last
215
+ @cur_method << [ name, class_def, indent == :static]
216
+ puts ">> #{'self.' if static?}#{src}" if debug? && indent
217
+ @indent += 1 if indent
218
+ ret = yield
219
+ @indent -= 1 if indent
220
+ @cur_method.pop
221
+ ret
222
+ end
223
+
224
+ #print appropriate # of spaces
225
+ def spaces
226
+ SPACES[@indent]
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,157 @@
1
+ require 'set'
2
+ require 'erb'
3
+
4
+ module CodeWeb
5
+ class HtmlReport
6
+ # @!attribute :method_calls [r]
7
+ # list of all the method_Calls
8
+ # @return [Array<MethodCall>]
9
+ attr_accessor :method_calls
10
+ attr_accessor :arg_regex
11
+ def arg_regex? ; ! arg_regex.nil? ; end
12
+
13
+ # @!attribute :class_map [rw]
14
+ # map from regex to class name
15
+ # if the filename that has the method matches the regex, the classname
16
+ # will get assigned to the link (to emphasize certain files/directories)
17
+ # @return [Map<Regexp,color>] regex expressing name of main file
18
+ attr_accessor :class_map
19
+
20
+ def initialize(method_calls, class_map={}, arg_regex=nil, out=STDOUT)
21
+ @method_calls = method_calls
22
+ @class_map = class_map
23
+ @arg_regex = arg_regex
24
+ @out = out
25
+ end
26
+
27
+ TEMPLATE=%{
28
+ <html>
29
+ <head><style>
30
+ table {border-collapse:collapse;}
31
+ table, td, th { border:1px solid black; }
32
+ <%- @class_map.each_with_index do |(pattern, color), i| -%>
33
+ .f<%=i%>, a.f<%=i%> { color: <%=color%>; }
34
+ <%- end -%>
35
+ </style>
36
+ </head>
37
+ <body>
38
+ <%- methods_by_name.each do |methods| -%>
39
+ <h2><%=methods.name%></h2>
40
+ <%- methods.group_by(:hash_args?).each do |methods_with_hash| -%>
41
+ <%- if methods_with_hash.hash_args? -%>
42
+ <%- methods_with_hash.group_by(:method_types).each do |methods_with_type| -%>
43
+ <%- display_yield_column = methods_with_type.detect(&:yields?) -%>
44
+ <table>
45
+ <thead><tr>
46
+ <%- methods_with_type.arg_keys.each do |arg| -%>
47
+ <td><%=arg%></td>
48
+ <%- end -%>
49
+ <%- if display_yield_column -%>
50
+ <td>yield?</td>
51
+ <%- end -%>
52
+ <td>ref</td>
53
+ </tr></thead>
54
+ <tbody>
55
+ <%- methods_with_type.group_by(:signature, arg_regex).each do |methods_by_signature| -%>
56
+ <tr>
57
+ <%- methods_with_type.arg_keys.each do |arg| -%>
58
+ <td><%= simplified_argument(methods_by_signature.hash_arg[arg]) %></td>
59
+ <%- end -%>
60
+ <%- if display_yield_column -%>
61
+ <td><%= methods_by_signature.f.yields? %></td>
62
+ <%- end -%>
63
+ <td><%- methods_by_signature.each_with_index do |method, i| -%>
64
+ <%= method_link(method, i+1) %>
65
+ <%- end -%></td>
66
+ </tr>
67
+ <%- end -%>
68
+ </tbody>
69
+ </table>
70
+ <%- end -%>
71
+ <%- else -%>
72
+ <table>
73
+ <tbody>
74
+ <%- methods_with_hash.group_by(:method_types).each do |methods_with_type| -%>
75
+ <%- display_yield_column = methods_with_type.detect(&:yields?) -%>
76
+ <%- methods_with_type.group_by(:signature, nil, :small_signature).each do |methods_by_signature| -%>
77
+ <tr>
78
+ <%- methods_by_signature.f.args.each do |arg| -%>
79
+ <td><%= arg.inspect %></td>
80
+ <%- end -%>
81
+ <%- if display_yield_column -%>
82
+ <td><%= methods_by_signature.f.yields? ? 'yields' : 'no yield'%></td>
83
+ <%- end -%>
84
+ <td><%- methods_by_signature.each_with_index do |method, i| -%>
85
+ <%= method_link(method, i+1) %>
86
+ <%- end -%></td>
87
+ </tr>
88
+ <%- end -%>
89
+ <%- end -%>
90
+ </tbody>
91
+ </table>
92
+ <%- end -%>
93
+ <%- end -%>
94
+
95
+ <%- end -%>
96
+ </body>
97
+ </html>
98
+ }
99
+
100
+ def report
101
+ template = ERB.new(TEMPLATE, nil, "-")
102
+ @out.puts template.result(binding)
103
+ rescue => e
104
+ e.backtrace.detect { |l| l =~ /\(erb\):([0-9]+)/ }
105
+ line_no=$1.to_i
106
+ raise RuntimeError, "error in #{__FILE__}:#{line_no+28} #{e}\n\n #{TEMPLATE.split(/\n/)[line_no-1]}\n\n ",
107
+ e.backtrace
108
+ end
109
+
110
+ # helpers
111
+
112
+ def methods_by_name
113
+ MethodList.group_by(method_calls, :short_method_name)
114
+ end
115
+
116
+ private
117
+
118
+ # shorten the argument
119
+ def simplified_argument(arg)
120
+ short_arg = case arg
121
+ when nil
122
+ nil
123
+ when String
124
+ arg.split("::").last[0..12]
125
+ else
126
+ arg.to_s[0..12]
127
+ end
128
+ %{<span title="#{html_safe(arg)}">#{short_arg}</span>}
129
+ end
130
+
131
+ def html_safe(str)
132
+ str.to_s.gsub('"','&quot;')
133
+ end
134
+
135
+ # @param collection [Array<Method>] methods (with a hash first argument)
136
+ # @return [Array<String>] list of all keys for all hashes
137
+ def all_hash_names(collection)
138
+ collection.inject(Set.new) {|acc, m| m.arg_keys.each {|k| acc << k} ; acc}.sort_by {|n| n}
139
+ end
140
+
141
+ # create a link to a method
142
+ # add a class if the method is in a particular file
143
+
144
+ def method_link(m, count=nil)
145
+ name = count ? "[#{count}]" : m.signature
146
+ class_name = nil
147
+ class_map.each_with_index do |(pattern, color), i|
148
+ if m.filename =~ pattern
149
+ class_name = "f#{i}"
150
+ break
151
+ end
152
+ end
153
+ #NOTE: may want to CGI::escape(m.filename)
154
+ %{<a href="subl://open?url=file://#{m.filename}&amp;line=#{m.line}" title="#{html_safe(m.signature)}"#{" class=\"#{class_name}\"" if class_name}>#{name}</a>}
155
+ end
156
+ end
157
+ end