1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Email notifier base module
19 """
20 __author__ = "André Malo"
21 __docformat__ = "epytext en"
22 __all__ = ["MailNotifier"]
23
24
25 from svnmailer.notifier import _text
26
27
29 """ Base exception for this module """
30 pass
31
33 """ No recipients found """
34 pass
35
36
38 """ Bases class for mail (like) notifiers
39
40 @ivar _header_re: Pattern for header name checking
41 @type _header_re: sre pattern or C{None}
42 """
43 __implements__ = [_text.TextNotifier]
44
45 COMMIT_SUBJECT = u"%(prefix)s r%(revision)s %(part)s - %(files/dirs)s"
46 REVPROP_SUBJECT = u"%(prefix)s r%(revision)s - %(property)s"
47 LOCK_SUBJECT = u"%(prefix)s %(files/dirs)s"
48 UNLOCK_SUBJECT = u"%(prefix)s %(files/dirs)s"
49
50
51 - def __init__(self, config, groupset, *args, **kwargs):
55
56
75
76
78 """ Returns the composed mail(s)
79
80 @return: The mails
81 @rtype: generator
82 """
83 for mail in self.composeMail():
84 yield mail
85
86
103
104
106 """ Returns the transfer encoding to use
107
108 @return: The configured value
109 @rtype: C{unicode}
110 """
111 return self.config.mail_transfer_encoding
112
113
114 - def sendMail(self, sender, to_addr, mail):
115 """ Sends the mail
116
117 @param sender: The mail sender (envelope from)
118 @type sender: C{str}
119
120 @param to_addr: The receivers
121 @type to_addr: C{list}
122
123 @param mail: The mail object
124 @type mail: C{_TextMail}
125 """
126 raise NotImplementedError()
127
128
130 """ Composes the mail
131
132 @return: The senders, the receivers, the mail(s)
133 @rtype: C{tuple}
134 """
135 raise NotImplementedError()
136
137
139 """ Returns the basic headers
140
141 @return: The headers
142 @rtype: C{dict}
143 """
144 from email import Utils, Header
145 from svnmailer import version
146
147 return {
148 'X-Mailer': Header.Header(
149 ("svnmailer-%s" % version).decode('utf-8'), 'iso-8859-1'
150 ),
151 'Date': Utils.formatdate(),
152 }
153
154
156 """ Compose the informational headers of the mail
157
158 @param groups: The groups to process
159 @type groups: C{list}
160
161 @return: sender (C{unicode}), recipients (C{list}), headers
162 (C{dict})
163 @rtype: C{tuple}
164 """
165 from email import Header
166
167 sender, from_addrs, reply_to, to_addrs, to_fakes, bcc_addrs = \
168 self.getMailAddresses(groups)
169 recipients = to_addrs + bcc_addrs
170 if not recipients:
171 raise NoRecipientsError()
172
173 headers = self.getBasicHeaders()
174 headers['From'] = ', '.join(from_addrs)
175
176 if self._settings.general.debug_all_mails_to:
177 headers['X-Supposed-Recipients'] = \
178 Header.Header(', '.join(recipients))
179 recipients = self._settings.general.debug_all_mails_to
180 if to_addrs:
181 headers['To'] = Header.Header(', '.join(to_addrs))
182 elif to_fakes:
183 headers['To'] = Header.Header(', '.join(to_fakes))
184
185 if reply_to:
186 headers['Reply-To'] = Header.Header(', '.join(reply_to))
187 if len(from_addrs) > 1:
188
189 headers['Sender'] = Header.Header(from_addrs[0])
190
191 headers.update(self.getCustomHeaders(groups))
192
193
194
195 if self._settings.runtime.debug:
196 headers['X-Config-Groups'] = Header.Header(', '.join([
197 "[%s]" % group._name for group in groups
198 ]), 'iso-8859-1')
199
200 return (sender, recipients, headers)
201
202
204 """ Returns the custom headers
205
206 @param groups: The groups to process
207 @type groups: C{list}
208
209 @return: The custom headers
210 @rtype: C{dict}
211 """
212 import re
213 from email import Header
214
215 header_re = self._header_re
216 if not header_re:
217 header_re = self._header_re = re.compile("[^%s]" %
218 re.escape(u''.join([
219
220 chr(num) for num in range(33,127) if num != 58
221 ]))
222 )
223
224 custom = {}
225 for group in [group for group in groups if group.custom_header]:
226 tup = group.custom_header.split(None, 1)
227 custom.setdefault(
228 'X-%s' % header_re.sub("", tup[0]), []
229 ).extend(tup[1:])
230
231 return dict([(name,
232 Header.Header(', '.join(values), 'iso-8859-1')
233 ) for name, values in custom.items()])
234
235
237 """ Returns the substituted mail addresses (from/to/reply-to)
238
239 @param groups: The groups to process
240 @type groups: C{list}
241
242 @return: The address lists (sender, from, reply-to, to, to-fake,
243 bcc)
244 @rtype: C{tuple}
245 """
246 from_addrs = []
247 to_addrs = []
248 to_fakes = []
249 bcc_addrs = []
250 reply_to = []
251
252 sender = None
253 for group in groups:
254 from_addrs.extend(group.from_addr or [])
255 to_addrs.extend(group.to_addr or [])
256 bcc_addrs.extend(group.bcc_addr or [])
257 to_fakes.extend(group.to_fake and [group.to_fake] or [])
258 reply_to.extend(
259 group.reply_to_addr and [group.reply_to_addr] or []
260 )
261
262 from_addrs = dict.fromkeys(from_addrs).keys() or [
263 (self.getAuthor() and self.getAuthor().decode('utf-8', 'replace'))
264 or u'no_author'
265 ]
266 to_addrs = dict.fromkeys(to_addrs).keys()
267 bcc_addrs = dict.fromkeys(bcc_addrs).keys()
268 reply_to = dict.fromkeys(reply_to).keys()
269 if to_addrs:
270 to_fakes = []
271 else:
272 to_fakes = dict.fromkeys(to_fakes).keys()
273
274 return (
275 sender or from_addrs[0], from_addrs, reply_to,
276 to_addrs, to_fakes, bcc_addrs
277 )
278
279
281 """ Returns the subject
282
283 @param countprefix: Optional countprefix (inserted after the rev
284 number)
285 @type countprefix: C{unicode}
286
287 @return: The subject line
288 @rtype: C{unicode}
289 """
290 from svnmailer import util
291 from svnmailer.settings import modes
292
293 runtime = self._settings.runtime
294 groups, changeset = (self._groupset.groups, self._groupset.changes[:])
295 xset = self._groupset.xchanges
296 if xset:
297 changeset.extend(xset)
298
299 max_length = max(0, groups[0].max_subject_length)
300 short_length = max_length or 255
301
302 template, mode = {
303 modes.commit: (self.COMMIT_SUBJECT, 'commit', ),
304 modes.propchange: (self.REVPROP_SUBJECT, 'propchange'),
305 modes.lock: (self.LOCK_SUBJECT, 'lock', ),
306 modes.unlock: (self.UNLOCK_SUBJECT, 'unlock', ),
307 }[runtime.mode]
308
309 template = getattr(groups[0], "%s_subject_template" % mode) \
310 or template
311
312 params = {
313 'prefix' : getattr(groups[0], "%s_subject_prefix" % mode),
314 'part' : countprefix,
315 'files' : self._getPrefixedFiles(changeset),
316 'dirs' : self._getPrefixedDirectories(changeset),
317 }
318
319
320
321 def dosubject(param):
322 """ Returns the subject """
323
324 params['files/dirs'] = params[param]
325 cparams = params.copy()
326 cparams.update(groups[0]._sub_.dict())
327 return " ".join(util.substitute(template, cparams).split())
328
329 subject = dosubject('files')
330 if len(subject) > short_length:
331 subject = dosubject('dirs')
332
333
334 if max_length and len(subject) > max_length:
335 subject = "%s..." % subject[:max_length - 3]
336
337 return subject
338
339
341 """ Returns the longest common directory prefix
342
343 @param changeset: The change set
344 @type changeset: list
345
346 @return: The common dir and the path list, human readable
347 @rtype: C{unicode}
348 """
349 import posixpath
350 from svnmailer import util
351
352 dirs = dict.fromkeys([
353 "%s/" % (change.isDirectory() and
354 [change.path] or [posixpath.dirname(change.path)])[0]
355 for change in changeset
356 ]).keys()
357
358 common, dirs = util.commonPaths(dirs)
359 dirs.sort()
360
361 return self._getPathString(common, dirs)
362
363
365 """ Returns the longest common path prefix
366
367 @param changeset: The change set
368 @type changeset: list
369
370 @return: The common dir and the path list, human readable
371 @rtype: C{unicode}
372 """
373 from svnmailer import util
374
375 paths = dict.fromkeys([
376 change.isDirectory() and "%s/" % change.path or change.path
377 for change in changeset
378 ]).keys()
379
380 common, paths = util.commonPaths(paths)
381 paths.sort()
382
383 return self._getPathString(common, paths)
384
385
387 """ Returns the (possibly) prefixed paths as string
388
389 All parameters are expected to be UTF-8 encoded
390
391 @param prefix: The prefix (may be empty)
392 @type prefix: C{str}
393
394 @param paths: List of paths (C{[str, str, ...]})
395 @type paths: C{list}
396
397 @return: The prefixed paths as unicode
398 @rtype: C{unicode}
399 """
400 slash = [u"/", u""][bool(prefix)]
401 paths = u" ".join([
402 u"%s%s" % (slash, path.decode("utf-8"))
403 for path in paths
404 ])
405
406 return (prefix and
407 u"in /%s: %s" % (prefix.decode("utf-8"), paths) or paths
408 )
409