~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to dotgraph.py

  • Committer: Michael Ellerman
  • Date: 2005-11-29 07:12:26 UTC
  • mto: (0.3.1 shelf-dev) (325.1.2 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 334.
  • Revision ID: michael@ellerman.id.au-20051129071226-a04b3f827880025d
Unshelve --pick was broken, because we deleted the whole patch, even when only
part of it was unshelved. So now if we unshelve part of a patch, the patch is
replaced with a new patch that has just the unshelved parts. That's a long way
of saying it does what you'd expect.

Implementing this required changing HunkSelector to return both the selected,
and unselected hunks (ie. patches to shelve, and patches to keep).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 Aaron Bentley
2
 
# <aaron@aaronbentley.com>
3
 
#
4
 
#    This program is free software; you can redistribute it and/or modify
5
 
#    it under the terms of the GNU General Public License as published by
6
 
#    the Free Software Foundation; either version 2 of the License, or
7
 
#    (at your option) any later version.
8
 
#
9
 
#    This program is distributed in the hope that it will be useful,
10
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
#    GNU General Public License for more details.
13
 
#
14
 
#    You should have received a copy of the GNU General Public License
15
 
#    along with this program; if not, write to the Free Software
16
 
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
 
18
 
from subprocess import Popen, PIPE
19
 
import os.path
20
 
import errno
21
 
import tempfile
22
 
import shutil
23
 
 
24
 
RSVG_OUTPUT_TYPES = ('png', 'jpg')
25
 
DOT_OUTPUT_TYPES = ('svg', 'svgz', 'gif', 'jpg', 'ps', 'fig', 'mif', 'png',
26
 
                    'cmapx')
27
 
 
28
 
class NoDot(Exception):
29
 
    def __init__(self):
30
 
        Exception.__init__(self, "Can't find dot!")
31
 
 
32
 
class NoRsvg(Exception):
33
 
    def __init__(self):
34
 
        Exception.__init__(self, "Can't find rsvg!")
35
 
 
36
 
class Node(object):
37
 
    def __init__(self, name, color=None, label=None, rev_id=None,
38
 
                 cluster=None, node_style=None, date=None, message=None):
39
 
        self.name = name
40
 
        self.color = color
41
 
        self.label = label
42
 
        self.committer = None
43
 
        self.rev_id = rev_id
44
 
        if node_style is None:
45
 
            self.node_style = []
46
 
        self.cluster = cluster
47
 
        self.rank = None
48
 
        self.date = date
49
 
        self.message = message
50
 
        self.href = None
51
 
 
52
 
    @staticmethod
53
 
    def get_attribute(name, value):
54
 
        if value is None:
55
 
            return ''
56
 
        value = value.replace("\\", "\\\\")
57
 
        value = value.replace('"', '\\"')
58
 
        value = value.replace('\n', '\\n')
59
 
        return '%s="%s"' % (name, value)
60
 
 
61
 
    def define(self):
62
 
        attributes = []
63
 
        style = []
64
 
        if self.color is not None:
65
 
            attributes.append('fillcolor="%s"' % self.color)
66
 
            style.append('filled')
67
 
        style.extend(self.node_style)
68
 
        if len(style) > 0:
69
 
            attributes.append('style="%s"' % ",".join(style))
70
 
        label = self.label
71
 
        if label is not None:
72
 
            attributes.append('label="%s"' % label)
73
 
        attributes.append('shape="box"')
74
 
        tooltip = None
75
 
        if self.message is not None:
76
 
            tooltip = self.message
77
 
        attributes.append(self.get_attribute('tooltip', tooltip))
78
 
        if self.href is not None:
79
 
            attributes.append('href="%s"' % self.href)
80
 
        elif tooltip:
81
 
            attributes.append('href="#"')
82
 
        if len(attributes) > 0:
83
 
            return '%s[%s]' % (self.name, " ".join(attributes))
84
 
 
85
 
    def __str__(self):
86
 
        return self.name
87
 
 
88
 
class Edge(object):
89
 
    def __init__(self, start, end, label=None):
90
 
        object.__init__(self)
91
 
        self.start = start
92
 
        self.end = end
93
 
        self.label = label
94
 
 
95
 
    def dot(self, do_weight=False):
96
 
        attributes = []
97
 
        if self.label is not None:
98
 
            attributes.append(('label', self.label))
99
 
        if do_weight:
100
 
            weight = '0'
101
 
            if self.start.cluster == self.end.cluster:
102
 
                weight = '1'
103
 
            elif self.start.rank is None:
104
 
                weight = '1'
105
 
            elif self.end.rank is None:
106
 
                weight = '1'
107
 
            attributes.append(('weight', weight))
108
 
        if len(attributes) > 0:
109
 
            atlist = []
110
 
            for key, value in attributes:
111
 
                atlist.append("%s=\"%s\"" % (key, value))
112
 
            pq = ' '.join(atlist)
113
 
            op = "[%s]" % pq
114
 
        else:
115
 
            op = ""
116
 
        return "%s->%s%s;" % (self.start.name, self.end.name, op)
117
 
 
118
 
def make_edge(relation):
119
 
    if hasattr(relation, 'start') and hasattr(relation, 'end'):
120
 
        return relation
121
 
    return Edge(relation[0], relation[1])
122
 
 
123
 
def dot_output(relations, ranking="forced"):
124
 
    defined = {}
125
 
    yield "digraph G\n"
126
 
    yield "{\n"
127
 
    clusters = set()
128
 
    edges = [make_edge(f) for f in relations]
129
 
    def rel_appropriate(start, end, cluster):
130
 
        if cluster is None:
131
 
            return (start.cluster is None and end.cluster is None) or \
132
 
                start.cluster != end.cluster
133
 
        else:
134
 
            return start.cluster==cluster and end.cluster==cluster
135
 
 
136
 
    for edge in edges:
137
 
        if edge.start.cluster is not None:
138
 
            clusters.add(edge.start.cluster)
139
 
        if edge.end.cluster is not None:
140
 
            clusters.add(edge.end.cluster)
141
 
    clusters = list(clusters)
142
 
    clusters.append(None)
143
 
    for index, cluster in enumerate(clusters):
144
 
        if cluster is not None and ranking == "cluster":
145
 
            yield "subgraph cluster_%s\n" % index
146
 
            yield "{\n"
147
 
            yield '    label="%s"\n' % cluster
148
 
        for edge in edges:
149
 
            if edge.start.name not in defined and edge.start.cluster == cluster:
150
 
                defined[edge.start.name] = edge.start
151
 
                my_def = edge.start.define()
152
 
                if my_def is not None:
153
 
                    yield "    %s\n" % my_def
154
 
            if edge.end.name not in defined and edge.end.cluster == cluster:
155
 
                defined[edge.end.name] = edge.end
156
 
                my_def = edge.end.define()
157
 
                if my_def is not None:
158
 
                    yield "    %s;\n" % my_def
159
 
            if rel_appropriate(edge.start, edge.end, cluster):
160
 
                yield "    %s\n" % edge.dot(do_weight=ranking=="forced")
161
 
        if cluster is not None and ranking == "cluster":
162
 
            yield "}\n"
163
 
 
164
 
    if ranking == "forced":
165
 
        ranks = {}
166
 
        for node in defined.itervalues():
167
 
            if node.rank not in ranks:
168
 
                ranks[node.rank] = set()
169
 
            ranks[node.rank].add(node.name)
170
 
        sorted_ranks = [n for n in ranks.iteritems()]
171
 
        sorted_ranks.sort()
172
 
        last_rank = None
173
 
        for rank, nodes in sorted_ranks:
174
 
            if rank is None:
175
 
                continue
176
 
            yield 'rank%d[style="invis"];\n' % rank
177
 
            if last_rank is not None:
178
 
                yield 'rank%d -> rank%d[style="invis"];\n' % (last_rank, rank)
179
 
            last_rank = rank
180
 
        for rank, nodes in ranks.iteritems():
181
 
            if rank is None:
182
 
                continue
183
 
            node_text = "; ".join('"%s"' % n for n in nodes)
184
 
            yield ' {rank = same; "rank%d"; %s}\n' % (rank, node_text)
185
 
    yield "}\n"
186
 
 
187
 
def invoke_dot_aa(input, out_file, file_type='png'):
188
 
    """\
189
 
    Produce antialiased Dot output, invoking rsvg on an intermediate file.
190
 
    rsvg only supports png, jpeg and .ico files."""
191
 
    tempdir = tempfile.mkdtemp()
192
 
    try:
193
 
        temp_file = os.path.join(tempdir, 'temp.svg')
194
 
        invoke_dot(input, temp_file, 'svg')
195
 
        cmdline = ['rsvg', temp_file, out_file]
196
 
        try:
197
 
            rsvg_proc = Popen(cmdline)
198
 
        except OSError, e:
199
 
            if e.errno == errno.ENOENT:
200
 
                raise NoRsvg()
201
 
        status = rsvg_proc.wait()
202
 
    finally:
203
 
        shutil.rmtree(tempdir)
204
 
    return status
205
 
 
206
 
def invoke_dot(input, out_file=None, file_type='svg', antialias=None,
207
 
               fontname="Helvetica", fontsize=11):
208
 
    cmdline = ['dot', '-T%s' % file_type, '-Nfontname=%s' % fontname,
209
 
               '-Efontname=%s' % fontname, '-Nfontsize=%d' % fontsize,
210
 
               '-Efontsize=%d' % fontsize]
211
 
    if out_file is not None:
212
 
        cmdline.extend(('-o', out_file))
213
 
    try:
214
 
        dot_proc = Popen(cmdline, stdin=PIPE)
215
 
    except OSError, e:
216
 
        if e.errno == errno.ENOENT:
217
 
            raise NoDot()
218
 
        else:
219
 
            raise
220
 
    for line in input:
221
 
        dot_proc.stdin.write(line.encode('utf-8'))
222
 
    dot_proc.stdin.close()
223
 
    return dot_proc.wait()
224
 
 
225
 
def invoke_dot_html(input, out_file):
226
 
    """\
227
 
    Produce an html file, which uses a .png file, and a cmap to provide
228
 
    annotated revisions.
229
 
    """
230
 
    tempdir = tempfile.mkdtemp()
231
 
    try:
232
 
        temp_dot = os.path.join(tempdir, 'temp.dot')
233
 
        status = invoke_dot(input, temp_dot, file_type='dot')
234
 
 
235
 
        dot = open(temp_dot)
236
 
        temp_file = os.path.join(tempdir, 'temp.cmapx')
237
 
        status = invoke_dot(dot, temp_file, 'cmapx')
238
 
 
239
 
        png_file = '.'.join(out_file.split('.')[:-1] + ['png'])
240
 
        dot.seek(0)
241
 
        status = invoke_dot(dot, png_file, 'png')
242
 
 
243
 
        png_relative = png_file.split('/')[-1]
244
 
        html = open(out_file, 'wb')
245
 
        w = html.write
246
 
        w('<html><head><title></title></head>\n')
247
 
        w('<body>\n')
248
 
        w('<img src="%s" usemap="#G" border=0/>' % png_relative)
249
 
        w(open(temp_file).read())
250
 
        w('</body></html>\n')
251
 
    finally:
252
 
        shutil.rmtree(tempdir)
253
 
    return status
254