# Copyright (C) 2009 Gianluca Montecchi # W. Trevor King # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """Generate a static HTML dump of the current repository status""" from libbe import cmdutil, bugdir, bug #from html_data import * import codecs, os, re, string, time import xml.sax.saxutils, htmlentitydefs __desc__ = __doc__ def execute(args, manipulate_encodings=True): """ >>> import os >>> bd = bugdir.SimpleBugDir() >>> os.chdir(bd.root) >>> execute([], manipulate_encodings=False) Creating the html output in html_export >>> os.path.exists("./html_export") True >>> os.path.exists("./html_export/index.html") True >>> os.path.exists("./html_export/index_inactive.html") True >>> os.path.exists("./html_export/bugs") True >>> os.path.exists("./html_export/bugs/a.html") True >>> os.path.exists("./html_export/bugs/b.html") True >>> bd.cleanup() """ parser = get_parser() options, args = parser.parse_args(args) complete(options, args, parser) cmdutil.default_complete(options, args, parser, bugid_args={0: lambda bug : bug.active==False}) if len(args) == 0: out_dir = options.outdir print "Creating the html output in %s"%out_dir else: out_dir = args[0] if len(args) > 0: raise cmdutil.UsageError, "Too many arguments." bd = bugdir.BugDir(from_disk=True, manipulate_encodings=manipulate_encodings) bd.load_all_bugs() status_list = bug.status_values severity_list = bug.severity_values st = {} se = {} stime = {} bugs_active = [] bugs_inactive = [] for s in status_list: st[s] = 0 for b in sorted(bd, reverse=True): stime[b.uuid] = b.time if b.status in ["open", "test", "unconfirmed", "assigned"]: bugs_active.append(b) else: bugs_inactive.append(b) st[b.status] += 1 ordered_bug_list = sorted([(value,key) for (key,value) in stime.items()]) ordered_bug_list_in = sorted([(value,key) for (key,value) in stime.items()]) #open_bug_list = sorted([(value,key) for (key,value) in bugs.items()]) html_gen = BEHTMLGen(bd) html_gen.create_index_file(out_dir, st, bugs_active, ordered_bug_list, "active", bd.encoding) html_gen.create_index_file(out_dir, st, bugs_inactive, ordered_bug_list, "inactive", bd.encoding) def get_parser(): parser = cmdutil.CmdOptionParser("be open OUTPUT_DIR") parser.add_option("-o", "--output", metavar="export_dir", dest="outdir", help="Set the output path, default is ./html_export", default="html_export") return parser longhelp=""" Generate a set of html pages representing the current state of the bug directory. """ def help(): return get_parser().help_str() + longhelp def complete(options, args, parser): for option, value in cmdutil.option_value_pairs(options, parser): if "--complete" in args: raise cmdutil.GetCompletions() # no positional arguments for list def escape(string): if string == None: return "" chars = [] for char in xml.sax.saxutils.escape(string): codepoint = ord(char) if codepoint in htmlentitydefs.codepoint2name: char = "&%s;" % htmlentitydefs.codepoint2name[codepoint] chars.append(char) return "".join(chars) class BEHTMLGen(): def __init__(self, bd): self.index_value = "" self.bd = bd self.css_file = """ body { font-family: "lucida grande", "sans serif"; color: #333; width: auto; margin: auto; } div.main { padding: 20px; margin: auto; padding-top: 0; margin-top: 1em; background-color: #fcfcfc; } .comment { padding: 20px; margin: auto; padding-top: 20px; margin-top: 0; } .commentF { padding: 0px; margin: auto; padding-top: 0px; paddin-bottom: 20px; margin-top: 0; } tb { border = 1; } .wishlist-row { background-color: #B4FF9B; width: auto; } .minor-row { background-color: #FCFF98; width: auto; } .serious-row { background-color: #FFB648; width: auto; } .critical-row { background-color: #FF752A; width: auto; } .fatal-row { background-color: #FF3300; width: auto; } .person { font-family: courier; } a, a:visited { background: inherit; text-decoration: none; } a { color: #003d41; } a:visited { color: #553d41; } ul { list-style-type: none; padding: 0; } p { width: auto; } .inline-status-image { position: relative; top: 0.2em; } .dimmed { color: #bbb; } table { border-style: 10px solid #313131; border-spacing: 0; width: auto; } table.log { } td { border-width: 0; border-style: none; padding-right: 0.5em; padding-left: 0.5em; width: auto; } .td_sel { background-color: #afafaf; border: 1px solid #afafaf; font-weight:bold; padding-right: 1em; padding-left: 1em; } .td_nsel { border: 0px; padding-right: 1em; padding-left: 1em; } tr { vertical-align: top; width: auto; } h1 { padding: 0.5em; background-color: #305275; margin-top: 0; margin-bottom: 0; color: #fff; margin-left: -20px; margin-right: -20px; } wid { text-transform: uppercase; font-size: smaller; margin-top: 1em; margin-left: -0.5em; /*background: #fffbce;*/ /*background: #628a0d;*/ padding: 5px; color: #305275; } .attrname { text-align: right; font-size: smaller; } .attrval { color: #222; } .issue-closed-fixed { background-image: "green-check.png"; } .issue-closed-wontfix { background-image: "red-check.png"; } .issue-closed-reorg { background-image: "blue-check.png"; } .inline-issue-link { text-decoration: underline; } img { border: 0; } div.footer { font-size: small; padding-left: 20px; padding-right: 20px; padding-top: 5px; padding-bottom: 5px; margin: auto; background: #305275; color: #fffee7; } .footer a { color: #508d91; } .header { font-family: "lucida grande", "sans serif"; font-size: smaller; background-color: #a9a9a9; text-align: left; padding-right: 0.5em; padding-left: 0.5em; } .selected-cell { background-color: #e9e9e2; } .plain-cell { background-color: #f9f9f9; } .logcomment { padding-left: 4em; font-size: smaller; } .id { font-family: courier; } .table_bug { background-color: #afafaf; border: 2px solid #afafaf; } .message { } .progress-meter-done { background-color: #03af00; } .progress-meter-undone { background-color: #ddd; } .progress-meter { } """ self.index_first = """ BugsEverywhere Issue Tracker

