41
41
self.config = config
43
43
def compose(self, prompt, to, subject, attachment, mime_subtype,
44
extension, basename=None):
44
extension, basename=None, body=None):
45
45
"""Compose (and possibly send) an email message
47
47
Must be implemented by subclasses.
62
62
raise NotImplementedError
64
def compose_merge_request(self, to, subject, directive, basename=None):
64
def compose_merge_request(self, to, subject, directive, basename=None,
65
66
"""Compose (and possibly send) a merge request
67
68
:param to: The address to send the request to
74
75
prompt = self._get_merge_prompt("Please describe these changes:", to,
75
76
subject, directive)
76
77
self.compose(prompt, to, subject, directive,
77
'x-patch', '.patch', basename)
78
'x-patch', '.patch', basename, body)
79
80
def _get_merge_prompt(self, prompt, to, subject, attachment):
80
81
"""Generate a prompt string. Overridden by Editor.
90
91
class Editor(MailClient):
91
92
"""DIY mail client that uses commit message editor"""
93
96
def _get_merge_prompt(self, prompt, to, subject, attachment):
94
97
"""See MailClient._get_merge_prompt"""
99
102
attachment.decode('utf-8', 'replace')))
101
104
def compose(self, prompt, to, subject, attachment, mime_subtype,
102
extension, basename=None):
105
extension, basename=None, body=None):
103
106
"""See MailClient.compose"""
105
108
raise errors.NoMailAddressSpecified()
106
body = msgeditor.edit_commit_message(prompt)
109
body = msgeditor.edit_commit_message(prompt, start_message=body)
108
111
raise errors.NoMessageSupplied()
109
112
email_message.EmailMessage.send(self.config,
129
133
return self._client_commands
131
135
def compose(self, prompt, to, subject, attachment, mime_subtype,
132
extension, basename=None):
136
extension, basename=None, body=None):
133
137
"""See MailClient.compose.
135
139
Writes the attachment to a temporary file, invokes _compose.
143
147
outfile.write(attachment)
151
kwargs = {'body': body}
146
154
self._compose(prompt, to, subject, attach_path, mime_subtype,
149
157
def _compose(self, prompt, to, subject, attach_path, mime_subtype,
158
extension, body=None):
151
159
"""Invoke a mail client as a commandline process.
153
161
Overridden by MAPIClient.
174
187
raise errors.MailClientNotFound(self._client_commands)
176
def _get_compose_commandline(self, to, subject, attach_path):
189
def _get_compose_commandline(self, to, subject, attach_path, body):
177
190
"""Determine the commandline to use for composing a message
179
192
Implemented by various subclasses
215
class Evolution(ExternalMailClient):
228
class ExternalMailClient(BodyExternalMailClient):
229
"""An external mail client."""
231
supports_body = False
234
class Evolution(BodyExternalMailClient):
216
235
"""Evolution mail client."""
218
237
_client_commands = ['evolution']
220
def _get_compose_commandline(self, to, subject, attach_path):
239
def _get_compose_commandline(self, to, subject, attach_path, body=None):
221
240
"""See ExternalMailClient._get_compose_commandline"""
222
241
message_options = {}
223
242
if subject is not None:
224
243
message_options['subject'] = subject
225
244
if attach_path is not None:
226
245
message_options['attach'] = attach_path
247
message_options['body'] = body
227
248
options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
228
249
sorted(message_options.iteritems())]
229
250
return ['mailto:%s?%s' % (self._encode_safe(to or ''),
266
287
'/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
267
288
'/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
269
def _get_compose_commandline(self, to, subject, attach_path):
290
def _get_compose_commandline(self, to, subject, attach_path, body=None):
270
291
"""See ExternalMailClient._get_compose_commandline"""
271
292
message_options = {}
272
293
if to is not None:
276
297
if attach_path is not None:
277
298
message_options['attachment'] = urlutils.local_path_to_url(
279
options_list = ["%s='%s'" % (k, v) for k, v in
280
sorted(message_options.iteritems())]
301
options_list = ['body=%s' % urllib.quote(self._encode_safe(body))]
304
options_list.extend(["%s='%s'" % (k, v) for k, v in
305
sorted(message_options.iteritems())])
281
306
return ['-compose', ','.join(options_list)]
282
307
mail_client_registry.register('thunderbird', Thunderbird,
283
308
help=Thunderbird.__doc__)
329
354
help=Claws.__doc__)
332
class XDGEmail(ExternalMailClient):
357
class XDGEmail(BodyExternalMailClient):
333
358
"""xdg-email attempts to invoke the user's preferred mail client"""
335
360
_client_commands = ['xdg-email']
337
def _get_compose_commandline(self, to, subject, attach_path):
362
def _get_compose_commandline(self, to, subject, attach_path, body=None):
338
363
"""See ExternalMailClient._get_compose_commandline"""
340
365
raise errors.NoMailAddressSpecified()
344
369
if attach_path is not None:
345
370
commandline.extend(['--attach',
346
371
self._encode_path(attach_path, 'attachment')])
373
commandline.extend(['--body', self._encode_safe(body)])
347
374
return commandline
348
375
mail_client_registry.register('xdg-email', XDGEmail,
349
376
help=XDGEmail.__doc__)
454
481
help=EmacsMail.__doc__)
457
class MAPIClient(ExternalMailClient):
484
class MAPIClient(BodyExternalMailClient):
458
485
"""Default Windows mail client launched using MAPI."""
460
487
def _compose(self, prompt, to, subject, attach_path, mime_subtype,
462
489
"""See ExternalMailClient._compose.
464
491
This implementation uses MAPI via the simplemapi ctypes wrapper
466
493
from bzrlib.util import simplemapi
468
simplemapi.SendMail(to or '', subject or '', '', attach_path)
495
simplemapi.SendMail(to or '', subject or '', body or '',
469
497
except simplemapi.MAPIError, e:
470
498
if e.code != simplemapi.MAPI_USER_ABORT:
471
499
raise errors.MailClientNotFound(['MAPI supported mail client'
486
514
return XDGEmail(self.config)
488
516
def compose(self, prompt, to, subject, attachment, mime_subtype,
489
extension, basename=None):
517
extension, basename=None, body=None):
490
518
"""See MailClient.compose"""
492
520
return self._mail_client().compose(prompt, to, subject,
493
521
attachment, mimie_subtype,
522
extension, basename, body)
495
523
except errors.MailClientNotFound:
496
524
return Editor(self.config).compose(prompt, to, subject,
497
attachment, mimie_subtype, extension)
525
attachment, mimie_subtype, extension, body)
499
def compose_merge_request(self, to, subject, directive, basename=None):
527
def compose_merge_request(self, to, subject, directive, basename=None,
500
529
"""See MailClient.compose_merge_request"""
502
531
return self._mail_client().compose_merge_request(to, subject,
503
directive, basename=basename)
532
directive, basename=basename, body=body)
504
533
except errors.MailClientNotFound:
505
534
return Editor(self.config).compose_merge_request(to, subject,
506
directive, basename=basename)
535
directive, basename=basename, body=body)
507
536
mail_client_registry.register('default', DefaultMail,
508
537
help=DefaultMail.__doc__)
509
538
mail_client_registry.default_key = 'default'