| 1 | from trac.core import Component, implements |
|---|
| 2 | from announcerplugin.api import IAnnouncementFormatter |
|---|
| 3 | from trac.config import Option, IntOption, ListOption |
|---|
| 4 | from genshi.template import NewTextTemplate, MarkupTemplate |
|---|
| 5 | from genshi import HTML |
|---|
| 6 | from trac.web.href import Href |
|---|
| 7 | from trac.web.chrome import Chrome |
|---|
| 8 | from genshi.template import TemplateLoader |
|---|
| 9 | from trac.util.text import wrap |
|---|
| 10 | from trac.versioncontrol.diff import diff_blocks |
|---|
| 11 | import difflib |
|---|
| 12 | |
|---|
| 13 | def diff_cleanup(gen): |
|---|
| 14 | for value in gen: |
|---|
| 15 | if value.startswith('---'): |
|---|
| 16 | continue |
|---|
| 17 | |
|---|
| 18 | if value.startswith('+++'): |
|---|
| 19 | continue |
|---|
| 20 | |
|---|
| 21 | if value.startswith('@@'): |
|---|
| 22 | yield '\n' |
|---|
| 23 | else: |
|---|
| 24 | yield value |
|---|
| 25 | |
|---|
| 26 | def lineup(gen): |
|---|
| 27 | for value in gen: |
|---|
| 28 | yield ' ' + value |
|---|
| 29 | |
|---|
| 30 | class TicketEmailFormatter(Component): |
|---|
| 31 | implements(IAnnouncementFormatter) |
|---|
| 32 | |
|---|
| 33 | ticket_email_subject = Option('announcer', 'ticket_email_subject', |
|---|
| 34 | "Ticket #${ticket.id}: ${ticket['summary']} {% if action %}[${action}]{% end %}") |
|---|
| 35 | |
|---|
| 36 | ticket_email_header_fields = ListOption('announcer', 'ticket_email_header_fields', 'owner, reporter, milestone, priority, severity') |
|---|
| 37 | |
|---|
| 38 | def get_format_transport(self): |
|---|
| 39 | return "email" |
|---|
| 40 | |
|---|
| 41 | def get_format_realms(self, transport): |
|---|
| 42 | if transport == "email": |
|---|
| 43 | yield "ticket" |
|---|
| 44 | return |
|---|
| 45 | |
|---|
| 46 | def get_format_styles(self, transport, realm): |
|---|
| 47 | if transport == "email": |
|---|
| 48 | if realm == "ticket": |
|---|
| 49 | yield "text/plain" |
|---|
| 50 | yield "text/html" |
|---|
| 51 | |
|---|
| 52 | return |
|---|
| 53 | |
|---|
| 54 | def get_format_alternative(self, transport, realm, style): |
|---|
| 55 | if transport == "email": |
|---|
| 56 | if realm == "ticket": |
|---|
| 57 | if style == "text/html": |
|---|
| 58 | return "text/plain" |
|---|
| 59 | |
|---|
| 60 | return None |
|---|
| 61 | |
|---|
| 62 | def format_headers(self, transport, realm, style, event): |
|---|
| 63 | ticket = event.target |
|---|
| 64 | return dict( |
|---|
| 65 | realm=realm, |
|---|
| 66 | ticket=ticket.id, |
|---|
| 67 | priority=ticket['priority'], |
|---|
| 68 | severity=ticket['severity'] |
|---|
| 69 | ) |
|---|
| 70 | |
|---|
| 71 | def format_subject(self, transport, realm, style, event): |
|---|
| 72 | action = None |
|---|
| 73 | if transport == "email": |
|---|
| 74 | if realm == "ticket": |
|---|
| 75 | if event.changes: |
|---|
| 76 | if 'status' in event.changes: |
|---|
| 77 | action = 'Status -> %s' % (event.target['status']) |
|---|
| 78 | |
|---|
| 79 | template = NewTextTemplate(self.ticket_email_subject) |
|---|
| 80 | return template.generate(ticket=event.target, event=event, action=action).render() |
|---|
| 81 | |
|---|
| 82 | def format(self, transport, realm, style, event): |
|---|
| 83 | if transport == "email": |
|---|
| 84 | if realm == "ticket": |
|---|
| 85 | if style == "text/plain": |
|---|
| 86 | return self._format_plaintext(event) |
|---|
| 87 | elif style == "text/html": |
|---|
| 88 | return self._format_html(event) |
|---|
| 89 | |
|---|
| 90 | def _format_plaintext(self, event): |
|---|
| 91 | ticket = event.target |
|---|
| 92 | short_changes = {} |
|---|
| 93 | long_changes = {} |
|---|
| 94 | |
|---|
| 95 | changed_items = [(field, unicode(old_value)) for field, old_value in |
|---|
| 96 | event.changes.items()] |
|---|
| 97 | for field, old_value in changed_items: |
|---|
| 98 | new_value = unicode(ticket[field]) |
|---|
| 99 | if ('\n' in new_value) or ('\n' in old_value): |
|---|
| 100 | # long_changes[field.capitalize()] = \ |
|---|
| 101 | # '\n'.join( |
|---|
| 102 | # diff_cleanup( |
|---|
| 103 | # difflib.context_diff( |
|---|
| 104 | # old_value.split('\r\n'), new_value.split('\r\n'), |
|---|
| 105 | # lineterm='', n=2 |
|---|
| 106 | # ) |
|---|
| 107 | # ) |
|---|
| 108 | # ) |
|---|
| 109 | long_changes[field.capitalize()] = '\n'.join( |
|---|
| 110 | lineup( |
|---|
| 111 | wrap(new_value, cols=67).split('\n') |
|---|
| 112 | ) |
|---|
| 113 | ) |
|---|
| 114 | else: |
|---|
| 115 | short_changes[field.capitalize()] = (old_value, new_value) |
|---|
| 116 | |
|---|
| 117 | data = dict( |
|---|
| 118 | ticket = ticket, |
|---|
| 119 | author = event.author, |
|---|
| 120 | comment = event.comment, |
|---|
| 121 | header = self.ticket_email_header_fields, |
|---|
| 122 | category = event.category, |
|---|
| 123 | ticket_link = self.env.abs_href('ticket', ticket.id), |
|---|
| 124 | project_name = self.env.project_name, |
|---|
| 125 | project_desc = self.env.project_description, |
|---|
| 126 | project_link = self.env.project_url or self.env.abs_href(), |
|---|
| 127 | has_changes = short_changes or long_changes, |
|---|
| 128 | long_changes = long_changes, |
|---|
| 129 | short_changes = short_changes, |
|---|
| 130 | attachment= event.attachment |
|---|
| 131 | ) |
|---|
| 132 | |
|---|
| 133 | chrome = Chrome(self.env) |
|---|
| 134 | dirs = [] |
|---|
| 135 | for provider in chrome.template_providers: |
|---|
| 136 | dirs += provider.get_templates_dirs() |
|---|
| 137 | |
|---|
| 138 | templates = TemplateLoader(dirs, variable_lookup='lenient') |
|---|
| 139 | |
|---|
| 140 | template = templates.load('ticket_email_plaintext.txt', cls=NewTextTemplate) |
|---|
| 141 | |
|---|
| 142 | if template: |
|---|
| 143 | stream = template.generate(**data) |
|---|
| 144 | output = stream.render('text') |
|---|
| 145 | |
|---|
| 146 | return output |
|---|
| 147 | |
|---|
| 148 | def _format_html(self, event): |
|---|
| 149 | ticket = event.target |
|---|
| 150 | short_changes = {} |
|---|
| 151 | long_changes = {} |
|---|
| 152 | chrome = Chrome(self.env) |
|---|
| 153 | |
|---|
| 154 | for field, old_value in event.changes.items(): |
|---|
| 155 | new_value = ticket[field] |
|---|
| 156 | if ('\n' in new_value) or ('\n' in old_value): |
|---|
| 157 | long_changes[field.capitalize()] = HTML( |
|---|
| 158 | "<pre>\n%s\n</pre>" % ( |
|---|
| 159 | '\n'.join( |
|---|
| 160 | diff_cleanup( |
|---|
| 161 | difflib.unified_diff( |
|---|
| 162 | wrap(old_value, cols=60).split('\n'), |
|---|
| 163 | wrap(new_value, cols=60).split('\n'), |
|---|
| 164 | lineterm='', n=3 |
|---|
| 165 | ) |
|---|
| 166 | ) |
|---|
| 167 | ) |
|---|
| 168 | ) |
|---|
| 169 | ) |
|---|
| 170 | |
|---|
| 171 | else: |
|---|
| 172 | short_changes[field.capitalize()] = (old_value, new_value) |
|---|
| 173 | |
|---|
| 174 | data = dict( |
|---|
| 175 | ticket = ticket, |
|---|
| 176 | author = event.author, |
|---|
| 177 | header = self.ticket_email_header_fields, |
|---|
| 178 | comment = event.comment, |
|---|
| 179 | category = event.category, |
|---|
| 180 | ticket_link = self.env.abs_href('ticket', ticket.id), |
|---|
| 181 | project_name = self.env.project_name, |
|---|
| 182 | project_desc = self.env.project_description, |
|---|
| 183 | project_link = self.env.project_url or self.env.abs_href(), |
|---|
| 184 | has_changes = short_changes or long_changes, |
|---|
| 185 | long_changes = long_changes, |
|---|
| 186 | short_changes = short_changes, |
|---|
| 187 | attachment= event.attachment |
|---|
| 188 | ) |
|---|
| 189 | |
|---|
| 190 | chrome = Chrome(self.env) |
|---|
| 191 | dirs = [] |
|---|
| 192 | for provider in chrome.template_providers: |
|---|
| 193 | dirs += provider.get_templates_dirs() |
|---|
| 194 | |
|---|
| 195 | templates = TemplateLoader(dirs, variable_lookup='lenient') |
|---|
| 196 | |
|---|
| 197 | template = templates.load('ticket_email_mimic.html', cls=MarkupTemplate) |
|---|
| 198 | |
|---|
| 199 | if template: |
|---|
| 200 | stream = template.generate(**data) |
|---|
| 201 | output = stream.render() |
|---|
| 202 | |
|---|
| 203 | return output |
|---|