code_node 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
- <% if tool.explicit_require %>require '<%= tool.path %>'
2
- <% else %>require '<%= tool.name %>'
1
+ <% if Cog.active_tool.explicit_require %>require '<%= Cog.active_tool.path %>'
2
+ <% else %>require '<%= Cog.active_tool.name %>'
3
3
  <% end %>
4
4
 
5
5
  # Makes a graph of the project_source_path.
data/lib/code_node.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  $LOAD_PATH << File.join(File.dirname(__FILE__))
2
2
  require 'code_node/version'
3
3
  require 'code_node/dsl'
4
+ require 'code_node/graph_builder'
4
5
  require 'code_node/ir'
5
6
  require 'code_node/sexp_walker'
6
7
  require 'cog'
@@ -13,57 +14,20 @@ module CodeNode
13
14
 
14
15
  # @param graph_name [String] name of the dot file to generate (without +.dot+ extension)
15
16
  # @option opt [Symbol] :ruby_version (:ruby19) either <tt>:ruby18</tt> or <tt>:ruby19</tt>, indicating which parser to use
16
- # @yield [GraphDefinition] define rules for creating the graph
17
+ # @yieldparam graph [DSL::GraphDefiner] define rules for creating the graph
17
18
  def self.graph(graph_name, opt={}, &block)
18
- feedback_color = :white
19
- root = Cog.project_source_path
20
- @graph = IR::Graph.new
21
- graph_definer = DSL::GraphDefiner.new @graph
22
- block.call graph_definer
23
-
24
- rp = case opt[:ruby_version]
25
- when :ruby18
19
+ parser = if opt[:ruby_version] == :ruby18
26
20
  Ruby18Parser.new
27
21
  else
28
22
  Ruby19Parser.new
29
23
  end
30
24
 
31
- sexp = []
32
- [:find_nodes, :find_relations].each_with_index do |mode, pass|
33
- puts "#{(pass+1).ordinalize} pass: #{mode.to_s.gsub('_', ' ')}".color(feedback_color)
34
- Dir.glob("#{root}/**/*.rb").each_with_index do |filename, i|
35
- sexp[i] ||= begin
36
- rp.parse(File.read filename)
37
- rescue Racc::ParseError
38
- STDERR.write "#{filename.relative_to_project_root}, skipped...\n".color(:yellow)
39
- nil
40
- end
41
- if sexp[i]
42
- walker = SexpWalker.new @graph, sexp[i], :mode => mode
43
- walker.walk
44
- end
45
- end
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
60
-
61
- # Activate code_node while rendering templates
62
- # so that cog will be able to find code_node templates
63
- Cog.activate_tool 'code_node' do
64
- stamp 'code_node/graph.dot', "#{graph_name}.dot"
65
- end
66
-
67
- nil
25
+ GraphBuilder.new(graph_name, parser).
26
+ define(&block).
27
+ find_nodes.
28
+ find_relations.
29
+ finalize.
30
+ render
68
31
  end
32
+
69
33
  end
