From 43a1622609db51e0f92622859291be70c6e8e123 Mon Sep 17 00:00:00 2001 From: Adam Vandenberg Date: Thu, 12 Aug 2010 22:03:09 -0700 Subject: [PATCH] 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 --- Library/Contributions/examples/brew-graph | 309 ++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100755 Library/Contributions/examples/brew-graph diff --git a/Library/Contributions/examples/brew-graph b/Library/Contributions/examples/brew-graph new file mode 100755 index 0000000000..dd87ffdf5d --- /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()