ograph 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. data/History.txt +12 -0
  2. data/README.txt +1 -3
  3. data/Rakefile +1 -1
  4. data/lib/ograph.rb +261 -49
  5. metadata +4 -4
@@ -1,3 +1,15 @@
1
+ == 0.1.0
2
+
3
+ Tons of changes thanks to Eric Hodel!
4
+
5
+ * ObjectGraph is now an instance so you can add more stuff to it
6
+ * Key Value pairs in Hashes are now listed in the hash box
7
+ * String values are displayed
8
+ * ObjectGraph knows how to handle ActiveRecord objects
9
+ * Labels are now ObjectID-ClassName
10
+ * Empty arrays or hashes will say that they are empty
11
+ * Much much more!
12
+
1
13
  == 0.0.1 / 2007-01-14
2
14
 
3
15
  * Birthday!
data/README.txt CHANGED
@@ -16,9 +16,7 @@ For sample output and more sample code see:
16
16
 
17
17
  == PROBLEMS
18
18
 
19
- The output graph kind of lies when displaying Hashes. A Hash will point to
20
- an Array with a key and value. If the hash contains 7 pairs, it will point
21
- to 7 Arrays.
19
+ None currently known.
22
20
 
23
21
  == SYNOPSYS
24
22
 
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'hoe'
3
3
  require './lib/ograph.rb'
4
4
 
5
5
  Hoe.new('ograph', ObjectGraph::VERSION) do |p|
6
- p.rubyforge_name = 'ograph'
6
+ p.rubyforge_name = 'seattlerb'
7
7
  p.author = 'Aaron Patterson'
8
8
  p.email = 'aaronp@rubyforge.org'
9
9
  p.summary = p.paragraphs_of('README.txt', 3).join("\n\n")
@@ -1,72 +1,284 @@
1
+ ##
2
+ # ObjectGraph creates a dot file that represents the connections between your
3
+ # Objects. ObjectGraph knows about Hash, Enumerable, instance variables and
4
+ # ActiveRecord::Base to create the prettiest graphs possible.
5
+ #
6
+ # Here's an example that graphs the fixtures of a Rails application. Run
7
+ # `rake db:test:prepare` beforehand.
8
+ #
9
+ # ENV['RAILS_ENV'] = 'test'
10
+ # require 'config/environment'
11
+ # require 'ograph'
12
+ #
13
+ # ar_descendents = []
14
+ # ObjectSpace.each_object Class do |klass|
15
+ # ar_descendents << klass if klass < ActiveRecord::Base
16
+ # end
17
+ #
18
+ # File.open 'world.dot', 'w' do |fp|
19
+ # graph = ObjectGraph.new
20
+ #
21
+ # graph.preprocess_callback do |object|
22
+ # next unless ActiveRecord::Base === object
23
+ #
24
+ # object.class.reflect_on_all_associations.each do |a|
25
+ # object.send a.name, true
26
+ # end
27
+ # end
28
+ #
29
+ # ar_descendents.each do |klass|
30
+ # next if klass.abstract_class?
31
+ # begin
32
+ # klass.find(:all).each do |obj|
33
+ # graph.add obj
34
+ # end
35
+ # rescue => e
36
+ # puts "#{klass} has problems:\n\t#{e}"
37
+ # end
38
+ # end
39
+ #
40
+ # fp.write graph
41
+ # end
42
+
1
43
  class ObjectGraph
