Merge lp:~rainct/merge-o-matic/redesign into lp:merge-o-matic
- redesign
- Merge into trunk
Status: | Needs review | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~rainct/merge-o-matic/redesign | ||||
Merge into: | lp:merge-o-matic | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp:~rainct/merge-o-matic/redesign | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott James Remnant (Canonical) | Pending | ||
Review via email: mp+9075@code.launchpad.net |
Commit message
- Move the HTML out of the Python files, make use of a very simple templating system (tinpy) instead.
- Give the page a real design which uses less space, looks more pleasant and where the comments input box is actually visible.
- Move some of the code out from merge-status.py and manual-status.py to a new file shared between both.
- Update the code to use the hashlib module for md5, suppressing a deprecation warning.
- Remove some unneeded imports, and shebangs where they are unnecessary.
Description of the change
Unmerged revisions
- 169. By Siegfried Gevatter
-
Couple of bug fixes, supress the warning about the md5 module being deprecated by switching to hashlib, move stuff from merge-status.py and manual-status.py into a new status_common.py and other changes..
- 168. By Siegfried Gevatter
-
Remove unneeded imports.
- 167. By Siegfried Gevatter
-
Display the uploader, if the upload was sponsored.
- 166. By Siegfried Gevatter
-
Replace < and > with < and > in the Last Uploader name, before passing it to status.py, so that the e-mail address is displayed.
- 165. By Siegfried Gevatter
-
Use user-readable varialbe names in the for loop in status.tpl instead of accessing the package details with their numeric index.
- 164. By Siegfried Gevatter
-
More progress...
- 163. By Siegfried Gevatter
-
Start putting in proper variables.
- 162. By Siegfried Gevatter
-
Revert momlib changes.
- 161. By Siegfried Gevatter
-
merge
- 160. By Siegfried Gevatter
-
merge
Preview Diff
1 | === added directory 'images' | |||
2 | === added file 'images/header.png' | |||
3 | 0 | Binary files images/header.png 1970-01-01 00:00:00 +0000 and images/header.png 2008-03-28 22:21:54 +0000 differ | 0 | Binary files images/header.png 1970-01-01 00:00:00 +0000 and images/header.png 2008-03-28 22:21:54 +0000 differ |
4 | === added file 'images/header_bg.png' | |||
5 | 1 | Binary files images/header_bg.png 1970-01-01 00:00:00 +0000 and images/header_bg.png 2008-03-28 22:21:54 +0000 differ | 1 | Binary files images/header_bg.png 1970-01-01 00:00:00 +0000 and images/header_bg.png 2008-03-28 22:21:54 +0000 differ |
6 | === modified file 'manual-status.py' | |||
7 | --- manual-status.py 2009-03-02 15:33:48 +0000 | |||
8 | +++ manual-status.py 2009-07-20 18:07:18 +0000 | |||
9 | @@ -5,6 +5,8 @@ | |||
10 | 5 | # Copyright © 2008 Canonical Ltd. | 5 | # Copyright © 2008 Canonical Ltd. |
11 | 6 | # Author: Scott James Remnant <scott@ubuntu.com>. | 6 | # Author: Scott James Remnant <scott@ubuntu.com>. |
12 | 7 | # | 7 | # |
13 | 8 | # Copyright © 2008-2009 Siegfried-A. Gevatter Pujals <rainct@ubuntu.com> | ||
14 | 9 | # | ||
15 | 8 | # This program is free software: you can redistribute it and/or modify | 10 | # This program is free software: you can redistribute it and/or modify |
16 | 9 | # it under the terms of version 3 of the GNU General Public License as | 11 | # it under the terms of version 3 of the GNU General Public License as |
17 | 10 | # published by the Free Software Foundation. | 12 | # published by the Free Software Foundation. |
18 | @@ -19,39 +21,11 @@ | |||
19 | 19 | 21 | ||
20 | 20 | import os | 22 | import os |
21 | 21 | import bz2 | 23 | import bz2 |
22 | 22 | import fcntl | ||
23 | 23 | |||
24 | 24 | from rfc822 import parseaddr | 24 | from rfc822 import parseaddr |
25 | 25 | |||
26 | 25 | from momlib import * | 26 | from momlib import * |
56 | 26 | 27 | from util.status_common import * | |
57 | 27 | 28 | ||
29 | 28 | # Order of priorities | ||
30 | 29 | PRIORITY = [ "unknown", "required", "important", "standard", "optional", | ||
31 | 30 | "extra" ] | ||
32 | 31 | COLOURS = [ "#ff8080", "#ffb580", "#ffea80", "#dfff80", "#abff80", "#80ff8b" ] | ||
33 | 32 | |||
34 | 33 | # Sections | ||
35 | 34 | SECTIONS = [ "new", "updated" ] | ||
36 | 35 | |||
37 | 36 | |||
38 | 37 | def options(parser): | ||
39 | 38 | parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO", | ||
40 | 39 | default=SRC_DISTRO, | ||
41 | 40 | help="Source distribution") | ||
42 | 41 | parser.add_option("-S", "--source-suite", type="string", metavar="SUITE", | ||
43 | 42 | default=SRC_DIST, | ||
44 | 43 | help="Source suite (aka distrorelease)") | ||
45 | 44 | |||
46 | 45 | parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO", | ||
47 | 46 | default=OUR_DISTRO, | ||
48 | 47 | help="Destination distribution") | ||
49 | 48 | parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE", | ||
50 | 49 | default=OUR_DIST, | ||
51 | 50 | help="Destination suite (aka distrorelease)") | ||
52 | 51 | |||
53 | 52 | parser.add_option("-c", "--component", type="string", metavar="COMPONENT", | ||
54 | 53 | action="append", | ||
55 | 54 | help="Process only these destination components") | ||
58 | 55 | 29 | ||
59 | 56 | def main(options, args): | 30 | def main(options, args): |
60 | 57 | src_distro = options.source_distro | 31 | src_distro = options.source_distro |
61 | @@ -60,6 +34,8 @@ | |||
62 | 60 | our_distro = options.dest_distro | 34 | our_distro = options.dest_distro |
63 | 61 | our_dist = options.dest_suite | 35 | our_dist = options.dest_suite |
64 | 62 | 36 | ||
65 | 37 | SECTIONS.remove("outstanding") | ||
66 | 38 | |||
67 | 63 | # For each package in the destination distribution, find out whether | 39 | # For each package in the destination distribution, find out whether |
68 | 64 | # there's an open merge, and if so add an entry to the table for it. | 40 | # there's an open merge, and if so add an entry to the table for it. |
69 | 65 | for our_component in DISTROS[our_distro]["components"]: | 41 | for our_component in DISTROS[our_distro]["components"]: |
70 | @@ -69,10 +45,10 @@ | |||
71 | 69 | 45 | ||
72 | 70 | merges = [] | 46 | merges = [] |
73 | 71 | 47 | ||
75 | 72 | for our_source in get_sources(our_distro, our_dist, our_component): | 48 | for source in get_sources(our_distro, our_dist, our_component): |
76 | 73 | try: | 49 | try: |
79 | 74 | package = our_source["Package"] | 50 | package = source["Package"] |
80 | 75 | our_version = Version(our_source["Version"]) | 51 | our_version = Version(source["Version"]) |
81 | 76 | our_pool_source = get_pool_source(our_distro, package, | 52 | our_pool_source = get_pool_source(our_distro, package, |
82 | 77 | our_version) | 53 | our_version) |
83 | 78 | logging.debug("%s: %s is %s", package, our_distro, our_version) | 54 | logging.debug("%s: %s is %s", package, our_distro, our_version) |
84 | @@ -97,11 +73,11 @@ | |||
85 | 97 | pass | 73 | pass |
86 | 98 | 74 | ||
87 | 99 | try: | 75 | try: |
89 | 100 | priority_idx = PRIORITY.index(our_source["Priority"]) | 76 | priority_idx = PRIORITY.index(source["Priority"]) |
90 | 101 | except KeyError: | 77 | except KeyError: |
91 | 102 | priority_idx = 0 | 78 | priority_idx = 0 |
92 | 103 | 79 | ||
94 | 104 | filename = changes_file(our_distro, our_source) | 80 | filename = changes_file(our_distro, source) |
95 | 105 | if os.path.isfile(filename): | 81 | if os.path.isfile(filename): |
96 | 106 | changes = open(filename) | 82 | changes = open(filename) |
97 | 107 | elif os.path.isfile(filename + ".bz2"): | 83 | elif os.path.isfile(filename + ".bz2"): |
98 | @@ -119,7 +95,7 @@ | |||
99 | 119 | user = None | 95 | user = None |
100 | 120 | uploaded = False | 96 | uploaded = False |
101 | 121 | 97 | ||
103 | 122 | uploader = get_uploader(our_distro, our_source) | 98 | uploader = get_uploader(our_distro, source) |
104 | 123 | 99 | ||
105 | 124 | if uploaded: | 100 | if uploaded: |
106 | 125 | section = "updated" | 101 | section = "updated" |
107 | @@ -127,177 +103,15 @@ | |||
108 | 127 | section = "new" | 103 | section = "new" |
109 | 128 | 104 | ||
110 | 129 | merges.append((section, priority_idx, package, user, uploader, | 105 | merges.append((section, priority_idx, package, user, uploader, |
112 | 130 | our_source, our_version, src_version)) | 106 | source, our_version, src_version)) |
113 | 131 | 107 | ||
115 | 132 | write_status_page(our_component, merges, our_distro, src_distro) | 108 | write_status_page(our_component, merges, our_distro, |
116 | 109 | src_distro, True) | ||
117 | 133 | 110 | ||
118 | 134 | status_file = "%s/merges/tomerge-%s-manual" % (ROOT, our_component) | 111 | status_file = "%s/merges/tomerge-%s-manual" % (ROOT, our_component) |
119 | 135 | remove_old_comments(status_file, merges) | 112 | remove_old_comments(status_file, merges) |
120 | 136 | write_status_file(status_file, merges) | 113 | write_status_file(status_file, merges) |
121 | 137 | 114 | ||
122 | 138 | |||
123 | 139 | def write_status_page(component, merges, left_distro, right_distro): | ||
124 | 140 | """Write out the manual merge status page.""" | ||
125 | 141 | merges.sort() | ||
126 | 142 | |||
127 | 143 | status_file = "%s/merges/%s-manual.html" % (ROOT, component) | ||
128 | 144 | status = open(status_file + ".new", "w") | ||
129 | 145 | try: | ||
130 | 146 | print >>status, "<html>" | ||
131 | 147 | print >>status | ||
132 | 148 | print >>status, "<head>" | ||
133 | 149 | print >>status, "<title>Ubuntu Merge-o-Matic: %s manual</title>" \ | ||
134 | 150 | % component | ||
135 | 151 | print >>status, "<style>" | ||
136 | 152 | print >>status, "img#ubuntu {" | ||
137 | 153 | print >>status, " border: 0;" | ||
138 | 154 | print >>status, "}" | ||
139 | 155 | print >>status, "h1 {" | ||
140 | 156 | print >>status, " padding-top: 0.5em;" | ||
141 | 157 | print >>status, " font-family: sans-serif;" | ||
142 | 158 | print >>status, " font-size: 2.0em;" | ||
143 | 159 | print >>status, " font-weight: bold;" | ||
144 | 160 | print >>status, "}" | ||
145 | 161 | print >>status, "h2 {" | ||
146 | 162 | print >>status, " padding-top: 0.5em;" | ||
147 | 163 | print >>status, " font-family: sans-serif;" | ||
148 | 164 | print >>status, " font-size: 1.5em;" | ||
149 | 165 | print >>status, " font-weight: bold;" | ||
150 | 166 | print >>status, "}" | ||
151 | 167 | print >>status, "p, td {" | ||
152 | 168 | print >>status, " font-family: sans-serif;" | ||
153 | 169 | print >>status, " margin-bottom: 0;" | ||
154 | 170 | print >>status, "}" | ||
155 | 171 | print >>status, "li {" | ||
156 | 172 | print >>status, " font-family: sans-serif;" | ||
157 | 173 | print >>status, " margin-bottom: 1em;" | ||
158 | 174 | print >>status, "}" | ||
159 | 175 | print >>status, "tr.first td {" | ||
160 | 176 | print >>status, " border-top: 2px solid white;" | ||
161 | 177 | print >>status, "}" | ||
162 | 178 | print >>status, "</style>" | ||
163 | 179 | print >>status, "<% from momlib import * %>" | ||
164 | 180 | print >>status, "</head>" | ||
165 | 181 | print >>status, "<body>" | ||
166 | 182 | print >>status, "<img src=\".img/ubuntulogo-100.png\" id=\"ubuntu\">" | ||
167 | 183 | print >>status, "<h1>Ubuntu Merge-o-Matic: %s manual</h1>" % component | ||
168 | 184 | |||
169 | 185 | for section in SECTIONS: | ||
170 | 186 | section_merges = [ m for m in merges if m[0] == section ] | ||
171 | 187 | print >>status, ("<p><a href=\"#%s\">%s %s merges</a></p>" | ||
172 | 188 | % (section, len(section_merges), section)) | ||
173 | 189 | |||
174 | 190 | print >>status, "<% comment = get_comments() %>" | ||
175 | 191 | |||
176 | 192 | for section in SECTIONS: | ||
177 | 193 | section_merges = [ m for m in merges if m[0] == section ] | ||
178 | 194 | |||
179 | 195 | print >>status, ("<h2 id=\"%s\">%s Merges</h2>" | ||
180 | 196 | % (section, section.title())) | ||
181 | 197 | |||
182 | 198 | do_table(status, section_merges, left_distro, right_distro, component) | ||
183 | 199 | |||
184 | 200 | print >>status, "</body>" | ||
185 | 201 | print >>status, "</html>" | ||
186 | 202 | finally: | ||
187 | 203 | status.close() | ||
188 | 204 | |||
189 | 205 | os.rename(status_file + ".new", status_file) | ||
190 | 206 | |||
191 | 207 | def get_uploader(distro, source): | ||
192 | 208 | """Obtain the uploader from the dsc file signature.""" | ||
193 | 209 | for md5sum, size, name in files(source): | ||
194 | 210 | if name.endswith(".dsc"): | ||
195 | 211 | dsc_file = name | ||
196 | 212 | break | ||
197 | 213 | else: | ||
198 | 214 | return None | ||
199 | 215 | |||
200 | 216 | filename = "%s/pool/%s/%s/%s/%s" \ | ||
201 | 217 | % (ROOT, distro, pathhash(source["Package"]), source["Package"], | ||
202 | 218 | dsc_file) | ||
203 | 219 | |||
204 | 220 | (a, b, c) = os.popen3("gpg --verify %s" % filename) | ||
205 | 221 | stdout = c.readlines() | ||
206 | 222 | try: | ||
207 | 223 | return stdout[1].split("Good signature from")[1].strip().strip("\"") | ||
208 | 224 | except IndexError: | ||
209 | 225 | return None | ||
210 | 226 | |||
211 | 227 | def do_table(status, merges, left_distro, right_distro, component): | ||
212 | 228 | """Output a table.""" | ||
213 | 229 | print >>status, "<table cellspacing=0>" | ||
214 | 230 | print >>status, "<tr bgcolor=#d0d0d0>" | ||
215 | 231 | print >>status, "<td rowspan=2><b>Package</b></td>" | ||
216 | 232 | print >>status, "<td colspan=2><b>Last Uploader</b></td>" | ||
217 | 233 | print >>status, "<td rowspan=2><b>Comment</b></td>" | ||
218 | 234 | print >>status, "<td rowspan=2><b>Bug</b></td>" | ||
219 | 235 | print >>status, "</tr>" | ||
220 | 236 | print >>status, "<tr bgcolor=#d0d0d0>" | ||
221 | 237 | print >>status, "<td><b>%s Version</b></td>" % left_distro.title() | ||
222 | 238 | print >>status, "<td><b>%s Version</b></td>" % right_distro.title() | ||
223 | 239 | print >>status, "</tr>" | ||
224 | 240 | |||
225 | 241 | for uploaded, priority, package, user, uploader, source, \ | ||
226 | 242 | left_version, right_version in merges: | ||
227 | 243 | if user is not None: | ||
228 | 244 | who = user | ||
229 | 245 | who = who.replace("&", "&") | ||
230 | 246 | who = who.replace("<", "<") | ||
231 | 247 | who = who.replace(">", ">") | ||
232 | 248 | |||
233 | 249 | if uploader is not None: | ||
234 | 250 | (usr_name, usr_mail) = parseaddr(user) | ||
235 | 251 | (upl_name, upl_mail) = parseaddr(uploader) | ||
236 | 252 | |||
237 | 253 | if len(usr_name) and usr_name != upl_name: | ||
238 | 254 | u_who = uploader | ||
239 | 255 | u_who = u_who.replace("&", "&") | ||
240 | 256 | u_who = u_who.replace("<", "<") | ||
241 | 257 | u_who = u_who.replace(">", ">") | ||
242 | 258 | |||
243 | 259 | who = "%s<br><small><em>Uploader:</em> %s</small>" \ | ||
244 | 260 | % (who, u_who) | ||
245 | 261 | else: | ||
246 | 262 | who = " " | ||
247 | 263 | |||
248 | 264 | print >>status, "<tr bgcolor=%s class=first>" % COLOURS[priority] | ||
249 | 265 | print >>status, "<td><tt><a href=\"https://patches.ubuntu.com/" \ | ||
250 | 266 | "%s/%s/%s_%s.patch\">%s</a></tt>" \ | ||
251 | 267 | % (pathhash(package), package, package, left_version, package) | ||
252 | 268 | print >>status, " <sup><a href=\"https://launchpad.net/ubuntu/" \ | ||
253 | 269 | "+source/%s\">LP</a></sup>" % package | ||
254 | 270 | print >>status, " <sup><a href=\"http://packages.qa.debian.org/" \ | ||
255 | 271 | "%s\">PTS</a></sup></td>" % package | ||
256 | 272 | print >>status, "<td colspan=2>%s</td>" % who | ||
257 | 273 | print >>status, "<td rowspan=2><form method=\"get\" action=\"addcomment.py\"><br />" | ||
258 | 274 | print >>status, "<input type=\"hidden\" name=\"component\" value=\"%s\" />" % component | ||
259 | 275 | print >>status, "<input type=\"hidden\" name=\"package\" value=\"%s\" />" % package | ||
260 | 276 | print >>status, "<%%\n\ | ||
261 | 277 | the_comment = \"\"\n\ | ||
262 | 278 | if(comment.has_key(\"%s\")):\n\ | ||
263 | 279 | the_comment = comment[\"%s\"]\n\ | ||
264 | 280 | req.write(\"<input type=\\\"text\\\" style=\\\"border-style: none; background-color: %s\\\" name=\\\"comment\\\" value=\\\"%%s\\\" title=\\\"%%s\\\" />\" %% (the_comment, the_comment))\n\ | ||
265 | 281 | %%>" % (package, package, COLOURS[priority]) | ||
266 | 282 | print >>status, "</form></td>" | ||
267 | 283 | print >>status, "<td rowspan=2>" | ||
268 | 284 | print >>status, "<%%\n\ | ||
269 | 285 | if(comment.has_key(\"%s\")):\n\ | ||
270 | 286 | req.write(\"%%s\" %% gen_buglink_from_comment(comment[\"%s\"]))\n\ | ||
271 | 287 | else:\n\ | ||
272 | 288 | req.write(\" \")\n\ | ||
273 | 289 | \n\ | ||
274 | 290 | %%>" % (package, package) | ||
275 | 291 | print >>status, "</td>" | ||
276 | 292 | print >>status, "</tr>" | ||
277 | 293 | print >>status, "<tr bgcolor=%s>" % COLOURS[priority] | ||
278 | 294 | print >>status, "<td><small>%s</small></td>" % source["Binary"] | ||
279 | 295 | print >>status, "<td>%s</td>" % left_version | ||
280 | 296 | print >>status, "<td>%s</td>" % right_version | ||
281 | 297 | print >>status, "</tr>" | ||
282 | 298 | |||
283 | 299 | print >>status, "</table>" | ||
284 | 300 | |||
285 | 301 | def write_status_file(status_file, merges): | 115 | def write_status_file(status_file, merges): |
286 | 302 | """Write out the merge status file.""" | 116 | """Write out the merge status file.""" |
287 | 303 | status = open(status_file + ".new", "w") | 117 | status = open(status_file + ".new", "w") |
288 | @@ -316,4 +130,3 @@ | |||
289 | 316 | if __name__ == "__main__": | 130 | if __name__ == "__main__": |
290 | 317 | run(main, options, usage="%prog", | 131 | run(main, options, usage="%prog", |
291 | 318 | description="output status of manual merges") | 132 | description="output status of manual merges") |
292 | 319 | |||
293 | 320 | 133 | ||
294 | === modified file 'merge-status.py' | |||
295 | --- merge-status.py 2009-03-02 15:33:48 +0000 | |||
296 | +++ merge-status.py 2009-07-20 18:07:18 +0000 | |||
297 | @@ -5,6 +5,8 @@ | |||
298 | 5 | # Copyright © 2008 Canonical Ltd. | 5 | # Copyright © 2008 Canonical Ltd. |
299 | 6 | # Author: Scott James Remnant <scott@ubuntu.com>. | 6 | # Author: Scott James Remnant <scott@ubuntu.com>. |
300 | 7 | # | 7 | # |
301 | 8 | # Copyright © 2008-2009 Siegfried-A. Gevatter Pujals <rainct@ubuntu.com> | ||
302 | 9 | # | ||
303 | 8 | # This program is free software: you can redistribute it and/or modify | 10 | # This program is free software: you can redistribute it and/or modify |
304 | 9 | # it under the terms of version 3 of the GNU General Public License as | 11 | # it under the terms of version 3 of the GNU General Public License as |
305 | 10 | # published by the Free Software Foundation. | 12 | # published by the Free Software Foundation. |
306 | @@ -19,41 +21,11 @@ | |||
307 | 19 | 21 | ||
308 | 20 | import os | 22 | import os |
309 | 21 | import bz2 | 23 | import bz2 |
310 | 22 | import sys | ||
311 | 23 | import glob | ||
312 | 24 | import fcntl | ||
313 | 25 | |||
314 | 26 | from rfc822 import parseaddr | 24 | from rfc822 import parseaddr |
315 | 25 | |||
316 | 27 | from momlib import * | 26 | from momlib import * |
346 | 28 | 27 | from util.status_common import * | |
347 | 29 | 28 | ||
319 | 30 | # Order of priorities | ||
320 | 31 | PRIORITY = [ "unknown", "required", "important", "standard", "optional", | ||
321 | 32 | "extra" ] | ||
322 | 33 | COLOURS = [ "#ff8080", "#ffb580", "#ffea80", "#dfff80", "#abff80", "#80ff8b" ] | ||
323 | 34 | |||
324 | 35 | # Sections | ||
325 | 36 | SECTIONS = [ "outstanding", "new", "updated" ] | ||
326 | 37 | |||
327 | 38 | |||
328 | 39 | def options(parser): | ||
329 | 40 | parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO", | ||
330 | 41 | default=SRC_DISTRO, | ||
331 | 42 | help="Source distribution") | ||
332 | 43 | parser.add_option("-S", "--source-suite", type="string", metavar="SUITE", | ||
333 | 44 | default=SRC_DIST, | ||
334 | 45 | help="Source suite (aka distrorelease)") | ||
335 | 46 | |||
336 | 47 | parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO", | ||
337 | 48 | default=OUR_DISTRO, | ||
338 | 49 | help="Destination distribution") | ||
339 | 50 | parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE", | ||
340 | 51 | default=OUR_DIST, | ||
341 | 52 | help="Destination suite (aka distrorelease)") | ||
342 | 53 | |||
343 | 54 | parser.add_option("-c", "--component", type="string", metavar="COMPONENT", | ||
344 | 55 | action="append", | ||
345 | 56 | help="Process only these destination components") | ||
348 | 57 | 29 | ||
349 | 58 | def main(options, args): | 30 | def main(options, args): |
350 | 59 | src_distro = options.source_distro | 31 | src_distro = options.source_distro |
351 | @@ -140,192 +112,12 @@ | |||
352 | 140 | 112 | ||
353 | 141 | merges.sort() | 113 | merges.sort() |
354 | 142 | 114 | ||
356 | 143 | write_status_page(our_component, merges, our_distro, src_distro) | 115 | write_status_page(our_component, merges, our_distro, src_distro, False) |
357 | 144 | 116 | ||
358 | 145 | status_file = "%s/merges/tomerge-%s" % (ROOT, our_component) | 117 | status_file = "%s/merges/tomerge-%s" % (ROOT, our_component) |
359 | 146 | remove_old_comments(status_file, merges) | 118 | remove_old_comments(status_file, merges) |
360 | 147 | write_status_file(status_file, merges) | 119 | write_status_file(status_file, merges) |
361 | 148 | 120 | ||
362 | 149 | |||
363 | 150 | def get_uploader(distro, source): | ||
364 | 151 | """Obtain the uploader from the dsc file signature.""" | ||
365 | 152 | for md5sum, size, name in files(source): | ||
366 | 153 | if name.endswith(".dsc"): | ||
367 | 154 | dsc_file = name | ||
368 | 155 | break | ||
369 | 156 | else: | ||
370 | 157 | return None | ||
371 | 158 | |||
372 | 159 | filename = "%s/pool/%s/%s/%s/%s" \ | ||
373 | 160 | % (ROOT, distro, pathhash(source["Package"]), source["Package"], | ||
374 | 161 | dsc_file) | ||
375 | 162 | |||
376 | 163 | (a, b, c) = os.popen3("gpg --verify %s" % filename) | ||
377 | 164 | stdout = c.readlines() | ||
378 | 165 | try: | ||
379 | 166 | return stdout[1].split("Good signature from")[1].strip().strip("\"") | ||
380 | 167 | except IndexError: | ||
381 | 168 | return None | ||
382 | 169 | |||
383 | 170 | |||
384 | 171 | def write_status_page(component, merges, left_distro, right_distro): | ||
385 | 172 | """Write out the merge status page.""" | ||
386 | 173 | status_file = "%s/merges/%s.html" % (ROOT, component) | ||
387 | 174 | status = open(status_file + ".new", "w") | ||
388 | 175 | try: | ||
389 | 176 | print >>status, "<html>" | ||
390 | 177 | print >>status | ||
391 | 178 | print >>status, "<head>" | ||
392 | 179 | print >>status, "<title>Ubuntu Merge-o-Matic: %s</title>" % component | ||
393 | 180 | print >>status, "<style>" | ||
394 | 181 | print >>status, "img#ubuntu {" | ||
395 | 182 | print >>status, " border: 0;" | ||
396 | 183 | print >>status, "}" | ||
397 | 184 | print >>status, "h1 {" | ||
398 | 185 | print >>status, " padding-top: 0.5em;" | ||
399 | 186 | print >>status, " font-family: sans-serif;" | ||
400 | 187 | print >>status, " font-size: 2.0em;" | ||
401 | 188 | print >>status, " font-weight: bold;" | ||
402 | 189 | print >>status, "}" | ||
403 | 190 | print >>status, "h2 {" | ||
404 | 191 | print >>status, " padding-top: 0.5em;" | ||
405 | 192 | print >>status, " font-family: sans-serif;" | ||
406 | 193 | print >>status, " font-size: 1.5em;" | ||
407 | 194 | print >>status, " font-weight: bold;" | ||
408 | 195 | print >>status, "}" | ||
409 | 196 | print >>status, "p, td {" | ||
410 | 197 | print >>status, " font-family: sans-serif;" | ||
411 | 198 | print >>status, " margin-bottom: 0;" | ||
412 | 199 | print >>status, "}" | ||
413 | 200 | print >>status, "li {" | ||
414 | 201 | print >>status, " font-family: sans-serif;" | ||
415 | 202 | print >>status, " margin-bottom: 1em;" | ||
416 | 203 | print >>status, "}" | ||
417 | 204 | print >>status, "tr.first td {" | ||
418 | 205 | print >>status, " border-top: 2px solid white;" | ||
419 | 206 | print >>status, "}" | ||
420 | 207 | print >>status, "</style>" | ||
421 | 208 | print >>status, "<% from momlib import * %>" | ||
422 | 209 | print >>status, "</head>" | ||
423 | 210 | print >>status, "<body>" | ||
424 | 211 | print >>status, "<img src=\".img/ubuntulogo-100.png\" id=\"ubuntu\">" | ||
425 | 212 | print >>status, "<h1>Ubuntu Merge-o-Matic: %s</h1>" % component | ||
426 | 213 | |||
427 | 214 | for section in SECTIONS: | ||
428 | 215 | section_merges = [ m for m in merges if m[0] == section ] | ||
429 | 216 | print >>status, ("<p><a href=\"#%s\">%s %s merges</a></p>" | ||
430 | 217 | % (section, len(section_merges), section)) | ||
431 | 218 | |||
432 | 219 | print >>status, "<ul>" | ||
433 | 220 | print >>status, ("<li>If you are not the previous uploader, ask the " | ||
434 | 221 | "previous uploader before doing the merge. This " | ||
435 | 222 | "prevents two people from doing the same work.</li>") | ||
436 | 223 | print >>status, ("<li>Before uploading, update the changelog to " | ||
437 | 224 | "have your name and a list of the outstanding " | ||
438 | 225 | "Ubuntu changes.</li>") | ||
439 | 226 | print >>status, ("<li>Try and keep the diff small, this may involve " | ||
440 | 227 | "manually tweaking <tt>po</tt> files and the" | ||
441 | 228 | "like.</li>") | ||
442 | 229 | print >>status, "</ul>" | ||
443 | 230 | |||
444 | 231 | print >>status, "<% comment = get_comments() %>" | ||
445 | 232 | |||
446 | 233 | for section in SECTIONS: | ||
447 | 234 | section_merges = [ m for m in merges if m[0] == section ] | ||
448 | 235 | |||
449 | 236 | print >>status, ("<h2 id=\"%s\">%s Merges</h2>" | ||
450 | 237 | % (section, section.title())) | ||
451 | 238 | |||
452 | 239 | do_table(status, section_merges, left_distro, right_distro, component) | ||
453 | 240 | |||
454 | 241 | print >>status, "<h2 id=stats>Statistics</h2>" | ||
455 | 242 | print >>status, ("<img src=\"%s-now.png\" title=\"Current stats\">" | ||
456 | 243 | % component) | ||
457 | 244 | print >>status, ("<img src=\"%s-trend.png\" title=\"Six month trend\">" | ||
458 | 245 | % component) | ||
459 | 246 | print >>status, "</body>" | ||
460 | 247 | print >>status, "</html>" | ||
461 | 248 | finally: | ||
462 | 249 | status.close() | ||
463 | 250 | |||
464 | 251 | os.rename(status_file + ".new", status_file) | ||
465 | 252 | |||
466 | 253 | def do_table(status, merges, left_distro, right_distro, component): | ||
467 | 254 | """Output a table.""" | ||
468 | 255 | print >>status, "<table cellspacing=0>" | ||
469 | 256 | print >>status, "<tr bgcolor=#d0d0d0>" | ||
470 | 257 | print >>status, "<td rowspan=2><b>Package</b></td>" | ||
471 | 258 | print >>status, "<td colspan=3><b>Last Uploader</b></td>" | ||
472 | 259 | print >>status, "<td rowspan=2><b>Comment</b></td>" | ||
473 | 260 | print >>status, "<td rowspan=2><b>Bug</b></td>" | ||
474 | 261 | print >>status, "</tr>" | ||
475 | 262 | print >>status, "<tr bgcolor=#d0d0d0>" | ||
476 | 263 | print >>status, "<td><b>%s Version</b></td>" % left_distro.title() | ||
477 | 264 | print >>status, "<td><b>%s Version</b></td>" % right_distro.title() | ||
478 | 265 | print >>status, "<td><b>Base Version</b></td>" | ||
479 | 266 | print >>status, "</tr>" | ||
480 | 267 | |||
481 | 268 | for uploaded, priority, package, user, uploader, source, \ | ||
482 | 269 | base_version, left_version, right_version in merges: | ||
483 | 270 | if user is not None: | ||
484 | 271 | who = user | ||
485 | 272 | who = who.replace("&", "&") | ||
486 | 273 | who = who.replace("<", "<") | ||
487 | 274 | who = who.replace(">", ">") | ||
488 | 275 | |||
489 | 276 | if uploader is not None: | ||
490 | 277 | (usr_name, usr_mail) = parseaddr(user) | ||
491 | 278 | (upl_name, upl_mail) = parseaddr(uploader) | ||
492 | 279 | |||
493 | 280 | if len(usr_name) and usr_name != upl_name: | ||
494 | 281 | u_who = uploader | ||
495 | 282 | u_who = u_who.replace("&", "&") | ||
496 | 283 | u_who = u_who.replace("<", "<") | ||
497 | 284 | u_who = u_who.replace(">", ">") | ||
498 | 285 | |||
499 | 286 | who = "%s<br><small><em>Uploader:</em> %s</small>" \ | ||
500 | 287 | % (who, u_who) | ||
501 | 288 | else: | ||
502 | 289 | who = " " | ||
503 | 290 | |||
504 | 291 | print >>status, "<tr bgcolor=%s class=first>" % COLOURS[priority] | ||
505 | 292 | print >>status, "<td><tt><a href=\"%s/%s/REPORT\">" \ | ||
506 | 293 | "%s</a></tt>" % (pathhash(package), package, package) | ||
507 | 294 | print >>status, " <sup><a href=\"https://launchpad.net/ubuntu/" \ | ||
508 | 295 | "+source/%s\">LP</a></sup>" % package | ||
509 | 296 | print >>status, " <sup><a href=\"http://packages.qa.debian.org/" \ | ||
510 | 297 | "%s\">PTS</a></sup></td>" % package | ||
511 | 298 | print >>status, "<td colspan=3>%s</td>" % who | ||
512 | 299 | print >>status, "<td rowspan=2><form method=\"get\" action=\"addcomment.py\"><br />" | ||
513 | 300 | print >>status, "<input type=\"hidden\" name=\"component\" value=\"%s\" />" % component | ||
514 | 301 | print >>status, "<input type=\"hidden\" name=\"package\" value=\"%s\" />" % package | ||
515 | 302 | print >>status, "<%%\n\ | ||
516 | 303 | the_comment = \"\"\n\ | ||
517 | 304 | if(comment.has_key(\"%s\")):\n\ | ||
518 | 305 | the_comment = comment[\"%s\"]\n\ | ||
519 | 306 | req.write(\"<input type=\\\"text\\\" style=\\\"border-style: none; background-color: %s\\\" name=\\\"comment\\\" value=\\\"%%s\\\" title=\\\"%%s\\\" />\" %% (the_comment, the_comment))\n\ | ||
520 | 307 | %%>" % (package, package, COLOURS[priority]) | ||
521 | 308 | print >>status, "</form></td>" | ||
522 | 309 | print >>status, "<td rowspan=2>" | ||
523 | 310 | print >>status, "<%%\n\ | ||
524 | 311 | if(comment.has_key(\"%s\")):\n\ | ||
525 | 312 | req.write(\"%%s\" %% gen_buglink_from_comment(comment[\"%s\"]))\n\ | ||
526 | 313 | else:\n\ | ||
527 | 314 | req.write(\" \")\n\ | ||
528 | 315 | \n\ | ||
529 | 316 | %%>" % (package, package) | ||
530 | 317 | print >>status, "</td>" | ||
531 | 318 | print >>status, "</tr>" | ||
532 | 319 | print >>status, "<tr bgcolor=%s>" % COLOURS[priority] | ||
533 | 320 | print >>status, "<td><small>%s</small></td>" % source["Binary"] | ||
534 | 321 | print >>status, "<td>%s</td>" % left_version | ||
535 | 322 | print >>status, "<td>%s</td>" % right_version | ||
536 | 323 | print >>status, "<td>%s</td>" % base_version | ||
537 | 324 | print >>status, "</tr>" | ||
538 | 325 | |||
539 | 326 | print >>status, "</table>" | ||
540 | 327 | |||
541 | 328 | |||
542 | 329 | def write_status_file(status_file, merges): | 121 | def write_status_file(status_file, merges): |
543 | 330 | """Write out the merge status file.""" | 122 | """Write out the merge status file.""" |
544 | 331 | status = open(status_file + ".new", "w") | 123 | status = open(status_file + ".new", "w") |
545 | @@ -341,6 +133,4 @@ | |||
546 | 341 | os.rename(status_file + ".new", status_file) | 133 | os.rename(status_file + ".new", status_file) |
547 | 342 | 134 | ||
548 | 343 | if __name__ == "__main__": | 135 | if __name__ == "__main__": |
552 | 344 | run(main, options, usage="%prog", | 136 | run(main, options, usage="%prog", description="output merge status") |
550 | 345 | description="output merge status") | ||
551 | 346 | |||
553 | 347 | 137 | ||
554 | === modified file 'momlib.py' | |||
555 | --- momlib.py 2009-05-29 07:33:55 +0000 | |||
556 | +++ momlib.py 2009-07-20 18:07:18 +0000 | |||
557 | @@ -20,7 +20,7 @@ | |||
558 | 20 | import os | 20 | import os |
559 | 21 | import re | 21 | import re |
560 | 22 | import sys | 22 | import sys |
562 | 23 | import md5 | 23 | import hashlib |
563 | 24 | import time | 24 | import time |
564 | 25 | import fcntl | 25 | import fcntl |
565 | 26 | import errno | 26 | import errno |
566 | @@ -156,7 +156,7 @@ | |||
567 | 156 | 156 | ||
568 | 157 | def md5sum(filename): | 157 | def md5sum(filename): |
569 | 158 | """Return an md5sum.""" | 158 | """Return an md5sum.""" |
571 | 159 | return md5.new(open(filename).read()).hexdigest() | 159 | return hashlib.md5(open(filename).read()).hexdigest() |
572 | 160 | 160 | ||
573 | 161 | 161 | ||
574 | 162 | # --------------------------------------------------------------------------- # | 162 | # --------------------------------------------------------------------------- # |
575 | 163 | 163 | ||
576 | === added directory 'templates' | |||
577 | === added file 'templates/status.tpl' | |||
578 | --- templates/status.tpl 1970-01-01 00:00:00 +0000 | |||
579 | +++ templates/status.tpl 2009-07-20 18:07:18 +0000 | |||
580 | @@ -0,0 +1,100 @@ | |||
581 | 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||
582 | 2 | |||
583 | 3 | <html xmlns="http://www.w3.org/1999/xhtml"><head> | ||
584 | 4 | <title>Ubuntu Merge-o-Matic: [% var component %][% if manual %] manual[% endif %]</title> | ||
585 | 5 | <link rel="stylesheet" href="./style.css" type="text/css" /> | ||
586 | 6 | <!--- <script language="javascript" src="./javascript.js" type="text/javascript"></script> ---> | ||
587 | 7 | <link rel="shortcut icon" href="http://ubuntu.com/favicon.ico" type="image/x-icon" > | ||
588 | 8 | <% from momlib import * %> | ||
589 | 9 | <% comments = get_comments() %> | ||
590 | 10 | </head><body> | ||
591 | 11 | |||
592 | 12 | <div id="header"> | ||
593 | 13 | <h1>Ubuntu Merge-o-Matic: [% var component %][% if manual %] manual[% endif %]</h1> | ||
594 | 14 | </div> | ||
595 | 15 | |||
596 | 16 | <div id="page"><div id="summary"> | ||
597 | 17 | <ul> | ||
598 | 18 | [% for section in sections %]<li><a href="#[% var section['name'] %]">[% var section['len'] %] [% var section['name'] %] merges</a></li>[% done %] | ||
599 | 19 | </ul><ul> | ||
600 | 20 | <li>If you are not the previous uploader, ask the previous uploader before doing the merge. This prevents two people from doing the same work.</li> | ||
601 | 21 | <li>Before uploading, update the changelog to have your name and a list of the outstanding Ubuntu changes, and don't forget the <em>-v</em> option with <em>debuild</em>.</li> | ||
602 | 22 | <li>Try to keep the diff small; this may involve manually tweaking po files and the like.</li> | ||
603 | 23 | </ul> | ||
604 | 24 | |||
605 | 25 | </div><div id="merges"> | ||
606 | 26 | [% for section in sections %] | ||
607 | 27 | <h2 id="[% var section['name'] %]">[% var section['title'] %] Merges</h2> | ||
608 | 28 | <table cellspacing="0"><thead> | ||
609 | 29 | <tr> | ||
610 | 30 | <th></th> | ||
611 | 31 | <th>Package</th> | ||
612 | 32 | <th>Ubuntu</th> | ||
613 | 33 | <th>Debian</th> | ||
614 | 34 | [% if not_manual %]<th>Base</th>[% endif %] | ||
615 | 35 | <th>Last Uploader</th> | ||
616 | 36 | <th>Comment</th> | ||
617 | 37 | <th>Bug</th> | ||
618 | 38 | </tr> | ||
619 | 39 | </thead><tbody> | ||
620 | 40 | [% for uploaded, priority, package, user, uploader, source, base_version, | ||
621 | 41 | left_version, right_version, pathhash in section['packages'] %] | ||
622 | 42 | <tr> | ||
623 | 43 | <td class="priority[% var priority %]" class="indicator"></td> | ||
624 | 44 | <td> | ||
625 | 45 | <a href="[% var pathhash %]/[% var package %]/REPORT">[% var package %]</a> | ||
626 | 46 | <a href="https://launchpad.net/distros/ubuntu/+source/[% var package %]">(LP)</a> | ||
627 | 47 | <a href="http://packages.qa.debian.org/[% var package %]">(PTS)</a> | ||
628 | 48 | <span>[% var source['Binary'] %]</span> | ||
629 | 49 | </td> | ||
630 | 50 | <td class="version">[% var left_version %]</td> | ||
631 | 51 | <td class="version">[% var right_version %]</td> | ||
632 | 52 | [% if not_manual %]<td class="version">[% var base_version %]</td>[% endif %] | ||
633 | 53 | <td>[% var user %] | ||
634 | 54 | [% if uploader %] | ||
635 | 55 | <br /><small><em>Uploader:</em> [% var uploader %]</small> | ||
636 | 56 | [% endif %] | ||
637 | 57 | </td> | ||
638 | 58 | <td class="form_row"> | ||
639 | 59 | <% | ||
640 | 60 | if comments.has_key("[% var package %]"): | ||
641 | 61 | comment = comments["[% var package %]"] | ||
642 | 62 | buglink = gen_buglink_from_comment(comments["[% var package %]"]) | ||
643 | 63 | else: | ||
644 | 64 | comment = buglink = "" | ||
645 | 65 | # Keep this line here to end the indentation | ||
646 | 66 | %> | ||
647 | 67 | <form method="GET" action="addcomment.py"> | ||
648 | 68 | <input type="hidden" name="component" value="[% var component %]" /> | ||
649 | 69 | <input type="hidden" name="package" value="[% var package %]" /> | ||
650 | 70 | <input type="text" name="comment" | ||
651 | 71 | value="<% req.write(comment) %>" | ||
652 | 72 | title="<% req.write(comment) %>" /> | ||
653 | 73 | </form> | ||
654 | 74 | </td> | ||
655 | 75 | <td class="bug"><% req.write("%s" % buglink) %></td> | ||
656 | 76 | </tr> | ||
657 | 77 | [% done %] | ||
658 | 78 | </tbody></table> | ||
659 | 79 | [% done %] | ||
660 | 80 | </div> | ||
661 | 81 | |||
662 | 82 | <br /> | ||
663 | 83 | <strong>Colour keys:</strong> | ||
664 | 84 | <table id="colours"><tr> | ||
665 | 85 | <td bgcolor="#ff8080">unknown</td> | ||
666 | 86 | <td bgcolor="#ffb580">required</td> | ||
667 | 87 | <td bgcolor="#ffea80">important</td> | ||
668 | 88 | <td bgcolor="#dfff80">standard</td> | ||
669 | 89 | <td bgcolor="#abff80">optional</td> | ||
670 | 90 | <td bgcolor="#80ff8b">extra</td> | ||
671 | 91 | </tr></table> | ||
672 | 92 | |||
673 | 93 | <div id="statistics"> | ||
674 | 94 | <h2 >Statistics</h2> | ||
675 | 95 | <img src="[% var component %]-now.png" title="Current stats" /> | ||
676 | 96 | <img src="[% var component %]-trend.png" title="Six month trend" /> | ||
677 | 97 | </div></div> | ||
678 | 98 | |||
679 | 99 | </body> | ||
680 | 100 | </html> | ||
681 | 0 | 101 | ||
682 | === added file 'templates/style.css' | |||
683 | --- templates/style.css 1970-01-01 00:00:00 +0000 | |||
684 | +++ templates/style.css 2009-07-20 18:07:18 +0000 | |||
685 | @@ -0,0 +1,97 @@ | |||
686 | 1 | body { | ||
687 | 2 | font-family: sans-serif; | ||
688 | 3 | } | ||
689 | 4 | |||
690 | 5 | body, div#header img, div#header h1 { | ||
691 | 6 | margin: 0; | ||
692 | 7 | padding: 0; | ||
693 | 8 | } | ||
694 | 9 | |||
695 | 10 | div#header { | ||
696 | 11 | background: url('images/header_bg.png') repeat-x; | ||
697 | 12 | height: 75px; | ||
698 | 13 | } | ||
699 | 14 | |||
700 | 15 | div#header h1 { | ||
701 | 16 | background: url('images/header.png') no-repeat; | ||
702 | 17 | padding: 20px 20px 0 320px; | ||
703 | 18 | height: 90px; | ||
704 | 19 | font-size: 32px; | ||
705 | 20 | } | ||
706 | 21 | |||
707 | 22 | div#page { | ||
708 | 23 | margin: 15px; | ||
709 | 24 | } | ||
710 | 25 | |||
711 | 26 | div#page h2 { | ||
712 | 27 | width: 100%; | ||
713 | 28 | color: #6D4C07; | ||
714 | 29 | padding-bottom: .0em; | ||
715 | 30 | padding-top: 0.4em; | ||
716 | 31 | font-family: Verdana, arial, helvetica, sans-serif; | ||
717 | 32 | font-size: 160%; | ||
718 | 33 | border-bottom: 2px solid #6D4C07; | ||
719 | 34 | } | ||
720 | 35 | |||
721 | 36 | div#page li { | ||
722 | 37 | margin-bottom: 1em; | ||
723 | 38 | } | ||
724 | 39 | |||
725 | 40 | div#summary ul { | ||
726 | 41 | font-size: 13px; | ||
727 | 42 | margin-bottom: -8px; | ||
728 | 43 | } | ||
729 | 44 | |||
730 | 45 | div#summary li { | ||
731 | 46 | margin: 4px 0 0 4px; | ||
732 | 47 | } | ||
733 | 48 | |||
734 | 49 | div#merges table, div#merges tr { | ||
735 | 50 | margin: 0; | ||
736 | 51 | width: 98%; | ||
737 | 52 | border: 1px solid #C1B496; | ||
738 | 53 | border-right: 0; | ||
739 | 54 | font-size: 90%; | ||
740 | 55 | } | ||
741 | 56 | |||
742 | 57 | div#merges thead tr { | ||
743 | 58 | background: #D9BB7A; | ||
744 | 59 | font-weight: bold; | ||
745 | 60 | } | ||
746 | 61 | |||
747 | 62 | div#merges td { | ||
748 | 63 | border-top: 1px solid #C1B496; | ||
749 | 64 | border-right: 1px solid #C1B496; | ||
750 | 65 | padding: 4px; | ||
751 | 66 | } | ||
752 | 67 | |||
753 | 68 | div#merges td span { | ||
754 | 69 | display: block; | ||
755 | 70 | margin: 3px 0 0 0; | ||
756 | 71 | font-size: 70%; | ||
757 | 72 | } | ||
758 | 73 | |||
759 | 74 | div#merges td.version, div#merges td.bug { | ||
760 | 75 | text-align: center; | ||
761 | 76 | } | ||
762 | 77 | |||
763 | 78 | table#colours td { | ||
764 | 79 | width: 100px; | ||
765 | 80 | text-align: center; | ||
766 | 81 | } | ||
767 | 82 | |||
768 | 83 | input { | ||
769 | 84 | width: 155px; | ||
770 | 85 | } | ||
771 | 86 | |||
772 | 87 | td.form_row { | ||
773 | 88 | width: 160px; | ||
774 | 89 | } | ||
775 | 90 | |||
776 | 91 | .indicator { width: 35px; } | ||
777 | 92 | .priority0 { background-color: #ff8080; } | ||
778 | 93 | .priority1 { background-color: #ffb580; } | ||
779 | 94 | .priority2 { background-color: #ffea80; } | ||
780 | 95 | .priority3 { background-color: #dfff80; } | ||
781 | 96 | .priority4 { background-color: #abff80; } | ||
782 | 97 | .priority5 { background-color: #80ff8b; } | ||
783 | 0 | 98 | ||
784 | === modified file 'util/__init__.py' | |||
785 | --- util/__init__.py 2008-01-10 13:19:44 +0000 | |||
786 | +++ util/__init__.py 2008-03-25 19:58:32 +0000 | |||
787 | @@ -1,4 +1,3 @@ | |||
788 | 1 | #!/usr/bin/env python | ||
789 | 2 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
790 | 3 | # | 2 | # |
791 | 4 | # Copyright © 2008 Canonical Ltd. | 3 | # Copyright © 2008 Canonical Ltd. |
792 | 5 | 4 | ||
793 | === modified file 'util/shell.py' | |||
794 | --- util/shell.py 2008-01-10 13:19:44 +0000 | |||
795 | +++ util/shell.py 2008-03-25 19:58:32 +0000 | |||
796 | @@ -1,4 +1,3 @@ | |||
797 | 1 | #!/usr/bin/env python | ||
798 | 2 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
799 | 3 | # util/shell.py - child process forking (popen-alike) | 2 | # util/shell.py - child process forking (popen-alike) |
800 | 4 | # | 3 | # |
801 | 5 | 4 | ||
802 | === added file 'util/status_common.py' | |||
803 | --- util/status_common.py 1970-01-01 00:00:00 +0000 | |||
804 | +++ util/status_common.py 2009-07-20 18:07:18 +0000 | |||
805 | @@ -0,0 +1,127 @@ | |||
806 | 1 | # -*- coding: utf-8 -*- | ||
807 | 2 | # manual-status.py - output status of manual merges | ||
808 | 3 | # | ||
809 | 4 | # Copyright © 2008 Canonical Ltd. | ||
810 | 5 | # Author: Scott James Remnant <scott@ubuntu.com>. | ||
811 | 6 | # | ||
812 | 7 | # This program is free software: you can redistribute it and/or modify | ||
813 | 8 | # it under the terms of version 3 of the GNU General Public License as | ||
814 | 9 | # published by the Free Software Foundation. | ||
815 | 10 | # | ||
816 | 11 | # This program is distributed in the hope that it will be useful, | ||
817 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
818 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
819 | 14 | # GNU General Public License for more details. | ||
820 | 15 | # | ||
821 | 16 | # You should have received a copy of the GNU General Public License | ||
822 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
823 | 18 | |||
824 | 19 | import sys | ||
825 | 20 | import os | ||
826 | 21 | |||
827 | 22 | from momlib import * | ||
828 | 23 | from util import tinpy | ||
829 | 24 | |||
830 | 25 | PRIORITY = [ "unknown", "required", "important", "standard", "optional", | ||
831 | 26 | "extra" ] | ||
832 | 27 | SECTIONS = [ "outstanding", "new", "updated" ] | ||
833 | 28 | |||
834 | 29 | |||
835 | 30 | def options(parser): | ||
836 | 31 | parser.add_option("-D", "--source-distro", type="string", metavar="DISTRO", | ||
837 | 32 | default=SRC_DISTRO, | ||
838 | 33 | help="Source distribution") | ||
839 | 34 | parser.add_option("-S", "--source-suite", type="string", metavar="SUITE", | ||
840 | 35 | default=SRC_DIST, | ||
841 | 36 | help="Source suite (aka distrorelease)") | ||
842 | 37 | |||
843 | 38 | parser.add_option("-d", "--dest-distro", type="string", metavar="DISTRO", | ||
844 | 39 | default=OUR_DISTRO, | ||
845 | 40 | help="Destination distribution") | ||
846 | 41 | parser.add_option("-s", "--dest-suite", type="string", metavar="SUITE", | ||
847 | 42 | default=OUR_DIST, | ||
848 | 43 | help="Destination suite (aka distrorelease)") | ||
849 | 44 | |||
850 | 45 | parser.add_option("-c", "--component", type="string", metavar="COMPONENT", | ||
851 | 46 | action="append", | ||
852 | 47 | help="Process only these destination components") | ||
853 | 48 | |||
854 | 49 | def get_uploader(distro, source): | ||
855 | 50 | """Obtain the uploader from the dsc file signature.""" | ||
856 | 51 | for md5sum, size, name in files(source): | ||
857 | 52 | if name.endswith(".dsc"): | ||
858 | 53 | dsc_file = name | ||
859 | 54 | break | ||
860 | 55 | else: | ||
861 | 56 | return None | ||
862 | 57 | |||
863 | 58 | filename = "%s/pool/%s/%s/%s/%s" \ | ||
864 | 59 | % (ROOT, distro, pathhash(source["Package"]), source["Package"], | ||
865 | 60 | dsc_file) | ||
866 | 61 | |||
867 | 62 | (a, b, c) = os.popen3("gpg --verify %s" % filename) | ||
868 | 63 | stdout = c.readlines() | ||
869 | 64 | try: | ||
870 | 65 | return stdout[1].split("Good signature from")[1].strip().strip("\"") | ||
871 | 66 | except IndexError: | ||
872 | 67 | return None | ||
873 | 68 | |||
874 | 69 | def escape_email(text): | ||
875 | 70 | """Takes a string and replaces <, > and & with their HTML representation.""" | ||
876 | 71 | return text.replace("&", "&").replace("<", "<").replace(">", ">") | ||
877 | 72 | |||
878 | 73 | def package_to_html(package): | ||
879 | 74 | """Gives the user and uploader field in a package tuple the appropriate | ||
880 | 75 | values expected by status.tpl (both fields escaped, and the uploader field | ||
881 | 76 | empty if it's the same as the user).""" | ||
882 | 77 | |||
883 | 78 | package = list(package) | ||
884 | 79 | |||
885 | 80 | if package[4]: | ||
886 | 81 | if package[3]: | ||
887 | 82 | user_name = parseaddr(package[3])[0] | ||
888 | 83 | uploader_name = parseaddr(package[4])[0] | ||
889 | 84 | |||
890 | 85 | if user_name == uploader_name: | ||
891 | 86 | package[4] = "" | ||
892 | 87 | else: | ||
893 | 88 | package[3] = package[4] | ||
894 | 89 | package[4] = "" | ||
895 | 90 | elif not package[3]: | ||
896 | 91 | package[3] = " " | ||
897 | 92 | |||
898 | 93 | package[3] = escape_email(package[3]) | ||
899 | 94 | package[4] = escape_email(package[4]) | ||
900 | 95 | package.append(pathhash(package[2])) | ||
901 | 96 | |||
902 | 97 | return package | ||
903 | 98 | |||
904 | 99 | def write_status_page(component, merges, left_distro, right_distro, manual): | ||
905 | 100 | """Write out the merge status page.""" | ||
906 | 101 | |||
907 | 102 | status_file = "%s/merges/%s.html" % (ROOT, component) | ||
908 | 103 | status = open(status_file + ".new", "w") | ||
909 | 104 | |||
910 | 105 | try: | ||
911 | 106 | sections = [] | ||
912 | 107 | |||
913 | 108 | for section in SECTIONS: | ||
914 | 109 | section_packages = [ package_to_html(m) for m in merges | ||
915 | 110 | if m[0] == section ] | ||
916 | 111 | sections.append({ | ||
917 | 112 | "name": section, | ||
918 | 113 | "title": section.title(), | ||
919 | 114 | "len": len(section_packages), | ||
920 | 115 | "packages": section_packages, | ||
921 | 116 | }) | ||
922 | 117 | |||
923 | 118 | html = tinpy.build(open("templates/status.tpl").read(), | ||
924 | 119 | root = ROOT, component = component, sections = sections, | ||
925 | 120 | manual = manual, not_manual = not manual) | ||
926 | 121 | |||
927 | 122 | status.write(html) | ||
928 | 123 | |||
929 | 124 | finally: | ||
930 | 125 | status.close() | ||
931 | 126 | |||
932 | 127 | os.rename(status_file + ".new", status_file) | ||
933 | 0 | 128 | ||
934 | === added file 'util/tinpy.py' | |||
935 | --- util/tinpy.py 1970-01-01 00:00:00 +0000 | |||
936 | +++ util/tinpy.py 2009-07-17 17:24:13 +0000 | |||
937 | @@ -0,0 +1,326 @@ | |||
938 | 1 | # -*- coding: utf-8 -*- | ||
939 | 2 | # | ||
940 | 3 | # Tiny Python Template (tinpy). Version 2.4. | ||
941 | 4 | # http://sourceforge.net/projects/tinpy/ | ||
942 | 5 | # | ||
943 | 6 | # Copyright © 2001-2006 Keisuke URAGO <bravo@resourcez.org> | ||
944 | 7 | # Released under the MIT License. | ||
945 | 8 | |||
946 | 9 | import string | ||
947 | 10 | import re | ||
948 | 11 | from StringIO import StringIO | ||
949 | 12 | |||
950 | 13 | class TemplateParser: | ||
951 | 14 | "Template parser." | ||
952 | 15 | |||
953 | 16 | def __init__(self): | ||
954 | 17 | # Template syntax pattern. e.g. "[%var foobar%]" | ||
955 | 18 | self.tppattern = re.compile('(\[%.*?%\])', re.DOTALL) | ||
956 | 19 | self.lineno = 1 | ||
957 | 20 | self.chunk = '' | ||
958 | 21 | |||
959 | 22 | def set_processhandler(self, handler): | ||
960 | 23 | self.handler = handler | ||
961 | 24 | |||
962 | 25 | def parse(self, source): | ||
963 | 26 | "Parsing source. source is string" | ||
964 | 27 | for chunk in self.tppattern.split(source): | ||
965 | 28 | self.lineno = self.lineno + chunk.count('\n') | ||
966 | 29 | self.chunk = chunk | ||
967 | 30 | if self.tppattern.match(chunk): | ||
968 | 31 | apply(self.handler.handle, chunk[2:-2].split()) | ||
969 | 32 | continue | ||
970 | 33 | try: self.handler.handle_data(chunk) | ||
971 | 34 | except: pass | ||
972 | 35 | if hasattr(self.handler, 'end_document'): | ||
973 | 36 | self.handler.end_document() | ||
974 | 37 | |||
975 | 38 | class ProcessingHandler: | ||
976 | 39 | "Parser processing handler." | ||
977 | 40 | |||
978 | 41 | def __init__(self): | ||
979 | 42 | self.template = TemplateManager(Template()) | ||
980 | 43 | self.in_comment = 0 | ||
981 | 44 | |||
982 | 45 | def op_comment(self, arg): | ||
983 | 46 | if arg == 'begin': | ||
984 | 47 | self.in_comment = 1 | ||
985 | 48 | elif arg == 'end': | ||
986 | 49 | self.in_comment = 0 | ||
987 | 50 | else: | ||
988 | 51 | raise SyntaxError, "invalid syntax: comment %s" % str(arg) | ||
989 | 52 | |||
990 | 53 | def op_stag(self, *args): | ||
991 | 54 | if len(args) != 0: | ||
992 | 55 | raise SyntaxError, \ | ||
993 | 56 | "invalid syntax: %s" % string.join(list(args)) | ||
994 | 57 | self.template.write('[%%') | ||
995 | 58 | |||
996 | 59 | def op_etag(self, *args): | ||
997 | 60 | if len(args) != 0: | ||
998 | 61 | raise SyntaxError, \ | ||
999 | 62 | "invalid syntax: %s" % string.join(list(args)) | ||
1000 | 63 | self.template.write('%%]') | ||
1001 | 64 | |||
1002 | 65 | def op_var(self, *args): | ||
1003 | 66 | if len(args) != 1: | ||
1004 | 67 | raise SyntaxError, \ | ||
1005 | 68 | "invalid syntax: args: %s" % string.join(list(args)) | ||
1006 | 69 | self.template.write('%%(%s)s' % args[0]) | ||
1007 | 70 | |||
1008 | 71 | def op_for(self, *args): | ||
1009 | 72 | args = list(args) | ||
1010 | 73 | try: | ||
1011 | 74 | in_index = args.index('in') | ||
1012 | 75 | vars = [token.strip() for token in ''.join(args[:in_index]).split(',')] | ||
1013 | 76 | if len(vars) == 1: | ||
1014 | 77 | vars = vars[0] | ||
1015 | 78 | srclist = ''.join(args[in_index+1:]) | ||
1016 | 79 | except Exception, e: | ||
1017 | 80 | raise SyntaxError, "invalid syntax: '%s' %s" % (string.join(list(args)), e) | ||
1018 | 81 | self.template.forblock_begin(vars, srclist) | ||
1019 | 82 | |||
1020 | 83 | def op_done(self): | ||
1021 | 84 | self.template.forblock_end() | ||
1022 | 85 | |||
1023 | 86 | def op_if(self, *args): | ||
1024 | 87 | if len(args) != 1: | ||
1025 | 88 | raise SyntaxError, \ | ||
1026 | 89 | "invalid syntax: args: %s" % string.join(list(args)) | ||
1027 | 90 | self.template.ifblock_begin(args[0]) | ||
1028 | 91 | |||
1029 | 92 | def op_endif(self): | ||
1030 | 93 | self.template.ifblock_end() | ||
1031 | 94 | |||
1032 | 95 | def handle_data(self, chunk): | ||
1033 | 96 | if self.in_comment == 1: | ||
1034 | 97 | return | ||
1035 | 98 | self.template.write(chunk.replace('%', '%%')) | ||
1036 | 99 | |||
1037 | 100 | def end_document(self): | ||
1038 | 101 | self.template.end_document() | ||
1039 | 102 | |||
1040 | 103 | def handle(self, op, *tokens): | ||
1041 | 104 | # Comment out process. | ||
1042 | 105 | if op == 'comment': | ||
1043 | 106 | self.op_comment(string.join(tokens)) | ||
1044 | 107 | return | ||
1045 | 108 | if self.in_comment: | ||
1046 | 109 | return | ||
1047 | 110 | |||
1048 | 111 | # Handle operation. | ||
1049 | 112 | apply(getattr(self, 'op_' + op), tokens) | ||
1050 | 113 | |||
1051 | 114 | class Template: | ||
1052 | 115 | "Template object." | ||
1053 | 116 | |||
1054 | 117 | def __init__(self): | ||
1055 | 118 | self.nodelist = [] | ||
1056 | 119 | self.initdescriptor() | ||
1057 | 120 | |||
1058 | 121 | def initdescriptor(self): | ||
1059 | 122 | self.__descriptor = StringIO() | ||
1060 | 123 | |||
1061 | 124 | def write(self, s): | ||
1062 | 125 | self.__descriptor.write(s) | ||
1063 | 126 | |||
1064 | 127 | def getvalue(self): | ||
1065 | 128 | return self.__descriptor.getvalue() | ||
1066 | 129 | |||
1067 | 130 | class ForBlock(Template): | ||
1068 | 131 | "For block object." | ||
1069 | 132 | |||
1070 | 133 | def __init__(self, parent, seqvarnames, seqname): | ||
1071 | 134 | Template.__init__(self) | ||
1072 | 135 | self.parent = parent | ||
1073 | 136 | |||
1074 | 137 | self.seqvarnames = seqvarnames | ||
1075 | 138 | self.seqname = seqname | ||
1076 | 139 | |||
1077 | 140 | class IfBlock(Template): | ||
1078 | 141 | "For block object." | ||
1079 | 142 | |||
1080 | 143 | def __init__(self, parent, varname): | ||
1081 | 144 | Template.__init__(self) | ||
1082 | 145 | self.parent = parent | ||
1083 | 146 | |||
1084 | 147 | self.varname = varname | ||
1085 | 148 | |||
1086 | 149 | class DictEnhanceAccessor: | ||
1087 | 150 | "Variable dictionary enhance interface." | ||
1088 | 151 | |||
1089 | 152 | p = re.compile('^(?P<var>[a-zA-Z_][a-zA-Z0-9_]*)(?P<modifier>(\[.+?\])+)$') | ||
1090 | 153 | d = re.compile('(\[[a-zA-Z_][a-zA-Z0-9_]*\])') | ||
1091 | 154 | |||
1092 | 155 | def __init__(self, strict, dic={}): | ||
1093 | 156 | self.strict = strict | ||
1094 | 157 | self.dic = dic | ||
1095 | 158 | |||
1096 | 159 | def __getitem__(self, key): | ||
1097 | 160 | m = self.p.match(key) | ||
1098 | 161 | if not m: return self.dic.get(key, '') # Normal key. | ||
1099 | 162 | val = self.dic.get(m.group('var'), '') | ||
1100 | 163 | buf = StringIO() | ||
1101 | 164 | for mo in self.d.split(m.group('modifier')): | ||
1102 | 165 | if self.d.match(mo): | ||
1103 | 166 | buf.write("['%s']" % self.dic[mo[1:-1]]) | ||
1104 | 167 | else: | ||
1105 | 168 | buf.write(mo) | ||
1106 | 169 | try: | ||
1107 | 170 | return eval('val'+ buf.getvalue(), # code | ||
1108 | 171 | {'__builtins__':{}}, # globals is restricted. | ||
1109 | 172 | {'val':val}) # locals is only 'val' | ||
1110 | 173 | except Exception, e: | ||
1111 | 174 | if self.strict: raise Exception, e | ||
1112 | 175 | return '' | ||
1113 | 176 | |||
1114 | 177 | def __setitem__(self, key, val): | ||
1115 | 178 | self.dic[key] = val | ||
1116 | 179 | |||
1117 | 180 | def __repr__(self): | ||
1118 | 181 | return str(self.dic) | ||
1119 | 182 | |||
1120 | 183 | class VariableStack: | ||
1121 | 184 | "Variable data stack." | ||
1122 | 185 | |||
1123 | 186 | def __init__(self, variables=None): | ||
1124 | 187 | self.stack = [] | ||
1125 | 188 | if variables: | ||
1126 | 189 | self.push(variables) | ||
1127 | 190 | |||
1128 | 191 | def __getitem__(self, num): | ||
1129 | 192 | return self.stack[num] | ||
1130 | 193 | |||
1131 | 194 | def __repr__(self): | ||
1132 | 195 | return str(self.stack) | ||
1133 | 196 | |||
1134 | 197 | def pop(self): | ||
1135 | 198 | return self.stack.pop(0) | ||
1136 | 199 | |||
1137 | 200 | def push(self, data): | ||
1138 | 201 | self.stack.insert(0, data) | ||
1139 | 202 | |||
1140 | 203 | def find(self, varname): | ||
1141 | 204 | value = None | ||
1142 | 205 | for variables in self.stack: | ||
1143 | 206 | if variables.has_key(varname): | ||
1144 | 207 | value = variables[varname] | ||
1145 | 208 | break | ||
1146 | 209 | return value | ||
1147 | 210 | |||
1148 | 211 | def normalize(self): | ||
1149 | 212 | mapitem = {} | ||
1150 | 213 | for variables in self.stack: | ||
1151 | 214 | for key in variables.keys(): | ||
1152 | 215 | if not mapitem.has_key(key): | ||
1153 | 216 | mapitem[key] = variables[key] | ||
1154 | 217 | return mapitem | ||
1155 | 218 | |||
1156 | 219 | class TemplateManager: | ||
1157 | 220 | "Template management object." | ||
1158 | 221 | |||
1159 | 222 | def __init__(self, template): | ||
1160 | 223 | self.template = template | ||
1161 | 224 | self.currentnode = self.template | ||
1162 | 225 | |||
1163 | 226 | def __pprint(self, nodelist): | ||
1164 | 227 | from types import StringType | ||
1165 | 228 | for node in nodelist: | ||
1166 | 229 | if type(node) == StringType: | ||
1167 | 230 | print node | ||
1168 | 231 | else: | ||
1169 | 232 | print node, 'for %s in %s' % (node.seqvarnames, node.seqname) | ||
1170 | 233 | self.__pprint(node.nodelist) | ||
1171 | 234 | print '<end of block>' | ||
1172 | 235 | |||
1173 | 236 | def pprint(self): | ||
1174 | 237 | self.__pprint(self.template.nodelist) | ||
1175 | 238 | |||
1176 | 239 | def write(self, s): | ||
1177 | 240 | self.currentnode.write(s) | ||
1178 | 241 | |||
1179 | 242 | def __crop_chunk(self, node): | ||
1180 | 243 | node.nodelist.append(node.getvalue()) | ||
1181 | 244 | node.initdescriptor() | ||
1182 | 245 | |||
1183 | 246 | def __append_child(self, node, child): | ||
1184 | 247 | node.nodelist.append(child) | ||
1185 | 248 | |||
1186 | 249 | def ifblock_begin(self, varname): | ||
1187 | 250 | self.__crop_chunk(self.currentnode) | ||
1188 | 251 | ifblock = IfBlock(self.currentnode, varname) | ||
1189 | 252 | self.__append_child(self.currentnode, ifblock) | ||
1190 | 253 | self.currentnode = ifblock | ||
1191 | 254 | |||
1192 | 255 | def ifblock_end(self): | ||
1193 | 256 | self.__crop_chunk(self.currentnode) | ||
1194 | 257 | self.currentnode = self.currentnode.parent | ||
1195 | 258 | |||
1196 | 259 | def forblock_begin(self, seqvarnames, seqname): | ||
1197 | 260 | self.__crop_chunk(self.currentnode) | ||
1198 | 261 | forblock = ForBlock(self.currentnode, seqvarnames, seqname) | ||
1199 | 262 | self.__append_child(self.currentnode, forblock) | ||
1200 | 263 | self.currentnode = forblock | ||
1201 | 264 | |||
1202 | 265 | def forblock_end(self): | ||
1203 | 266 | self.__crop_chunk(self.currentnode) | ||
1204 | 267 | self.currentnode = self.currentnode.parent | ||
1205 | 268 | |||
1206 | 269 | def end_document(self): | ||
1207 | 270 | self.__crop_chunk(self.currentnode) | ||
1208 | 271 | |||
1209 | 272 | class TemplateRenderer: | ||
1210 | 273 | "Template renderer." | ||
1211 | 274 | |||
1212 | 275 | def __init__(self, template, variable, strict): | ||
1213 | 276 | self.varstack = VariableStack(variable) | ||
1214 | 277 | self.template = template | ||
1215 | 278 | self.strict = strict | ||
1216 | 279 | self.__buffer = StringIO() | ||
1217 | 280 | |||
1218 | 281 | def render(self): | ||
1219 | 282 | self.__build(self.template.nodelist) | ||
1220 | 283 | return self.__buffer.getvalue() | ||
1221 | 284 | |||
1222 | 285 | def __build(self, nodelist): | ||
1223 | 286 | for node in nodelist: | ||
1224 | 287 | varmap = DictEnhanceAccessor(self.strict, self.varstack.normalize()) | ||
1225 | 288 | if isinstance(node, IfBlock): | ||
1226 | 289 | if varmap[node.varname]: | ||
1227 | 290 | self.__build(node.nodelist) | ||
1228 | 291 | elif isinstance(node, ForBlock): | ||
1229 | 292 | for var in varmap[node.seqname]: | ||
1230 | 293 | if type(node.seqvarnames) == str: | ||
1231 | 294 | self.varstack.push({node.seqvarnames: var}) | ||
1232 | 295 | else: | ||
1233 | 296 | self.varstack.push(dict(zip(node.seqvarnames, var))) | ||
1234 | 297 | self.__build(node.nodelist) | ||
1235 | 298 | self.varstack.pop() | ||
1236 | 299 | else: | ||
1237 | 300 | self.__buffer.write(node % varmap) | ||
1238 | 301 | |||
1239 | 302 | class ParseError(Exception): | ||
1240 | 303 | 'Parse error' | ||
1241 | 304 | |||
1242 | 305 | def compile(template): | ||
1243 | 306 | "Compile from template characters." | ||
1244 | 307 | |||
1245 | 308 | tpl = TemplateParser() | ||
1246 | 309 | hn = ProcessingHandler() | ||
1247 | 310 | tpl.set_processhandler(hn) | ||
1248 | 311 | try: | ||
1249 | 312 | tpl.parse(template) | ||
1250 | 313 | except SyntaxError, e: | ||
1251 | 314 | raise SyntaxError, "line %d, in '%s' %s" % ( | ||
1252 | 315 | tpl.lineno, tpl.chunk, e) | ||
1253 | 316 | return hn.template.template | ||
1254 | 317 | |||
1255 | 318 | def build(template, vardict=None, strict=False, **kw): | ||
1256 | 319 | "Building document from template and variables." | ||
1257 | 320 | |||
1258 | 321 | if vardict == None: vardict = {} | ||
1259 | 322 | for key in kw.keys(): | ||
1260 | 323 | vardict[key] = kw[key] | ||
1261 | 324 | if not isinstance(template, Template): | ||
1262 | 325 | template = compile(template) | ||
1263 | 326 | return TemplateRenderer(template, vardict, strict).render() | ||
1264 | 0 | 327 | ||
1265 | === modified file 'util/tree.py' | |||
1266 | --- util/tree.py 2008-01-10 13:19:44 +0000 | |||
1267 | +++ util/tree.py 2008-03-25 19:58:32 +0000 | |||
1268 | @@ -1,4 +1,3 @@ | |||
1269 | 1 | #!/usr/bin/env python | ||
1270 | 2 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
1271 | 3 | # util/tree.py - useful functions for dealing with trees of files | 2 | # util/tree.py - useful functions for dealing with trees of files |
1272 | 4 | # | 3 | # |