~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to dotgraph.py

  • Committer: Aaron Bentley
  • Date: 2005-09-22 23:30:34 UTC
  • Revision ID: aaron.bentley@utoronto.ca-20050922233034-0bb63c8bef90f19a
Updated NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python2.4
 
2
from subprocess import Popen, PIPE
 
3
from urllib import urlencode
 
4
from xml.sax.saxutils import escape
 
5
import os.path
 
6
import errno
 
7
import tempfile
 
8
import shutil
 
9
 
 
10
RSVG_OUTPUT_TYPES = ('png', 'jpg')
 
11
DOT_OUTPUT_TYPES = ('svg', 'svgz', 'gif', 'jpg', 'ps', 'fig', 'mif', 'png', 
 
12
                    'cmapx')
 
13
 
 
14
class NoDot(Exception):
 
15
    def __init__(self):
 
16
        Exception.__init__(self, "Can't find dot!")
 
17
 
 
18
class NoRsvg(Exception):
 
19
    def __init__(self):
 
20
        Exception.__init__(self, "Can't find rsvg!")
 
21
 
 
22
class Node(object):
 
23
    def __init__(self, name, color=None, label=None, rev_id=None,
 
24
                 cluster=None, node_style=None):
 
25
        self.name = name
 
26
        self.color = color
 
27
        self.label = label
 
28
        self.committer = None
 
29
        self.rev_id = rev_id
 
30
        if node_style is None:
 
31
            self.node_style = []
 
32
        self.cluster = cluster
 
33
        self.rank = None
 
34
        self.href = None
 
35
 
 
36
    def define(self):
 
37
        attributes = []
 
38
        style = []
 
39
        if self.color is not None:
 
40
            attributes.append('fillcolor="%s"' % self.color)
 
41
            style.append('filled')
 
42
        style.extend(self.node_style)
 
43
        if len(style) > 0:
 
44
            attributes.append('style="%s"' % ",".join(style))
 
45
        label = self.label
 
46
        if label is not None:
 
47
            attributes.append('label="%s"' % label)
 
48
        attributes.append('shape="box"')
 
49
        if self.href is not None:
 
50
            attributes.append('href="%s"' % self.href)
 
51
        if len(attributes) > 0:
 
52
            return '%s[%s]' % (self.name, " ".join(attributes))
 
53
 
 
54
    def __str__(self):
 
55
        return self.name
 
56
 
 
57
class Edge(object):
 
58
    def __init__(self, start, end, label=None):
 
59
        object.__init__(self)
 
60
        self.start = start
 
61
        self.end = end
 
62
        self.label = label
 
63
 
 
64
    def dot(self, do_weight=False):
 
65
        attributes = []
 
66
        if self.label is not None:
 
67
            attributes.append(('label', self.label))
 
68
        if do_weight:
 
69
            weight = '0'
 
70
            if self.start.cluster == self.end.cluster:
 
71
                weight = '1'
 
72
            elif self.start.rank is None:
 
73
                weight = '1'
 
74
            elif self.end.rank is None:
 
75
                weight = '1'
 
76
            attributes.append(('weight', weight))
 
77
        if len(attributes) > 0:
 
78
            atlist = []
 
79
            for key, value in attributes:
 
80
                atlist.append("%s=\"%s\"" % (key, value))
 
81
            pq = ' '.join(atlist)
 
82
            op = "[%s]" % pq
 
83
        else:
 
84
            op = ""
 
85
        return "%s->%s%s;" % (self.start.name, self.end.name, op)
 
86
 
 
87
def make_edge(relation):
 
88
    if hasattr(relation, 'start') and hasattr(relation, 'end'):
 
89
        return relation
 
90
    return Edge(relation[0], relation[1])
 
91
 
 
92
def dot_output(relations, ranking="forced"):
 
93
    defined = {}
 
94
    yield "digraph G\n"
 
95
    yield "{\n"
 
96
    clusters = set()
 
97
    edges = [make_edge(f) for f in relations]
 
98
    def rel_appropriate(start, end, cluster):
 
99
        if cluster is None:
 
100
            return (start.cluster is None and end.cluster is None) or \
 
101
                start.cluster != end.cluster
 
102
        else:
 
103
            return start.cluster==cluster and end.cluster==cluster
 
