125
by Aaron Bentley
Added pastegraph |
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 |
|
139
by Aaron Bentley
Tweaked missing-dot handling |
6 |
import errno |
143
by Aaron Bentley
Used rsvga for nice antialiasing |
7 |
import tempfile |
8 |
import shutil |
|
139
by Aaron Bentley
Tweaked missing-dot handling |
9 |
|
140
by Aaron Bentley
Mapped some email addresses to names |
10 |
mail_map = {} |
11 |
||
139
by Aaron Bentley
Tweaked missing-dot handling |
12 |
class NoDot(Exception): |
13 |
def __init__(self): |
|
14 |
Exception.__init__(self, "Can't find dot!") |
|
125
by Aaron Bentley
Added pastegraph |
15 |
|
143
by Aaron Bentley
Used rsvga for nice antialiasing |
16 |
class NoRsvg(Exception): |
17 |
def __init__(self): |
|
18 |
Exception.__init__(self, "Can't find rsvg!") |
|
19 |
||
125
by Aaron Bentley
Added pastegraph |
20 |
class Node(object): |
142
by Aaron Bentley
Clustered the branch revision history |
21 |
def __init__(self, name, color=None, label=None, rev_id=None, |
22 |
cluster=None): |
|
125
by Aaron Bentley
Added pastegraph |
23 |
self.name = name |
24 |
self.color = color |
|
128
by Aaron Bentley
Got initial graphing functionality working |
25 |
self.label = label |
130
by Aaron Bentley
Added committer to revisions |
26 |
self.committer = None |
137
by Aaron Bentley
Put dotted outlines on missing revisions |
27 |
self.rev_id = rev_id |
28 |
self.node_style = [] |
|
142
by Aaron Bentley
Clustered the branch revision history |
29 |
self.cluster = cluster |
135
by Aaron Bentley
Enhanced revision-crediting |
30 |
|
140
by Aaron Bentley
Mapped some email addresses to names |
31 |
def get_committer(self): |
135
by Aaron Bentley
Enhanced revision-crediting |
32 |
if self.committer is not None: |
140
by Aaron Bentley
Mapped some email addresses to names |
33 |
if '@' in self.committer: |
34 |
try: |
|
35 |
return mail_map[self.committer] |
|
36 |
except KeyError: |
|
37 |
pass
|
|
38 |
return self.committer |
|
135
by Aaron Bentley
Enhanced revision-crediting |
39 |
elif self.rev_id is not None: |
40 |
try: |
|
140
by Aaron Bentley
Mapped some email addresses to names |
41 |
first_segment = '-'.join(self.rev_id.split('-')[:-2])\ |
42 |
.strip(' ') |
|
135
by Aaron Bentley
Enhanced revision-crediting |
43 |
except ValueError: |
44 |
first_segment = [] |
|
45 |
if '@' in first_segment: |
|
140
by Aaron Bentley
Mapped some email addresses to names |
46 |
try: |
47 |
return mail_map[first_segment] |
|
48 |
except KeyError: |
|
49 |
return first_segment |
|
50 |
||
51 |
def get_label(self): |
|
52 |
label = None |
|
53 |
committer = self.get_committer() |
|
54 |
if committer is not None: |
|
55 |
label = "%s\\n%s" % (self.name, committer) |
|
135
by Aaron Bentley
Enhanced revision-crediting |
56 |
return label |
125
by Aaron Bentley
Added pastegraph |
57 |
|
58 |
def define(self): |
|
137
by Aaron Bentley
Put dotted outlines on missing revisions |
59 |
attributes = [] |
130
by Aaron Bentley
Added committer to revisions |
60 |
style = [] |
125
by Aaron Bentley
Added pastegraph |
61 |
if self.color is not None: |
137
by Aaron Bentley
Put dotted outlines on missing revisions |
62 |
attributes.append('fillcolor="%s"' % self.color) |
63 |
style.append('filled') |
|
64 |
style.extend(self.node_style) |
|
65 |
if len(style) > 0: |
|
66 |
attributes.append('style="%s"' % ",".join(style)) |
|
135
by Aaron Bentley
Enhanced revision-crediting |
67 |
label = self.get_label() |
68 |
if label is not None: |
|
137
by Aaron Bentley
Put dotted outlines on missing revisions |
69 |
attributes.append('label="%s"' % label) |
141
by Aaron Bentley
Switched to use boxes |
70 |
attributes.append('shape="box"') |
137
by Aaron Bentley
Put dotted outlines on missing revisions |
71 |
if len(attributes) > 0: |
72 |
return '%s[%s]' % (self.name, " ".join(attributes)) |
|
125
by Aaron Bentley
Added pastegraph |
73 |
|
74 |
def __str__(self): |
|
75 |
return self.name |
|
76 |
||
128
by Aaron Bentley
Got initial graphing functionality working |
77 |
def dot_output(relations): |
125
by Aaron Bentley
Added pastegraph |
78 |
defined = set() |
79 |
yield "digraph G\n" |
|
80 |
yield "{\n" |
|
142
by Aaron Bentley
Clustered the branch revision history |
81 |
clusters = set() |
82 |
def rel_appropriate(start, end, cluster): |
|
83 |
if cluster is None: |
|
84 |
return start.cluster is None or end.cluster is None |
|
85 |
else: |
|
86 |
return start.cluster==cluster and end.cluster==cluster |
|
87 |
||
125
by Aaron Bentley
Added pastegraph |
88 |
for (start, end) in relations: |
142
by Aaron Bentley
Clustered the branch revision history |
89 |
if start.cluster is not None: |
90 |
clusters.add(start.cluster) |
|
91 |
if end.cluster is not None: |
|
92 |
clusters.add(end.cluster) |
|
93 |
clusters = list(clusters) |
|
94 |
clusters.append(None) |
|
95 |
for index, cluster in enumerate(clusters): |
|
96 |
if cluster is not None: |
|
97 |
yield "subgraph cluster_%s\n" % index |
|
98 |
yield "{\n" |
|
99 |
yield ' label="%s"\n' % cluster |
|
100 |
for (start, end) in relations: |
|
101 |
if start.name not in defined and start.cluster == cluster: |
|
102 |
defined.add(start.name) |
|
103 |
my_def = start.define() |
|
104 |
if my_def is not None: |
|
105 |
yield " %s;\n" % my_def |
|
106 |
if end.name not in defined and end.cluster == cluster: |
|
107 |
defined.add(end.name) |
|
108 |
my_def = end.define() |
|
109 |
if my_def is not None: |
|
110 |
yield " %s;\n" % my_def |
|
111 |
if rel_appropriate(start, end, cluster): |
|
112 |
yield " %s->%s;\n" % (start.name, end.name) |
|
113 |
if cluster is not None: |
|
114 |
yield "}\n" |
|
125
by Aaron Bentley
Added pastegraph |
115 |
yield "}\n" |
116 |
||
143
by Aaron Bentley
Used rsvga for nice antialiasing |
117 |
def invoke_dot_aa(input, out_file, file_type='png'): |
118 |
"""\
|
|
119 |
Produce antialiased Dot output, invoking rsvg on an intermediate file.
|
|
120 |
rsvg only supports png, jpeg and .ico files."""
|
|
121 |
tempdir = tempfile.mkdtemp() |
|
122 |
try: |
|
123 |
temp_file = os.path.join(tempdir, 'temp.svg') |
|
124 |
invoke_dot(input, temp_file, 'svg') |
|
125 |
cmdline = ['rsvg', temp_file, out_file] |
|
126 |
try: |
|
127 |
rsvg_proc = Popen(cmdline) |
|
128 |
except OSError, e: |
|
129 |
if e.errno == errno.ENOENT: |
|
130 |
raise NoRsvg() |
|
131 |
status = rsvg_proc.wait() |
|
132 |
finally: |
|
133 |
shutil.rmtree(tempdir) |
|
134 |
return status |
|
135 |
||
142
by Aaron Bentley
Clustered the branch revision history |
136 |
def invoke_dot(input, out_file=None, file_type='svg', antialias=None): |
134
by Aaron Bentley
support multiple image formats for graph-ancestry |
137 |
cmdline = ['dot', '-T%s' % file_type] |
125
by Aaron Bentley
Added pastegraph |
138 |
if out_file is not None: |
139 |
cmdline.extend(('-o', out_file)) |
|
138
by Aaron Bentley
Handle systems without dot (the horror!). From Magnus Therning. |
140 |
try: |
141 |
dot_proc = Popen(cmdline, stdin=PIPE) |
|
142 |
except OSError, e: |
|
139
by Aaron Bentley
Tweaked missing-dot handling |
143 |
if e.errno == errno.ENOENT: |
144 |
raise NoDot() |
|
145 |
else: |
|
146 |
raise
|
|
125
by Aaron Bentley
Added pastegraph |
147 |
for line in input: |
148 |
dot_proc.stdin.write(line) |
|
149 |
dot_proc.stdin.close() |
|
150 |
return dot_proc.wait() |