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