2
- VERSION = '0.0.1'
44
+
45
+ ##
46
+ # The version of ObjectGraph you are currently using.
47
+
48
+ VERSION = '0.1.0'
49
+
50
+ ##
51
+ # Characters we need to escape
52
+
53
+ ESCAPE = /([<>"\\])/
54
+
55
+ ##
56
+ # Handy-dandy super-friendly graph generator shortcut.
57
+ #
58
+ # Just hand it and object and let it rip! Returns a dot graph.
3
59
 
4
60
  def self.graph(target, opts = {})
5
- opts = { :class_filter => /./,
6
- :show_ivars => true,
7
- :show_nil => true,
8
- }.merge(opts)
9
-
10
- stack = [target]
11
- object_links = []
12
- seen_objects = []
13
- seen_hash = {}
14
-
15
- while stack.length > 0
16
- object = stack.pop
17
- next if seen_hash.key? object.object_id
18
- seen_hash[object.object_id] = 1
19
-
20
- if object.is_a?(Enumerable) && ! object.is_a?(String)
21
- object.each { |iv|
22
- next if iv.nil? && ! opts[:show_nil]
23
- if iv.class.to_s =~ opts[:class_filter] || object.is_a?(Enumerable)
24
- object_links.push([object.object_id, iv.object_id, nil])
25
- stack.push(iv)
61
+ graph = new opts
62
+ graph.add target
63
+ graph.to_s
64
+ end
65
+
66
+ ##
67
+ # Creates a new ObjectGraph instance. Options may include:
68
+ #
69
+ # :class_filter:: Regular expression that matches classes to include.
70
+ # :show_ivars:: Show instance variables (in the record, edges still get
71
+ # drawn.)
72
+ # :show_nil:: Draw nil (and edges to nil.)
73
+
74
+ def initialize(opts = {})
75
+ defaults = { :class_filter => //, :show_ivars => true, :show_nil => true }
76
+ @opts = defaults.merge opts
77
+
78
+ @stack = []
79
+ @object_links = []
80
+ @seen_objects = []
81
+ @seen_hash = {}
82
+
83
+ @preprocess_callback = nil
84
+ end
85
+
86
+ ##
87
+ # Adds +target+ to the graph.
88
+
89
+ def add(target)
90
+ return if @seen_hash.key? object_name(target)
91
+
92
+ @stack << target
93
+
94
+ while @stack.length > 0
95
+ object = @stack.pop
96
+ @name = object_name object
97
+ next if @seen_hash.key? @name
98
+ @seen_hash[@name] = 1
99
+ @preprocess_callback.call object if @preprocess_callback
100
+
101
+ @record = []
102
+ @record << @name
103
+
104
+ case object
105
+ when String then
106
+ str = object[0..15].gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/,' ')
107
+ str << '...' if object.length > 15
108
+ @record.push "'#{str}'"
109
+ when Numeric then
110
+ @record.push object
111
+ when Hash then
112
+ if object.respond_to? :empty? and object.empty? then
113
+ @record.push 'Empty!'
114
+ else
115
+ object.each_with_index do |(k,v),i|
116
+ next if v.nil? && ! @opts[:show_nil]
117
+ next unless v.class.to_s =~ @opts[:class_filter]
118
+
119
+ add_to_record k, v
26
120
  end
27
- }
121
+ end
122
+ when Enumerable then
123
+ if object.respond_to? :empty? and object.empty? then
124
+ @record.push 'Empty!'
125
+ else
126
+ object.each_with_index do |v, i|
127
+ next if v.nil? && ! @opts[:show_nil]
128
+ if v.class.to_s =~ @opts[:class_filter] or
129
+ (v.is_a? Enumerable and not v.is_a? String) then
130
+ add_to_record i, v
131
+ end
132
+ end
133
+ end
28
134
  end
29
135
 
30
- ivars = object.instance_variables.sort
31
- ivars.each_with_index do |iv_sym, i|
32
- iv = object.instance_variable_get iv_sym
33
- next if iv.nil? && ! opts[:show_nil]
34
- if iv.class.to_s =~ opts[:class_filter] || iv.is_a?(Enumerable)
35
- object_links.push([ object.object_id,
36
- iv.object_id,
37
- opts[:show_ivars] ? i : nil])
38
- stack.push(iv)
136
+ # This is a HACK around bug 1345 (I think)
137
+ if object.class and object.class.ancestors.include? Array and
138
+ not Enumerable === object then
139
+ if object.respond_to? :empty? and object.empty? then
140
+ @record.push 'Empty!'
141
+ else
142
+ object.each_with_index do |v, i|
143
+ next if v.nil? && ! @opts[:show_nil]
144
+ if v.class.to_s =~ @opts[:class_filter] or
145
+ (v.is_a? Enumerable and not v.is_a? String) then
146
+ add_to_record i, v
147
+ end
148
+ end
39
149
  end
40
150
  end
41
151
 
42
- seen_objects.push([ object.object_id,
43
- object.class ] + (opts[:show_ivars] ? ivars : []))
152
+ add_ivars object
153
+
154
+ @seen_objects.push @record
44
155
  end
156
+ end
157
+
158
+ ##
159
+ # Adds ivars for +object+ to the graph.
160
+
161
+ def add_ivars(object)
162
+ return unless object.instance_variables # HACK WTF Rails?
163
+
164
+ ivars = object.instance_variables.sort
165
+
166
+ if defined? ActiveRecord::Base && ActiveRecord::Base === object then
167
+ ivars.delete '@attributes'
168
+ attrs = object.instance_variable_get :@attributes
169
+
170
+ unless attrs.nil? then
171
+ attrs.each_with_index do |(k,v),i|
172
+ next if v.nil? && ! @opts[:show_nil]
173
+ next unless v.class.to_s =~ @opts[:class_filter]
45
174
 
175
+ add_to_record k, v
176
+ end
177
+ end
178
+ end
179
+
180
+ ivars.each_with_index do |iv_sym, i|
181
+ iv = object.instance_variable_get iv_sym
182
+ next if iv.nil? && ! @opts[:show_nil]
183
+ if iv.class.to_s =~ @opts[:class_filter] ||
184
+ (object.is_a?(Enumerable) && !object.is_a?(String))
185
+ index = @opts[:show_ivars] ? @record.length + i : nil
186
+ @object_links << [@name, object_name(iv), index]
187
+ @stack.push(iv)
188
+ end
189
+ end
190
+
191
+ @record.push(*ivars) if @opts[:show_ivars]
192
+ end
193
+
194
+ ##
195
+ # Adds +key+ and +value+ to the record for the object currently being
196
+ # processed.
197
+
198
+ def add_to_record(key, value)
199
+ @record.push(
200
+ [key, value].map { |val|
201
+ case val
202
+ when NilClass
203
+ 'nil'
204
+ when Fixnum
205
+ "#{val}"
206
+ when String
207
+ string_val = val[0..12].gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/, ' ')
208
+ string_val << '...' if val.length > 12
209
+ string_val
210
+ else
211
+ string_val = object_name val
212
+ @object_links.push [@name, string_val, @record.length]
213
+ @stack.push val
214
+ string_val
215
+ end
216
+ }.join(' is '))
217
+ end
218
+
219
+ ##
220
+ # The name of an Object. Understands ActiveRecord::Base objects.
221
+
222
+ def object_name(object)
223
+ id = if defined? ActiveRecord::Base and ActiveRecord::Base === object then
224
+ object.id
225
+ else
226
+ object.object_id
227
+ end
228
+ "#{id}-#{object.class}"
229
+ end
230
+
231
+ ##
232
+ # Stores a callback for #add to call on each object it processes before
233
+ # adding to the graph.
234
+ #
235
+ # To add all the associations of an ActiveRecord::Base object:
236
+ #
237
+ # graph.preprocess_callback do |object|
238
+ # next unless ActiveRecord::Base === object
239
+ #
240
+ # object.class.reflect_on_all_associations.each do |a|
241
+ # object.send a.name, true
242
+ # end
243
+ # end
244
+
245
+ def preprocess_callback(&block)
246
+ @preprocess_callback = block
247
+ end
248
+
249
+ ##
250
+ # Returns dot for the objects added to the graph. Use GraphViz to turn the
251
+ # graph into a PNG (or whatever).
252
+
253
+ def to_s
46
254
  s = <<END
47
255
  digraph g {
48
- graph [ rankdir = "LR" ];
49
- node [ fontsize = "8"
50
- shape = "ellipse"
51
- ];
52
- edge [ ];
256
+ \tgraph [ rankdir = "LR" ];
257
+ \tnode [ fontsize = "8"
258
+ \t\tshape = "ellipse"
259
+ \t];
260
+ \tedge [ ];
261
+
53
262
  END
54
- seen_objects.each { |id, *rest|
55
- s += "\"#{id}\" [\nlabel="
263
+
264
+ @seen_objects.each { |id, *rest|
265
+ s << "\"#{id}\" [\n\tlabel="
56
266
  list = []
57
267
  [id, *rest].each_with_index { |field, i|
58
268
  list << "<f#{i}>#{field}"
59
269
  }
60
- s += "\"#{list.join('|')}\"\n"
61
- s += <<END
62
- shape = "record"
63
- ]
64
- END
270
+ s << "\"#{list.join('|')}\"\n"
271
+ s << "\tshape = \"record\"\n]\n\n"
65
272
  }
66
- object_links.each_with_index { |(from, to, x), i|
67
- s += "\"#{from}\":f#{x ? x + 2 : 0} -> \"#{to}\":f0 [ id = #{i} ]\n"
273
+
274
+ @object_links.each_with_index { |(from, to, x), i|
275
+ s << "\"#{from}\":f#{x || 0} -> \"#{to}\":f0 [ id = #{i} ]\n"
68
276
  }
69
- s += "}\n"
277
+
278
+ s << "}\n"
279
+
70
280
  s
71
281
  end
282
+
72
283
  end
284
+
metadata CHANGED
@@ -3,14 +3,14 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ograph
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.1
7
- date: 2007-01-20 00:00:00 -08:00
6
+ version: 0.1.0
7
+ date: 2007-02-06 00:00:00 -08:00
8
8
  summary: ObjectGraph will output Graphviz dot files of your objects in memory. It will ferret out your instance variables and enumerate over your enumerables to give you a graph of your object and its relationships.
9
9
  require_paths:
10
10
  - lib
11
11
  email: aaronp@rubyforge.org
12
12
  homepage: http://seattlerb.rubyforge.org/
13
- rubyforge_project: ograph
13
+ rubyforge_project: seattlerb
14
14
  description: "ObjectGraph will output Graphviz dot files of your objects in memory. It will
15
15
  ferret out your instance variables and enumerate over your enumerables to give
16
16
  you a graph of your object and its relationships. For sample output and more
@@ -54,5 +54,5 @@ dependencies:
54
54
  -
55
55
  - ">="
56
56
  - !ruby/object:Gem::Version
57
- version: 1.1.6
57
+ version: 1.1.7
58
58
  version: