code_web 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +85 -0
- data/Rakefile +6 -0
- data/code_web.gemspec +26 -0
- data/exe/code_web +6 -0
- data/lib/code_web/cli.rb +101 -0
- data/lib/code_web/code_parser.rb +229 -0
- data/lib/code_web/html_report.rb +157 -0
- data/lib/code_web/method_cache.rb +29 -0
- data/lib/code_web/method_call.rb +116 -0
- data/lib/code_web/method_list.rb +60 -0
- data/lib/code_web/text_report.rb +41 -0
- data/lib/code_web/version.rb +3 -0
- data/lib/code_web.rb +11 -0
- data/spec/code_parser_spec.rb +252 -0
- data/spec/method_cache_spec.rb +29 -0
- data/spec/method_call_spec.rb +69 -0
- data/spec/method_list_spec.rb +32 -0
- data/spec/spec_helper.rb +7 -0
- metadata +129 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
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
data/lib/code_web/cli.rb
ADDED
@@ -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('"','"')
|
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}&line=#{m.line}" title="#{html_safe(m.signature)}"#{" class=\"#{class_name}\"" if class_name}>#{name}</a>}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|