code_node 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,5 +4,9 @@
4
4
 
5
5
  # Makes a graph of the project_source_path
6
6
  CodeNode.graph '<%= @name %>' do |g|
7
+
8
+ g.ignore /ClassMethods$/
9
+
10
+ g.ignore &:island?
7
11
 
8
12
  end
@@ -3,46 +3,40 @@ digraph
3
3
  graph [rankdir="LR"]
4
4
  node [style="filled"]
5
5
 
6
- <% @graph.nodes.each do |node| %>
7
- <% if node.should_render? %>
8
- <%= node.key %> [label="<%= node.label %>" shape="<%= node.shape %>" fillcolor="<%= node.fillcolor %>" fontcolor="<%= node.fontcolor %>"];
9
- <% end %>
6
+ /* Module nodes */
7
+ node [shape="box" fillcolor="#333333" fontcolor="#ffffff"];
8
+ <% @graph.each_module do |node| %>
9
+ <%= node.stamp 'code_node/node.dot' %>
10
10
  <% end %>
11
11
 
12
- /* Contains */
13
- edge [color="#e8e8e8"]
14
- <% @graph.nodes.each do |node| %>
15
- <% if node.parent && node.should_render? && node.parent.should_render? %>
16
- <%= node.parent.key %> -> <%= node.key %>;
17
- <% end %>
12
+ /* Class nodes */
13
+ node [shape="ellipse" fillcolor="#cccccc" fontcolor="#000000"];
14
+ <% @graph.each_class do |node| %>
15
+ <%= node.stamp 'code_node/node.dot' %>
18
16
  <% end %>
19
17
 
20
- /* Extended by */
21
- edge [color="#000000"]
22
- <% @graph.nodes.each do |node| %>
23
- <% node.extended_by.values.sort.each do |other| %>
24
- <% if node.should_render? && other.should_render? %>
25
- <%= node.key %> -> <%= other.key %>;
26
- <% end %>
27
- <% end %>
18
+ /* A contains B */
19
+ edge [color="#e8e8e8"];
20
+ <% @graph.each_containment do |a, b| %>
21
+ <%= a.key %> -> <%= b.key %>;
28
22
  <% end %>
29
23
 
30
- /* Includes */
31
- edge [color="#336699"]
32
- <% @graph.nodes.each do |node| %>
33
- <% node.includes.values.sort.each do |other| %>
34
- <% if node.should_render? && other.should_render? %>
35
- <%= node.key %> -> <%= other.key %>;
36
- <% end %>
37
- <% end %>
24
+ /* A inherits from B */
25
+ edge [color="#996633"];
26
+ <% @graph.each_inheritance do |a, b| %>
27
+ <%= a.key %> -> <%= b.key %>;
38
28
  <% end %>
39
29
 
40
- /* Inherits from */
41
- edge [color="#996633"]
42
- <% @graph.nodes.each do |node| %>
43
- <% if node.should_render? && node.inherits_from && node.inherits_from.should_render? %>
44
- <%= node.key %> -> <%= node.inherits_from.key %>;
30
+ /* A includes B */
31
+ edge [color="#336699"];
32
+ <% @graph.each_inclusion do |a, b| %>
33
+ <%= a.key %> -> <%= b.key %>;
45
34
  <% end %>
35
+
36
+ /* A extends B */
37
+ edge [color="#000000"];
38
+ <% @graph.each_extension do |a, b| %>
39
+ <%= a.key %> -> <%= b.key %>;
46
40
  <% end %>
47
41
 
48
42
  }
@@ -0,0 +1 @@
1
+ <%= key %> [label="<%= label %>"];
data/lib/code_node.rb CHANGED
@@ -28,8 +28,9 @@ module CodeNode
28
28
  end
29
29
 
30
30
  sexp = []
31
- [:find_nodes, :find_relations].each_with_index do |purpose, pass|
32
- puts "#{(pass+1).ordinalize} pass: #{purpose.to_s.gsub('_', ' ')}".color(:cyan)
31
+ [:find_nodes, :find_relations].each_with_index do |mode, pass|
32
+ puts "#{(pass+1).ordinalize} pass: #{mode.to_s.gsub('_', ' ')}".color(:cyan)
33
+
33
34
  Dir.glob("#{root}/**/*.rb").each_with_index do |filename, i|
34
35
  sexp[i] ||= begin
35
36
  rp.parse(File.read filename)