@@ -0,0 +1,102 @@
1
+ require 'cog'
2
+ require 'code_node/ir/graph'
3
+
4
+ module CodeNode
5
+
6
+ # Helps to build an {IR::Graph}
7
+ class GraphBuilder
8
+
9
+ # @return [IR::Graph] the graph being built
10
+ attr_reader :graph
11
+
12
+ # @param name [String] the name of the graph
13
+ # @param parser [Ruby18Parser, Ruby19Parser] a ruby parser instance
14
+ def initialize(name, parser)
15
+ @name = name
16
+ @graph = IR::Graph.new
17
+ @parser = parser
18
+ @sexp = [] # array of file sexp, one per file
19
+ end
20
+
21
+ # @return [Array<String>] paths to ruby source files
22
+ def sources
23
+ Dir.glob("#{Cog.project_source_path}/**/*.rb")
24
+ end
25
+
26
+ # Define the custom graph generation rules
27
+ # @return [self]
28
+ def define(&block)
29
+ block.call DSL::GraphDefiner.new(@graph)
30
+ self
31
+ end
32
+
33
+ # Search the sources for nodes
34
+ # @return [self]
35
+ def find_nodes
36
+ puts '1st pass: find nodes'
37
+ find :nodes
38
+ self
39
+ end
40
+
41
+ # Search the sources for relations
42
+ # @return [nil]
43
+ def find_relations
44
+ puts '2nd pass: find relations'
45
+ find :relations
46
+ self
47
+ end
48
+
49
+ # Apply styles and prune the graph
50
+ # @return [nil]
51
+ def finalize
52
+ # Apply styles before pruning because some relations may be destroyed while pruning
53
+ puts "Applying styles"
54
+ @graph.apply_styles
55
+
56
+ # Prune the graph according to ignore rules.
57
+ # We keep pruning until there are no more changes because some rules don't apply the first time (for example: &:island?)
58
+ puts "Pruning nodes"
59
+ i = 1
60
+ while (x = @graph.prune) > 0
61
+ puts " #{x} nodes pruned on #{i.ordinalize} pass"
62
+ i += 1
63
+ end
64
+ self
65
+ end
66
+
67
+ # Render the graph
68
+ # @return [nil]
69
+ def render
70
+ # Activate code_node while rendering templates
71
+ # so that cog will be able to find code_node templates
72
+ Cog.activate_tool 'code_node' do
73
+ stamp 'code_node/graph.dot', "#{@name}.dot"
74
+ end
75
+ nil
76
+ end
77
+
78
+ private
79
+
80
+ # @param type [Symbol] one of <tt>:nodes</tt> or <tt>:relations</tt>
81
+ # @return [nil]
82
+ def find(type)
83
+ sources.each_with_index do |filename, i|
84
+ @sexp[i] ||= parse filename
85
+ if @sexp[i]
86
+ walker = SexpWalker.new @graph, @sexp[i], :mode => "find_#{type}".to_sym
87
+ walker.walk
88
+ end
89
+ end
90
+ nil
91
+ end
92
+
93
+ # @param filename [String] path to the file to parse
94
+ # @return [Sexp]
95
+ def parse(filename)
96
+ @parser.parse(File.read filename)
97
+ rescue Racc::ParseError
98
+ STDERR.write "#{filename.relative_to_project_root}, skipped...\n".color(:yellow)
99
+ nil
100
+ end
101
+ end
102
+ end
@@ -1,4 +1,6 @@
1
1
  require 'code_node/ir/node'
2
+ require 'code_node/ir/graph/builder_methods'
3
+ require 'code_node/ir/graph/template_methods'
2
4
 
3
5
  module CodeNode
4
6
  module IR
@@ -6,188 +8,6 @@ module CodeNode
6
8
  # A collection of {Node}s
7
9
  class Graph
8
10
 
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
29
-
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
47
-
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
61
- end
62
- end
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
78
- end
79
- end
80
-
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
96
- end
97
-
98
- end
99
-
100
- # {Graph} methods used during the graph building phase
101
- # @api developer
102
- module BuilderMethods
103
-
104
- attr_reader :scope
105
-
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
113
- end
114
- end
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
128
- end
129
- prunees.length
130
- end
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]
147
- end
148
- x << s[1]
149
- x.reverse
150
- elsif s[0] == :self
151
- @scope.last.mark_as_singleton
152
- nil
153
- end
154
- return if name.nil?
155
-
156
- node = if opt[:not_sure_if_nested]
157
- candidate = if name.is_a?(Array)
158
- parts = name.dup
159
- n = !@scope.empty? && @scope.last.find(parts.shift)
160
- while n && !parts.empty?
161
- n = n.find parts.shift
162
- end
163
- n
164
- else
165
- !@scope.empty? && @scope.last.find(name)
166
- end
167
- candidate || Node.new(name, :node_type => node_type)
168
- else
169
- Node.new name, :parent => @scope.last, :node_type => node_type
170
- end
171
-
172
- node = self << node
173
- unless block.nil? || node.nil?
174
- @scope << node
175
- block.call node
176
- @scope.pop
177
- end
178
- node
179
- end
180
-
181
- # 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.
182
- # @param node [Node] a node to add to the graph
183
- # @return [Node] the newly added node, or another node with the same path which was already in the graph
184
- def <<(node)
185
- @nodes[node.path] ||= node
186
- @nodes[node.path]
187
- end
188
-
189
- end
190
-
191
11
  include BuilderMethods