104
 
 
105
    for edge in edges:
 
106
        if edge.start.cluster is not None:
 
107
            clusters.add(edge.start.cluster)
 
108
        if edge.end.cluster is not None:
 
109
            clusters.add(edge.end.cluster)
 
110
    clusters = list(clusters)
 
111
    clusters.append(None)
 
112
    for index, cluster in enumerate(clusters):
 
113
        if cluster is not None and ranking == "cluster":
 
114
            yield "subgraph cluster_%s\n" % index
 
115
            yield "{\n"
 
116
            yield '    label="%s"\n' % cluster
 
117
        for edge in edges:
 
118
            if edge.start.name not in defined and edge.start.cluster == cluster:
 
119
                defined[edge.start.name] = edge.start
 
120
                my_def = edge.start.define()
 
121
                if my_def is not None:
 
122
                    yield "    %s\n" % my_def
 
123
            if edge.end.name not in defined and edge.end.cluster == cluster:
 
124
                defined[edge.end.name] = edge.end
 
125
                my_def = edge.end.define()
 
126
                if my_def is not None:
 
127
                    yield "    %s;\n" % my_def
 
128
            if rel_appropriate(edge.start, edge.end, cluster):
 
129
                yield "    %s\n" % edge.dot(do_weight=ranking=="forced")
 
130
        if cluster is not None and ranking == "cluster":
 
131
            yield "}\n"
 
132
 
 
133
    if ranking == "forced":
 
134
        ranks = {}
 
135
        for node in defined.itervalues():
 
136
            if node.rank not in ranks:
 
137
                ranks[node.rank] = set()
 
138
            ranks[node.rank].add(node.name)
 
139
        sorted_ranks = [n for n in ranks.iteritems()]
 
140
        sorted_ranks.sort()
 
141
        last_rank = None
 
142
        for rank, nodes in sorted_ranks:
 
143
            if rank is None:
 
144
                continue
 
145
            yield 'rank%d[style="invis"];\n' % rank
 
146
            if last_rank is not None:
 
147
                yield 'rank%d -> rank%d[style="invis"];\n' % (last_rank, rank)
 
148
            last_rank = rank
 
149
        for rank, nodes in ranks.iteritems():
 
150
            if rank is None:
 
151
                continue
 
152
            node_text = "; ".join('"%s"' % n for n in nodes)
 
153
            yield ' {rank = same; "rank%d"; %s}\n' % (rank, node_text)
 
154
    yield "}\n"
 
155
 
 
156
def invoke_dot_aa(input, out_file, file_type='png'):
 
157
    """\
 
158
    Produce antialiased Dot output, invoking rsvg on an intermediate file.
 
159
    rsvg only supports png, jpeg and .ico files."""
 
160
    tempdir = tempfile.mkdtemp()
 
161
    try:
 
162
        temp_file = os.path.join(tempdir, 'temp.svg')
 
163
        invoke_dot(input, temp_file, 'svg')
 
164
        cmdline = ['rsvg', temp_file, out_file]
 
165
        try:
 
166
            rsvg_proc = Popen(cmdline)
 
167
        except OSError, e:
 
168
            if e.errno == errno.ENOENT:
 
169
                raise NoRsvg()
 
170
        status = rsvg_proc.wait()
 
171
    finally:
 
172
        shutil.rmtree(tempdir)
 
173
    return status
 
174
 
 
175
def invoke_dot(input, out_file=None, file_type='svg', antialias=None, 
 
176
               fontname="Helvetica", fontsize=11):
 
177
    cmdline = ['dot', '-T%s' % file_type, '-Nfontname=%s' % fontname, 
 
178
               '-Efontname=%s' % fontname, '-Nfontsize=%d' % fontsize,
 
179
               '-Efontsize=%d' % fontsize]
 
180
    if out_file is not None:
 
181
        cmdline.extend(('-o', out_file))
 
182
    try:
 
183
        dot_proc = Popen(cmdline, stdin=PIPE)
 
184
    except OSError, e:
 
185
        if e.errno == errno.ENOENT:
 
186
            raise NoDot()
 
187
        else:
 
188
            raise
 
189
    for line in input:
 
190
        dot_proc.stdin.write(line)
 
191
    dot_proc.stdin.close()
 
192
    return dot_proc.wait()