From 5b6be7a511670cdf1961d13040cddcb34e70b8a1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 17 Apr 2011 02:41:07 -0400 Subject: [PATCH] Rework `be html` to use Jinja2 templates. --- libbe/command/html.py | 1088 ++++++++++++++++++++--------------------- 1 file changed, 530 insertions(+), 558 deletions(-) diff --git a/libbe/command/html.py b/libbe/command/html.py index bb5b554..c403fcb 100644 --- a/libbe/command/html.py +++ b/libbe/command/html.py @@ -26,6 +26,8 @@ import string import time import xml.sax.saxutils +from jinja2 import Environment, FileSystemLoader, DictLoader, ChoiceLoader + import libbe import libbe.command import libbe.command.util @@ -82,12 +84,12 @@ class HTML (libbe.command.Command): help='Set the bug repository title (%default)', arg=libbe.command.Argument( name='title', metavar='STRING', - default='BugsEverywhere Issue Tracker')), + default='Bugs Everywhere Issue Tracker')), libbe.command.Option(name='index-header', help='Set the index page headers (%default)', arg=libbe.command.Argument( name='index-header', metavar='STRING', - default='BugsEverywhere Bug List')), + default='Bugs Everywhere Bug List')), libbe.command.Option(name='export-template', short_name='e', help='Export the default template and exit.'), libbe.command.Option(name='export-template-dir', short_name='d', @@ -112,9 +114,9 @@ class HTML (libbe.command.Command): bugdir = self._get_bugdir() bugdir.load_all_bugs() html_gen = HTMLGen(bugdir, - template=params['template-dir'], + template_dir=params['template-dir'], title=params['title'], - index_header=params['index-header'], + header=params['index-header'], min_id_length=params['min-id-length'], verbose=params['verbose'], stdout=self.stdout) @@ -132,28 +134,22 @@ directory. Html = HTML # alias for libbe.command.base.get_command_class() class HTMLGen (object): - def __init__(self, bd, template=None, - title="Site Title", index_header="Index Header", + def __init__(self, bd, template_dir=None, + title="Site Title", header="Header", min_id_length=-1, verbose=False, encoding=None, stdout=None, ): self.generation_time = time.ctime() self.bd = bd - if template == None: - self.template = "default" - else: - self.template = os.path.abspath(os.path.expanduser(template)) self.title = title - self.index_header = index_header + self.header = header self.verbose = verbose self.stdout = stdout if encoding != None: self.encoding = encoding else: self.encoding = libbe.util.encoding.get_filesystem_encoding() - self._load_default_templates() - if template != None: - self._load_user_templates() + self._load_templates(template_dir) self.min_id_length = min_id_length def run(self, out_dir): @@ -179,21 +175,24 @@ class HTMLGen (object): self._create_output_directories(out_dir) self._write_css_file() for b in bugs: - if b.active: + if b.severity == 'target': + up_link = '../../index_target.html' + elif b.active: up_link = '../../index.html' else: - up_link = '../../index_inactive.html' - - self._write_bug_file(b, up_link) + up_link = '../../index_inactive.html' + self._write_bug_file( + b, title=self.title, header=self.header, + up_link=up_link) self._write_index_file( bugs_active, title=self.title, - index_header=self.index_header, bug_type='active') + header=self.header, bug_type='active') self._write_index_file( bugs_inactive, title=self.title, - index_header=self.index_header, bug_type='inactive') + header=self.header, bug_type='inactive') self._write_index_file( bugs_target, title=self.title, - index_header=self.index_header, bug_type='target') + header=self.header, bug_type='target') def _truncated_bug_id(self, bug): return libbe.util.id._truncate( @@ -217,10 +216,10 @@ class HTMLGen (object): print >> self.stdout, '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']) + template = self.template.get_template('style.css') + self._write_file(template.render(), [self.out_dir, 'style.css']) - def _write_bug_file(self, bug, up_link): + def _write_bug_file(self, bug, title, header, up_link): if self.verbose: print >> self.stdout, '\tCreating bug file for %s' % bug.id.user() assert hasattr(self, 'out_dir_bugs'), \ @@ -229,96 +228,77 @@ class HTMLGen (object): if bug.active == True: index_type = 'Active' - else : + else: index_type = 'Inactive' if bug.severity == 'target': index_type = 'Target' bug.load_comments(load_full=True) - comment_entries = self._generate_bug_comment_entries(bug) + bug.comment_root.sort(cmp=libbe.comment.cmp_time, reverse=True) dirname = self._truncated_bug_id(bug) fullpath = os.path.join(self.out_dir_bugs, dirname, 'index.html') - template_info = {'title':self.title, - 'charset':self.encoding, - 'up_link':up_link, - 'shortname':bug.id.user(), - 'comment_entries':comment_entries, - 'generation_time':self.generation_time, - 'index_type':index_type} - for attr in ['uuid', 'severity', 'status', 'assigned', - 'reporter', 'creator', 'time_string', 'summary']: - template_info[attr] = self._escape(getattr(bug, attr)) + template_info = { + 'title': title, + 'charset': self.encoding, + 'stylesheet': '../../style.css', + 'header': header, + 'backlinks': self.template.get_template('bug_backlinks.html'), + 'up_link': up_link, + 'index_type': index_type, + 'bug': bug, + 'comment_entry': self.template.get_template( + 'bug_comment_entry.html'), + 'comments': [(depth,comment) for depth,comment + in bug.comment_root.thread(flatten=False)], + 'comment_dir': self._truncated_comment_id, + 'format_body': self._format_comment_body, + 'div_close': _DivCloser(), + 'generation_time': self.generation_time, + } fulldir = os.path.join(self.out_dir_bugs, dirname) if not os.path.exists(fulldir): os.mkdir(fulldir) - self._write_file(self.bug_file % template_info, [fullpath]) + template = self.template.get_template('bug.html') + self._write_file(template.render(template_info), [fullpath]) - def _generate_bug_comment_entries(self, bug): - assert hasattr(self, 'out_dir_bugs'), \ - 'Must run after ._create_output_directories()' + def _write_index_file(self, bugs, title, header, bug_type='active'): + if self.verbose: + print >> self.stdout, 'Writing %s index file for %d bugs' % (bug_type, len(bugs)) + assert hasattr(self, 'out_dir'), 'Must run after ._create_output_directories()' - stack = [] - comment_entries = [] - bug.comment_root.sort(cmp=libbe.comment.cmp_time, reverse=True) - 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
') - else: - comment_entries.append( - '
' - % template_info['truncated_id']) - for attr in ['uuid', 'author', 'date', 'body']: - value = getattr(comment, attr) - if attr == 'body': - link_long_ids = False - save_body = False - if comment.content_type == 'text/html': - link_long_ids = True - elif comment.content_type.startswith('text/'): - value = '
\n'+self._escape(value)+'\n
' - link_long_ids = True - elif comment.content_type.startswith('image/'): - save_body = True - value = '' \ - % (self._truncated_bug_id(bug), - self._truncated_comment_id(comment)) - else: - save_body = True - value = 'Link to %s file.' \ - % (self._truncated_bug_id(bug), - self._truncated_comment_id(comment), - comment.content_type) - if link_long_ids == True: - value = self._long_to_linked_user(value) - 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
> self.stdout, 'Writing %s index file for %d bugs' % (bug_type, len(bugs)) - assert hasattr(self, 'out_dir'), 'Must run after ._create_output_directories()' - esc = self._escape - if (bug_type == 'target'): - bug_entries = self._generate_index_bug_entries_target(bugs) + def _format_comment_body(self, bug, comment): + link_long_ids = False + save_body = False + value = comment.body + if comment.content_type == 'text/html': + link_long_ids = True + elif comment.content_type.startswith('text/'): + value = '
\n'+self._escape(value)+'\n
' + link_long_ids = True + elif comment.content_type.startswith('image/'): + save_body = True + value = '' % ( + self._truncated_bug_id(bug), + self._truncated_comment_id(comment)) else: - bug_entries = self._generate_index_bug_entries(bugs) - - if bug_type == 'active': - filename = 'index.html' - elif bug_type == 'inactive': - filename = 'index_inactive.html' - elif bug_type == 'target': - filename = 'index_by_target.html' - else: - raise Exception, 'Unrecognized bug_type: "%s"' % bug_type - template_info = {'title':title, - 'index_header':index_header, - 'charset':self.encoding, - 'active_class':'tab sel', - 'inactive_class':'tab nsel', - 'target_class':'tab nsel', - 'bug_entries':bug_entries, - 'generation_time':self.generation_time} - if bug_type == 'inactive': - template_info['active_class'] = 'tab nsel' - template_info['inactive_class'] = 'tab sel' - if bug_type == 'target': - template_info['active_class'] = 'tab nsel' - template_info['target_class'] = 'tab sel' - self._write_file(self.index_file % template_info, - [self.out_dir, filename]) - - def _generate_index_bug_entries_target(self, targets): - - target_entries = [] - for target in targets: - bug_entries = [] - template_info_list = {'target':target.summary, 'bug_entries': '', 'status': target.status} - blocker = libbe.command.depend.get_blocked_by(self.bd, target) - for bug in blocker: - if self.verbose: - print >> self.stdout, '\tCreating bug entry for %s' % bug.id.user() - template_info = {'shortname':bug.id.user()} - for attr in ['uuid', 'severity', 'status', 'assigned', - 'reporter', 'creator', 'time_string', 'summary']: - template_info[attr] = self._escape(getattr(bug, attr)) - template_info['dir'] = self._truncated_bug_id(bug) - bug_entries.append(self.index_bug_entry % template_info) - template_info_list['bug_entries'] = '\n'.join(bug_entries) - target_entries.append(self.target_bug_list % template_info_list) - return '\n'.join(target_entries) - - def _generate_index_bug_entries(self, bugs): - bug_entries = [] - template_info_list = {'bug_entries': ''} - for bug in bugs: - if self.verbose: - print >> self.stdout, '\tCreating bug entry for %s' % bug.id.user() - template_info = {'shortname':bug.id.user()} - for attr in ['uuid', 'severity', 'status', 'assigned', - 'reporter', 'creator', 'time_string', 'summary']: - template_info[attr] = self._escape(getattr(bug, attr)) - template_info['dir'] = self._truncated_bug_id(bug) - bug_entries.append(self.index_bug_entry % template_info) - template_info_list['bug_entries'] = '\n'.join(bug_entries) - return self.bug_list % template_info_list + save_body = True + value = 'Link to %s file.' % ( + self._truncated_bug_id(bug), + self._truncated_comment_id(comment), + comment.content_type) + if link_long_ids == True: + value = self._long_to_linked_user(value) + 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') + return value def _escape(self, string): if string == None: return '' return xml.sax.saxutils.escape(string) - def _load_user_templates(self): - for filename,attr in [('style.css','css_file'), - ('index_file.tpl','index_file'), - ('index_bug_entry.tpl','index_bug_entry'), - ('bug_file.tpl','bug_file'), - ('bug_comment_entry.tpl','bug_comment_entry')]: - fullpath = os.path.join(self.template, filename) - if os.path.exists(fullpath): - setattr(self, attr, self._read_file([fullpath])) - def _make_dir(self, dir_path): dir_path = os.path.abspath(os.path.expanduser(dir_path)) if not os.path.exists(dir_path): @@ -489,388 +424,425 @@ class HTMLGen (object): if self.verbose: print >> self.stdout, 'Creating output directories' self.out_dir = self._make_dir(out_dir) - if self.verbose: - print >> self.stdout, 'Creating css file' - self._write_css_file() - if self.verbose: - print >> self.stdout, 'Creating index_file.tpl file' - self._write_file(self.index_file, - [self.out_dir, 'index_file.tpl']) - if self.verbose: - print >> self.stdout, 'Creating index_bug_entry.tpl file' - self._write_file(self.index_bug_entry, - [self.out_dir, 'index_bug_entry.tpl']) - if self.verbose: - print >> self.stdout, 'Creating bug_file.tpl file' - self._write_file(self.bug_file, - [self.out_dir, 'bug_file.tpl']) - if self.verbose: - print >> self.stdout, 'Creating bug_comment_entry.tpl file' - self._write_file(self.bug_comment_entry, - [self.out_dir, 'bug_comment_entry.tpl']) - - def _load_default_templates(self): - self.css_file = """ - body { - font-family: "lucida grande", "sans serif"; - font-size: 14px; - color: #333; - width: auto; - margin: auto; - } - - div.main { - padding: 20px; - margin: auto; - padding-top: 0; - margin-top: 1em; - background-color: #fcfcfc; - -moz-border-radius: 10px; - - } - - div.footer { - font-size: small; - padding-left: 20px; - padding-right: 20px; - padding-top: 5px; - padding-bottom: 5px; - margin: auto; - background: #305275; - color: #fffee7; - -moz-border-radius: 10px; - } - - div.header { - font-size: xx-large; - padding-left: 20px; - padding-right: 20px; - padding-top: 10px; - font-weight:bold; - padding-bottom: 10px; - background: #305275; - color: #fffee7; - -moz-border-radius: 10px; - } - - div.target_name { - border: 1px solid; - border-color: #305275; - background-color: #305275; - color: #fff; - width: auto%; - -moz-border-radius-topleft: 8px; - -moz-border-radius-topright: 8px; - padding-left: 5px; - padding-right: 5px; - } - - table { - border-style: solid; - border: 1px #c3d9ff; - border-spacing: 0px 0px; - width: auto; - padding: 0px; - - } - - tb { border: 1px; } - - tr { - vertical-align: top; - border: 1px #c3d9ff; - border-style: dotted; - width: auto; - padding: 0px; - } - - th { - border-width: 1px; - border-style: solid; - border-color: #c3d9ff; - border-collapse: collapse; - padding-left: 5px; - padding-right: 5px; - } - - - td { - border-width: 1px; - border-color: #c3d9ff; - border-collapse: collapse; - padding-left: 5px; - padding-right: 5px; - width: auto%; - } - - img { border-style: none; } - - h1 { - padding: 0.5em; - background-color: #305275; - margin-top: 0; - margin-bottom: 0; - color: #fff; - margin-left: -20px; - margin-right: -20px; - } - - ul { - list-style-type: none; - padding: 0; - } - - p { width: auto; } - - a, a:visited { - background: inherit; - text-decoration: none; - } - - a { color: #003d41; } - a:visited { color: #553d41; } - .footer a { color: #508d91; } - - /* bug index pages */ - - td.tab { - padding-right: 1em; - padding-left: 1em; - } - - td.sel.tab { - background-color: #c3d9ff ; - border: 1px solid #c3d9ff; - font-weight:bold; - -moz-border-radius-topleft: 15px; - -moz-border-radius-topright: 15px; - } - - td.nsel.tab { - border: 1px solid #c3d9ff; - font-weight:bold; - -moz-border-radius-topleft: 5px; - -moz-border-radius-topright: 5px; - } - - table.bug_list { - border-width: 1px; - border-style: solid; - border-color: #c3d9ff; - padding: 0px; - width: 100%; - border: 1px solid #c3d9ff; - } - - table.target_list { - border-width: 1px; - border-style: solid; - border-collapse: collapse; - border-color: #c3d9ff; - padding: 0px; - width: 100%; - margin-bottom: 10px; - } - - table.target_list.td { - border-width: 1px; - } - - tr.wishlist { background-color: #DCFAFF;} - tr.wishlist:hover { background-color: #C2DCE1; } - - tr.minor { background-color: #FFFFA6; } - tr.minor:hover { background-color: #E6E696; } - - tr.serious { background-color: #FF9077;} - tr.serious:hover { background-color: #E6826B; } - - tr.critical { background-color: #FF752A; } - tr.critical:hover { background-color: #D63905;} - - tr.fatal { background-color: #FF3300;} - tr.fatal:hover { background-color: #D60000;} - - td.uuid { width: 5%; border-style: dotted;} - td.status { width: 5%; border-style: dotted;} - td.severity { width: 5%; border-style: dotted;} - td.summary { border-style: dotted;} - td.date { width: 25%; border-style: dotted;} - - /* bug detail pages */ - - td.bug_detail_label { text-align: right; border: none;} - td.bug_detail { border: none;} - td.bug_comment_label { text-align: right; vertical-align: top; } - td.bug_comment { } - - div.comment { - padding: 20px; - padding-top: 20px; - margin: auto; - margin-top: 0; + for filename,text in self.template_dict.iteritems(): + if self.verbose: + print >> self.stdout, 'Creating %s file' + self._write_file(text, [self.out_dir, filename]) + + def _load_templates(self, template_dir=None): + if template_dir is not None: + template_dir = os.path.abspath(os.path.expanduser(template_dir)) + + self.template_dict = { +## + 'style.css': +"""body { + font-family: "lucida grande", "sans serif"; + font-size: 14px; + color: #333; + width: auto; + margin: auto; +} + +div.main { + padding: 20px; + margin: auto; + padding-top: 0; + margin-top: 1em; + background-color: #fcfcfc; + -moz-border-radius: 10px; + +} + +div.footer { + font-size: small; + padding-left: 20px; + padding-right: 20px; + padding-top: 5px; + padding-bottom: 5px; + margin: auto; + background: #305275; + color: #fffee7; + -moz-border-radius: 10px; +} + +div.header { + font-size: xx-large; + padding-left: 20px; + padding-right: 20px; + padding-top: 10px; + font-weight:bold; + padding-bottom: 10px; + background: #305275; + color: #fffee7; + -moz-border-radius: 10px; +} + +th.target_name { + text-align:left; + border: 1px solid; + border-color: #305275; + background-color: #305275; + color: #fff; + width: auto%; + -moz-border-radius-topleft: 8px; + -moz-border-radius-topright: 8px; + padding-left: 5px; + padding-right: 5px; +} + +table { + border-style: solid; + border: 1px #c3d9ff; + border-spacing: 0px 0px; + width: auto; + padding: 0px; + + } + +tb { border: 1px; } + +tr { + vertical-align: top; + border: 1px #c3d9ff; + border-style: dotted; + width: auto; + padding: 0px; +} + +th { + border-width: 1px; + border-style: solid; + border-color: #c3d9ff; + border-collapse: collapse; + padding-left: 5px; + padding-right: 5px; +} + + +td { + border-width: 1px; + border-color: #c3d9ff; + border-collapse: collapse; + padding-left: 5px; + padding-right: 5px; + width: auto%; +} + +img { border-style: none; } + +ul { + list-style-type: none; + padding: 0; +} + +p { width: auto; } + +p.backlink { + width: auto; + font-weight: bold; +} + +a { + background: inherit; + text-decoration: none; +} + +a { color: #553d41; } +a:hover { color: #003d41; } +a:visited { color: #305275; } +.footer a { color: #508d91; } + +/* bug index pages */ + +td.tab { + padding-right: 1em; + padding-left: 1em; +} + +td.sel.tab { + background-color: #c3d9ff ; + border: 1px solid #c3d9ff; + font-weight:bold; + -moz-border-radius-topleft: 15px; + -moz-border-radius-topright: 15px; +} + +td.nsel.tab { + border: 1px solid #c3d9ff; + font-weight:bold; + -moz-border-radius-topleft: 5px; + -moz-border-radius-topright: 5px; +} + +table.bug_list { + border-width: 1px; + border-style: solid; + border-color: #c3d9ff; + padding: 0px; + width: 100%; + border: 1px solid #c3d9ff; +} + +table.target_list { + padding: 0px; + width: 100%; + margin-bottom: 10px; +} + +table.target_list.td { + border-width: 1px; +} + +tr.wishlist { background-color: #DCFAFF;} +tr.wishlist:hover { background-color: #C2DCE1; } + +tr.minor { background-color: #FFFFA6; } +tr.minor:hover { background-color: #E6E696; } + +tr.serious { background-color: #FF9077;} +tr.serious:hover { background-color: #E6826B; } + +tr.critical { background-color: #FF752A; } +tr.critical:hover { background-color: #D63905;} + +tr.fatal { background-color: #FF3300;} +tr.fatal:hover { background-color: #D60000;} + +td.uuid { width: 5%; border-style: dotted;} +td.status { width: 5%; border-style: dotted;} +td.severity { width: 5%; border-style: dotted;} +td.summary { border-style: dotted;} +td.date { width: 25%; border-style: dotted;} + +/* bug detail pages */ + +td.bug_detail_label { text-align: right; border: none;} +td.bug_detail { border: none;} +td.bug_comment_label { text-align: right; vertical-align: top; } +td.bug_comment { } + +div.comment { + padding: 20px; + padding-top: 20px; + margin: auto; + margin-top: 0; +} + +div.root.comment { + padding: 0px; + /* padding-top: 0px; */ + padding-bottom: 20px; +} +""", +## + 'base.html': +""" + + + {{ title }} + + + + +
{{ header }}
+
+ {% block content %}{% endblock %} +
+ + + +""", + 'index.html': +"""{% extends "base.html" %} + +{% block content %} + + + + + + + + +
Active BugsInactive BugsDivided by target
+{% if bugs %} +{% block bug_table %}{% endblock %} +{% else %} +

No bugs.

+{% endif %} +{% endblock %} +""", +## + 'standard_index.html': +"""{% extends "index.html" %} + +{% block bug_table %} + + + + + + + + + + + + {% for bug in bugs %} + {{ bug_entry.render({'bug':bug, 'dir':bug_dir(bug)}) }} + {% endfor %} + +
UUIDStatusSeveritySummaryDate
+{% endblock %} +""", +## + 'target_index.html': +"""{% extends "index.html" %} + +{% block bug_table %} +{% for target,bugs in targets %} + + + + + + + + + + + + + + + {% for bug in bugs %} + {{ bug_entry.render({'bug':bug, 'dir':bug_dir(bug)}) }} + {% endfor %} + +
+ Target: {{ target.summary|e }} ({{ target.status|e }}) +
UUIDStatusSeveritySummaryDate
+{% endfor %} +{% endblock %} +""", +## + 'index_bug_entry.html': +""" + {{ bug.id.user()|e }} + {{ bug.status|e }} + {{ bug.severity|e }} + {{ bug.summary|e }} + {{ bug.time_string or ''|e }} + +""", +## + 'bug.html': +"""{% extends "base.html" %} + +{% block content %} +{{ backlinks.render({'up_link': up_link, 'index_type':index_type}) }} +

Bug: {{ bug.id.user()|e }}

+ + + + + + + + + + + + + + + + + + + + + + +
ID :{{ bug.uuid|e }}
Short name :{{ bug.id.user()|e }}
Status :{{ bug.status|e }}
Severity :{{ bug.severity|e }}
Assigned :{{ bug.assigned or ''|e }}
Reporter :{{ bug.reporter or ''|e }}
Creator :{{ bug.creator or ''|e }}
Created :{{ bug.time_string or ''|e }}
Summary :{{ bug.summary|e }}
+ +
+ +{% if comments %} +{% for depth,comment in comments %} +{% if depth == 0 %} +
+{% else %} +
+{% endif %} +{{ comment_entry.render({ + 'depth':depth, 'bug': bug, 'comment':comment, 'comment_dir':comment_dir, + 'format_body': format_body, 'div_close': div_close}) }} +{{ div_close(depth) }} +{% endfor %} +{% if comments[-1][0] > 0 %} +{{ div_close(0) }} +{% endif %} +{% else %} +

No comments.

+{% endif %} +{{ backlinks.render({'up_link': up_link, 'index_type': index_type}) }} +{% endblock %} +""", +## + 'bug_backlinks.html': +""" + +""", +## + 'bug_comment_entry.html': +""" + + + + + + +
Comment: + --------- Comment ---------
+ ID: {{ comment.uuid }}
+ Short name: {{ comment.id.user() }}
+ From: {{ comment.author or ''|e }}
+ Date: {{ comment.date or ''|e }}
+
+ {{ format_body(bug, comment) }} +
+""", } - div.root.comment { - padding: 0px; - /* padding-top: 0px; */ - padding-bottom: 20px; - } - """ - - self.index_file = """ - - - - %(title)s - - - - - -
%(index_header)s
-
-

- - - - - - - -
Active BugsInactive BugsDivided by target
+ loader = DictLoader(self.template_dict) + if template_dir: + file_system_loader = FileSystemLoader(template_dir) + loader = ChoiceLoader([file_system_loader, loader]) - %(bug_entries)s + self.template = Environment(loader=loader) -
- - - - - - """ - - self.index_bug_entry =""" - - %(shortname)s - %(status)s - %(severity)s - %(summary)s - %(time_string)s - - """ - self.target_bug_list = """ - - -
- Target: %(target)s (%(status)s) -
-
- - - %(bug_entries)s -
-
- - - """ - self.bug_list = """ - - - %(bug_entries)s - -
- - """ - self.bug_file = """ - - - - %(title)s - - - - - -
BugsEverywhere Bug List
-
-
Back to %(index_type)s Index
-
Back to Target 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_type)s Index
-
Back to Target Index
- - - - - """ - self.bug_comment_entry =""" - - - - - -
Comment: - --------- Comment ---------
- ID: %(uuid)s
- Short name: %(shortname)s
- From: %(author)s
- Date: %(date)s
-
- %(body)s -
- """ +class _DivCloser (object): + def __init__(self, depth=0): + self.depth = depth - # 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') + def __call__(self, depth): + ret = [] + while self.depth >= depth: + self.depth -= 1 + ret.append('
') + self.depth = depth + return '\n'.join(ret) -- 2.26.2