1 # Copyright (C) 2009 Gianluca Montecchi <gian@grys.it>
2 # W. Trevor King <wking@drexel.edu>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 """Generate a static HTML dump of the current repository status"""
18 from libbe import cmdutil, bugdir, bug
19 #from html_data import *
20 import codecs, os, re, string, time
21 import xml.sax.saxutils, htmlentitydefs
25 def execute(args, manipulate_encodings=True):
28 >>> bd = bugdir.SimpleBugDir()
30 >>> execute([], manipulate_encodings=False)
31 Creating the html output in html_export
32 >>> os.path.exists("./html_export")
34 >>> os.path.exists("./html_export/index.html")
36 >>> os.path.exists("./html_export/index_inactive.html")
38 >>> os.path.exists("./html_export/bugs")
40 >>> os.path.exists("./html_export/bugs/a.html")
42 >>> os.path.exists("./html_export/bugs/b.html")
47 options, args = parser.parse_args(args)
48 complete(options, args, parser)
49 cmdutil.default_complete(options, args, parser,
50 bugid_args={0: lambda bug : bug.active==False})
53 out_dir = options.outdir
54 print "Creating the html output in %s"%out_dir
58 raise cmdutil.UsageError, "Too many arguments."
60 bd = bugdir.BugDir(from_disk=True,
61 manipulate_encodings=manipulate_encodings)
63 status_list = bug.status_values
64 severity_list = bug.severity_values
72 for b in sorted(bd, reverse=True):
73 stime[b.uuid] = b.time
77 bugs_inactive.append(b)
79 ordered_bug_list = sorted([(value,key) for (key,value) in stime.items()])
80 ordered_bug_list_in = sorted([(value,key) for (key,value) in stime.items()])
81 #open_bug_list = sorted([(value,key) for (key,value) in bugs.items()])
83 html_gen = BEHTMLGen(bd)
84 html_gen.create_index_file(out_dir, st, bugs_active, ordered_bug_list, "active", bd.encoding)
85 html_gen.create_index_file(out_dir, st, bugs_inactive, ordered_bug_list, "inactive", bd.encoding)
88 parser = cmdutil.CmdOptionParser("be open OUTPUT_DIR")
89 parser.add_option("-o", "--output", metavar="export_dir", dest="outdir",
90 help="Set the output path, default is ./html_export", default="html_export")
94 Generate a set of html pages representing the current state of the bug
99 return get_parser().help_str() + longhelp
101 def complete(options, args, parser):
102 for option, value in cmdutil.option_value_pairs(options, parser):
103 if "--complete" in args:
104 raise cmdutil.GetCompletions() # no positional arguments for list
111 for char in xml.sax.saxutils.escape(string):
112 codepoint = ord(char)
113 if codepoint in htmlentitydefs.codepoint2name:
114 char = "&%s;" % htmlentitydefs.codepoint2name[codepoint]
116 return "".join(chars)
119 def __init__(self, bd):
120 self.index_value = ""
125 font-family: "lucida grande", "sans serif";
137 background-color: #fcfcfc;
160 background-color: #B4FF9B;
165 background-color: #FCFF98;
171 background-color: #FFB648;
176 background-color: #FF752A;
181 background-color: #FF3300;
186 font-family: courier;
191 text-decoration: none;
203 list-style-type: none;
211 .inline-status-image {
221 border-style: 10px solid #313131;
232 padding-right: 0.5em;
238 background-color: #afafaf;
239 border: 1px solid #afafaf;
259 background-color: #305275;
268 text-transform: uppercase;
272 /*background: #fffbce;*/
273 /*background: #628a0d;*/
287 .issue-closed-fixed {
288 background-image: "green-check.png";
291 .issue-closed-wontfix {
292 background-image: "red-check.png";
295 .issue-closed-reorg {
296 background-image: "blue-check.png";
300 text-decoration: underline;
325 font-family: "lucida grande", "sans serif";
327 background-color: #a9a9a9;
330 padding-right: 0.5em;
337 background-color: #e9e9e2;
341 background-color: #f9f9f9;
351 font-family: courier;
355 background-color: #afafaf;
356 border: 2px solid #afafaf;
362 .progress-meter-done {
363 background-color: #03af00;
366 .progress-meter-undone {
367 background-color: #ddd;
375 self.index_first = """
376 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
377 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
378 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
380 <title>BugsEverywhere Issue Tracker</title>
381 <meta http-equiv="Content-Type" content="text/html; charset=%s" />
382 <link rel="stylesheet" href="style.css" type="text/css" />
388 <h1>BugsEverywhere Bug List</h1>
393 <td class="%%s"><a href="index.html">Active Bugs</a></td>
394 <td class="%%s"><a href="index_inactive.html">Inactive Bugs</a></td>
398 <table class="table_bug">
400 """ % self.bd.encoding
404 <td ><a href="bugs/%s.html">%s</a></td>
405 <td ><a href="bugs/%s.html">%s</a></td>
406 <td><a href="bugs/%s.html">%s</a></td>
407 <td><a href="bugs/%s.html">%s</a></td>
408 <td><a href="bugs/%s.html">%s</a></td>
412 self.detail_first = """
413 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
414 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
415 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
417 <title>BugsEverywhere Issue Tracker</title>
418 <meta http-equiv="Content-Type" content="text/html; charset=%s" />
419 <link rel="stylesheet" href="../style.css" type="text/css" />
425 <h1>BugsEverywhere Bug List</h1>
426 <h5><a href="%%s">Back to Index</a></h5>
427 <h2>Bug: _bug_id_</h2>
430 """ % self.bd.encoding
434 self.detail_line ="""
436 <td align="right">%s</td><td>%s</td>
440 self.index_last = """
446 <div class="footer">Generated by <a href="http://www.bugseverywhere.org/">BugsEverywhere</a> on %s</div>
452 self.comment_section = """
455 self.begin_comment_section ="""
457 <td align="right">Comments:
463 self.end_comment_section ="""
468 self.detail_last = """
472 <h5><a href="%s">Back to Index</a></h5>
473 <div class="footer">Generated by <a href="http://www.bugseverywhere.org/">BugsEverywhere</a>.</div>
479 def create_index_file(self, out_dir_path, summary, bugs, ordered_bug, fileid, encoding):
481 os.stat(out_dir_path)
484 os.mkdir(out_dir_path)
486 raise cmdutil.UsageError, "Cannot create output directory."
488 FO = codecs.open(out_dir_path+"/style.css", "w", encoding)
489 FO.write(self.css_file)
492 raise cmdutil.UsageError, "Cannot create the style.css file."
495 os.mkdir(out_dir_path+"/bugs")
500 if fileid == "active":
501 FO = codecs.open(out_dir_path+"/index.html", "w", encoding)
502 FO.write(self.index_first%('td_sel','td_nsel'))
503 if fileid == "inactive":
504 FO = codecs.open(out_dir_path+"/index_inactive.html", "w", encoding)
505 FO.write(self.index_first%('td_nsel','td_sel'))
507 raise cmdutil.UsageError, "Cannot create the index.html file."
511 for l in range(t, -1, -1):
512 line = self.bug_line%(escape(bugs[l].severity),
513 escape(bugs[l].uuid), escape(bugs[l].uuid[0:3]),
514 escape(bugs[l].uuid), escape(bugs[l].status),
515 escape(bugs[l].uuid), escape(bugs[l].severity),
516 escape(bugs[l].uuid), escape(bugs[l].summary),
517 escape(bugs[l].uuid), escape(bugs[l].time_string)
521 self.create_detail_file(bugs[l], out_dir_path, fileid, encoding)
523 FO.write(self.index_last%when)
526 def create_detail_file(self, bug, out_dir_path, fileid, encoding):
527 f = "%s.html"%bug.uuid
528 p = out_dir_path+"/bugs/"+f
530 FD = codecs.open(p, "w", encoding)
532 raise cmdutil.UsageError, "Cannot create the detail html file."
534 detail_first_ = re.sub('_bug_id_', bug.uuid[0:3], self.detail_first)
535 if fileid == "active":
536 FD.write(detail_first_%"../index.html")
537 if fileid == "inactive":
538 FD.write(detail_first_%"../index_inactive.html")
542 bug_ = self.bd.bug_from_shortname(bug.uuid)
543 bug_.load_comments(load_full=True)
545 FD.write(self.detail_line%("ID : ", bug.uuid))
546 FD.write(self.detail_line%("Short name : ", escape(bug.uuid[0:3])))
547 FD.write(self.detail_line%("Severity : ", escape(bug.severity)))
548 FD.write(self.detail_line%("Status : ", escape(bug.status)))
549 FD.write(self.detail_line%("Assigned : ", escape(bug.assigned)))
550 FD.write(self.detail_line%("Target : ", escape(bug.target)))
551 FD.write(self.detail_line%("Reporter : ", escape(bug.reporter)))
552 FD.write(self.detail_line%("Creator : ", escape(bug.creator)))
553 FD.write(self.detail_line%("Created : ", escape(bug.time_string)))
554 FD.write(self.detail_line%("Summary : ", escape(bug.summary)))
555 FD.write("<tr><td colspan=\"2\"><hr /></td></tr>")
556 FD.write(self.begin_comment_section)
561 for depth,comment in bug_.comment_root.thread(flatten=False):
562 while len(stack) > depth:
563 stack.pop(-1) # pop non-parents off the stack
564 FD.write("</div>\n") # close non-parent <div class="comment...
565 assert len(stack) == depth
566 stack.append(comment)
567 lines = ["--------- Comment ---------",
568 "Name: %s" % comment.uuid,
569 "From: %s" % escape(comment.author),
570 "Date: %s" % escape(comment.date),
572 lines.extend(escape(comment.body).splitlines())
574 FD.write('<div class="commentF">')
576 FD.write('<div class="comment">')
577 FD.write("<br />\n".join(lines)+"<br />\n")
578 while len(stack) > 0:
580 FD.write("</div>\n") # close every remaining <div class="comment...
581 FD.write(self.end_comment_section)
582 if fileid == "active":
583 FD.write(self.detail_last%"../index.html")
584 if fileid == "inactive":
585 FD.write(self.detail_last%"../index_inactive.html")