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.
@@ -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