code-explorer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -2
- data/VERSION +1 -1
- data/bin/class-dependencies +2 -155
- data/bin/code-explorer +32 -6
- data/lib/code_explorer/const_binding.rb +54 -0
- data/lib/code_explorer/consts.rb +96 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37cc2f42a16925fac9f2dde64d9e2a174d915187
|
4
|
+
data.tar.gz: e016d6a05e33163a83a970f8a70b693ccff90fdd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c47e95b326b405eecbe74654071c93db23f4ffa32ddb7c99b4da13fc247d56989c20d9892b5abebd32140ec9d23a9bddc9c8190aa69af26bb67938b4aed15cbe
|
7
|
+
data.tar.gz: 8514c9663ddeebce427ba42c8f0d315d5213319df57855501e0778b169359cc4d4f22d3c1ecae7384bc4d3bf8ab5f801212005f97249fd11bcc66e00d5ac3e5c
|
data/README.md
CHANGED
@@ -1,16 +1,29 @@
|
|
1
|
-
#
|
1
|
+
# Code Explorer
|
2
|
+
|
3
|
+
## Tools
|
4
|
+
|
5
|
+
### code-explorer
|
6
|
+
|
7
|
+
Starts a local web server which lets you apply the other tools to all `*.rb`
|
8
|
+
files in a directory subtree.
|
9
|
+
|
10
|
+
### call-graph
|
2
11
|
|
3
12
|
This makes a call graph among methods of a single Ruby file.
|
4
13
|
|
5
14
|
I made it to help me orient myself in unfamiliar legacy code and to help
|
6
15
|
identify cohesive parts that could be split out.
|
7
16
|
|
8
|
-
|
17
|
+
### class-dependencies
|
18
|
+
|
19
|
+
Identifies fully qualified class names and makes an inheritance graph
|
9
20
|
|
10
21
|
## Requirements
|
11
22
|
|
12
23
|
- [parser gem](https://github.com/whitequark/parser)
|
13
24
|
- [Graphviz](http://www.graphviz.org/)
|
25
|
+
- Sinatra
|
26
|
+
- Cheetah
|
14
27
|
|
15
28
|
## License
|
16
29
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/bin/class-dependencies
CHANGED
@@ -5,11 +5,12 @@
|
|
5
5
|
require "parser/current"
|
6
6
|
require "pp"
|
7
7
|
|
8
|
+
require "code_explorer/consts"
|
8
9
|
require "code_explorer/dot"
|
9
10
|
|
10
11
|
def main
|
11
12
|
asts = ARGV.map {|fn| ast_for_filename(fn)}
|
12
|
-
cs = Consts.new
|
13
|
+
cs = CodeExplorer::Consts.new
|
13
14
|
cs.report_modules(asts)
|
14
15
|
graph = cs.superclasses
|
15
16
|
puts dot_from_hash(graph)
|
@@ -20,158 +21,4 @@ def ast_for_filename(fn)
|
|
20
21
|
Parser::CurrentRuby.parse(ruby)
|
21
22
|
end
|
22
23
|
|
23
|
-
# ruby [String] a ruby program
|
24
|
-
# @return a dot graph string
|
25
|
-
def dep_graph(ast)
|
26
|
-
Consts.new.report_modules(ast)
|
27
|
-
dot_from_hash({})
|
28
|
-
end
|
29
|
-
|
30
|
-
# tracks what constants are resolvable
|
31
|
-
class ConstBinding
|
32
|
-
def initialize(fqname, parent = nil)
|
33
|
-
@fqname = fqname
|
34
|
-
@parent = parent
|
35
|
-
@known = {}
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [ConstBinding] the new scope
|
39
|
-
def open_namespace(fqname)
|
40
|
-
ns = @known[fqname]
|
41
|
-
if ns.is_a? ConstBinding
|
42
|
-
# puts "(reopening #{fqname})"
|
43
|
-
else
|
44
|
-
ns = self.class.new(fqname, self)
|
45
|
-
@known[fqname] = ns
|
46
|
-
end
|
47
|
-
ns
|
48
|
-
end
|
49
|
-
|
50
|
-
# @return [ConstBinding] the parent scope
|
51
|
-
def close_namespace
|
52
|
-
@parent
|
53
|
-
end
|
54
|
-
|
55
|
-
def declare_const(fqname)
|
56
|
-
if @known[fqname]
|
57
|
-
# puts "warning: #{fqname} already declared"
|
58
|
-
end
|
59
|
-
@known[fqname] = :const
|
60
|
-
end
|
61
|
-
|
62
|
-
def resolve_declared_const(name)
|
63
|
-
if @fqname.empty?
|
64
|
-
name
|
65
|
-
else
|
66
|
-
"#{@fqname}::#{name}"
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def resolve_used_const(name)
|
71
|
-
# puts "resolving #{name} in #{@fqname}, known #{@known.inspect}"
|
72
|
-
candidate = resolve_declared_const(name)
|
73
|
-
if @known.include?(candidate)
|
74
|
-
candidate
|
75
|
-
elsif @parent
|
76
|
-
@parent.resolve_used_const(name)
|
77
|
-
else
|
78
|
-
name
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
class Consts < Parser::AST::Processor
|
84
|
-
include AST::Sexp
|
85
|
-
|
86
|
-
# @return [ConstBinding]
|
87
|
-
attr_reader :cb
|
88
|
-
# @return [Hash{String => Array<String>}]
|
89
|
-
attr_reader :superclasses
|
90
|
-
|
91
|
-
def initialize
|
92
|
-
@cb = ConstBinding.new("")
|
93
|
-
@superclasses = {}
|
94
|
-
end
|
95
|
-
|
96
|
-
def report_modules(asts)
|
97
|
-
Array(asts).each do |ast|
|
98
|
-
process(ast)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def const_name_from_sexp(node)
|
103
|
-
case node.type
|
104
|
-
when :self
|
105
|
-
"self"
|
106
|
-
when :cbase
|
107
|
-
""
|
108
|
-
when :const, :casgn
|
109
|
-
parent, name, _maybe_value = *node
|
110
|
-
if parent
|
111
|
-
const_name_from_sexp(parent) + "::#{name}"
|
112
|
-
else
|
113
|
-
name.to_s
|
114
|
-
end
|
115
|
-
else
|
116
|
-
raise "Unexpected #{node.type}"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def new_scope(name, &block)
|
121
|
-
@cb = cb.open_namespace(name)
|
122
|
-
block.call
|
123
|
-
@cb = cb.close_namespace
|
124
|
-
end
|
125
|
-
|
126
|
-
def on_module(node)
|
127
|
-
name, _body = *node
|
128
|
-
name = cb.resolve_declared_const(const_name_from_sexp(name))
|
129
|
-
# puts "module #{name}"
|
130
|
-
|
131
|
-
new_scope(name) do
|
132
|
-
super
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def on_class(node)
|
137
|
-
name, parent, _body = *node
|
138
|
-
parent ||= s(:const, s(:cbase), :Object)
|
139
|
-
|
140
|
-
name = cb.resolve_declared_const(const_name_from_sexp(name))
|
141
|
-
parent = cb.resolve_used_const(const_name_from_sexp(parent))
|
142
|
-
# puts "class #{name} < #{parent}"
|
143
|
-
|
144
|
-
@superclasses[name] = [parent]
|
145
|
-
|
146
|
-
new_scope(name) do
|
147
|
-
super
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def on_sclass(node)
|
152
|
-
parent, _body = *node
|
153
|
-
|
154
|
-
parent = const_name_from_sexp(parent)
|
155
|
-
name = "<< #{parent}" # cheating
|
156
|
-
# puts "class #{name}"
|
157
|
-
|
158
|
-
new_scope(name) do
|
159
|
-
super
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def on_casgn(node)
|
164
|
-
name = cb.resolve_declared_const(const_name_from_sexp(node))
|
165
|
-
cb.declare_const(name)
|
166
|
-
# puts "casgn #{name}"
|
167
|
-
end
|
168
|
-
|
169
|
-
def on_const(node)
|
170
|
-
name = const_name_from_sexp(node)
|
171
|
-
fqname = cb.resolve_used_const(name)
|
172
|
-
# puts "CONST #{fqname}"
|
173
|
-
end
|
174
|
-
|
175
|
-
end
|
176
|
-
|
177
24
|
main
|
data/bin/code-explorer
CHANGED
@@ -4,6 +4,7 @@ require "sinatra"
|
|
4
4
|
require "cheetah"
|
5
5
|
|
6
6
|
require "code_explorer/call_graph"
|
7
|
+
require "code_explorer/consts"
|
7
8
|
require "code_explorer/numbered_lines"
|
8
9
|
|
9
10
|
def path_link(prefix, path, text)
|
@@ -16,11 +17,40 @@ configure do
|
|
16
17
|
end
|
17
18
|
|
18
19
|
get "/" do
|
20
|
+
page = ""
|
21
|
+
|
22
|
+
page << path_link("", "class-inheritance", "Class inheritance in /lib/")
|
23
|
+
page << "<hr>\n"
|
24
|
+
|
19
25
|
files = Dir.glob("**/*.rb").sort
|
20
|
-
files.map do |f|
|
26
|
+
page << files.map do |f|
|
21
27
|
path_link("/files", f, f) + " " +
|
22
28
|
path_link("/call-graph", f, "call graph")
|
23
29
|
end.join("<br>\n")
|
30
|
+
page
|
31
|
+
end
|
32
|
+
|
33
|
+
def ast_for_filename(fn)
|
34
|
+
ruby = File.read(fn)
|
35
|
+
Parser::CurrentRuby.parse(ruby)
|
36
|
+
end
|
37
|
+
|
38
|
+
def serve_dot(dot)
|
39
|
+
type = :svg
|
40
|
+
graph = Cheetah.run(["dot", "-T#{type}"], stdin: dot, stdout: :capture)
|
41
|
+
|
42
|
+
content_type type
|
43
|
+
graph
|
44
|
+
end
|
45
|
+
|
46
|
+
get "/class-inheritance" do
|
47
|
+
files = Dir.glob("lib/**/*.rb").sort
|
48
|
+
asts = files.map {|fn| ast_for_filename(fn)}
|
49
|
+
cs = CodeExplorer::Consts.new
|
50
|
+
cs.report_modules(asts)
|
51
|
+
graph = cs.superclasses
|
52
|
+
|
53
|
+
serve_dot(dot_from_hash(graph))
|
24
54
|
end
|
25
55
|
|
26
56
|
get "/files/*" do |path|
|
@@ -31,9 +61,5 @@ end
|
|
31
61
|
get "/call-graph/*" do |path|
|
32
62
|
dot = call_graph(path)
|
33
63
|
|
34
|
-
|
35
|
-
graph = Cheetah.run(["dot", "-T#{type}"], stdin: dot, stdout: :capture)
|
36
|
-
|
37
|
-
content_type type
|
38
|
-
graph
|
64
|
+
serve_dot(dot)
|
39
65
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module CodeExplorer
|
2
|
+
# tracks what constants are resolvable
|
3
|
+
class ConstBinding
|
4
|
+
def initialize(fqname, parent = nil)
|
5
|
+
@fqname = fqname
|
6
|
+
@parent = parent
|
7
|
+
@known = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [ConstBinding] the new scope
|
11
|
+
def open_namespace(fqname)
|
12
|
+
ns = @known[fqname]
|
13
|
+
if ns.is_a? ConstBinding
|
14
|
+
# puts "(reopening #{fqname})"
|
15
|
+
else
|
16
|
+
ns = self.class.new(fqname, self)
|
17
|
+
@known[fqname] = ns
|
18
|
+
end
|
19
|
+
ns
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [ConstBinding] the parent scope
|
23
|
+
def close_namespace
|
24
|
+
@parent
|
25
|
+
end
|
26
|
+
|
27
|
+
def declare_const(fqname)
|
28
|
+
if @known[fqname]
|
29
|
+
# puts "warning: #{fqname} already declared"
|
30
|
+
end
|
31
|
+
@known[fqname] = :const
|
32
|
+
end
|
33
|
+
|
34
|
+
def resolve_declared_const(name)
|
35
|
+
if @fqname.empty?
|
36
|
+
name
|
37
|
+
else
|
38
|
+
"#{@fqname}::#{name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolve_used_const(name)
|
43
|
+
# puts "resolving #{name} in #{@fqname}, known #{@known.inspect}"
|
44
|
+
candidate = resolve_declared_const(name)
|
45
|
+
if @known.include?(candidate)
|
46
|
+
candidate
|
47
|
+
elsif @parent
|
48
|
+
@parent.resolve_used_const(name)
|
49
|
+
else
|
50
|
+
name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require_relative "const_binding"
|
2
|
+
|
3
|
+
module CodeExplorer
|
4
|
+
class Consts < Parser::AST::Processor
|
5
|
+
include AST::Sexp
|
6
|
+
|
7
|
+
# @return [ConstBinding]
|
8
|
+
attr_reader :cb
|
9
|
+
# @return [Hash{String => Array<String>}]
|
10
|
+
attr_reader :superclasses
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@cb = ConstBinding.new("")
|
14
|
+
@superclasses = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def report_modules(asts)
|
18
|
+
Array(asts).each do |ast|
|
19
|
+
process(ast)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def const_name_from_sexp(node)
|
24
|
+
case node.type
|
25
|
+
when :self
|
26
|
+
"self"
|
27
|
+
when :cbase
|
28
|
+
""
|
29
|
+
when :const, :casgn
|
30
|
+
parent, name, _maybe_value = *node
|
31
|
+
if parent
|
32
|
+
const_name_from_sexp(parent) + "::#{name}"
|
33
|
+
else
|
34
|
+
name.to_s
|
35
|
+
end
|
36
|
+
else
|
37
|
+
raise "Unexpected #{node.type}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_scope(name, &block)
|
42
|
+
@cb = cb.open_namespace(name)
|
43
|
+
block.call
|
44
|
+
@cb = cb.close_namespace
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_module(node)
|
48
|
+
name, _body = *node
|
49
|
+
name = cb.resolve_declared_const(const_name_from_sexp(name))
|
50
|
+
# puts "module #{name}"
|
51
|
+
|
52
|
+
new_scope(name) do
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_class(node)
|
58
|
+
name, parent, _body = *node
|
59
|
+
parent ||= s(:const, s(:cbase), :Object)
|
60
|
+
|
61
|
+
name = cb.resolve_declared_const(const_name_from_sexp(name))
|
62
|
+
parent = cb.resolve_used_const(const_name_from_sexp(parent))
|
63
|
+
# puts "class #{name} < #{parent}"
|
64
|
+
|
65
|
+
@superclasses[name] = [parent]
|
66
|
+
|
67
|
+
new_scope(name) do
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def on_sclass(node)
|
73
|
+
parent, _body = *node
|
74
|
+
|
75
|
+
parent = const_name_from_sexp(parent)
|
76
|
+
name = "<< #{parent}" # cheating
|
77
|
+
# puts "class #{name}"
|
78
|
+
|
79
|
+
new_scope(name) do
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def on_casgn(node)
|
85
|
+
name = cb.resolve_declared_const(const_name_from_sexp(node))
|
86
|
+
cb.declare_const(name)
|
87
|
+
# puts "casgn #{name}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def on_const(node)
|
91
|
+
name = const_name_from_sexp(node)
|
92
|
+
fqname = cb.resolve_used_const(name)
|
93
|
+
# puts "CONST #{fqname}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code-explorer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Vidner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-06-
|
11
|
+
date: 2016-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -69,6 +69,8 @@ files:
|
|
69
69
|
- bin/code-explorer
|
70
70
|
- bin/required-files
|
71
71
|
- lib/code_explorer/call_graph.rb
|
72
|
+
- lib/code_explorer/const_binding.rb
|
73
|
+
- lib/code_explorer/consts.rb
|
72
74
|
- lib/code_explorer/dot.rb
|
73
75
|
- lib/code_explorer/numbered_lines.rb
|
74
76
|
- lib/code_explorer/version.rb
|