graph 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +36 -0
- data/Manifest.txt +6 -0
- data/README.txt +21 -18
- data/Rakefile +18 -0
- data/bin/graph +6 -1
- data/gallery/cluster.rb +66 -0
- data/gallery/fsm.rb +49 -0
- data/gallery/ruby_exceptions.rb +20 -0
- data/gallery/simple_example.rb +19 -0
- data/gallery/unix.rb +115 -0
- data/lib/dep_analyzer.rb +36 -6
- data/lib/graph.rb +328 -108
- data/lib/homebrew_analyzer.rb +19 -0
- data/lib/rake_analyzer.rb +19 -22
- data/lib/rubygems_analyzer.rb +25 -4
- data/test/test_graph.rb +227 -90
- metadata +41 -27
- metadata.gz.sig +1 -0
data/lib/graph.rb
CHANGED
@@ -1,130 +1,165 @@
|
|
1
1
|
#!/usr/local/bin/ruby -w
|
2
2
|
|
3
3
|
##
|
4
|
-
# Graph
|
4
|
+
# Graph models directed graphs and subgraphs and outputs in graphviz's
|
5
|
+
# dot format.
|
6
|
+
|
7
|
+
class Graph
|
8
|
+
VERSION = "2.0.0" # :nodoc:
|
9
|
+
|
10
|
+
LIGHT_COLORS = %w(gray lightblue lightcyan lightgray lightpink
|
11
|
+
lightslategray lightsteelblue white)
|
12
|
+
|
13
|
+
# WTF -- can't be %w() because of a bug in rcov
|
14
|
+
BOLD_COLORS = ["black", "brown", "mediumblue", "blueviolet",
|
15
|
+
"orange", "magenta", "darkgreen", "maroon",
|
16
|
+
"violetred", "purple", "greenyellow", "deeppink",
|
17
|
+
"midnightblue", "firebrick", "darkturquoise",
|
18
|
+
"mediumspringgreen", "chartreuse", "navy",
|
19
|
+
"lightseagreen", "chocolate", "lawngreen", "green",
|
20
|
+
"indigo", "darkgoldenrod", "darkviolet", "red",
|
21
|
+
"springgreen", "saddlebrown", "mediumvioletred",
|
22
|
+
"goldenrod", "tomato", "cyan", "forestgreen",
|
23
|
+
"darkorchid", "crimson", "coral", "deepskyblue",
|
24
|
+
"seagreen", "peru", "turquoise", "orangered",
|
25
|
+
"dodgerblue", "sienna", "limegreen", "royalblue",
|
26
|
+
"darkorange", "blue"]
|
27
|
+
|
28
|
+
SHAPES = %w(Mcircle Mdiamond Msquare box box3d circle component
|
29
|
+
diamond doublecircle doubleoctagon egg ellipse folder
|
30
|
+
hexagon house invhouse invtrapezium invtriangle none
|
31
|
+
note octagon parallelogram pentagon plaintext point
|
32
|
+
polygon rect rectangle septagon square tab trapezium
|
33
|
+
triangle tripleoctagon)
|
34
|
+
|
35
|
+
STYLES = %w(dashed dotted solid invis bold filled diagonals rounded)
|
36
|
+
|
37
|
+
STYLES.each do |name|
|
38
|
+
define_method(name) { style name }
|
39
|
+
end
|
40
|
+
|
41
|
+
(BOLD_COLORS + LIGHT_COLORS).each do |name|
|
42
|
+
define_method(name) { color name }
|
43
|
+
end
|
5
44
|
|
6
|
-
|
7
|
-
|
45
|
+
SHAPES.each do |name|
|
46
|
+
define_method(name.downcase) { shape name }
|
47
|
+
end
|
8
48
|
|
9
49
|
##
|
10
|
-
# A
|
11
|
-
#
|
12
|
-
# graph.attribs["a"] << "color = red"
|
13
|
-
#
|
14
|
-
# Will color node "a" red.
|
50
|
+
# A parent graph, if any. Only used for subgraphs.
|
15
51
|
|
16
|
-
|
52
|
+
attr_accessor :graph
|
17
53
|
|
18
54
|
##
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# You (generally) should leave this alone.
|
55
|
+
# The name of the graph. Optional for graphs and subgraphs. Prefix
|
56
|
+
# the name of a subgraph with "cluster" for subgraph that is boxed.
|
22
57
|
|
23
|
-
|
58
|
+
attr_accessor :name
|
24
59
|
|
25
60
|
##
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# graph.prefix << "ratio = 1.5"
|
61
|
+
# Global attributes for edges in this graph.
|
29
62
|
|
30
|
-
attr_reader :
|
63
|
+
attr_reader :edge_attribs
|
31
64
|
|
32
65
|
##
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# graph.edge["a"]["b"] << "color = blue"
|
36
|
-
#
|
37
|
-
# Will color the edge between a and b blue.
|
66
|
+
# The hash of hashes of edges in this graph. Use #[] or #node to create edges.
|
38
67
|
|
39
|
-
attr_reader :
|
68
|
+
attr_reader :edges
|
40
69
|
|
41
|
-
|
42
|
-
|
43
|
-
super
|
44
|
-
end
|
70
|
+
##
|
71
|
+
# Global attributes for this graph.
|
45
72
|
|
46
|
-
|
47
|
-
super
|
48
|
-
@prefix.clear
|
49
|
-
@order.clear
|
50
|
-
@attribs.clear
|
51
|
-
@edge.clear
|
52
|
-
end
|
73
|
+
attr_reader :graph_attribs
|
53
74
|
|
54
75
|
##
|
55
|
-
#
|
76
|
+
# Global attributes for nodes in this graph.
|
56
77
|
|
57
|
-
|
58
|
-
(keys + values).flatten.uniq
|
59
|
-
end
|
78
|
+
attr_reader :node_attribs
|
60
79
|
|
61
80
|
##
|
62
|
-
#
|
63
|
-
# good for longer text nodes.
|
81
|
+
# The hash of nodes in this graph. Use #[] or #node to create nodes.
|
64
82
|
|
65
|
-
|
66
|
-
|
83
|
+
attr_reader :nodes
|
84
|
+
|
85
|
+
##
|
86
|
+
# An array of subgraphs.
|
87
|
+
|
88
|
+
attr_reader :subgraphs
|
89
|
+
|
90
|
+
##
|
91
|
+
# Creates a new graph object. Optional name and parent graph are
|
92
|
+
# available. Also takes an optional block for DSL-like use.
|
93
|
+
|
94
|
+
def initialize name = nil, graph = nil, &block
|
95
|
+
@name = name
|
96
|
+
@graph = graph
|
97
|
+
graph << self if graph
|
98
|
+
@nodes = Hash.new { |h,k| h[k] = Node.new self, k }
|
99
|
+
@edges = Hash.new { |h,k|
|
100
|
+
h[k] = Hash.new { |h2, k2| h2[k2] = Edge.new self, self[k], self[k2] }
|
101
|
+
}
|
102
|
+
@graph_attribs = []
|
103
|
+
@node_attribs = []
|
104
|
+
@edge_attribs = []
|
105
|
+
@subgraphs = []
|
106
|
+
|
107
|
+
instance_eval(&block) if block
|
67
108
|
end
|
68
109
|
|
69
110
|
##
|
70
|
-
#
|
111
|
+
# Push a subgraph into the current graph. Sets the subgraph's graph to self.
|
71
112
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
end
|
113
|
+
def << subgraph
|
114
|
+
subgraphs << subgraph
|
115
|
+
subgraph.graph = self
|
76
116
|
end
|
77
117
|
|
78
118
|
##
|
79
|
-
#
|
119
|
+
# Access a node by name
|
80
120
|
|
81
|
-
def
|
82
|
-
|
83
|
-
each_pair do |from, to|
|
84
|
-
result[from] += 1
|
85
|
-
end
|
86
|
-
result
|
121
|
+
def [] name
|
122
|
+
nodes[name]
|
87
123
|
end
|
88
124
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
125
|
+
##
|
126
|
+
# A convenience method to set the global node attributes to use boxes.
|
127
|
+
|
128
|
+
def boxes
|
129
|
+
node_attribs << shape("box")
|
93
130
|
end
|
94
131
|
|
95
132
|
##
|
96
|
-
#
|
97
|
-
#
|
98
|
-
# g["a"] << "b"
|
99
|
-
# g["a"] << "b"
|
100
|
-
# g.each_pair { |from, to| ... }
|
101
|
-
#
|
102
|
-
# goes over a -> b *twice*.
|
133
|
+
# Shortcut method to create a new color Attribute instance.
|
103
134
|
|
104
|
-
def
|
105
|
-
|
106
|
-
self[from].each do |to|
|
107
|
-
yield from, to
|
108
|
-
end
|
109
|
-
end
|
135
|
+
def color color
|
136
|
+
Attribute.new "color = #{color}"
|
110
137
|
end
|
111
138
|
|
112
139
|
##
|
113
|
-
#
|
140
|
+
# Shortcut method to create a new colorscheme Attribute instance.
|
114
141
|
|
115
|
-
def
|
116
|
-
|
117
|
-
next unless count < minimum
|
118
|
-
delete node
|
119
|
-
end
|
142
|
+
def colorscheme name
|
143
|
+
Attribute.new "colorscheme = #{name}"
|
120
144
|
end
|
121
145
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
146
|
+
##
|
147
|
+
# Define one or more edges.
|
148
|
+
#
|
149
|
+
# edge "a", "b", "c", ...
|
150
|
+
#
|
151
|
+
# is equivalent to:
|
152
|
+
#
|
153
|
+
# edge "a", "b"
|
154
|
+
# edge "b", "c"
|
155
|
+
# ...
|
156
|
+
|
157
|
+
def edge(*names)
|
158
|
+
last = nil
|
159
|
+
names.each_cons(2) do |from, to|
|
160
|
+
last = self[from][to]
|
161
|
+
end
|
162
|
+
last
|
128
163
|
end
|
129
164
|
|
130
165
|
##
|
@@ -132,76 +167,261 @@ class Graph < Hash
|
|
132
167
|
|
133
168
|
def invert
|
134
169
|
result = self.class.new
|
135
|
-
|
136
|
-
|
170
|
+
edges.each do |from, h|
|
171
|
+
h.each do |to, edge|
|
172
|
+
result[to][from]
|
173
|
+
end
|
137
174
|
end
|
138
175
|
result
|
139
176
|
end
|
140
177
|
|
141
178
|
##
|
142
|
-
#
|
179
|
+
# Shortcut method to create a new fillcolor Attribute instance.
|
143
180
|
|
144
|
-
def
|
145
|
-
|
181
|
+
def fillcolor n
|
182
|
+
Attribute.new "fillcolor = #{n}"
|
146
183
|
end
|
147
184
|
|
148
185
|
##
|
149
|
-
#
|
186
|
+
# Shortcut method to create a new font Attribute instance. You can
|
187
|
+
# pass in both the name and an optional font size.
|
150
188
|
|
151
|
-
def
|
152
|
-
|
189
|
+
def font name, size=nil
|
190
|
+
Attribute.new "fontname = #{name.inspect}" +
|
191
|
+
(size ? ", fontsize = #{size}" : "")
|
153
192
|
end
|
154
193
|
|
155
194
|
##
|
156
|
-
#
|
195
|
+
# Shortcut method to set the graph's label. Usually used with subgraphs.
|
157
196
|
|
158
|
-
def
|
159
|
-
|
197
|
+
def label name
|
198
|
+
graph_attribs << "label = \"#{name}\""
|
160
199
|
end
|
161
200
|
|
162
201
|
##
|
163
|
-
#
|
202
|
+
# Access a node by name, supplying an optional label
|
164
203
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
168
|
-
|
204
|
+
def node name, label = nil
|
205
|
+
n = nodes[name]
|
206
|
+
n.label label if label
|
207
|
+
n
|
208
|
+
end
|
209
|
+
|
210
|
+
##
|
211
|
+
# Shortcut method to specify the orientation of the graph. Defaults
|
212
|
+
# to the graphviz default "TB".
|
213
|
+
|
214
|
+
def orient dir = "TB"
|
215
|
+
graph_attribs << "rankdir = #{dir}"
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Shortcut method to specify the orientation of the graph. Defaults to "LR".
|
220
|
+
|
221
|
+
def rotate dir = "LR"
|
222
|
+
orient dir
|
169
223
|
end
|
170
224
|
|
171
225
|
##
|
172
226
|
# Saves out both a dot file to path and an image for the specified type.
|
173
227
|
# Specify type as nil to skip exporting an image.
|
174
228
|
|
175
|
-
def save path, type=
|
229
|
+
def save path, type = nil
|
176
230
|
File.open "#{path}.dot", "w" do |f|
|
177
231
|
f.write self.to_s
|
178
232
|
end
|
179
233
|
system "dot -T#{type} #{path}.dot > #{path}.#{type}" if type
|
180
234
|
end
|
181
235
|
|
236
|
+
##
|
237
|
+
# Shortcut method to create a new shape Attribute instance.
|
238
|
+
|
239
|
+
def shape shape
|
240
|
+
Attribute.new "shape = #{shape}"
|
241
|
+
end
|
242
|
+
|
243
|
+
##
|
244
|
+
# Shortcut method to create a new style Attribute instance.
|
245
|
+
|
246
|
+
def style name
|
247
|
+
Attribute.new "style = #{name}"
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Shortcut method to create a subgraph in the current graph. Use
|
252
|
+
# with the top-level +digraph+ method in block form for a graph DSL.
|
253
|
+
|
254
|
+
def subgraph name = nil, &block
|
255
|
+
Graph.new name, self, &block
|
256
|
+
end
|
257
|
+
|
182
258
|
##
|
183
259
|
# Outputs a graphviz graph.
|
184
260
|
|
185
261
|
def to_s
|
186
262
|
result = []
|
187
|
-
|
263
|
+
|
264
|
+
type = graph ? "subgraph" : "digraph"
|
265
|
+
result << "#{type} #{name}"
|
188
266
|
result << " {"
|
189
267
|
|
190
|
-
|
268
|
+
graph_attribs.each do |line|
|
191
269
|
result << " #{line};"
|
192
270
|
end
|
193
271
|
|
194
|
-
|
195
|
-
result << "
|
272
|
+
unless node_attribs.empty? then
|
273
|
+
result << " node [ #{node_attribs.join(", ")} ];"
|
274
|
+
end
|
275
|
+
|
276
|
+
unless edge_attribs.empty? then
|
277
|
+
result << " edge [ #{edge_attribs.join(", ")} ];"
|
196
278
|
end
|
197
279
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
280
|
+
subgraphs.each do |line|
|
281
|
+
result << " #{line};"
|
282
|
+
end
|
283
|
+
|
284
|
+
nodes.each do |name, node|
|
285
|
+
result << " #{node};" if graph or not node.attributes.empty?
|
286
|
+
end
|
287
|
+
|
288
|
+
edges.each do |from, deps|
|
289
|
+
deps.each do |to, edge|
|
290
|
+
result << " #{edge};"
|
291
|
+
end
|
202
292
|
end
|
203
293
|
|
204
294
|
result << " }"
|
205
295
|
result.join "\n"
|
206
296
|
end
|
297
|
+
|
298
|
+
##
|
299
|
+
# An attribute for a graph, node, or edge. Really just a composable
|
300
|
+
# string (via #+) with a convenience method #<< that allows you to
|
301
|
+
# "paint" nodes and edges with this attribute.
|
302
|
+
|
303
|
+
class Attribute < Struct.new :attr
|
304
|
+
##
|
305
|
+
# "Paint" graphs, nodes, and edges with this attribute.
|
306
|
+
#
|
307
|
+
# red << node1 << node2 << node3
|
308
|
+
#
|
309
|
+
# is the same as:
|
310
|
+
#
|
311
|
+
# node1.attributes << red
|
312
|
+
# node2.attributes << red
|
313
|
+
# node3.attributes << red
|
314
|
+
|
315
|
+
def << thing
|
316
|
+
thing.attributes << self
|
317
|
+
self
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Returns the attribute in string form.
|
322
|
+
|
323
|
+
alias :to_s :attr
|
324
|
+
|
325
|
+
##
|
326
|
+
# Compose a new attribute from two existing attributes:
|
327
|
+
#
|
328
|
+
# bad_nodes = red + filled + diamond
|
329
|
+
|
330
|
+
def + style
|
331
|
+
Attribute.new "#{self}, #{style}"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
##
|
336
|
+
# An edge in a graph.
|
337
|
+
|
338
|
+
class Edge < Struct.new :graph, :from, :to, :attributes
|
339
|
+
|
340
|
+
##
|
341
|
+
# Create a new edge in +graph+ from +from+ to +to+.
|
342
|
+
|
343
|
+
def initialize graph, from, to
|
344
|
+
super graph, from, to, []
|
345
|
+
end
|
346
|
+
|
347
|
+
##
|
348
|
+
# Shortcut method to set the label attribute on an edge.
|
349
|
+
|
350
|
+
def label name
|
351
|
+
attributes << "label = \"#{name}\""
|
352
|
+
self
|
353
|
+
end
|
354
|
+
|
355
|
+
##
|
356
|
+
# Returns the edge in dot syntax.
|
357
|
+
|
358
|
+
def to_s
|
359
|
+
fromto = "%p -> %p" % [from.name, to.name]
|
360
|
+
if attributes.empty? then
|
361
|
+
fromto
|
362
|
+
else
|
363
|
+
"%-20s [ %-20s ]" % [fromto, attributes.join(',')]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
##
|
369
|
+
# Nodes in the graph.
|
370
|
+
|
371
|
+
class Node < Struct.new :graph, :name, :attributes
|
372
|
+
|
373
|
+
##
|
374
|
+
# Create a new Node. Takes a parent graph and a name.
|
375
|
+
|
376
|
+
def initialize graph, name
|
377
|
+
super graph, name, []
|
378
|
+
end
|
379
|
+
|
380
|
+
##
|
381
|
+
# Shortcut method to set the node's label.
|
382
|
+
|
383
|
+
def label name
|
384
|
+
attributes << "label = #{name.inspect}"
|
385
|
+
end
|
386
|
+
|
387
|
+
##
|
388
|
+
# Create a new node with +name+ and an edge between them pointing
|
389
|
+
# from self to the new node.
|
390
|
+
|
391
|
+
def >> name
|
392
|
+
self[name] # creates node and edge
|
393
|
+
self
|
394
|
+
end
|
395
|
+
|
396
|
+
alias :"<<" :">>"
|
397
|
+
|
398
|
+
##
|
399
|
+
# Returns the edge between self and +dep_name+.
|
400
|
+
|
401
|
+
def [] dep_name
|
402
|
+
graph.edges[name][dep_name]
|
403
|
+
end
|
404
|
+
|
405
|
+
##
|
406
|
+
# Returns the node in dot syntax.
|
407
|
+
|
408
|
+
def to_s
|
409
|
+
if attributes.empty? then
|
410
|
+
"#{name.inspect}"
|
411
|
+
else
|
412
|
+
"%-20p [ %-20s ]" % [name, attributes.join(',')]
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
##
|
419
|
+
# Convenience method to create a new graph. Used for DSL-style:
|
420
|
+
#
|
421
|
+
# g = digraph do
|
422
|
+
# edge "a", "b", "c"
|
423
|
+
# end
|
424
|
+
|
425
|
+
def digraph name = nil, &block
|
426
|
+
Graph.new name, &block
|
207
427
|
end
|