code_node 0.0.1 → 0.0.2

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