-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdigraph.rb
145 lines (124 loc) · 3.26 KB
/
digraph.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
=begin rdoc
A small lib for generating GraphViz dot digraphs from ruby.
=end
module Digraph
GRAPH_DIR = '/tmp'
class Node
attr_reader :attributes
def initialize(name, graph)
@name, @graph = name, graph
end
def >>(other_node)
@graph.add_edge(self, other_node)
other_node
end
def to_s; @name end
def [](atts); @attributes = atts; self end
def inspect
out = "#{@name}"
out += " [#{@attributes}]" if @attributes
out
end
end
class Edge
def initialize(from, to, atts=nil)
@from = from
if to.is_a?(Grapher::E)
@to = to.node
@attributes = to.attributes
else
@to = to
@attributes = atts
end
end
def inspect
out = "#{@from} -> #{@to}"
out += " [#{@attributes}]" if @attributes
out
end
end
class Graph
attr_reader :name
def initialize(name=nil, globals={})
@name = name
@globals = globals
@nodes = {}
@edges = []
@attributes = {}
@subgraphs = []
end
def add_node(name)
node = @nodes[name]
unless node
node = Node.new(name, self)
@nodes[name] = node
end
node
end
def add_edge(from, to, atts=nil)
edge = Edge.new(from,to,atts)
@edges << edge
edge
end
def add_attribute(att, val); @attributes[att] = val end
def add_subgraph(graph); @subgraphs << graph end
def to_dot(sub=false)
out = sub ? "subgraph #{name} {\n" : "digraph G {\n"
@subgraphs.each {|g| out << "\n" + g.to_dot(true) }
@globals.each {|k,v| out << " #{k} [#{v}]\n"}
@attributes.each {|k,v| out << " #{k} =\"#{v}\";\n"}
@nodes.values.each do |n|
out << " #{n.inspect};\n"
end
@edges.each {|e| out << " #{e.inspect};\n"}
out << "}\n"
out
end
def write(format='png', target="graph")
dot_file = File.join(GRAPH_DIR, "#{target}.dot")
target_file = File.join(GRAPH_DIR, target+".#{format}")
File.open(dot_file, 'w') {|f| f << self.to_dot}
system("dot -T#{format} -o #{target_file} #{dot_file}")
target_file
end
end
class Grapher
class E
attr_reader :node, :attributes
def initialize(node, atts)
@node, @attributes = node, atts
end
def >>(other); @node >> other end
end
attr_reader :graph
def initialize(globals={}, graph_name=nil)
@graph = Graph.new(graph_name, globals)
end
def E(node, atts); E.new(node, atts) end
def subgraph(name, globals={}, &blk)
subgrapher = Grapher.new(globals, name)
subgrapher.instance_eval(&blk) if block_given?
subgrapher.transfer_attributes
@graph.add_subgraph subgrapher.graph
end
def transfer_attributes
(instance_variables - ["@graph"]).each do |v|
@graph.add_attribute(v[1..-1], instance_eval(v))
end
end
private
def method_missing(meth, *args)
if args.empty?
@graph.add_node(meth.to_s)
else
super
end
end
end
def self.new(globals={}, &blk)
grapher = Grapher.new(globals)
grapher.instance_eval(&blk) if block_given?
grapher.transfer_attributes
grapher.graph
end
end