@@ -38,8 +39,8 @@ module CodeNode
38
39
  nil
39
40
  end
40
41
  if sexp[i]
41
- walker = SexpWalker.new @graph, sexp[i]
42
- walker.walk purpose
42
+ walker = SexpWalker.new @graph, sexp[i], :mode => mode
43
+ walker.walk
43
44
  end
44
45
  end
45
46
  end
@@ -1,12 +1,39 @@
1
1
  module CodeNode
2
2
  module DSL
3
3
 
4
+ # Specify rules for generating the graph
4
5
  class GraphDefiner
5
6
 
7
+ # @api developer
8
+ # @param graph [IR::Graph] a graph for which rules can be defined
6
9
  def initialize(graph)
7
10
  @graph = graph
8
11
  end
9
12
 
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.
16
+ #
17
+ # * +String+ provide a fully qualified path
18
+ # * +Regexp+ provide a pattern that will be tested against the {IR::Node#path}
19
+ # * +Proc+ provide a block. If the block returns +true+ the node will be ignored
20
+ #
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
23
+ # @return [nil]
24
+ def ignore(name=nil, &block)
25
+ if (name.nil? && block.nil?) || (name && block)
26
+ raise ArgumentError.new('Provide either a name or a block')
27
+ 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}
34
+ end
35
+ nil
36
+ end
10
37
  end
11
38
 
12
39
  end
data/lib/code_node/ir.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'code_node/ir/class_node'
2
- require 'code_node/ir/module_node'
1
+ require 'code_node/ir/node'
3
2
  require 'code_node/ir/graph'
4
3
 
5
4
  module CodeNode
@@ -1,18 +1,23 @@
1
- require 'code_node/ir/class_node'
2
- require 'code_node/ir/module_node'
1
+ require 'code_node/ir/node'
3
2
 
4
3
  module CodeNode
5
4
  module IR
6
5
 
7
6
  class Graph
8
7
 
8
+ # @api developer
9
9
  attr_reader :scope
10
-
10
+
11
+ # @api developer
11
12
  def initialize
13
+ @exclude_paths = []
14
+ @exclude_patterns = []
15
+ @exclude_procs = []
12
16
  @nodes = {}
13
17
  @scope = []
14
18
  end
15
19
 
20
+ # @api developer
16
21
  def node_for(node_type, s, opt={}, &block)
17
22
  name = if s.is_a? Symbol
18
23
  s
@@ -35,10 +40,10 @@ module CodeNode
35
40
  if @scope.length > 1 && @scope[-2].find(name)
36
41
  @scope[-2].find name
37
42
  else
38
- (node_type == :module ? ModuleNode : ClassNode).new(name)
43
+ Node.new name, :node_type => node_type
39
44
  end
40
45
  else
41
- (node_type == :module ? ModuleNode : ClassNode).new(name, @scope.last)
46
+ Node.new name, :parent => @scope.last, :node_type => node_type
42
47
  end
43
48
 
44
49
  node = self << node
@@ -51,14 +56,74 @@ module CodeNode
51
56
  end
52
57
 
53
58
  def <<(node)
54
- @nodes[node.key] ||= node
55
- @nodes[node.key]
59
+ @nodes[node.path] ||= node
60
+ @nodes[node.path]
61
+ end
62
+
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
71
+
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
80
+
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
86
+ end
87
+ end
56
88
  end
57
- def [](key)
58
- @nodes[key]
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
95
+ end
96
+ end
59
97
  end
60
- def nodes
61
- @nodes.values.sort
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
105
+ end
106
+ 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
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ private
122
+
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)}
62
127
  end
63
128
  end
64
129
 
@@ -1,51 +1,162 @@
1
+ require 'cog'
2
+
1
3
  module CodeNode
2
4
  module IR
3
5
 
6
+ # A node in the {Graph}
7
+ # Nodes come in two flavors: {#class?} and {#module?} nodes
4
8
  class Node
5
- attr_reader :name, :parent, :children, :includes, :extended_by, :inherits_from
6
- def initialize(name, parent = nil)
7
- parent_path = parent ? parent.instance_eval {@path} : []
9
+
10
+ include Cog::Generator
11
+
12
+ # @return [String] the name of the node. Not necessarilly unique.
13
+ # @see {#path}
14
+ attr_reader :name
15
+
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
23
+
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
34
+
35
+ # @param name [String, Array]
36
+ # @option opt [Node] :parent (nil)
37
+ # @option opt [Symbol] :node_type (:module) either <tt>:module</tt> or <tt>:class</tt>
38
+ def initialize(name, opt={})
39
+ @node_type = opt[:node_type] || :module
40
+ @parent = opt[:parent]
41
+ parent_path = @parent ? @parent.instance_eval {@path} : []
8
42
  @path = if name.is_a? Array