192
12
  include TemplateMethods
193
13
 
@@ -0,0 +1,113 @@
1
+ module CodeNode
2
+ module IR
3
+ class Graph
4
+
5
+ # {Graph} methods used during the graph building phase
6
+ # @api developer
7
+ module BuilderMethods
8
+
9
+ attr_reader :scope
10
+
11
+ def apply_styles
12
+ @nodes.each_value do |node|
13
+ @style_matchers.each do |pair|
14
+ if pair[0].matches? node
15
+ node.style.update pair[1]
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ # @return [FixNum] were any more nodes pruned?
22
+ def prune
23
+ prunees = []
24
+ @nodes.each_value do |node|
25
+ if @exclude_matchers.any? {|m| m.matches? node}
26
+ prunees << node
27
+ end
28
+ end
29
+ prunees.each do |node|
30
+ puts " #{node.path}"
31
+ node.prune
32
+ @nodes.delete node.path
33
+ end
34
+ prunees.length
35
+ end
36
+
37
+ # Find a node or create it and add it to the graph
38
+ # @api developer
39
+ # @param node_type [Symbol] either <tt>:module</tt> or <tt>:class</tt>
40
+ # @param s [Symbol, Sexp] either flat name, or a Sexp representing a color (<tt>:</tt>) separated path.
41
+ # @option opt [Boolean] :not_sure_if_nested (false)
42
+ # @yieldparam node [Node]
43
+ # @return [Node]
44
+ def node_for(node_type, s, opt={}, &block)
45
+ name = determine_name s
46
+ return if name.nil?
47
+
48
+ node = if opt[:not_sure_if_nested]
49
+ search_for_node_in_parent(@scope.last, name) ||
50
+ Node.new(name, :node_type => node_type)
51
+ else
52
+ Node.new name, :parent => @scope.last, :node_type => node_type
53
+ end
54
+
55
+ node = add_or_find_duplicate node
56
+ unless block.nil?
57
+ @scope << node
58
+ block.call node
59
+ @scope.pop
60
+ end
61
+ node
62
+ end
63
+
64
+ # 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.
65
+ # @param node [Node] a node to add to the graph
66
+ # @return [Node] the newly added node, or another node with the same path which was already in the graph
67
+ def add_or_find_duplicate(node)
68
+ @nodes[node.path] ||= node
69
+ @nodes[node.path]
70
+ end
71
+
72
+ private
73
+
74
+ # @param s [Sexp]
75
+ # @return [Symbol, Array<Symbol>, nil]
76
+ def determine_name(s)
77
+ name = if s.is_a? Symbol
78
+ s
79
+ elsif s[0] == :const
80
+ s[1]
81
+ elsif s[0] == :colon2
82
+ x = []
83
+ while s[0] == :colon2
84
+ x << s[2] ; s = s[1]
85
+ end
86
+ x << s[1]
87
+ x.reverse
88
+ elsif s[0] == :self
89
+ @scope.last.mark_as_singleton
90
+ nil
91
+ end
92
+ end
93
+
94
+ # @param parent [Node, nil] the parent to search in
95
+ # @param name [Array<Symbol>, Symbol] name of the node to search for
96
+ # @return [Node, nil] the node, if found in the parent
97
+ def search_for_node_in_parent(parent, name)
98
+ if name.is_a?(Array)
99
+ parts = name.dup
100
+ n = parent && parent.find(parts.shift)
101
+ while n && !parts.empty?
102
+ n = n.find parts.shift
103
+ end
104
+ n
105
+ else
106
+ parent && parent.find(name)
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,97 @@
1
+ module CodeNode
2
+ module IR
3
+ class Graph
4
+
5
+ # {Graph} methods which are useful in templates
6
+ module TemplateMethods
7
+
8
+ # Iterate through each {Node} with {Node::QueryMethods#class?} in the graph
9
+ # @yieldparam node [Node::TemplateMethods] a class node. Does not yield ignored nodes.
10
+ # @return [nil]
11
+ def each_class(&block)
12
+ @nodes.values.select do |node|
13
+ node.class?
14
+ end.sort.each &block
15
+ end
16
+
17
+ # Iterate through each {Node} with {Node::QueryMethods#module?} in the graph
18
+ # @yieldparam node [Node::TemplateMethods] a module node. Does not yield ignored nodes.
19
+ # @return [nil]
20
+ def each_module(&block)
21
+ @nodes.values.select do |node|
22
+ node.module?
23
+ end.sort.each &block
24
+ end
25
+
26
+ # Iterate through each containment relation in the graph
27
+ # @example
28
+ # # a -> b (A contains B)
29
+ # module A
30
+ # module B
31
+ # end
32
+ # end
33
+ # @yieldparam a [Node::TemplateMethods] the container node
34
+ # @yieldparam b [Node::TemplateMethods] the contained node
35
+ # @return [nil]
36
+ def each_containment(&block)
37
+ @nodes.values.sort.each do |node|
38
+ if node.parent
39
+ block.call node.parent, node
40
+ end
41
+ end
42
+ end
43
+
44
+ # Iterate through each inheritance relation in the graph
45
+ # @example
46
+ # # a -> b (A inherits from B)
47
+ # class A < B
48
+ # end
49
+ # @yieldparam a [Node::TemplateMethods] the derived class node
50
+ # @yieldparam b [Node::TemplateMethods] the super class node
51
+ # @return [nil]
52
+ def each_inheritance(&block)
53
+ @nodes.values.sort.each do |node|
54
+ if node.super_class_node
55
+ block.call node, node.super_class_node
56
+ end
57
+ end
58
+ end
59
+
60
+ # Iterate through each inclusion relation in the graph
61
+ # @example
62
+ # # a -> b (A includes B)
63
+ # module A
64
+ # include B
65
+ # end
66
+ # @yieldparam a [Node::TemplateMethods] the which includes
67
+ # @yieldparam b [Node::TemplateMethods] the included node
68
+ # @return [nil]
69
+ def each_inclusion(&block)
70
+ @nodes.values.sort.each do |node|
71
+ node.inclusions.each do |other|
72
+ block.call node, other
73
+ end
74
+ end
75
+ end
76
+
77
+ # Iterate through each extension relation in the graph
78
+ # @example
79
+ # # a -> b (A extends B)
80
+ # module A
81
+ # extend B
82
+ # end
83
+ # @yieldparam a [Node::TemplateMethods] the which extends
84
+ # @yieldparam b [Node::TemplateMethods] the extended node
85
+ # @return [nil]
86
+ def each_extension(&block)
87
+ @nodes.values.sort.each do |node|
88
+ node.extensions.each do |other|
89
+ block.call node, other
90
+ end
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,4 +1,7 @@
1
1
  require 'cog'
