aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Vandenberg2010-08-12 22:03:09 -0700
committerAdam Vandenberg2010-08-13 09:46:17 -0700
commit3ca44e1858f4efd79a3c255dd4f68a64fac8a6a8 (patch)
tree39c629e98f747dee69ee94560aa0a37feedceea5
parentbed8867a97f051460474ef133e3031f1d40467cd (diff)
downloadhomebrew-3ca44e1858f4efd79a3c255dd4f68a64fac8a6a8.tar.bz2
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
-rwxr-xr-xLibrary/Contributions/examples/brew-graph309
1 files changed, 309 insertions, 0 deletions
diff --git a/Library/Contributions/examples/brew-graph b/Library/Contributions/examples/brew-graph
new file mode 100755
index 000000000..dd87ffdf5
--- /dev/null
+++ b/Library/Contributions/examples/brew-graph
@@ -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()