Add 'brew graph' external command.
This command generates a GraphViz dot file from the Hoembrew dependency graph. $ brew install graphviz $ brew graph | dot -Tsvg -ohomebrew.svg $ open homebrew.svg
This commit is contained in:
parent
741acf916e
commit
43a1622609
309
Library/Contributions/examples/brew-graph
Executable file
309
Library/Contributions/examples/brew-graph
Executable file
@ -0,0 +1,309 @@
|
||||
#!/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():
|
||||
code, output = run(["brew", "deps", "--all"])
|
||||
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()
|
||||
Loading…
x
Reference in New Issue
Block a user