2
+ require 'code_node/ir/node/builder_methods'
3
+ require 'code_node/ir/node/template_methods'
4
+ require 'code_node/ir/node/query_methods'
2
5
 
3
6
  module CodeNode
4
7
  module IR
@@ -8,210 +11,6 @@ module CodeNode
8
11
  class Node
9
12
 
10
13
  include Cog::Generator
11
-
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
77
-
78
- # @return [Boolean] does this node represent a module?
79
- def module?
80
- @node_type == :module
81
- end
82
-
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
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
- # TODO: need process all nodes first, marking for deletion on the first pass, because the superclass gets deleting and then the inherits from relation breaks down
109
- self.path == path || @inherits_from && @inherits_from.inherits_from?(path)
110
- end
111
-
112
- # @return [Boolean] whether or not this node represents a singleton module. A singleton module is one which contains an <tt>extend self</tt> statement.
113
- def singleton?
114
- @singleton
115
- end
116
-
117
- # @return [Boolean] whether or not this node is an island. An island is a node with no connections to other nodes.
118
- def island?
119
- ([@parent, @inherits_from].all?(&:nil?) &&
120
- [@children, @inherited_by, @extends, @includes, @extended_by, @included_by].all?(&:empty?))
121
- end
122
-
123
- end
124
-
125
- # {Node} methods used during the graph building phase
126
- # @api developer
127
- module BuilderMethods
128
-
129
- attr_reader :style
130
-
131
- # Find a node contained in this node, or contained in this nodes {#parent}, recursively.
132
- # @return [Node, nil]
133
- def find(name)
134
- path = (@path + [name].flatten).join '::'
135
- @children[path] || (@parent && @parent.find(name))
136
- end
137
-
138
- # Add other as a child of this node
139
- # @param other [Node] another node
140
- # @return [nil]
141
- def contains(other)
142
- this = self
143
- @children[other.path] = other
144
- other.instance_eval {@parent = this}
145
- nil
146
- end
147
-
148
- # Add other as the super class of this node
149
- # @param other [Node] another node
150
- # @return [nil]
151
- def inherits_from(other)
152
- this = self
153
- @inherits_from = other
154
- other.instance_eval {@inherited_by[this.path] = this}
155
- nil
156
- end
157
-
158
- # Add other to this nodes includes set
159
- # @param other [Node] another node
160
- # @return [nil]
161
- def includes(other)
162
- this = self
163
- @includes[other.path] = other
164
- other.instance_eval {@included_by[this.path] = this}
165
- nil
166
- end
167
-
168
- # Add other to this nodes extends set
169
- # @param other [Node] another node
170
- # @return [nil]
171
- def extends(other)
172
- this = self
173
- @extends[other.path] = other
174
- other.instance_eval {@extended_by[this.path] = this}
175
- nil
176
- end
177
-
178
- # Mark this module node as a singleton
179
- # @return [nil]
180
- def mark_as_singleton
181
- throw :NodeNotAModule unless module?
182
- @singleton = true
183
- end
184
-
185
- # Remove any relations involving this node
186
- def prune
187
- this = self
188
- if @inherits_from
189
- @inherits_from.instance_eval {@inherited_by.delete this.path}
190
- end
191
- @inherited_by.each_value do |other|
192
- other.instance_eval {@inherits_from = nil}
193
- end
194
- if @parent
195
- @parent.instance_eval {@children.delete this.path}
196
- end
197
- @children.each_value do |other|
198
- other.instance_eval {@parent = nil}
199
- end
200
- @includes.each_value do |other|
201
- other.instance_eval {@included_by.delete this.path}
202
- end
203
- @included_by.each_value do |other|
204
- other.instance_eval {@includes.delete this.path}
205
- end
206
- @extends.each_value do |other|
207
- other.instance_eval {@extended_by.delete this.path}
208
- end
209
- @extended_by.each_value do |other|
210
- other.instance_eval {@extends.delete this.path}
211
- end
212
- end
213
- end
214
-
215
14
  include BuilderMethods