9
43
  parent_path + name
10
44
  else
11
45
  parent_path + [name]
12
46
  end
13
47
  @name = @path.last
14
- @parent = parent
15
- parent.children[key] = self unless parent.nil?
48
+ @parent.children[path] = self unless @parent.nil?
16
49
  @children = {}
50
+ @inherited_by = {}
17
51
  @includes = {}
52
+ @included_by = {}
53
+ @extends = {}
18
54
  @extended_by = {}
19
55
  end
56
+
20
57
  def find(name)
21
- key = (@path + [name].flatten).join '_'
22
- @children[key] || (orphan? ? nil : @parent.find(name))
58
+ path = (@path + [name].flatten).join '::'
59
+ @children[path] || (@parent && @parent.find(name))
23
60
  end
61
+
62
+ # @return [String] fully qualified identifier for the node in the form <tt>Foo_Bar_Car</tt>. Ideal for graphviz identifiers.
24
63
  def key
25
64
  @path.join '_'
26
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.
27
68
  def path
28
69
  @path.join '::'
29
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}.
30
73
  def label
31
- orphan? ? path : name
32
- end
33
- def to_s
34
- path
74
+ @parent.nil? ? path : name
35
75
  end
76
+
77
+ # @return [FixNum] order nodes by {#path}
36
78
  def <=>(other)
37
79
  path <=> other.path
38
80
  end
39
- def orphan?
40
- @parent.nil?
81
+
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
41
147
  end
42
- def derives_from?(k)
43
- key == k || @inherits_from && (@inherits_from.key == k || @inherits_from.derives_from?(k))
148
+
149
+ # @return [Boolean] whether or not this node represents a singleton module
150
+ def singleton?
151
+ @singleton
44
152
  end
45
- def should_render?
46
- !(orphan? && @children.empty? && @inherits_from.nil? && @extended_by.empty? && @includes.empty?) && key != 'ActiveSupport_Concern' && !derives_from?('ActiveRecord_ActiveRecordError') && name != :ClassMethods
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?))
47
158
  end
159
+
48
160
  end
49
-
50
161
  end
51
162
  end
@@ -1,40 +1,59 @@
1
1
  module CodeNode
2
2
 
3
+ # Walks a Sexp representing a ruby file looking for classes and modules.
3
4
  class SexpWalker
4
- def initialize(graph, sexp)
5
+
6
+ # Initialize a walker with a graph and sexp
7
+ #
8
+ # 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
+ # @param graph [Graph] a graph to which nodes and relations will be added
11
+ # @param sexp [Sexp] the root sexp of a ruby file
12
+ # @option opt [Symbol] :mode (:find_nodes) one of <tt>:find_nodes</tt> or <tt>:find_relations</tt>
13
+ def initialize(graph, sexp, opt={})
5
14
  @graph = graph
6
15
  @root = sexp
16
+ @mode = opt[:mode] || :find_nodes
7
17
  end
8
- def walk(purpose, s = nil)
18
+
19
+ # Walk the tree rooted at the given sexp
20
+ # @param s [Sexp] if +nil+ will be the root sexp
21
+ # @return [nil]
22
+ def walk(s = nil)
9
23
  s ||= @root
10
24
  if [:module, :class].member?(s[0])
11
25
  @graph.node_for(s[0], s[1]) do |node|
12
- if purpose == :find_relations && s[0] == :class && !s[2].nil?
26
+ if find_relations? && s[0] == :class && !s[2].nil?
13
27
  super_node = @graph.node_for :class, s[2], :not_sure_if_nested => true
14
- unless super_node.nil?
15
- node.inherits_from = super_node
16
- end
28
+ node.inherits_from super_node unless super_node.nil?
17
29
  end
18
30
  rest = s[0] == :module ? s.slice(2..-1) : s.slice(3..-1)
19
31
  rest.each do |c|
20
- walk(purpose, c) if c.class == Sexp
32
+ walk(c) if c.class == Sexp
21
33
  end
22
34
  end
