-
Notifications
You must be signed in to change notification settings - Fork 5
/
graphviz.lua
189 lines (144 loc) · 4.04 KB
/
graphviz.lua
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
local Graph
local supported_ext = {
ps = true,
svg = true,
fig = true,
png = true,
imap = true,
cmapx = true,
pdf = true
}
local function pcall_wrap(f, ...)
local ok, cont = pcall(f, ...)
if not ok then
error(cont, 2)
end
return cont
end
local __Graph = {
node = function(self, nodename, label)
table.insert(self.nodes.node, {node =nodename, label = label})
return self
end,
edge = function(self, ...)
local args = {...}
for i = 2, #args do
table.insert(self.edges.edge, {prev = args[1], succ = args[i]})
end
return self
end,
source = function(self , graphtype , graphname , level)
graphtype = graphtype or "digraph"
graphname = graphname or "defaultname"
level = level or 0
local src = graphtype .. " " .. graphname .. " {\n" ..
"\tgraph [" .. self.graph.style:expand() .. "]\n" ..
"\tnode [" .. self.nodes.style:expand() .. "]\n" ..
"\tedge [" .. self.edges.style:expand() .. "]\n"
local node = self.nodes.node
for i = 1, #node do
src = src .. ("\t\t%s [label=\"%s\"]\n"):format(node[i].node, node[i].label)
end
local subgraphs = self.subgraphs
for subgraphname , subgraph in pairs(subgraphs) do
src = src .. ("\n%s\n"):format( subgraph:source("subgraph" , subgraphname , level + 1) )
end
local edge = self.edges.edge
for i = 1, #edge do
src = src .. ("\t\t\t%s -> %s\n"):format(edge[i].prev, edge[i].succ)
end
src = src .. "}"
local function split(str)
if not str:match("\n$") then
str = str .. "\n"
end
local ret = {}
for w in str:gmatch("(.-)\n") do
table.insert(ret, w)
end
return ret
end
local lines = split(src)
local indentstr = string.rep("\t\t" , level)
src = indentstr .. table.concat(lines , "\n" .. indentstr)
return src
end,
subgraph = function(self , subgraphname)
local subgraph = Graph()
self.subgraphs[subgraphname] = subgraph
return subgraph
end,
write = function(self, filename)
local file = pcall_wrap(io.open, filename, "w+")
file:write(self:source())
pcall_wrap(io.close, file)
return self
end,
compile = function(self, filename, format, generate_filename)
format = format or "pdf"
if not supported_ext[format] then
local format_aligned = {}
for fmt in pairs(supported_ext) do
table.insert(format_aligned, fmt)
end
local flen = #format_aligned
table.sort(format_aligned)
error(("Graphviz supports the following output formats: %s, and %s (got `%s')"):format(
table.concat(format_aligned, ", ", 1, flen - 1), format_aligned[flen], format))
end
self:write(filename)
generate_filename = generate_filename or ("%s.%s"):format(filename, format)
-- compile dot file
local cmd_str = ("dot -T%s %s -o %s"):format(
format --[[output format]],
filename --[[input dot file]],
generate_filename --[[output file]])
local cmd = io.popen(cmd_str, "r")
pcall_wrap(cmd.read, cmd, "*a")
pcall_wrap(io.close, cmd)
return self
end,
render = function(self, filename, format, generate_filename)
format = format or "pdf"
generate_filename = generate_filename or ("%s.%s"):format(filename, format)
self:compile(filename, format, generate_filename)
-- open generated file with `xdg-open`
local cmd_str = ("xdg-open %s &"):format(generate_filename)
local cmd = io.popen(cmd_str, "r")
pcall_wrap(cmd.read, cmd, "*a")
pcall_wrap(io.close, cmd)
return self
end,
}
local style_index = {
update = function(orig_style, styles)
for k, v in pairs(styles) do
orig_style[k] = v
end
end,
expand = function(tbl)
local style_str = ""
for k, v in pairs(tbl) do
if k == "fontname" then
v = ([["%s"]]):format(v)
end
style_str = style_str .. (" %s=%s"):format(k, v)
end
return style_str:gsub("^ ", "")
end
}
-- pseudo Graph class
Graph = function()
return setmetatable({
edges = {
edge = {},
style = setmetatable({}, {__index = style_index})},
nodes = {
node = {},
style = setmetatable({}, {__index = style_index})},
graph = {
style = setmetatable({}, {__index = style_index})},
subgraphs = {},
}, {__index = __Graph})
end
return Graph