216
15
  include TemplateMethods
217
16
  include QueryMethods
@@ -231,14 +30,21 @@ module CodeNode
231
30
  parent_path + [name]
232
31
  end
233
32
  @name = @path.last
234
- @parent = nil
235
- @children = {}
236
- @inherits_from = nil
237
- @inherited_by = {}
238
- @includes = {}
239
- @included_by = {}
240
- @extends = {}
241
- @extended_by = {}
33
+ @inverse_relation = {} # :rel => :inv_rel
34
+ @edge = {} # :rel => { 'node::path' => }
35
+ define_relation :parent, :children
36
+ define_relation :inherits_from, :inherited_by
37
+ define_relation :includes, :included_by
38
+ define_relation :extends, :extended_by
39
+ end
40
+
41
+ # @param rel [Symbol] the name of a relation
42
+ # @param inv [Symbol] the name of the inverse relation
43
+ def define_relation(rel, inv)
44
+ @inverse_relation[rel] = inv
45
+ @inverse_relation[inv] = rel
46
+ @edge[rel] = {}
47
+ @edge[inv] = {}
242
48
  end
243
49
 
244
50
  # @return [FixNum] order nodes by {#path}
@@ -0,0 +1,83 @@
1
+ module CodeNode
2
+ module IR
3
+ class Node
4
+
5
+ # {Node} methods used during the graph building phase
6
+ # @api developer
7
+ module BuilderMethods
8
+
9
+ attr_reader :style
10
+
11
+ # Find a node contained in this node, or contained in this nodes {#parent}, recursively.
12
+ # @return [Node, nil]
13
+ def find(name)
14
+ path = (@path + [name].flatten).join '::'
15
+ children[path] || (parent && parent.find(name))
16
+ end
17
+
18
+ # Add other as a child of this node
19
+ # @param other [Node] another node
20
+ # @return [nil]
21
+ def contains(other)
22
+ add_edge :children, other
23
+ end
24
+
25
+ # Add other as the super class of this node
26
+ # @param other [Node] another node
27
+ # @return [nil]
28
+ def inherits_from(other)
29
+ add_edge :inherits_from, other
30
+ end
31
+
32
+ # Add other to this nodes includes set
33
+ # @param other [Node] another node
34
+ # @return [nil]
35
+ def includes(other)
36
+ add_edge :includes, other
37
+ end
38
+
39
+ # Add other to this nodes extends set
40
+ # @param other [Node] another node
41
+ # @return [nil]
42
+ def extends(other)
43
+ add_edge :extends, other
44
+ end
45
+
46
+ # Add an edge between this node and another with the given relation type
47
+ # @param rel [Symbol] the type of relation
48
+ # @param other [Node] another node
49
+ # @return [nil]
50
+ def add_edge(rel, other)
51
+ this = self
52
+ inv = @inverse_relation[rel]
53
+ @edge[rel][other.path] = other
54
+ other.instance_eval do
55
+ @edge[inv][this.path] = this
56
+ end
57
+ nil
58
+ end
59
+
60
+ # Mark this module node as a singleton
61
+ # @return [nil]
62
+ def mark_as_singleton
63
+ throw :NodeNotAModule unless module?
64
+ @singleton = true
65
+ end
66
+
67
+ # Remove any relations involving this node
68
+ def prune
69
+ this = self
70
+ @edge.each_pair do |rel, edges|
71
+ inv = @inverse_relation[rel]
72
+ edges.each_value do |other|
73
+ other.instance_eval do
74
+ @edge[inv].delete this.path
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,55 @@
1
+ module CodeNode
2
+ module IR
3
+ class Node
4
+
5
+ # {Node} methods which are useful for querying in matchers
6
+ module QueryMethods
7
+
8
+ # @return [Boolean] does this node represent a module?
9
+ def module?
10
+ @node_type == :module
11
+ end
12
+
13
+ # @return [Boolean] does this node represent a class?
14
+ def class?
15
+ @node_type == :class
16
+ end
17
+
18
+ # @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.
19
+ def path
20
+ @path.join '::'
21
+ end
22
+
23
+ # @param path [String] a class or module path in the form <tt>Foo::Bar::Car</tt>
24
+ # @return [Boolean] does this node include a {#module?} node with the given {#path}?
25
+ def includes?(path)
26
+ includes.member? path
27
+ end
28
+
29
+ # @param path [String] a class or module path in the form <tt>Foo::Bar::Car</tt>
30
+ # @return [Boolean] does this node extend a {#module?} node with the given {#path}?
31
+ def extends?(path)
32
+ extends.member? path
33
+ end
34
+
35
+ # @param path [String] a class or module path in the form <tt>Foo::Bar::Car</tt>
36
+ # @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.
37
+ def inherits_from?(path)
38
+ # TODO: need process all nodes first, marking for deletion on the first pass, because the superclass gets deleting and then the inherits from relation breaks down
39
+ self.path == path || !@edge[:inherits_from].empty? && @edge[:inherits_from].values.first.inherits_from?(path)
40
+ end
41
+
42
+ # @return [Boolean] whether or not this node represents a singleton module. A singleton module is one which contains an <tt>extend self</tt> statement.
43
+ def singleton?
44
+ @singleton
45
+ end
46
+
47
+ # @return [Boolean] whether or not this node is an island. An island is a node with no connections to other nodes.
48
+ def island?
49
+ @edge.values.all? &:empty?
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,73 @@
1
+ module CodeNode
2
+ module IR
3
+ class Node
4
+
5
+ # Node methods which are useful in templates
6
+ module TemplateMethods
7
+
8
+ # @return [String] the name of the node. Not necessarilly unique.
9
+ # @see {#path}
10
+ attr_reader :name
11
+
12
+ # @return [Node] node which contains this node
13
+ # @example
14
+ # module Foo # Foo is the parent
15
+ # module Bar # to Bar
16
+ # end
17
+ # end
18
+ def parent
19
+ @edge[:parent].values.first
20
+ end
21
+
22
+ # The child nodes of this node
23
+ # @return [Hash<String,Node>] a mapping from node {#path} names to nodes
24
+ # @example
25
+ # module Foo # Foo has children
26
+ # module Bar # Bar
27
+ # end # and
28
+ # class Car # Car
29
+ # end
30
+ # end
31
+ def children
32
+ @edge[:children]
33
+ end
34
+
35
+ # @return [Node,nil] the super class of this class. Will be +nil+ for modules.
36
+ def super_class_node
37
+ @edge[:inherits_from].values.first
38
+ end
39
+
40
+ # @return [Array<Node>] module nodes for which this node has an +include+ statement
41
+ def inclusions
42
+ @edge[:includes].values.sort
43
+ end
44
+
45
+ # @return [Array<Node>] module nodes for which this node has an +extend+ statement
46
+ def extensions
47
+ @edge[:extends].values.sort
48
+ end
49
+
50
+ # @return [String] fully qualified identifier for the node in the form <tt>Foo_Bar_Car</tt>. Ideal for graphviz identifiers.
51
+ def key
52
+ @path.join '_'
53
+ end
54
+
55
+ # @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}.
56
+ def label
57
+ parent.nil? ? path : name
58
+ end
59
+
60
+ # Stamp the accumulated GraphViz styles in a format suitable for inclusion in a <tt>.dot</tt> file
61
+ # @return [String] style in the form <tt>key1="value1" key2="value2"</tt>...
62
+ def stamp_styles
63
+ x = []
64
+ style.each_pair do |key, value|
65
+ x << "#{key}=\"#{value}\""
66
+ end
67
+ x.join ' '
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+ end
@@ -23,39 +23,48 @@ module CodeNode
23
23
  def walk(s = nil)
