code_web 0.0.4

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.
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