# 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 import codecs, os, os.path, re, string, time import xml.sax.saxutils, htmlentitydefs __desc__ = __doc__ def execute(args, manipulate_encodings=True, restrict_file_access=False): """ >>> import os >>> bd = bugdir.SimpleBugDir() >>> os.chdir(bd.root) >>> execute([], manipulate_encodings=False) >>> 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) if len(args) > 0: raise cmdutil.UsageError, 'Too many arguments.' bd = bugdir.BugDir(from_disk=True, manipulate_encodings=manipulate_encodings) bd.load_all_bugs() html_gen = HTMLGen(bd, template=options.template, verbose=options.verbose, title=options.title, index_header=options.index_header) if options.exp_template == True: html_gen.write_default_template(options.exp_template_dir) return html_gen.run(options.out_dir) def get_parser(): parser = cmdutil.CmdOptionParser('be html [options]') parser.add_option('-o', '--output', metavar='DIR', dest='out_dir', help='Set the output path (%default)', default='./html_export') parser.add_option('-t', '--template-dir', metavar='DIR', dest='template', help='Use a different template, defaults to internal templates', default=None) parser.add_option('--title', metavar='STRING', dest='title', help='Set the bug repository title (%default)', default='BugsEverywhere Issue Tracker') parser.add_option('--index-header', metavar='STRING', dest='index_header', help='Set the index page headers (%default)', default='BugsEverywhere Bug List') parser.add_option('-v', '--verbose', action='store_true', metavar='verbose', dest='verbose', help='Verbose output, default is %default', default=False) parser.add_option('-e', '--export-template', action='store_true', dest='exp_template', help='Export the default template and exit.', default=False) parser.add_option('-d', '--export-template-dir', metavar='DIR', dest='exp_template_dir', default='./default-templates/', help='Set the directory for the template export (%default)') 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 class HTMLGen (object): def __init__(self, bd, template=None, verbose=False, encoding=None, title="Site Title", index_header="Index Header", ): self.generation_time = time.ctime() self.bd = bd self.verbose = verbose self.title = title self.index_header = index_header if encoding != None: self.encoding = encoding else: self.encoding = self.bd.encoding if template == None: self.template = "default" else: self.template = os.path.abspath(os.path.expanduser(template)) self._load_default_templates() if template != None: self._load_user_templates() def run(self, out_dir): if self.verbose == True: print "Creating the html output in %s using templates in %s" \ % (out_dir, self.template) bugs_active = [] bugs_inactive = [] bugs = [b for b in self.bd] bugs.sort() bugs_active = [b for b in bugs if b.active == True] bugs_inactive = [b for b in bugs if b.active != True] self._create_output_directories(out_dir) self._write_css_file() for b in bugs: if b.active: up_link = "../index.html" else: up_link = "../index_inactive.html" self._write_bug_file(b, up_link) self._write_index_file( bugs_active, title=self.title, index_header=self.index_header, bug_type="active") self._write_index_file( bugs_inactive, title=self.title, index_header=self.index_header, bug_type="inactive") def _create_output_directories(self, out_dir): if self.verbose: print "Creating output directories" self.out_dir = self._make_dir(out_dir) self.out_dir_bugs = self._make_dir( os.path.join(self.out_dir, "bugs")) def _write_css_file(self): if self.verbose: print "Writing css file" assert hasattr(self, "out_dir"), \ "Must run after ._create_output_directories()" self._write_file(self.css_file, [self.out_dir,"style.css"]) def _write_bug_file(self, bug, up_link): if self.verbose: print "\tCreating bug file for %s" % self.bd.bug_shortname(bug) assert hasattr(self, "out_dir_bugs"), \ "Must run after ._create_output_directories()" bug.load_comments(load_full=True) comment_entries = self._generate_bug_comment_entries(bug) filename = "%s.html" % bug.uuid fullpath = os.path.join(self.out_dir_bugs, filename) template_info = {'title':self.title, 'charset':self.encoding, 'up_link':up_link, 'shortname':self.bd.bug_shortname(bug), 'comment_entries':comment_entries, 'generation_time':self.generation_time} for attr in ['uuid', 'severity', 'status', 'assigned', 'reporter', 'creator', 'time_string', 'summary']: template_info[attr] = self._escape(getattr(bug, attr)) self._write_file(self.bug_file % template_info, [fullpath]) def _generate_bug_comment_entries(self, bug): assert hasattr(self, "out_dir_bugs"), \ "Must run after ._create_output_directories()" stack = [] comment_entries = [] for depth,comment in bug.comment_root.thread(flatten=False): while len(stack) > depth: # pop non-parents off the stack stack.pop(-1) # close non-parent
\n") assert len(stack) == depth stack.append(comment) if depth == 0: comment_entries.append('
') else: comment_entries.append('
') template_info = {} for attr in ['uuid', 'author', 'date', 'body']: value = getattr(comment, attr) if attr == 'body': save_body = False if comment.content_type == 'text/html': pass # no need to escape html... elif comment.content_type.startswith('text/'): value = '
\n'+self._escape(value)+'\n
' elif comment.content_type.startswith('image/'): save_body = True value = '' \ % (bug.uuid, comment.uuid) else: save_body = True value = 'Link to %s file.' \ % (bug.uuid, comment.uuid, comment.content_type) if save_body == True: per_bug_dir = os.path.join(self.out_dir_bugs, bug.uuid) if not os.path.exists(per_bug_dir): os.mkdir(per_bug_dir) comment_path = os.path.join(per_bug_dir, comment.uuid) self._write_file( '\n ForceType %s\n' \ % (comment.uuid, comment.content_type), [per_bug_dir, '.htaccess'], mode='a') self._write_file( comment.body, [per_bug_dir, comment.uuid], mode='wb') else: value = self._escape(value) template_info[attr] = value comment_entries.append(self.bug_comment_entry % template_info) while len(stack) > 0: stack.pop(-1) comment_entries.append("
\n") # close every remaining
%(title)s

%(index_header)s

Active Bugs Inactive Bugs
%(bug_entries)s
""" self.index_bug_entry =""" %(shortname)s %(status)s %(severity)s %(summary)s %(time_string)s """ self.bug_file = """ %(title)s

BugsEverywhere Bug List

Back to Index

Bug: %(shortname)s

ID : %(uuid)s
Short name : %(shortname)s
Status : %(status)s
Severity : %(severity)s
Assigned : %(assigned)s
Reporter : %(reporter)s
Creator : %(creator)s
Created : %(time_string)s
Summary : %(summary)s

%(comment_entries)s
Back to Index
""" self.bug_comment_entry ="""
Comment: --------- Comment ---------
Name: %(uuid)s
From: %(author)s
Date: %(date)s

%(body)s
""" # strip leading whitespace for attr in ['css_file', 'index_file', 'index_bug_entry', 'bug_file', 'bug_comment_entry']: value = getattr(self, attr) value = value.replace('\n'+' '*12, '\n') setattr(self, attr, value.strip()+'\n')