24
24
  s ||= @root
25
25
  if [:module, :class].member?(s[0])
26
- node = @graph.node_for(s[0], s[1]) do |node|
27
- if find_relations? && s[0] == :class && !s[2].nil?
28
- super_node = @graph.node_for :class, s[2], :not_sure_if_nested => true
29
- node.inherits_from super_node unless super_node.nil?
30
- end
31
- rest = s[0] == :module ? s.slice(2..-1) : s.slice(3..-1)
32
- rest.each do |c|
33
- walk(c) if c.class == Sexp
34
- end
35
- end
36
- unless @graph.scope.empty?
37
- @graph.scope.last.contains node
38
- end
26
+ add_node s
39
27
  elsif find_relations? && s[0] == :call && s.length >= 4 && [:extend, :include].member?(s[2]) && !@graph.scope.empty?
40
- s.slice(3..-1).each do |mod_sexp|
41
- node = @graph.node_for :module, mod_sexp, :not_sure_if_nested => true
42
- unless node.nil?
43
- if s[2] == :extend
44
- @graph.scope.last.extends node
45
- else
46
- @graph.scope.last.includes node
47
- end
48
- end
49
- end
28
+ add_relation s
50
29
  else
51
- s.slice(1..-1).each do |c|
52
- walk(c) if c.class == Sexp
53
- end
30
+ walk_siblings s.slice(1..-1)
54
31
  end
