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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb4f5f3aec4c52c667605028d891dccf6bab2b6d
4
- data.tar.gz: c3590e42ee2bf91facc0553fee487c19873b884c
3
+ metadata.gz: 37cc2f42a16925fac9f2dde64d9e2a174d915187
4
+ data.tar.gz: e016d6a05e33163a83a970f8a70b693ccff90fdd
5
5
  SHA512:
6
- metadata.gz: 7757f66ac7088828f58beb5ddcfb84f84dd415e5680c5493a1f3bcc23e22c6e602b3f59a2aaf38eafe1eb641f289d1a10ae8dc8170d8078a55049119bc745436
7
- data.tar.gz: d0e8a1630fdf7a037ffbced5fa1bf7daad69990d33377cc4daa8c41965e9b0632804784924e559a475a3154b670a0b031b0824701e312a4fc89594272762aa92
6
+ metadata.gz: c47e95b326b405eecbe74654071c93db23f4ffa32ddb7c99b4da13fc247d56989c20d9892b5abebd32140ec9d23a9bddc9c8190aa69af26bb67938b4aed15cbe
7
+ data.tar.gz: 8514c9663ddeebce427ba42c8f0d315d5213319df57855501e0778b169359cc4d4f22d3c1ecae7384bc4d3bf8ab5f801212005f97249fd11bcc66e00d5ac3e5c
data/README.md CHANGED
@@ -1,16 +1,29 @@
1
- # Call Graph
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
- Yes, it is quick and dirty.
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
1
+ 0.2.0
@@ -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
@@ -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
- type = :svg
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.1.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-20 00:00:00.000000000 Z
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