code_node 0.1.2 → 0.1.3

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