23
- elsif purpose == :find_relations && s[0] == :call && s.length >= 4 && [:extend, :include].member?(s[2]) && !@graph.scope.empty?
35
+ elsif find_relations? && s[0] == :call && s.length >= 4 && [:extend, :include].member?(s[2]) && !@graph.scope.empty?
24
36
  node = @graph.node_for :module, s[3], :not_sure_if_nested => true
25
37
  unless node.nil?
26
38
  if s[2] == :extend
27
- @graph.scope.last.extended_by[node.key] = node
39
+ @graph.scope.last.extends node
28
40
  else
29
- @graph.scope.last.includes[node.key] = node
41
+ @graph.scope.last.includes node
30
42
  end
31
43
  end
32
44
  else
33
45
  s.slice(1..-1).each do |c|
34
- walk(purpose, c) if c.class == Sexp
46
+ walk(c) if c.class == Sexp
35
47
  end
36
48
  end
37
49
  end
50
+
51
+ private
52
+
53
+ # @return [Boolean] whether or not the walker should look for relations
54
+ def find_relations?
55
+ @mode == :find_relations
56
+ end
57
+
38
58
  end
39
-
40
59
  end
@@ -1,5 +1,5 @@
1
1
  module Code_node
2
2
  unless const_defined? :VERSION
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
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: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Kevin Tonon
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-11-18 00:00:00 Z
18
+ date: 2012-11-19 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: cog
@@ -60,7 +60,7 @@ dependencies:
60
60
  type: :development
61
61
  version_requirements: *id003
62
62
  - !ruby/object:Gem::Dependency
63
- name: yard
63
+ name: redcarpet
64
64
  prerelease: false
65
65
  requirement: &id004 !ruby/object:Gem::Requirement
66
66
  none: false
@@ -73,6 +73,34 @@ dependencies:
73
73
  version: "0"
74
74
  type: :development
75
75
  version_requirements: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ name: rspec
78
+ prerelease: false
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ type: :development
89
+ version_requirements: *id005
90
+ - !ruby/object:Gem::Dependency
91
+ name: yard
92
+ prerelease: false
93
+ requirement: &id006 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ type: :development
103
+ version_requirements: *id006
76
104
  description:
77
105
  email: kevin@betweenconcepts.com
78
106
  executables: []
@@ -85,18 +113,17 @@ files:
85
113
  - LICENSE
86
114
  - cog/templates/code_node/generator.rb.erb
87
115
  - cog/templates/code_node/graph.dot.erb
116
+ - cog/templates/code_node/node.dot.erb
88
117
  - lib/code_node/cog_tool.rb
89
118
  - lib/code_node/dsl/graph_definer.rb
90
119
  - lib/code_node/dsl.rb
91
- - lib/code_node/ir/class_node.rb
92
120
  - lib/code_node/ir/graph.rb
93
- - lib/code_node/ir/module_node.rb
94
121
  - lib/code_node/ir/node.rb
95
122
  - lib/code_node/ir.rb
96
123
  - lib/code_node/sexp_walker.rb
97
124
  - lib/code_node/version.rb
98
125
  - lib/code_node.rb
99
- homepage: http://betweenconcepts.com
126
+ homepage: https://github.com/ktonon/code_node
100
127
  licenses: []
101
128
 
102
129
  post_install_message:
@@ -1,22 +0,0 @@
1
- require 'code_node/ir/node'
2
-
3
- module CodeNode
4
- module IR
5
-
6
- class ClassNode < Node
7
- def inherits_from=(super_node)
8
- @inherits_from = super_node
9
- end
10
- def shape
11
- :ellipse
12
- end
13
- def fillcolor
14
- '#cccccc'
15
- end
16
- def fontcolor
17
- '#000000'
18
- end
19
- end
20
-
21
- end
22
- end
@@ -1,26 +0,0 @@
1
- require 'code_node/ir/node'
2
-
3
- module CodeNode
4
- module IR
5
-
6
- class ModuleNode < Node
7
- def shape
8
- :box
9
- end
10
- def fillcolor
11
- if children["#{key}_ClassMethods"] && extended_by['ActiveSupport_Concern']
12
- '#000000'
13
- else
14
- '#666666'
15
- end
16
- end
17
- def fontcolor
18
- '#ffffff'
19
- end
20
- def mark_as_singleton
21
- @singleton = true
22
- end
23
- end
24
-
25
- end
26
- end