code_node 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- g.ignore /ClassMethods$/
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
- # Speficy an ignore rule for the graph.
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
- # @param name [String, Regexp, nil] fully qualified path or regular expression which will be compared to {IR::Node#path}
22
- # @yield [Node] if provided, return +true+ to ignore the node
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
- if block
29
- @graph.instance_eval {@exclude_procs << block}
30
- elsif name.is_a? Regexp
31
- @graph.instance_eval {@exclude_patterns << name}
32
- else
33
- @graph.instance_eval {@exclude_paths << name}
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
@@ -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
- # @api developer
9
- attr_reader :scope
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
- # @api developer
12
- def initialize
13
- @exclude_paths = []
14
- @exclude_patterns = []
15
- @exclude_procs = []
16
- @nodes = {}
17
- @scope = []
18
- end
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
- # @api developer
21
- def node_for(node_type, s, opt={}, &block)
22
- name = if s.is_a? Symbol
23
- s
24
- elsif s[0] == :const
25
- s[1]
26
- elsif s[0] == :colon2
27
- x = []
28
- while s[0] == :colon2
29
- x << s[2] ; s = s[1]
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
- return if name.nil?
38
-
39
- node = if opt[:not_sure_if_nested]
40
- if @scope.length > 1 && @scope[-2].find(name)
41
- @scope[-2].find name
42
- else
43
- Node.new name, :node_type => node_type
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
- node = self << node
50
- unless block.nil? || node.nil?
51
- @scope << node
52
- block.call node
53
- @scope.pop
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
- node
56
- end
57
-
58
- def <<(node)
59
- @nodes[node.path] ||= node
60
- @nodes[node.path]
97
+
61
98
  end
62
99
 
63
- # Iterate through each {Node} with {Node#class?} in the graph
64
- # @yield [Node] a class node. Does not yield ignored nodes.
65
- # @return [nil]
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
- # Iterate through each {Node} with {Node#module?} in the graph
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
- # Iterate through each containment relation in the graph
82
- def each_containment(&block)
83
- @nodes.values.sort.each do |node|
84
- if node.parent && !should_exclude?(node) && !should_exclude?(node.parent)
85
- block.call node.parent, node
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
- end
89
-
90
- # Iterate through each inheritance relation in the graph
91
- def each_inheritance(&block)
92
- @nodes.values.sort.each do |node|
93
- if node.super_class_node && !should_exclude?(node) && !should_exclude?(node.super_class_node)
94
- block.call node, node.super_class_node
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
- end
98
-
99
- # Iterate through each inclusion relation in the graph
100
- def each_inclusion(&block)
101
- @nodes.values.sort.each do |node|
102
- node.inclusions.each do |other|
103
- if !should_exclude?(node) && !should_exclude?(other)
104
- block.call node, other
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
- end
108
- end
109
-
110
- # Iterate through each extension relation in the graph
111
- def each_extension(&block)
112
- @nodes.values.sort.each do |node|
113
- node.extensions.each do |other|
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
- def should_exclude?(node)
124
- @exclude_paths.any? {|path| path == node.path} ||
125
- @exclude_patterns.any? {|pattern| pattern =~ node.path} ||
126
- @exclude_procs.any? {|block| block.call(node)}
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
@@ -9,44 +9,230 @@ module CodeNode
9
9
 
10
10
  include Cog::Generator
11
11
 
12
- # @return [String] the name of the node. Not necessarilly unique.
13
- # @see {#path}
14
- attr_reader :name
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
- # @return [Node] node which contains this node
17
- # @example
18
- # module Foo # Foo is the parent
19
- # module Bar # to Bar
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
- # The child nodes of this node
25
- # @return [Hash<String,Node>] a mapping from node {#path} names to nodes
26
- # @example
27
- # module Foo # Foo has children
28
- # module Bar # Bar
29
- # end # and
30
- # class Car # Car
31
- # end
32
- # end
33
- attr_reader :children
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
- @parent = opt[:parent]
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.children[path] = self unless @parent.nil?
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?
@@ -1,5 +1,5 @@
1
1
  module Code_node
2
2
  unless const_defined? :VERSION
3
- VERSION = '0.0.2'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
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(:cyan)
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(:red)
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
- - 2
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-19 00:00:00 Z
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