ograph 0.1.0 → 0.2.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.
- data/History.txt +5 -0
- data/Manifest.txt +4 -0
- data/README.txt +6 -1
- data/Rakefile +1 -1
- data/examples/ascendants_graph.rb +10 -0
- data/examples/diff_graph.rb +11 -0
- data/examples/regular_graph.rb +11 -0
- data/lib/ograph.rb +251 -85
- data/test/test_all.rb +10 -0
- metadata +10 -4
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -12,6 +12,7 @@ give you a graph of your object and its relationships.
|
|
12
12
|
For sample output and more sample code see:
|
13
13
|
|
14
14
|
* http://flickr.com/photos/aaronp/tags/graphviz/
|
15
|
+
* http://tenderlovemaking.com/2007/06/17/graphing-ruby-objects/
|
15
16
|
* http://tenderlovemaking.com/2007/01/13/graphing-objects-in-memory-with-ruby/
|
16
17
|
|
17
18
|
== PROBLEMS
|
@@ -22,7 +23,11 @@ None currently known.
|
|
22
23
|
|
23
24
|
list = %w{ hello world how are you? }
|
24
25
|
hash = { :list => list, :string => "tenderlovemaking.com" }
|
25
|
-
|
26
|
+
ograph = ObjectGraph.new
|
27
|
+
ograph.graph(hash) do |h|
|
28
|
+
h.delete(:string)
|
29
|
+
end
|
30
|
+
puts ograph
|
26
31
|
|
27
32
|
== INSTALL
|
28
33
|
|
data/Rakefile
CHANGED
@@ -9,6 +9,6 @@ Hoe.new('ograph', ObjectGraph::VERSION) do |p|
|
|
9
9
|
p.summary = p.paragraphs_of('README.txt', 3).join("\n\n")
|
10
10
|
p.description = p.paragraphs_of('README.txt', 3..5).join("\n\n")
|
11
11
|
p.url = p.paragraphs_of('README.txt', 1).first.split(/\n/)[1..-1].first.strip
|
12
|
-
p.changes = p.paragraphs_of('History.txt', 0..
|
12
|
+
p.changes = p.paragraphs_of('History.txt', 0..2).join("\n\n")
|
13
13
|
end
|
14
14
|
|
data/lib/ograph.rb
CHANGED
@@ -41,11 +41,12 @@
|
|
41
41
|
# end
|
42
42
|
|
43
43
|
class ObjectGraph
|
44
|
+
attr_accessor :nodes
|
44
45
|
|
45
46
|
##
|
46
47
|
# The version of ObjectGraph you are currently using.
|
47
48
|
|
48
|
-
VERSION = '0.
|
49
|
+
VERSION = '0.2.0'
|
49
50
|
|
50
51
|
##
|
51
52
|
# Characters we need to escape
|
@@ -59,7 +60,7 @@ class ObjectGraph
|
|
59
60
|
|
60
61
|
def self.graph(target, opts = {})
|
61
62
|
graph = new opts
|
62
|
-
graph.
|
63
|
+
graph.graph target
|
63
64
|
graph.to_s
|
64
65
|
end
|
65
66
|
|
@@ -72,94 +73,199 @@ class ObjectGraph
|
|
72
73
|
# :show_nil:: Draw nil (and edges to nil.)
|
73
74
|
|
74
75
|
def initialize(opts = {})
|
75
|
-
|
76
|
-
|
76
|
+
@opts = { :class_filter => //,
|
77
|
+
:show_ivars => true,
|
78
|
+
:show_nil => true,
|
79
|
+
:ascendants => false,
|
80
|
+
:descendants => true,
|
81
|
+
}.merge opts
|
82
|
+
|
83
|
+
@nodes = {}
|
84
|
+
@preprocess_callback = nil
|
85
|
+
end
|
77
86
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
87
|
+
##
|
88
|
+
# Adds +target+ to the graph
|
89
|
+
def graph(target)
|
90
|
+
GC.start
|
91
|
+
add_ascendants(target) if @opts[:ascendants]
|
92
|
+
add_descendants(target) if @opts[:descendants]
|
93
|
+
if block_given?
|
94
|
+
yield target
|
95
|
+
GC.start
|
96
|
+
diff(target)
|
97
|
+
end
|
98
|
+
end
|
82
99
|
|
83
|
-
|
100
|
+
##
|
101
|
+
# Combines +target+ with the current graph and highlights differences
|
102
|
+
|
103
|
+
def diff(target)
|
104
|
+
old_nodes = @nodes
|
105
|
+
@nodes = {}
|
106
|
+
graph(target)
|
107
|
+
new_nodes = @nodes.keys - old_nodes.keys
|
108
|
+
lost_nodes = old_nodes.keys - @nodes.keys
|
109
|
+
|
110
|
+
(@nodes.keys & old_nodes.keys).each do |object_id|
|
111
|
+
old = old_nodes[object_id].pointers.values
|
112
|
+
new = @nodes[object_id].pointers.values
|
113
|
+
|
114
|
+
(old - new).flatten.each do |lost_link|
|
115
|
+
lost_link.options = { :color => 'red' }
|
116
|
+
@nodes[object_id].lost_links << lost_link
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Color the lost nodes and add them to the graph
|
121
|
+
lost_nodes.each do |object_id|
|
122
|
+
old_nodes[object_id].options['color'] = 'hotpink2'
|
123
|
+
@nodes[object_id] = old_nodes[object_id]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Color the new nodes
|
127
|
+
new_nodes.each do |object_id|
|
128
|
+
@nodes[object_id].options['color'] = 'yellowgreen'
|
129
|
+
end
|
84
130
|
end
|
85
131
|
|
86
132
|
##
|
87
|
-
# Adds +target+ to
|
133
|
+
# Adds +target+ plus ascendants to graph
|
134
|
+
|
135
|
+
def add_ascendants(target)
|
136
|
+
ascendants = []
|
137
|
+
stack = [target]
|
138
|
+
|
139
|
+
while stack.length > 0
|
140
|
+
o_target = stack.pop
|
141
|
+
ascendants << o_target
|
142
|
+
|
143
|
+
GC.start
|
144
|
+
ObjectSpace.each_object do |object|
|
145
|
+
next if @nodes.key? object.object_id
|
146
|
+
next if object.equal? o_target
|
147
|
+
next if object.equal? ascendants
|
148
|
+
next if object.equal? stack
|
149
|
+
next if ascendants.any? { |x| x.equal? object }
|
150
|
+
|
151
|
+
case object
|
152
|
+
when IO, String, ARGF then
|
153
|
+
# Do nothing
|
154
|
+
when Hash then
|
155
|
+
object.each do |k,v|
|
156
|
+
if k.equal?(o_target) || v.equal?(o_target)
|
157
|
+
stack << object
|
158
|
+
break
|
159
|
+
end
|
160
|
+
end
|
161
|
+
when Enumerable then
|
162
|
+
object.each do |v|
|
163
|
+
if v.equal?(o_target)
|
164
|
+
stack << object
|
165
|
+
break
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
88
169
|
|
89
|
-
|
90
|
-
|
170
|
+
if object.instance_variables.length > 0
|
171
|
+
stack << object if object.instance_variables.any? do |iv_sym|
|
172
|
+
object.instance_variable_get(iv_sym).equal?(o_target)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
add_object_to_graph(target, { :style => 'dashed' })
|
179
|
+
(ascendants - [target]).each { |x| add_object_to_graph(x) }
|
180
|
+
ascendants.clear
|
181
|
+
stack.clear
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Adds +target+ to the graph, descending in to the target
|
91
186
|
|
92
|
-
|
187
|
+
def add_descendants(target)
|
188
|
+
stack = add_object_to_graph(target, { :style => 'dashed' })
|
93
189
|
|
94
|
-
while
|
95
|
-
object =
|
96
|
-
|
97
|
-
next if @seen_hash.key? @name
|
98
|
-
@seen_hash[@name] = 1
|
190
|
+
while stack.length > 0
|
191
|
+
object = stack.pop
|
192
|
+
next if @nodes.key? object.object_id
|
99
193
|
@preprocess_callback.call object if @preprocess_callback
|
100
194
|
|
101
|
-
|
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]
|
195
|
+
stack += add_object_to_graph(object, {:style => 'filled'})
|
118
196
|
|
119
|
-
|
120
|
-
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Adds +object+ to tree
|
202
|
+
|
203
|
+
def add_object_to_graph(object, opts = {})
|
204
|
+
record = Node.new(object.object_id, object_name(object))
|
205
|
+
record.options = opts
|
206
|
+
|
207
|
+
stack = []
|
208
|
+
case object
|
209
|
+
when String then
|
210
|
+
str = object[0..15].gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/,' ')
|
211
|
+
str << '...' if object.length > 15
|
212
|
+
record.values << "'#{str}'"
|
213
|
+
when Numeric then
|
214
|
+
record.values << object
|
215
|
+
when IO then
|
216
|
+
when Hash then
|
217
|
+
if object.respond_to? :empty? and object.empty? then
|
218
|
+
record.values << 'Empty!'
|
219
|
+
else
|
220
|
+
object.each_with_index do |(k,v),i|
|
221
|
+
next if v.nil? && ! @opts[:show_nil]
|
222
|
+
next unless v.class.to_s =~ @opts[:class_filter]
|
223
|
+
|
224
|
+
stack += add_to_record(record, k, v)
|
121
225
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
226
|
+
end
|
227
|
+
when Enumerable then
|
228
|
+
if object.respond_to? :empty? and object.empty? then
|
229
|
+
record.values.push 'Empty!'
|
230
|
+
else
|
231
|
+
object.each_with_index do |v, i|
|
232
|
+
next if v.nil? && ! @opts[:show_nil]
|
233
|
+
if v.class.to_s =~ @opts[:class_filter] or
|
234
|
+
(v.is_a? Enumerable and not v.is_a? String) then
|
235
|
+
stack += add_to_record(record, i, v)
|
132
236
|
end
|
133
237
|
end
|
134
238
|
end
|
239
|
+
end
|
135
240
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
241
|
+
# This is a HACK around bug 1345 (I think)
|
242
|
+
if object.class and object.class.ancestors.include? Array and
|
243
|
+
not Enumerable === object then
|
244
|
+
if object.respond_to? :empty? and object.empty? then
|
245
|
+
record.values.push 'Empty!'
|
246
|
+
else
|
247
|
+
object.each_with_index do |v, i|
|
248
|
+
next if v.nil? && ! @opts[:show_nil]
|
249
|
+
if v.class.to_s =~ @opts[:class_filter] or
|
250
|
+
(v.is_a? Enumerable and not v.is_a? String) then
|
251
|
+
stack += add_to_record(record, i, v)
|
148
252
|
end
|
149
253
|
end
|
150
254
|
end
|
255
|
+
end
|
151
256
|
|
152
|
-
|
257
|
+
stack += add_ivars(record, object)
|
153
258
|
|
154
|
-
|
155
|
-
|
259
|
+
@nodes[record.node_id] = record
|
260
|
+
stack
|
156
261
|
end
|
157
262
|
|
158
263
|
##
|
159
264
|
# Adds ivars for +object+ to the graph.
|
160
265
|
|
161
|
-
def add_ivars(object)
|
266
|
+
def add_ivars(record, object)
|
162
267
|
return unless object.instance_variables # HACK WTF Rails?
|
268
|
+
stack = []
|
163
269
|
|
164
270
|
ivars = object.instance_variables.sort
|
165
271
|
|
@@ -172,48 +278,52 @@ class ObjectGraph
|
|
172
278
|
next if v.nil? && ! @opts[:show_nil]
|
173
279
|
next unless v.class.to_s =~ @opts[:class_filter]
|
174
280
|
|
175
|
-
add_to_record k, v
|
281
|
+
stack += add_to_record(record, k, v)
|
176
282
|
end
|
177
283
|
end
|
178
284
|
end
|
179
285
|
|
180
|
-
ivars.
|
286
|
+
ivars.each do |iv_sym|
|
181
287
|
iv = object.instance_variable_get iv_sym
|
182
288
|
next if iv.nil? && ! @opts[:show_nil]
|
183
289
|
if iv.class.to_s =~ @opts[:class_filter] ||
|
184
290
|
(object.is_a?(Enumerable) && !object.is_a?(String))
|
185
|
-
|
186
|
-
|
187
|
-
|
291
|
+
|
292
|
+
obj_name = object_name(iv)
|
293
|
+
record.pointers["#{iv_sym}"] << Pointer.new(obj_name, iv.object_id)
|
294
|
+
record.values.push("#{iv_sym}")
|
295
|
+
stack.push(iv)
|
188
296
|
end
|
189
297
|
end
|
190
298
|
|
191
|
-
|
299
|
+
stack
|
192
300
|
end
|
193
301
|
|
194
302
|
##
|
195
|
-
# Adds +key+ and +value+ to
|
196
|
-
# processed.
|
303
|
+
# Adds +key+ and +value+ to +record+
|
197
304
|
|
198
|
-
def add_to_record(key, value)
|
199
|
-
|
305
|
+
def add_to_record(record, key, value)
|
306
|
+
stack = []
|
307
|
+
record.values.push(
|
200
308
|
[key, value].map { |val|
|
201
309
|
case val
|
202
310
|
when NilClass
|
203
311
|
'nil'
|
204
312
|
when Fixnum
|
205
313
|
"#{val}"
|
206
|
-
when String
|
314
|
+
when String, Symbol
|
315
|
+
val = ":#{val}" if val.is_a? Symbol
|
207
316
|
string_val = val[0..12].gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/, ' ')
|
208
317
|
string_val << '...' if val.length > 12
|
209
318
|
string_val
|
210
319
|
else
|
211
320
|
string_val = object_name val
|
212
|
-
|
213
|
-
|
321
|
+
record.pointers[string_val] << Pointer.new(string_val, val.object_id)
|
322
|
+
stack.push val
|
214
323
|
string_val
|
215
324
|
end
|
216
325
|
}.join(' is '))
|
326
|
+
stack
|
217
327
|
end
|
218
328
|
|
219
329
|
##
|
@@ -261,24 +371,80 @@ digraph g {
|
|
261
371
|
|
262
372
|
END
|
263
373
|
|
264
|
-
@
|
265
|
-
s << "\"#{
|
374
|
+
@nodes.values.sort.each do |node|
|
375
|
+
s << "\"#{node.name}\" [\n\tlabel="
|
266
376
|
list = []
|
267
|
-
|
268
|
-
|
269
|
-
|
377
|
+
list << "<f0>#{node.name}"
|
378
|
+
node.values.each_with_index do |value,i|
|
379
|
+
list << "<f#{i + 1}>#{value}"
|
380
|
+
end
|
270
381
|
s << "\"#{list.join('|')}\"\n"
|
382
|
+
node.options.each do |k,v|
|
383
|
+
s << "#{k}=#{v}\n"
|
384
|
+
end
|
271
385
|
s << "\tshape = \"record\"\n]\n\n"
|
272
|
-
|
386
|
+
end
|
273
387
|
|
274
|
-
@
|
275
|
-
|
276
|
-
|
388
|
+
@nodes.values.each do |node|
|
389
|
+
node.pointers.each_with_index do |(from,pointer),i|
|
390
|
+
pointer.each do |p|
|
391
|
+
to = @nodes[p.object_id]
|
392
|
+
next unless to
|
393
|
+
s<< "\"#{node.name}\":f#{i + 1} -> \"#{p.name}\":f0 [ id = #{i}\n"
|
394
|
+
p.options.each do |k,v|
|
395
|
+
s << "#{k}=#{v}\n"
|
396
|
+
end
|
397
|
+
s << "]\n"
|
398
|
+
end
|
399
|
+
end
|
400
|
+
node.lost_links.each do |p|
|
401
|
+
s<< "\"#{node.name}\":f0 -> \"#{p.name}\":f0 [\n"
|
402
|
+
p.options.each do |k,v|
|
403
|
+
s << "#{k}=#{v}\n"
|
404
|
+
end
|
405
|
+
s << "]\n"
|
406
|
+
end
|
407
|
+
end
|
277
408
|
|
278
409
|
s << "}\n"
|
279
410
|
|
280
411
|
s
|
281
412
|
end
|
282
413
|
|
414
|
+
class Pointer
|
415
|
+
attr_accessor :name, :object_id, :options
|
416
|
+
def initialize(name, object_id, options = {})
|
417
|
+
@name = name
|
418
|
+
@object_id = object_id
|
419
|
+
@options = options
|
420
|
+
end
|
421
|
+
|
422
|
+
def hash
|
423
|
+
name.hash
|
424
|
+
end
|
425
|
+
|
426
|
+
def eql?(other)
|
427
|
+
name.eql? other.name
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
class Node
|
432
|
+
include Comparable
|
433
|
+
attr_accessor :node_id, :name, :values, :pointers, :options, :lost_links
|
434
|
+
|
435
|
+
def initialize(node_id, name)
|
436
|
+
@node_id = node_id
|
437
|
+
@name = name
|
438
|
+
@values = []
|
439
|
+
@pointers = Hash.new { |h,k| h[k] = [] }
|
440
|
+
@options = {}
|
441
|
+
@lost_links = []
|
442
|
+
end
|
443
|
+
|
444
|
+
def <=>(other)
|
445
|
+
self.node_id <=> other.node_id
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
283
449
|
end
|
284
450
|
|
data/test/test_all.rb
ADDED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ograph
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2007-06-17 00:00:00 -07: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
|
@@ -15,6 +15,7 @@ description: "ObjectGraph will output Graphviz dot files of your objects in memo
|
|
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
|
17
17
|
sample code see: * http://flickr.com/photos/aaronp/tags/graphviz/ *
|
18
|
+
http://tenderlovemaking.com/2007/06/17/graphing-ruby-objects/ *
|
18
19
|
http://tenderlovemaking.com/2007/01/13/graphing-objects-in-memory-with-ruby/"
|
19
20
|
autorequire:
|
20
21
|
default_executable:
|
@@ -38,8 +39,13 @@ files:
|
|
38
39
|
- Manifest.txt
|
39
40
|
- README.txt
|
40
41
|
- Rakefile
|
42
|
+
- examples/ascendants_graph.rb
|
43
|
+
- examples/diff_graph.rb
|
44
|
+
- examples/regular_graph.rb
|
41
45
|
- lib/ograph.rb
|
42
|
-
|
46
|
+
- test/test_all.rb
|
47
|
+
test_files:
|
48
|
+
- test/test_all.rb
|
43
49
|
rdoc_options: []
|
44
50
|
extra_rdoc_files: []
|
45
51
|
executables: []
|
@@ -54,5 +60,5 @@ dependencies:
|
|
54
60
|
-
|
55
61
|
- ">="
|
56
62
|
- !ruby/object:Gem::Version
|
57
|
-
version: 1.
|
63
|
+
version: 1.2.0
|
58
64
|
version:
|