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.
- data/History.txt +12 -0
- data/README.txt +1 -3
- data/Rakefile +1 -1
- data/lib/ograph.rb +261 -49
- metadata +4 -4
data/History.txt
CHANGED
@@ -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
|
-
|
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 = '
|
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")
|
data/lib/ograph.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
256
|
+
\tgraph [ rankdir = "LR" ];
|
257
|
+
\tnode [ fontsize = "8"
|
258
|
+
\t\tshape = "ellipse"
|
259
|
+
\t];
|
260
|
+
\tedge [ ];
|
261
|
+
|
53
262
|
END
|
54
|
-
|
55
|
-
|
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
|
61
|
-
s
|
62
|
-
shape = "record"
|
63
|
-
]
|
64
|
-
END
|
270
|
+
s << "\"#{list.join('|')}\"\n"
|
271
|
+
s << "\tshape = \"record\"\n]\n\n"
|
65
272
|
}
|
66
|
-
|
67
|
-
|
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
|
-
|
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
|
7
|
-
date: 2007-
|
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:
|
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.
|
57
|
+
version: 1.1.7
|
58
58
|
version:
|