BugsEverywhere Bug List

Active Bugs Inactive Bugs
""" % self.bd.encoding self.bug_line =""" """ self.detail_first = """ BugsEverywhere Issue Tracker

BugsEverywhere Bug List

Back to Index

Bug: _bug_id_

%s %s %s %s %s
""" % self.bd.encoding self.detail_line =""" """ self.index_last = """
%s%s
""" self.comment_section = """ """ self.begin_comment_section =""" Comments: """ self.end_comment_section =""" """ self.detail_last = """
Back to Index
""" def create_index_file(self, out_dir_path, summary, bugs, ordered_bug, fileid, encoding): try: os.stat(out_dir_path) except: try: os.mkdir(out_dir_path) except: raise cmdutil.UsageError, "Cannot create output directory." try: FO = codecs.open(out_dir_path+"/style.css", "w", encoding) FO.write(self.css_file) FO.close() except: raise cmdutil.UsageError, "Cannot create the style.css file." try: os.mkdir(out_dir_path+"/bugs") except: pass try: if fileid == "active": FO = codecs.open(out_dir_path+"/index.html", "w", encoding) FO.write(self.index_first%('td_sel','td_nsel')) if fileid == "inactive": FO = codecs.open(out_dir_path+"/index_inactive.html", "w", encoding) FO.write(self.index_first%('td_nsel','td_sel')) except: raise cmdutil.UsageError, "Cannot create the index.html file." c = 0 t = len(bugs) - 1 for l in range(t, -1, -1): line = self.bug_line%(escape(bugs[l].severity), escape(bugs[l].uuid), escape(bugs[l].uuid[0:3]), escape(bugs[l].uuid), escape(bugs[l].status), escape(bugs[l].uuid), escape(bugs[l].severity), escape(bugs[l].uuid), escape(bugs[l].summary), escape(bugs[l].uuid), escape(bugs[l].time_string) ) FO.write(line) c += 1 self.create_detail_file(bugs[l], out_dir_path, fileid, encoding) when = time.ctime() FO.write(self.index_last%when) def create_detail_file(self, bug, out_dir_path, fileid, encoding): f = "%s.html"%bug.uuid p = out_dir_path+"/bugs/"+f try: FD = codecs.open(p, "w", encoding) except: raise cmdutil.UsageError, "Cannot create the detail html file." detail_first_ = re.sub('_bug_id_', bug.uuid[0:3], self.detail_first) if fileid == "active": FD.write(detail_first_%"../index.html") if fileid == "inactive": FD.write(detail_first_%"../index_inactive.html") bug_ = self.bd.bug_from_shortname(bug.uuid) bug_.load_comments(load_full=True) FD.write(self.detail_line%("ID : ", bug.uuid)) FD.write(self.detail_line%("Short name : ", escape(bug.uuid[0:3]))) FD.write(self.detail_line%("Severity : ", escape(bug.severity))) FD.write(self.detail_line%("Status : ", escape(bug.status))) FD.write(self.detail_line%("Assigned : ", escape(bug.assigned))) FD.write(self.detail_line%("Target : ", escape(bug.target))) FD.write(self.detail_line%("Reporter : ", escape(bug.reporter))) FD.write(self.detail_line%("Creator : ", escape(bug.creator))) FD.write(self.detail_line%("Created : ", escape(bug.time_string))) FD.write(self.detail_line%("Summary : ", escape(bug.summary))) FD.write("
") FD.write(self.begin_comment_section) tr = [] b = '' level = 0 stack = [] for depth,comment in bug_.comment_root.thread(flatten=False): while len(stack) > depth: stack.pop(-1) # pop non-parents off the stack FD.write("\n") # close non-parent
') else: FD.write('
') FD.write("
\n".join(lines)+"
\n") while len(stack) > 0: stack.pop(-1) FD.write("
\n") # close every remaining