ograph 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: