code-explorer 0.1.0 → 0.2.0
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 +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
|