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