code_node 0.0.2 → 0.1.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.
- data/cog/templates/code_node/generator.rb.erb +42 -3
- data/cog/templates/code_node/graph.dot.erb +0 -2
- data/cog/templates/code_node/node.dot.erb +1 -1
- data/lib/code_node/dsl/graph_definer.rb +60 -13
- data/lib/code_node/ir/graph.rb +164 -97
- data/lib/code_node/ir/node.rb +210 -122
- data/lib/code_node/ir/node_matcher.rb +26 -0
- data/lib/code_node/sexp_walker.rb +6 -2
- data/lib/code_node/version.rb +1 -1
- data/lib/code_node.rb +16 -3
- metadata +4 -3
|
@@ -2,11 +2,50 @@
|
|
|
2
2
|
<% else %>require '<%= tool.name %>'
|
|
3
3
|
<% end %>
|
|
4
4
|
|
|
5
|
-
# Makes a graph of the project_source_path
|
|
5
|
+
# Makes a graph of the project_source_path.
|
|
6
|
+
# Nodes in the graph are clases and modules from your project.
|
|
6
7
|
CodeNode.graph '<%= @name %>' do |g|
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
# Nodes
|
|
10
|
+
# =====
|
|
11
|
+
|
|
12
|
+
# Ignore nodes with no relations to other nodes
|
|
10
13
|
g.ignore &:island?
|
|
14
|
+
|
|
15
|
+
# Uncomment and change the value to ignore a specific node
|
|
16
|
+
# g.ignore 'Foo::Bar::Car'
|
|
17
|
+
|
|
18
|
+
# Uncomment to ignore nodes named ClassMethods
|
|
19
|
+
# g.ignore /::ClassMethods$/
|
|
20
|
+
|
|
21
|
+
# Uncomment and change the value to ignore all nodes descending
|
|
22
|
+
# from a specific node
|
|
23
|
+
# g.ignore {|node| node.inherits_from? 'Foo::Bar::Car'}
|
|
24
|
+
|
|
25
|
+
# Apply styles common to all classes
|
|
26
|
+
g.style :shape => 'ellipse', :fillcolor => '#cccccc', :fontcolor => :black do |node|
|
|
27
|
+
node.class?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Apply styles common to all nodes
|
|
31
|
+
g.style :shape => 'box', :fillcolor => '#333333', :fontcolor => :white do |node|
|
|
32
|
+
node.module?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Apply more specific style here
|
|
36
|
+
# ...
|
|
11
37
|
|
|
38
|
+
# Edges
|
|
39
|
+
# =====
|
|
40
|
+
#
|
|
41
|
+
# There is currently no way to style edges from the DSL.
|
|
42
|
+
# For now, there are only four categories of edges:
|
|
43
|
+
# - containment
|
|
44
|
+
# - inheritance
|
|
45
|
+
# - inclusion
|
|
46
|
+
# - extension
|
|
47
|
+
#
|
|
48
|
+
# They are style in the template. You can override the template
|
|
49
|
+
# and change the color if you like.
|
|
50
|
+
# $ cog -t code_node template new -f code_node/graph.dot
|
|
12
51
|
end
|
|
@@ -4,13 +4,11 @@ graph [rankdir="LR"]
|
|
|
4
4
|
node [style="filled"]
|
|
5
5
|
|
|
6
6
|
/* Module nodes */
|
|
7
|
-
node [shape="box" fillcolor="#333333" fontcolor="#ffffff"];
|
|
8
7
|
<% @graph.each_module do |node| %>
|
|
9
8
|
<%= node.stamp 'code_node/node.dot' %>
|
|
10
9
|
<% end %>
|
|
11
10
|
|
|
12
11
|
/* Class nodes */
|
|
13
|
-
node [shape="ellipse" fillcolor="#cccccc" fontcolor="#000000"];
|
|
14
12
|
<% @graph.each_class do |node| %>
|
|
15
13
|
<%= node.stamp 'code_node/node.dot' %>
|
|
16
14
|
<% end %>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<%= key %> [label="<%= label %>"];
|
|
1
|
+
<%= key %> [label="<%= label %>" <%= stamp_styles %>];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'code_node/ir/node_matcher'
|
|
2
|
+
|
|
1
3
|
module CodeNode
|
|
2
4
|
module DSL
|
|
3
5
|
|
|
@@ -10,28 +12,73 @@ module CodeNode
|
|
|
10
12
|
@graph = graph
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
#
|
|
14
|
-
# Nodes matching the ignore rule will not be included in the graph.
|
|
15
|
-
# Ignore rules can be given in one of three ways.
|
|
15
|
+
# Specify an ignore rule for the graph.
|
|
16
|
+
# Nodes matching the ignore rule will not be included in the graph. Ignore rules can be given in one of three ways,
|
|
16
17
|
#
|
|
17
|
-
# * +String+ provide a fully qualified path
|
|
18
|
-
# * +Regexp+ provide a pattern that will be tested against the {IR::Node#path}
|
|
18
|
+
# * +String+ provide a fully qualified path which will have to match {IR::Node::QueryMethods#path} exactly
|
|
19
|
+
# * +Regexp+ provide a pattern that will be tested against the {IR::Node::QueryMethods#path}
|
|
19
20
|
# * +Proc+ provide a block. If the block returns +true+ the node will be ignored
|
|
20
21
|
#
|
|
21
|
-
# @
|
|
22
|
-
#
|
|
22
|
+
# @example
|
|
23
|
+
# CodeNode.graph 'my_graph' do |g|
|
|
24
|
+
#
|
|
25
|
+
# # Using a full path
|
|
26
|
+
# g.ignore 'Foo::Bar::Car'
|
|
27
|
+
#
|
|
28
|
+
# # Using a regular expression
|
|
29
|
+
# g.ignore /ClassMethods$/
|
|
30
|
+
#
|
|
31
|
+
# # Using a block
|
|
32
|
+
# g.ignore do |node|
|
|
33
|
+
# node.inherits_from? 'Exception'
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# @param name [String, Regexp, nil] fully qualified path or regular expression which will be compared to {IR::Node::QueryMethods#path}
|
|
38
|
+
# @yieldparam node [IR::Node::QueryMethods] if provided, return +true+ to ignore the node
|
|
23
39
|
# @return [nil]
|
|
24
40
|
def ignore(name=nil, &block)
|
|
25
41
|
if (name.nil? && block.nil?) || (name && block)
|
|
26
42
|
raise ArgumentError.new('Provide either a name or a block')
|
|
27
43
|
end
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
matcher = IR::NodeMatcher.new name || block
|
|
45
|
+
@graph.instance_eval {@exclude_matchers << matcher}
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Specify a rule for styling nodes.
|
|
50
|
+
# Nodes matching the given rule will have the provided style attributes applied. Rules can be given in one of three ways,
|
|
51
|
+
#
|
|
52
|
+
# * +String+ provide a fully qualified path which will have to match {IR::Node::QueryMethods#path} exactly
|
|
53
|
+
# * +Regexp+ provide a pattern that will be tested against the {IR::Node::QueryMethods#path}
|
|
54
|
+
# * +Proc+ provide a block
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# CodeNode.graph 'my_graph' do |g|
|
|
58
|
+
#
|
|
59
|
+
# # Using a full path
|
|
60
|
+
# g.style 'Foo::Bar::Car', :shape => 'box'
|
|
61
|
+
#
|
|
62
|
+
# # Using a regular expression
|
|
63
|
+
# g.style /ClassMethods$/, :fillcolor => '#336699'
|
|
64
|
+
#
|
|
65
|
+
# # Using a block
|
|
66
|
+
# g.style :penwidth => 3 do |node|
|
|
67
|
+
# node.extends? 'ActiveSupport::Concern'
|
|
68
|
+
# end
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# @param name [String, Regexp, nil] fully qualified path or regular expression which will be compared to {IR::Node::QueryMethods#path}
|
|
72
|
+
# @param style [Hash] a set of attributes and values to apply to matching nodes. For a full list of applicable to GraphViz nodes: http://www.graphviz.org/content/attrs
|
|
73
|
+
# @yieldparam node [IR::Node::QueryMethods] if provided, return +true+ to indicate a match
|
|
74
|
+
# @return [nil]
|
|
75
|
+
def style(name=nil, style={}, &block)
|
|
76
|
+
style, name = name, nil if name.is_a?(Hash)
|
|
77
|
+
if (name.nil? && block.nil?) || (name && block)
|
|
78
|
+
raise ArgumentError.new('Provide either a name or a block')
|
|
34
79
|
end
|
|
80
|
+
matcher = IR::NodeMatcher.new name || block
|
|
81
|
+
@graph.instance_eval {@style_matchers << [matcher, style]}
|
|
35
82
|
nil
|
|
36
83
|
end
|
|
37
84
|
end
|
data/lib/code_node/ir/graph.rb
CHANGED
|
@@ -3,129 +3,196 @@ require 'code_node/ir/node'
|
|
|
3
3
|
module CodeNode
|
|
4
4
|
module IR
|
|
5
5
|
|
|
6
|
+
# A collection of {Node}s
|
|
6
7
|
class Graph
|
|
7
8
|
|
|
8
|
-
#
|
|
9
|
-
|
|
9
|
+
# {Graph} methods which are useful in templates
|
|
10
|
+
module TemplateMethods
|
|
11
|
+
|
|
12
|
+
# Iterate through each {Node} with {Node::QueryMethods#class?} in the graph
|
|
13
|
+
# @yieldparam node [Node::TemplateMethods] a class node. Does not yield ignored nodes.
|
|
14
|
+
# @return [nil]
|
|
15
|
+
def each_class(&block)
|
|
16
|
+
@nodes.values.select do |node|
|
|
17
|
+
node.class?
|
|
18
|
+
end.sort.each &block
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Iterate through each {Node} with {Node::QueryMethods#module?} in the graph
|
|
22
|
+
# @yieldparam node [Node::TemplateMethods] a module node. Does not yield ignored nodes.
|
|
23
|
+
# @return [nil]
|
|
24
|
+
def each_module(&block)
|
|
25
|
+
@nodes.values.select do |node|
|
|
26
|
+
node.module?
|
|
27
|
+
end.sort.each &block
|
|
28
|
+
end
|
|
10
29
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
# Iterate through each containment relation in the graph
|
|
31
|
+
# @example
|
|
32
|
+
# # a -> b (A contains B)
|
|
33
|
+
# module A
|
|
34
|
+
# module B
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
# @yieldparam a [Node::TemplateMethods] the container node
|
|
38
|
+
# @yieldparam b [Node::TemplateMethods] the contained node
|
|
39
|
+
# @return [nil]
|
|
40
|
+
def each_containment(&block)
|
|
41
|
+
@nodes.values.sort.each do |node|
|
|
42
|
+
if node.parent
|
|
43
|
+
block.call node.parent, node
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
19
47
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
# Iterate through each inheritance relation in the graph
|
|
49
|
+
# @example
|
|
50
|
+
# # a -> b (A inherits from B)
|
|
51
|
+
# class A < B
|
|
52
|
+
# end
|
|
53
|
+
# @yieldparam a [Node::TemplateMethods] the derived class node
|
|
54
|
+
# @yieldparam b [Node::TemplateMethods] the super class node
|
|
55
|
+
# @return [nil]
|
|
56
|
+
def each_inheritance(&block)
|
|
57
|
+
@nodes.values.sort.each do |node|
|
|
58
|
+
if node.super_class_node
|
|
59
|
+
block.call node, node.super_class_node
|
|
60
|
+
end
|
|
30
61
|
end
|
|
31
|
-
x << s[1]
|
|
32
|
-
x.reverse
|
|
33
|
-
elsif s[0] == :self
|
|
34
|
-
@scope.last.mark_as_singleton
|
|
35
|
-
nil
|
|
36
62
|
end
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
|
|
64
|
+
# Iterate through each inclusion relation in the graph
|
|
65
|
+
# @example
|
|
66
|
+
# # a -> b (A includes B)
|
|
67
|
+
# module A
|
|
68
|
+
# include B
|
|
69
|
+
# end
|
|
70
|
+
# @yieldparam a [Node::TemplateMethods] the which includes
|
|
71
|
+
# @yieldparam b [Node::TemplateMethods] the included node
|
|
72
|
+
# @return [nil]
|
|
73
|
+
def each_inclusion(&block)
|
|
74
|
+
@nodes.values.sort.each do |node|
|
|
75
|
+
node.inclusions.each do |other|
|
|
76
|
+
block.call node, other
|
|
77
|
+
end
|
|
44
78
|
end
|
|
45
|
-
else
|
|
46
|
-
Node.new name, :parent => @scope.last, :node_type => node_type
|
|
47
79
|
end
|
|
48
80
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
# Iterate through each extension relation in the graph
|
|
82
|
+
# @example
|
|
83
|
+
# # a -> b (A extends B)
|
|
84
|
+
# module A
|
|
85
|
+
# extend B
|
|
86
|
+
# end
|
|
87
|
+
# @yieldparam a [Node::TemplateMethods] the which extends
|
|
88
|
+
# @yieldparam b [Node::TemplateMethods] the extended node
|
|
89
|
+
# @return [nil]
|
|
90
|
+
def each_extension(&block)
|
|
91
|
+
@nodes.values.sort.each do |node|
|
|
92
|
+
node.extensions.each do |other|
|
|
93
|
+
block.call node, other
|
|
94
|
+
end
|
|
95
|
+
end
|
|
54
96
|
end
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def <<(node)
|
|
59
|
-
@nodes[node.path] ||= node
|
|
60
|
-
@nodes[node.path]
|
|
97
|
+
|
|
61
98
|
end
|
|
62
99
|
|
|
63
|
-
#
|
|
64
|
-
# @
|
|
65
|
-
|
|
66
|
-
def each_class(&block)
|
|
67
|
-
@nodes.values.select do |node|
|
|
68
|
-
node.class? && !should_exclude?(node)
|
|
69
|
-
end.sort.each &block
|
|
70
|
-
end
|
|
100
|
+
# {Graph} methods used during the graph building phase
|
|
101
|
+
# @api developer
|
|
102
|
+
module BuilderMethods
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
# @yield [Node] a module node. Does not yield ignored nodes.
|
|
74
|
-
# @return [nil]
|
|
75
|
-
def each_module(&block)
|
|
76
|
-
@nodes.values.select do |node|
|
|
77
|
-
node.module? && !should_exclude?(node)
|
|
78
|
-
end.sort.each &block
|
|
79
|
-
end
|
|
104
|
+
attr_reader :scope
|
|
80
105
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
106
|
+
def apply_styles
|
|
107
|
+
@nodes.each_value do |node|
|
|
108
|
+
@style_matchers.each do |pair|
|
|
109
|
+
if pair[0].matches? node
|
|
110
|
+
node.style.update pair[1]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
86
113
|
end
|
|
87
114
|
end
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
115
|
+
|
|
116
|
+
# @return [FixNum] were any more nodes pruned?
|
|
117
|
+
def prune
|
|
118
|
+
prunees = []
|
|
119
|
+
@nodes.each_value do |node|
|
|
120
|
+
if @exclude_matchers.any? {|m| m.matches? node}
|
|
121
|
+
prunees << node
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
prunees.each do |node|
|
|
125
|
+
puts " #{node.path}"
|
|
126
|
+
node.prune
|
|
127
|
+
@nodes.delete node.path
|
|
95
128
|
end
|
|
129
|
+
prunees.length
|
|
96
130
|
end
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
131
|
+
|
|
132
|
+
# Find a node or create it and add it to the graph
|
|
133
|
+
# @api developer
|
|
134
|
+
# @param node_type [Symbol] either <tt>:module</tt> or <tt>:class</tt>
|
|
135
|
+
# @param s [Symbol, Sexp] either flat name, or a Sexp representing a color (<tt>:</tt>) separated path.
|
|
136
|
+
# @yieldparam node [Node]
|
|
137
|
+
# @return [Node]
|
|
138
|
+
def node_for(node_type, s, opt={}, &block)
|
|
139
|
+
name = if s.is_a? Symbol
|
|
140
|
+
s
|
|
141
|
+
elsif s[0] == :const
|
|
142
|
+
s[1]
|
|
143
|
+
elsif s[0] == :colon2
|
|
144
|
+
x = []
|
|
145
|
+
while s[0] == :colon2
|
|
146
|
+
x << s[2] ; s = s[1]
|
|
105
147
|
end
|
|
148
|
+
x << s[1]
|
|
149
|
+
x.reverse
|
|
150
|
+
elsif s[0] == :self
|
|
151
|
+
@scope.last.mark_as_singleton
|
|
152
|
+
nil
|
|
106
153
|
end
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if !should_exclude?(node) && !should_exclude?(other)
|
|
115
|
-
block.call node, other
|
|
154
|
+
return if name.nil?
|
|
155
|
+
|
|
156
|
+
node = if opt[:not_sure_if_nested]
|
|
157
|
+
if @scope.length > 1 && @scope[-2].find(name)
|
|
158
|
+
@scope[-2].find name
|
|
159
|
+
else
|
|
160
|
+
Node.new name, :node_type => node_type
|
|
116
161
|
end
|
|
162
|
+
else
|
|
163
|
+
Node.new name, :parent => @scope.last, :node_type => node_type
|
|
117
164
|
end
|
|
165
|
+
|
|
166
|
+
node = self << node
|
|
167
|
+
unless block.nil? || node.nil?
|
|
168
|
+
@scope << node
|
|
169
|
+
block.call node
|
|
170
|
+
@scope.pop
|
|
171
|
+
end
|
|
172
|
+
node
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Add the given node to the graph and return it. If a node with the same path is already in the graph, do not add it again, and return the original node.
|
|
176
|
+
# @param node [Node] a node to add to the graph
|
|
177
|
+
# @return [Node] the newly added node, or another node with the same path which was already in the graph
|
|
178
|
+
def <<(node)
|
|
179
|
+
@nodes[node.path] ||= node
|
|
180
|
+
@nodes[node.path]
|
|
118
181
|
end
|
|
182
|
+
|
|
119
183
|
end
|
|
120
|
-
|
|
121
|
-
private
|
|
122
184
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
185
|
+
include BuilderMethods
|
|
186
|
+
include TemplateMethods
|
|
187
|
+
|
|
188
|
+
# @api developer
|
|
189
|
+
def initialize
|
|
190
|
+
@exclude_matchers = []
|
|
191
|
+
@style_matchers = []
|
|
192
|
+
@nodes = {}
|
|
193
|
+
@scope = []
|
|
127
194
|
end
|
|
195
|
+
|
|
128
196
|
end
|
|
129
|
-
|
|
130
197
|
end
|
|
131
198
|
end
|
data/lib/code_node/ir/node.rb
CHANGED
|
@@ -9,44 +9,230 @@ module CodeNode
|
|
|
9
9
|
|
|
10
10
|
include Cog::Generator
|
|
11
11
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
# Node methods which are useful in templates
|
|
13
|
+
module TemplateMethods
|
|
14
|
+
|
|
15
|
+
# @return [String] the name of the node. Not necessarilly unique.
|
|
16
|
+
# @see {#path}
|
|
17
|
+
attr_reader :name
|
|
18
|
+
|
|
19
|
+
# @return [Node] node which contains this node
|
|
20
|
+
# @example
|
|
21
|
+
# module Foo # Foo is the parent
|
|
22
|
+
# module Bar # to Bar
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
attr_reader :parent
|
|
26
|
+
|
|
27
|
+
# The child nodes of this node
|
|
28
|
+
# @return [Hash<String,Node>] a mapping from node {#path} names to nodes
|
|
29
|
+
# @example
|
|
30
|
+
# module Foo # Foo has children
|
|
31
|
+
# module Bar # Bar
|
|
32
|
+
# end # and
|
|
33
|
+
# class Car # Car
|
|
34
|
+
# end
|
|
35
|
+
# end
|
|
36
|
+
attr_reader :children
|
|
37
|
+
|
|
38
|
+
# @return [Node,nil] the super class of this class. Will be +nil+ for modules.
|
|
39
|
+
def super_class_node
|
|
40
|
+
@inherits_from
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Array<Node>] module nodes for which this node has an +include+ statement
|
|
44
|
+
def inclusions
|
|
45
|
+
@includes.values.sort
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Array<Node>] module nodes for which this node has an +extend+ statement
|
|
49
|
+
def extensions
|
|
50
|
+
@extends.values.sort
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [String] fully qualified identifier for the node in the form <tt>Foo_Bar_Car</tt>. Ideal for graphviz identifiers.
|
|
54
|
+
def key
|
|
55
|
+
@path.join '_'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [String] how the node will be labelled in the graph. Nodes without parents display their full {#path}, while nodes with parents only display their {#name}.
|
|
59
|
+
def label
|
|
60
|
+
@parent.nil? ? path : name
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Stamp the accumulated GraphViz styles in a format suitable for inclusion in a <tt>.dot</tt> file
|
|
64
|
+
# @return [String] style in the form <tt>key1="value1" key2="value2"</tt>...
|
|
65
|
+
def stamp_styles
|
|
66
|
+
x = []
|
|
67
|
+
style.each_pair do |key, value|
|
|
68
|
+
x << "#{key}=\"#{value}\""
|
|
69
|
+
end
|
|
70
|
+
x.join ' '
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# {Node} methods which are useful for querying in matchers
|
|
76
|
+
module QueryMethods
|
|
15
77
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# end
|
|
21
|
-
# end
|
|
22
|
-
attr_reader :parent
|
|
78
|
+
# @return [Boolean] does this node represent a module?
|
|
79
|
+
def module?
|
|
80
|
+
@node_type == :module
|
|
81
|
+
end
|
|
23
82
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
83
|
+
# @return [Boolean] does this node represent a class?
|
|
84
|
+
def class?
|
|
85
|
+
@node_type == :class
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @return [String] fully qualified name of the node in the form <tt>'Foo::Bar::Car'</tt>. Not good as a graphviz identifier because of the colon (<tt>:</tt>) characters. Use {TemplateMethods#key} for graphviz identifiers instead.
|
|
89
|
+
def path
|
|
90
|
+
@path.join '::'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @param path [String] a class or module path in the form <tt>Foo::Bar::Car</tt>
|
|
94
|
+
# @return [Boolean] does this node include a {#module?} node with the given {#path}?
|
|
95
|
+
def includes?(path)
|
|
96
|
+
@includes.member? path
|
|
97
|
+
end
|
|
34
98
|
|
|
99
|
+
# @param path [String] a class or module path in the form <tt>Foo::Bar::Car</tt>
|
|
100
|
+
# @return [Boolean] does this node extend a {#module?} node with the given {#path}?
|
|
101
|
+
def extends?(path)
|
|
102
|
+
@extends.member? path
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @param path [String] a class or module path in the form <tt>Foo::Bar::Car</tt>
|
|
106
|
+
# @return [Boolean] does this node inherit from (directly or indirectly) a {#class?} node with the given {#path}? Note that a node inherits from itself according to this method. Recursively checks the ancestry of the node.
|
|
107
|
+
def inherits_from?(path)
|
|
108
|
+
self.path == path || @inherits_from && (@inherits_from.path == path || @inherits_from.inherits_from?(path))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @return [Boolean] whether or not this node represents a singleton module. A singleton module is one which contains an <tt>extend self</tt> statement.
|
|
112
|
+
def singleton?
|
|
113
|
+
@singleton
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @return [Boolean] whether or not this node is an island. An island is a node with no connections to other nodes.
|
|
117
|
+
def island?
|
|
118
|
+
([@parent, @inherits_from].all?(&:nil?) &&
|
|
119
|
+
[@children, @inherited_by, @extends, @includes, @extended_by, @included_by].all?(&:empty?))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# {Node} methods used during the graph building phase
|
|
125
|
+
# @api developer
|
|
126
|
+
module BuilderMethods
|
|
127
|
+
|
|
128
|
+
attr_reader :style
|
|
129
|
+
|
|
130
|
+
# Find a node contained in this node, or contained in this nodes {#parent}, recursively.
|
|
131
|
+
# @return [Node, nil]
|
|
132
|
+
def find(name)
|
|
133
|
+
path = (@path + [name].flatten).join '::'
|
|
134
|
+
@children[path] || (@parent && @parent.find(name))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Add other as a child of this node
|
|
138
|
+
# @param other [Node] another node
|
|
139
|
+
# @return [nil]
|
|
140
|
+
def contains(other)
|
|
141
|
+
this = self
|
|
142
|
+
@children[other.path] = other
|
|
143
|
+
other.instance_eval {@parent = this}
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Add other as the super class of this node
|
|
148
|
+
# @param other [Node] another node
|
|
149
|
+
# @return [nil]
|
|
150
|
+
def inherits_from(other)
|
|
151
|
+
this = self
|
|
152
|
+
@inherits_from = other
|
|
153
|
+
other.instance_eval {@inherited_by[this.path] = this}
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Add other to this nodes includes set
|
|
158
|
+
# @param other [Node] another node
|
|
159
|
+
# @return [nil]
|
|
160
|
+
def includes(other)
|
|
161
|
+
this = self
|
|
162
|
+
@includes[other.path] = other
|
|
163
|
+
other.instance_eval {@included_by[this.path] = this}
|
|
164
|
+
nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Add other to this nodes extends set
|
|
168
|
+
# @param other [Node] another node
|
|
169
|
+
# @return [nil]
|
|
170
|
+
def extends(other)
|
|
171
|
+
this = self
|
|
172
|
+
@extends[other.path] = other
|
|
173
|
+
other.instance_eval {@extended_by[this.path] = this}
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Mark this module node as a singleton
|
|
178
|
+
# @return [nil]
|
|
179
|
+
def mark_as_singleton
|
|
180
|
+
throw :NodeNotAModule unless module?
|
|
181
|
+
@singleton = true
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Remove any relations involving this node
|
|
185
|
+
def prune
|
|
186
|
+
this = self
|
|
187
|
+
if @inherits_from
|
|
188
|
+
@inherits_from.instance_eval {@inherited_by.delete this.path}
|
|
189
|
+
end
|
|
190
|
+
@inherited_by.each_value do |other|
|
|
191
|
+
other.instance_eval {@inherits_from = nil}
|
|
192
|
+
end
|
|
193
|
+
if @parent
|
|
194
|
+
@parent.instance_eval {@children.delete this.path}
|
|
195
|
+
end
|
|
196
|
+
@children.each_value do |other|
|
|
197
|
+
other.instance_eval {@parent = nil}
|
|
198
|
+
end
|
|
199
|
+
@includes.each_value do |other|
|
|
200
|
+
other.instance_eval {@included_by.delete this.path}
|
|
201
|
+
end
|
|
202
|
+
@included_by.each_value do |other|
|
|
203
|
+
other.instance_eval {@includes.delete this.path}
|
|
204
|
+
end
|
|
205
|
+
@extends.each_value do |other|
|
|
206
|
+
other.instance_eval {@extended_by.delete this.path}
|
|
207
|
+
end
|
|
208
|
+
@extended_by.each_value do |other|
|
|
209
|
+
other.instance_eval {@extends.delete this.path}
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
include BuilderMethods
|
|
215
|
+
include TemplateMethods
|
|
216
|
+
include QueryMethods
|
|
217
|
+
|
|
218
|
+
# Initialize a node
|
|
219
|
+
# @api developer
|
|
35
220
|
# @param name [String, Array]
|
|
36
|
-
# @option opt [Node] :parent (nil)
|
|
221
|
+
# @option opt [Node] :parent (nil) if provided, the parent's path is prepended to name. The parent child is not made at this time though. See {BuilderMethods#contains} instead.
|
|
37
222
|
# @option opt [Symbol] :node_type (:module) either <tt>:module</tt> or <tt>:class</tt>
|
|
38
223
|
def initialize(name, opt={})
|
|
224
|
+
@style = {}
|
|
39
225
|
@node_type = opt[:node_type] || :module
|
|
40
|
-
|
|
41
|
-
parent_path = @parent ? @parent.instance_eval {@path} : []
|
|
226
|
+
parent_path = opt[:parent] ? opt[:parent].instance_eval {@path} : []
|
|
42
227
|
@path = if name.is_a? Array
|
|
43
228
|
parent_path + name
|
|
44
229
|
else
|
|
45
230
|
parent_path + [name]
|
|
46
231
|
end
|
|
47
232
|
@name = @path.last
|
|
48
|
-
@parent
|
|
233
|
+
@parent = nil
|
|
49
234
|
@children = {}
|
|
235
|
+
@inherits_from = nil
|
|
50
236
|
@inherited_by = {}
|
|
51
237
|
@includes = {}
|
|
52
238
|
@included_by = {}
|
|
@@ -54,109 +240,11 @@ module CodeNode
|
|
|
54
240
|
@extended_by = {}
|
|
55
241
|
end
|
|
56
242
|
|
|
57
|
-
def find(name)
|
|
58
|
-
path = (@path + [name].flatten).join '::'
|
|
59
|
-
@children[path] || (@parent && @parent.find(name))
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# @return [String] fully qualified identifier for the node in the form <tt>Foo_Bar_Car</tt>. Ideal for graphviz identifiers.
|
|
63
|
-
def key
|
|
64
|
-
@path.join '_'
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# @return [String] fully qualified name of the node in the form <tt>'Foo::Bar::Car'</tt>. Not good as a graphviz identifier because of the colon (+:+) characters. Use {#key} for graphviz identifiers instead.
|
|
68
|
-
def path
|
|
69
|
-
@path.join '::'
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# @return [String] how the node will be labelled in the graph. Nodes without parents display their full {#path}, while nodes with parents only display their {#name}.
|
|
73
|
-
def label
|
|
74
|
-
@parent.nil? ? path : name
|
|
75
|
-
end
|
|
76
|
-
|
|
77
243
|
# @return [FixNum] order nodes by {#path}
|
|
78
244
|
def <=>(other)
|
|
79
245
|
path <=> other.path
|
|
80
246
|
end
|
|
81
247
|
|
|
82
|
-
# @return [Boolean] whether or not this node represents a module
|
|
83
|
-
def module?
|
|
84
|
-
@node_type == :module
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# @return [Boolean] whether or not this node represents a class
|
|
88
|
-
def class?
|
|
89
|
-
@node_type == :class
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# @param other [Node]
|
|
93
|
-
def inherits_from(other)
|
|
94
|
-
this = self
|
|
95
|
-
@inherits_from = other
|
|
96
|
-
other.instance_eval {@inherited_by[this.path] = this}
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# @return [Node,nil] the super class of this class. Will be +nil+ for modules.
|
|
100
|
-
def super_class_node
|
|
101
|
-
@inherits_from
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# @param other [Node]
|
|
105
|
-
def includes(other)
|
|
106
|
-
this = self
|
|
107
|
-
@includes[other.path] = other
|
|
108
|
-
other.instance_eval {@included_by[this.path] = this}
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# @return [Array<Node>] module nodes for which this node has an +include+ statement
|
|
112
|
-
def inclusions
|
|
113
|
-
@includes.values.sort
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# @param other [Node]
|
|
117
|
-
def extends(other)
|
|
118
|
-
this = self
|
|
119
|
-
@extends[other.path] = other
|
|
120
|
-
other.instance_eval {@extended_by[this.path] = this}
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# @return [Array<Node>] module nodes for which this node has an +extend+ statement
|
|
124
|
-
def extensions
|
|
125
|
-
@extends.values.sort
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Set the given class node as the super class of this node
|
|
129
|
-
# @param super_node [Node] a {#class?} node
|
|
130
|
-
# @return [nil]
|
|
131
|
-
def inherits_from=(super_node)
|
|
132
|
-
throw :NodeNotAClass unless class?
|
|
133
|
-
throw :SuperNodeNotAClass unless super_node.class?
|
|
134
|
-
@inherits_from = super_node
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# @return [Boolean] whether or not this node inherits from a given node. Note that a node does inherit from itself, according to this method. Recursively checks the ancestry of the node.
|
|
138
|
-
def inherits_from?(k)
|
|
139
|
-
key == k || @inherits_from && (@inherits_from.key == k || @inherits_from.inherits_from?(k))
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Mark this module node as a singleton
|
|
143
|
-
# @return [nil]
|
|
144
|
-
def mark_as_singleton
|
|
145
|
-
throw :NodeNotAModule unless module?
|
|
146
|
-
@singleton = true
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# @return [Boolean] whether or not this node represents a singleton module
|
|
150
|
-
def singleton?
|
|
151
|
-
@singleton
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# @return [Boolean] whether or not this node is an island. An island is a node with no connections to other nodes.
|
|
155
|
-
def island?
|
|
156
|
-
([@parent, @inherits_from].all?(&:nil?) &&
|
|
157
|
-
[@children, @inherited_by, @extends, @includes, @extended_by, @included_by].all?(&:empty?))
|
|
158
|
-
end
|
|
159
|
-
|
|
160
248
|
end
|
|
161
249
|
end
|
|
162
250
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module CodeNode
|
|
2
|
+
module IR
|
|
3
|
+
|
|
4
|
+
# @api developer
|
|
5
|
+
# Encapsulates a pattern match test for nodes
|
|
6
|
+
class NodeMatcher
|
|
7
|
+
|
|
8
|
+
def initialize(pattern)
|
|
9
|
+
@pattern = pattern
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @param node [Node]
|
|
13
|
+
# @return [Boolean] does the node match?
|
|
14
|
+
def matches?(node)
|
|
15
|
+
if @pattern.is_a? Proc
|
|
16
|
+
@pattern.call node
|
|
17
|
+
elsif @pattern.is_a? Regexp
|
|
18
|
+
@pattern =~ node.path
|
|
19
|
+
else
|
|
20
|
+
@pattern.to_s == node.path
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module CodeNode
|
|
2
2
|
|
|
3
|
+
# @api developer
|
|
3
4
|
# Walks a Sexp representing a ruby file looking for classes and modules.
|
|
4
5
|
class SexpWalker
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ module CodeNode
|
|
|
7
8
|
#
|
|
8
9
|
# All files in a code base should be walked once in <tt>:find_nodes</tt> mode, and then walked again in <tt>:find_relations</tt> mode.
|
|
9
10
|
#
|
|
10
|
-
# @param graph [Graph] a graph to which nodes and relations will be added
|
|
11
|
+
# @param graph [IR::Graph] a graph to which nodes and relations will be added
|
|
11
12
|
# @param sexp [Sexp] the root sexp of a ruby file
|
|
12
13
|
# @option opt [Symbol] :mode (:find_nodes) one of <tt>:find_nodes</tt> or <tt>:find_relations</tt>
|
|
13
14
|
def initialize(graph, sexp, opt={})
|
|
@@ -22,7 +23,7 @@ module CodeNode
|
|
|
22
23
|
def walk(s = nil)
|
|
23
24
|
s ||= @root
|
|
24
25
|
if [:module, :class].member?(s[0])
|
|
25
|
-
@graph.node_for(s[0], s[1]) do |node|
|
|
26
|
+
node = @graph.node_for(s[0], s[1]) do |node|
|
|
26
27
|
if find_relations? && s[0] == :class && !s[2].nil?
|
|
27
28
|
super_node = @graph.node_for :class, s[2], :not_sure_if_nested => true
|
|
28
29
|
node.inherits_from super_node unless super_node.nil?
|
|
@@ -32,6 +33,9 @@ module CodeNode
|
|
|
32
33
|
walk(c) if c.class == Sexp
|
|
33
34
|
end
|
|
34
35
|
end
|
|
36
|
+
if find_relations? && !@graph.scope.empty?
|
|
37
|
+
@graph.scope.last.contains node
|
|
38
|
+
end
|
|
35
39
|
elsif find_relations? && s[0] == :call && s.length >= 4 && [:extend, :include].member?(s[2]) && !@graph.scope.empty?
|
|
36
40
|
node = @graph.node_for :module, s[3], :not_sure_if_nested => true
|
|
37
41
|
unless node.nil?
|
data/lib/code_node/version.rb
CHANGED
data/lib/code_node.rb
CHANGED
|
@@ -15,6 +15,7 @@ module CodeNode
|
|
|
15
15
|
# @option opt [Symbol] :ruby_version (:ruby19) either <tt>:ruby18</tt> or <tt>:ruby19</tt>, indicating which parser to use
|
|
16
16
|
# @yield [GraphDefinition] define rules for creating the graph
|
|
17
17
|
def self.graph(graph_name, opt={}, &block)
|
|
18
|
+
feedback_color = :white
|
|
18
19
|
root = Cog::Config.instance.project_source_path
|
|
19
20
|
@graph = IR::Graph.new
|
|
20
21
|
graph_definer = DSL::GraphDefiner.new @graph
|
|
@@ -29,13 +30,12 @@ module CodeNode
|
|
|
29
30
|
|
|
30
31
|
sexp = []
|
|
31
32
|
[:find_nodes, :find_relations].each_with_index do |mode, pass|
|
|
32
|
-
puts "#{(pass+1).ordinalize} pass: #{mode.to_s.gsub('_', ' ')}".color(
|
|
33
|
-
|
|
33
|
+
puts "#{(pass+1).ordinalize} pass: #{mode.to_s.gsub('_', ' ')}".color(feedback_color)
|
|
34
34
|
Dir.glob("#{root}/**/*.rb").each_with_index do |filename, i|
|
|
35
35
|
sexp[i] ||= begin
|
|
36
36
|
rp.parse(File.read filename)
|
|
37
37
|
rescue Racc::ParseError
|
|
38
|
-
STDERR.write "{filename.relative_to_project_root}, skipped...\n".color(:
|
|
38
|
+
STDERR.write "#{filename.relative_to_project_root}, skipped...\n".color(:yellow)
|
|
39
39
|
nil
|
|
40
40
|
end
|
|
41
41
|
if sexp[i]
|
|
@@ -44,6 +44,19 @@ module CodeNode
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
# Apply styles before pruning because some relations may be destroyed while pruning
|
|
49
|
+
puts "Applying styles".color(feedback_color)
|
|
50
|
+
@graph.apply_styles
|
|
51
|
+
|
|
52
|
+
# Prune the graph according to ignore rules.
|
|
53
|
+
# We keep pruning until there are no more changes because some rules don't apply the first time (for example: &:island?)
|
|
54
|
+
puts "Pruning nodes".color(feedback_color)
|
|
55
|
+
i = 1
|
|
56
|
+
while (x = @graph.prune) > 0
|
|
57
|
+
puts " #{x} nodes pruned on #{i.ordinalize} pass".color(feedback_color)
|
|
58
|
+
i += 1
|
|
59
|
+
end
|
|
47
60
|
|
|
48
61
|
# Activate code_node while rendering templates
|
|
49
62
|
# so that cog will be able to find code_node templates
|
metadata
CHANGED
|
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
+
- 1
|
|
8
9
|
- 0
|
|
9
|
-
|
|
10
|
-
version: 0.0.2
|
|
10
|
+
version: 0.1.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Kevin Tonon
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2012-11-
|
|
18
|
+
date: 2012-11-20 00:00:00 Z
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
21
21
|
name: cog
|
|
@@ -119,6 +119,7 @@ files:
|
|
|
119
119
|
- lib/code_node/dsl.rb
|
|
120
120
|
- lib/code_node/ir/graph.rb
|
|
121
121
|
- lib/code_node/ir/node.rb
|
|
122
|
+
- lib/code_node/ir/node_matcher.rb
|
|
122
123
|
- lib/code_node/ir.rb
|
|
123
124
|
- lib/code_node/sexp_walker.rb
|
|
124
125
|
- lib/code_node/version.rb
|