55
32
  end
56
33
 
57
34
  private
58
35
 
36
+ def walk_siblings(s)
37
+ s.each do |c|
38
+ walk(c) if c.class == Sexp
39
+ end
40
+ end
41
+
42
+ def add_node(s)
43
+ node = @graph.node_for(s[0], s[1]) do |node|
44
+ if find_relations? && s[0] == :class && !s[2].nil?
45
+ super_node = @graph.node_for :class, s[2], :not_sure_if_nested => true
46
+ node.inherits_from super_node unless super_node.nil?
47
+ end
48
+ walk_siblings s.slice((s[0] == :module ? 2 : 3)..-1)
49
+ end
50
+ unless @graph.scope.empty?
51
+ @graph.scope.last.contains node
52
+ end
53
+ end
54
+
55
+ def add_relation(s)
56
+ s.slice(3..-1).each do |mod_sexp|
57
+ node = @graph.node_for :module, mod_sexp, :not_sure_if_nested => true
58
+ unless node.nil?
59
+ if s[2] == :extend
60
+ @graph.scope.last.extends node
61
+ else
62
+ @graph.scope.last.includes node
63
+ end
64
+ end
65
+ end
66
+ end
67
+
59
68
  # @return [Boolean] whether or not the walker should look for relations
60
69
  def find_relations?
