2010-08-12 22:03:09 -07:00
|
|
|
#!/usr/bin/env python
|
|
|
|
"""
|
|
|
|
$ brew install graphviz
|
|
|
|
$ brew graph | dot -Tsvg -ohomebrew.svg
|
|
|
|
$ open homebrew.svg
|
|
|
|
"""
|
|
|
|
from __future__ import with_statement
|
|
|
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
import re
|
|
|
|
from subprocess import Popen, PIPE
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
def run(command, print_command=False):
|
|
|
|
"Run a command, returning the exit code and output."
|
|
|
|
if print_command: print command
|
|
|
|
p = Popen(command, stdout=PIPE)
|
|
|
|
output, errput = p.communicate()
|
|
|
|
return p.returncode, output
|
|
|
|
|
|
|
|
|
|
|
|
def _quote_id(id):
|
|
|
|
return '"' + id.replace('"', '\"') + '"'
|
|
|
|
|
|
|
|
|
|
|
|
def format_attribs(attrib):
|
|
|
|
if len(attrib) == 0:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
values = ['%s="%s"' % (k, attrib[k]) for k in attrib]
|
|
|
|
return '[' + ','.join(values) + ']'
|
|
|
|
|
|
|
|
|
|
|
|
class Output(object):
|
|
|
|
def __init__(self, fd=sys.stdout, tabstyle=" "):
|
|
|
|
self.fd = fd
|
|
|
|
self.tabstyle = tabstyle
|
|
|
|
self.tablevel = 0
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.fd = None
|
|
|
|
|
|
|
|
def out(self, s):
|
|
|
|
self.tabout()
|
|
|
|
self.fd.write(s)
|
|
|
|
|
|
|
|
def outln(self, s=None):
|
|
|
|
if s is not None:
|
|
|
|
self.tabout()
|
|
|
|
self.fd.write(s)
|
|
|
|
self.fd.write('\n')
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def indented(self):
|
|
|
|
self.indent()
|
|
|
|
yield self
|
|
|
|
self.dedent()
|
|
|
|
|
|
|
|
def indent(self):
|
|
|
|
self.tablevel += 1
|
|
|
|
|
|
|
|
def dedent(self):
|
|
|
|
if self.tablevel == 0:
|
|
|
|
raise Exception('No existing indent level.')
|
|
|
|
self.tablevel -= 1
|
|
|
|
|
|
|
|
def tabout(self):
|
|
|
|
if self.tablevel:
|
|
|
|
self.fd.write(self.tabstyle * self.tablevel)
|
|
|
|
|
|
|
|
|
|
|
|
class NodeContainer(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.nodes = list()
|
|
|
|
self.node_defaults = dict()
|
|
|
|
# Stack of node attribs
|
|
|
|
self._node_styles = list()
|
|
|
|
|
|
|
|
def _node_style(self):
|
|
|
|
if (len(self._node_styles) > 0):
|
|
|
|
return self._node_styles[-1]
|
|
|
|
else:
|
|
|
|
return dict()
|
|
|
|
|
|
|
|
def _push_node_style(self, attrib):
|
|
|
|
self._node_styles.append(attrib)
|
|
|
|
|
|
|
|
def _pop_node_style(self):
|
|
|
|
return self._node_styles.pop()
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def node_styles(self, attrib):
|
|
|
|
self._push_node_style(attrib)
|
|
|
|
yield
|
|
|
|
self._pop_node_style()
|
|
|
|
|
|
|
|
def node(self, nodeid, label, attrib=None):
|
|
|
|
_attrib = dict(self._node_style())
|
|
|
|
if attrib is not None:
|
|
|
|
_attrib.update(attrib)
|
|
|
|
|
|
|
|
n = Node(nodeid, label, _attrib)
|
|
|
|
self.nodes.append(n)
|
|
|
|
return n
|
|
|
|
|
|
|
|
def nodes_to_dot(self, out):
|
|
|
|
if len(self.node_defaults) > 0:
|
|
|
|
out.outln("node " + format_attribs(self.node_defaults) + ";")
|
|
|
|
|
|
|
|
if len(self.nodes) == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
id_width = max([len(_quote_id(n.id)) for n in self.nodes])
|
|
|
|
for node in self.nodes:
|
|
|
|
node.to_dot(out, id_width)
|
|
|
|
|
|
|
|
|
|
|
|
class Node(object):
|
|
|
|
def __init__(self, nodeid, label, attrib=None):
|
|
|
|
self.id = nodeid
|
|
|
|
self.label = label
|
|
|
|
self.attrib = attrib if attrib is not None else dict()
|
|
|
|
|
|
|
|
def as_dot(self, id_width=1):
|
|
|
|
_attribs = dict(self.attrib)
|
|
|
|
_attribs['label'] = self.label
|
|
|
|
|
|
|
|
return '%-*s %s' % (id_width, _quote_id(self.id), format_attribs(_attribs))
|
|
|
|
|
|
|
|
|
|
|
|
def to_dot(self, out, id_width=1):
|
|
|
|
out.outln(self.as_dot(id_width))
|
|
|
|
|
|
|
|
|
|
|
|
class ClusterContainer(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.clusters = list()
|
|
|
|
|
|
|
|
def cluster(self, clusterid, label, attrib=None):
|
|
|
|
c = Cluster(clusterid, label, self, attrib)
|
|
|
|
self.clusters.append(c)
|
|
|
|
return c
|
|
|
|
|
|
|
|
|
|
|
|
class Cluster(NodeContainer, ClusterContainer):
|
|
|
|
def __init__(self, clusterid, label, parentcluster=None, attrib=None):
|
|
|
|
NodeContainer.__init__(self)
|
|
|
|
ClusterContainer.__init__(self)
|
|
|
|
|
|
|
|
self.id = clusterid
|
|
|
|
self.label = label
|
|
|
|
self.attrib = attrib if attrib is not None else dict()
|
|
|
|
self.parentcluster = parentcluster
|
|
|
|
|
|
|
|
def cluster_id(self):
|
|
|
|
return _quote_id("cluster_" + self.id)
|
|
|
|
|
|
|
|
def to_dot(self, out):
|
|
|
|
out.outln("subgraph %s {" % self.cluster_id())
|
|
|
|
with out.indented():
|
|
|
|
out.outln('label = "%s"' % self.label)
|
|
|
|
for k in self.attrib:
|
|
|
|
out.outln('%s = "%s"' % (k, self.attrib[k]))
|
|
|
|
|
|
|
|
for cluster in self.clusters:
|
|
|
|
cluster.to_dot(out)
|
|
|
|
|
|
|
|
self.nodes_to_dot(out)
|
|
|
|
out.outln("}")
|
|
|
|
|
|
|
|
|
|
|
|
class Edge(object):
|
|
|
|
def __init__(self, source, target, attrib=None):
|
|
|
|
if attrib is None:
|
|
|
|
attrib = dict()
|
|
|
|
|
|
|
|
self.source = source
|
|
|
|
self.target = target
|
|
|
|
self.attrib = attrib
|
|
|
|
|
|
|
|
def to_dot(self, out):
|
|
|
|
out.outln(self.as_dot())
|
|
|
|
|
|
|
|
def as_dot(self):
|
|
|
|
return " ".join((_quote_id(self.source), "->", _quote_id(self.target), format_attribs(self.attrib)))
|
|
|
|
|
|
|
|
|
|
|
|
class EdgeContainer(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.edges = list()
|
|
|
|
self.edge_defaults = dict()
|
|
|
|
# Stack of edge attribs
|
|
|
|
self._edge_styles = list()
|
|
|
|
|
|
|
|
def _edge_style(self):
|
|
|
|
if (len(self._edge_styles) > 0):
|
|
|
|
return self._edge_styles[-1]
|
|
|
|
else:
|
|
|
|
return dict()
|
|
|
|
|
|
|
|
def _push_edge_style(self, attrib):
|
|
|
|
self._edge_styles.append(attrib)
|
|
|
|
|
|
|
|
def _pop_edge_style(self):
|
|
|
|
return self._edge_styles.pop()
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def edge_styles(self, attrib):
|
|
|
|
self._push_edge_style(attrib)
|
|
|
|
yield
|
|
|
|
self._pop_edge_style()
|
|
|
|
|
|
|
|
def link(self, source, target, attrib=None):
|
|
|
|
_attrib = dict(self._edge_style())
|
|
|
|
if attrib is not None:
|
|
|
|
_attrib.update(attrib)
|
|
|
|
|
|
|
|
e = Edge(source, target, _attrib)
|
|
|
|
self.edges.append(e)
|
|
|
|
return e
|
|
|
|
|
|
|
|
def edges_to_dot(self, out):
|
|
|
|
if len(self.edge_defaults) > 0:
|
|
|
|
out.outln("edge " + format_attribs(self.edge_defaults) + ";")
|
|
|
|
|
|
|
|
if len(self.edges) == 0:
|
|
|
|
return
|
|
|
|
|
|
|
|
for edge in self.edges:
|
|
|
|
edge.to_dot(out)
|
|
|
|
|
|
|
|
|
|
|
|
class Graph(NodeContainer, EdgeContainer, ClusterContainer):
|
|
|
|
"""
|
|
|
|
Contains the nodes, edges, and subgraph definitions for a graph to be
|
|
|
|
turned into a Graphviz DOT file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, label=None, attrib=None):
|
|
|
|
NodeContainer.__init__(self)
|
|
|
|
EdgeContainer.__init__(self)
|
|
|
|
ClusterContainer.__init__(self)
|
|
|
|
|
|
|
|
self.label = label if label is not None else "Default Label"
|
|
|
|
self.attrib = attrib if attrib is not None else dict()
|
|
|
|
|
|
|
|
def dot(self, fd=sys.stdout):
|
|
|
|
try:
|
|
|
|
self.o = Output(fd)
|
|
|
|
self._dot()
|
|
|
|
finally:
|
|
|
|
self.o.close()
|
|
|
|
|
|
|
|
def _dot(self):
|
|
|
|
self.o.outln("digraph G {")
|
|
|
|
|
|
|
|
with self.o.indented():
|
|
|
|
self.o.outln('label = "%s"' % self.label)
|
|
|
|
for k in self.attrib:
|
|
|
|
self.o.outln('%s = "%s"' % (k, self.attrib[k]))
|
|
|
|
|
|
|
|
self.nodes_to_dot(self.o)
|
|
|
|
|
|
|
|
for cluster in self.clusters:
|
|
|
|
self.o.outln()
|
|
|
|
cluster.to_dot(self.o)
|
|
|
|
|
|
|
|
self.o.outln()
|
|
|
|
self.edges_to_dot(self.o)
|
|
|
|
|
|
|
|
self.o.outln("}")
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2011-02-13 23:23:00 +01:00
|
|
|
cmd = ["brew", "deps"]
|
|
|
|
cmd.extend(sys.argv[1:] or ["--all"])
|
|
|
|
code, output = run(cmd)
|
2010-08-12 22:03:09 -07:00
|
|
|
output = output.strip()
|
|
|
|
depgraph = list()
|
|
|
|
|
|
|
|
for f in output.split("\n"):
|
|
|
|
stuff = f.split(":",2)
|
|
|
|
name = stuff[0]
|
|
|
|
deps = stuff[1].strip()
|
|
|
|
if not deps:
|
|
|
|
deps = list()
|
|
|
|
else:
|
|
|
|
deps = deps.split(" ")
|
|
|
|
depgraph.append((name, deps))
|
|
|
|
|
|
|
|
hb = Graph("Homebrew Dependencies", attrib={'labelloc':'b', 'rankdir':'LR', 'ranksep':'5'})
|
|
|
|
|
|
|
|
used = set()
|
|
|
|
for f in depgraph:
|
|
|
|
for d in f[1]:
|
|
|
|
used.add(f[0])
|
|
|
|
used.add(d)
|
|
|
|
|
|
|
|
for f in depgraph:
|
|
|
|
if f[0] not in used:
|
|
|
|
continue
|
|
|
|
n = hb.node(f[0], f[0])
|
|
|
|
for d in f[1]:
|
|
|
|
hb.link(d, f[0])
|
|
|
|
|
|
|
|
hb.dot()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|