61
70
  @mode == :find_relations
@@ -59,27 +59,19 @@ module CodeNode
59
59
  end
60
60
 
61
61
  def has_containment?(path1, path2)
62
- key1 = path1.to_s.split('::').join '_'
63
- key2 = path2.to_s.split('::').join '_'
64
- @edges[:containment].member?([key1, key2])
62
+ has_relation? :containment, path1, path2
65
63
  end
66
64
 
67
65
  def has_inheritance?(path1, path2)
68
- key1 = path1.to_s.split('::').join '_'
69
- key2 = path2.to_s.split('::').join '_'
70
- @edges[:inheritance].member?([key1, key2])
66
+ has_relation? :inheritance, path1, path2
71
67
  end
72
68
 
73
69
  def has_inclusion?(path1, path2)
74
- key1 = path1.to_s.split('::').join '_'
75
- key2 = path2.to_s.split('::').join '_'
76
- @edges[:inclusion].member?([key1, key2])
70
+ has_relation? :inclusion, path1, path2
77
71
  end
78
72
 
79
73
  def has_extension?(path1, path2)
80
- key1 = path1.to_s.split('::').join '_'
81
- key2 = path2.to_s.split('::').join '_'
82
- @edges[:extension].member?([key1, key2])
74
+ has_relation? :extension, path1, path2
83
75
  end
84
76
 
85
77
  private
@@ -108,6 +100,12 @@ module CodeNode
108
100
  end
109
101
  @i += 1
110
102
  end
103
+
104
+ def has_relation?(relation, path1, path2)
105
+ key1 = path1.to_s.split('::').join '_'
106
+ key2 = path2.to_s.split('::').join '_'
107
+ @edges[relation].member?([key1, key2])
108
+ end
111
109
  end
112
110
 
113
111
  end
@@ -1,5 +1,5 @@
1
1
  module Code_node
2
2
  unless const_defined? :VERSION
3
- VERSION = '0.1.2'
3
+ VERSION = '0.1.3'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_node
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 29
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 2
10
- version: 0.1.2
9
+ - 3
10
+ version: 0.1.3
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-12-16 00:00:00 Z
18
+ date: 2012-12-17 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: cog
@@ -117,7 +117,13 @@ files:
117
117
  - lib/code_node/cog_tool.rb
118
118
  - lib/code_node/dsl/graph_definer.rb
119
119
  - lib/code_node/dsl.rb
120
+ - lib/code_node/graph_builder.rb
121
+ - lib/code_node/ir/graph/builder_methods.rb
122
+ - lib/code_node/ir/graph/template_methods.rb
120
123
  - lib/code_node/ir/graph.rb
124
+ - lib/code_node/ir/node/builder_methods.rb
125
+ - lib/code_node/ir/node/query_methods.rb
126
+ - lib/code_node/ir/node/template_methods.rb
121
127
  - lib/code_node/ir/node.rb
122
128
  - lib/code_node/ir/node_matcher.rb
123
129
  - lib/code_node/ir.rb