25
config as _mod_config,
34
34
mail_client_registry = registry.Registry()
37
class MailClientNotFound(errors.BzrError):
39
_fmt = "Unable to find mail client with the following names:"\
40
" %(mail_command_list_string)s"
42
def __init__(self, mail_command_list):
43
mail_command_list_string = ', '.join(mail_command_list)
44
errors.BzrError.__init__(
45
self, mail_command_list=mail_command_list,
46
mail_command_list_string=mail_command_list_string)
49
class NoMessageSupplied(errors.BzrError):
51
_fmt = "No message supplied."
54
class NoMailAddressSpecified(errors.BzrError):
56
_fmt = "No mail-to address (--mail-to) or output (-o) specified."
37
59
class MailClient(object):
38
60
"""A mail client that can send messages with attachements."""
105
127
extension, basename=None, body=None):
106
128
"""See MailClient.compose"""
108
raise errors.NoMailAddressSpecified()
130
raise NoMailAddressSpecified()
109
131
body = msgeditor.edit_commit_message(prompt, start_message=body)
111
raise errors.NoMessageSupplied()
133
raise NoMessageSupplied()
112
134
email_message.EmailMessage.send(self.config,
113
self.config.username(),
135
self.config.get('email'),
118
140
attachment_mime_subtype=mime_subtype)
119
143
mail_client_registry.register('editor', Editor,
120
144
help=Editor.__doc__)
266
282
"""See ExternalMailClient._get_compose_commandline"""
267
283
message_options = []
268
284
if subject is not None:
269
message_options.extend(['-s', self._encode_safe(subject)])
285
message_options.extend(
286
['-s', self._encode_safe(subject)])
270
287
if attach_path is not None:
271
message_options.extend(['-a',
272
self._encode_path(attach_path, 'attachment')])
288
message_options.extend(
289
['-a', self._encode_path(attach_path, 'attachment')])
273
290
if body is not None:
274
291
# Store the temp file object in self, so that it does not get
275
292
# garbage collected and delete the file before mutt can read it.
276
293
self._temp_file = tempfile.NamedTemporaryFile(
277
prefix="mutt-body-", suffix=".txt")
294
prefix="mutt-body-", suffix=".txt", mode="w+")
278
295
self._temp_file.write(body)
279
296
self._temp_file.flush()
280
297
message_options.extend(['-i', self._temp_file.name])
281
298
if to is not None:
282
299
message_options.extend(['--', self._encode_safe(to)])
283
300
return message_options
284
303
mail_client_registry.register('mutt', Mutt,
285
304
help=Mutt.__doc__)
352
377
"""See ExternalMailClient._get_compose_commandline"""
354
379
if from_ is not None:
355
compose_url.append('from=' + urllib.quote(from_))
380
compose_url.append('from=' + urlutils.quote(from_))
356
381
if subject is not None:
357
# Don't use urllib.quote_plus because Claws doesn't seem
382
# Don't use urlutils.quote_plus because Claws doesn't seem
358
383
# to recognise spaces encoded as "+".
359
384
compose_url.append(
360
'subject=' + urllib.quote(self._encode_safe(subject)))
385
'subject=' + urlutils.quote(self._encode_safe(subject)))
361
386
if body is not None:
362
387
compose_url.append(
363
'body=' + urllib.quote(self._encode_safe(body)))
388
'body=' + urlutils.quote(self._encode_safe(body)))
364
389
# to must be supplied for the claws-mail --compose syntax to work.
366
raise errors.NoMailAddressSpecified()
391
raise NoMailAddressSpecified()
367
392
compose_url = 'mailto:%s?%s' % (
368
393
self._encode_safe(to), '&'.join(compose_url))
369
394
# Collect command-line options.
389
414
class XDGEmail(BodyExternalMailClient):
390
__doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
415
__doc__ = """xdg-email attempts to invoke the preferred mail client"""
392
417
_client_commands = ['xdg-email']
394
419
def _get_compose_commandline(self, to, subject, attach_path, body=None):
395
420
"""See ExternalMailClient._get_compose_commandline"""
397
raise errors.NoMailAddressSpecified()
422
raise NoMailAddressSpecified()
398
423
commandline = [self._encode_safe(to)]
399
424
if subject is not None:
400
425
commandline.extend(['--subject', self._encode_safe(subject)])
401
426
if attach_path is not None:
402
427
commandline.extend(['--attach',
403
self._encode_path(attach_path, 'attachment')])
428
self._encode_path(attach_path, 'attachment')])
404
429
if body is not None:
405
430
commandline.extend(['--body', self._encode_safe(body)])
406
431
return commandline
407
434
mail_client_registry.register('xdg-email', XDGEmail,
408
435
help=XDGEmail.__doc__)
528
557
This implementation uses MAPI via the simplemapi ctypes wrapper
530
from bzrlib.util import simplemapi
559
from .util import simplemapi
532
561
simplemapi.SendMail(to or '', subject or '', body or '',
534
except simplemapi.MAPIError, e:
563
except simplemapi.MAPIError as e:
535
564
if e.code != simplemapi.MAPI_USER_ABORT:
536
raise errors.MailClientNotFound(['MAPI supported mail client'
537
' (error %d)' % (e.code,)])
565
raise MailClientNotFound(['MAPI supported mail client'
566
' (error %d)' % (e.code,)])
538
569
mail_client_registry.register('mapi', MAPIClient,
539
570
help=MAPIClient.__doc__)
552
583
_client_commands = ['osascript']
554
585
def _get_compose_commandline(self, to, subject, attach_path, body=None,
556
"""See ExternalMailClient._get_compose_commandline"""
558
fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
561
os.write(fd, 'tell application "Mail"\n')
562
os.write(fd, 'set newMessage to make new outgoing message\n')
563
os.write(fd, 'tell newMessage\n')
565
os.write(fd, 'make new to recipient with properties'
566
' {address:"%s"}\n' % to)
567
if from_ is not None:
568
# though from_ doesn't actually seem to be used
569
os.write(fd, 'set sender to "%s"\n'
570
% sender.replace('"', '\\"'))
571
if subject is not None:
572
os.write(fd, 'set subject to "%s"\n'
573
% subject.replace('"', '\\"'))
575
# FIXME: would be nice to prepend the body to the
576
# existing content (e.g., preserve signature), but
577
# can't seem to figure out the right applescript
579
os.write(fd, 'set content to "%s\\n\n"\n' %
580
body.replace('"', '\\"').replace('\n', '\\n'))
582
if attach_path is not None:
583
# FIXME: would be nice to first append a newline to
584
# ensure the attachment is on a new paragraph, but
585
# can't seem to figure out the right applescript
587
os.write(fd, 'tell content to make new attachment'
588
' with properties {file name:"%s"}'
589
' at after the last paragraph\n'
590
% self._encode_path(attach_path, 'attachment'))
591
os.write(fd, 'set visible to true\n')
592
os.write(fd, 'end tell\n')
593
os.write(fd, 'end tell\n')
595
os.close(fd) # Just close the handle but do not remove the file.
596
return [self.temp_file]
587
"""See ExternalMailClient._get_compose_commandline"""
589
fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
592
os.write(fd, 'tell application "Mail"\n')
593
os.write(fd, 'set newMessage to make new outgoing message\n')
594
os.write(fd, 'tell newMessage\n')
596
os.write(fd, 'make new to recipient with properties'
597
' {address:"%s"}\n' % to)
598
if from_ is not None:
599
# though from_ doesn't actually seem to be used
600
os.write(fd, 'set sender to "%s"\n'
601
% from_.replace('"', '\\"'))
602
if subject is not None:
603
os.write(fd, 'set subject to "%s"\n'
604
% subject.replace('"', '\\"'))
606
# FIXME: would be nice to prepend the body to the
607
# existing content (e.g., preserve signature), but
608
# can't seem to figure out the right applescript
610
os.write(fd, 'set content to "%s\\n\n"\n' %
611
body.replace('"', '\\"').replace('\n', '\\n'))
613
if attach_path is not None:
614
# FIXME: would be nice to first append a newline to
615
# ensure the attachment is on a new paragraph, but
616
# can't seem to figure out the right applescript
618
os.write(fd, 'tell content to make new attachment'
619
' with properties {file name:"%s"}'
620
' at after the last paragraph\n'
621
% self._encode_path(attach_path, 'attachment'))
622
os.write(fd, 'set visible to true\n')
623
os.write(fd, 'end tell\n')
624
os.write(fd, 'end tell\n')
626
os.close(fd) # Just close the handle but do not remove the file.
627
return [self.temp_file]
597
630
mail_client_registry.register('mail.app', MailApp,
598
631
help=MailApp.__doc__)
616
649
"""See MailClient.compose"""
618
651
return self._mail_client().compose(prompt, to, subject,
619
attachment, mimie_subtype,
652
attachment, mime_subtype,
620
653
extension, basename, body)
621
except errors.MailClientNotFound:
622
return Editor(self.config).compose(prompt, to, subject,
623
attachment, mimie_subtype, extension, body)
654
except MailClientNotFound:
655
return Editor(self.config).compose(
656
prompt, to, subject, attachment, mime_subtype, extension, body)
625
658
def compose_merge_request(self, to, subject, directive, basename=None,
627
660
"""See MailClient.compose_merge_request"""
629
return self._mail_client().compose_merge_request(to, subject,
630
directive, basename=basename, body=body)
631
except errors.MailClientNotFound:
632
return Editor(self.config).compose_merge_request(to, subject,
633
directive, basename=basename, body=body)
634
mail_client_registry.register('default', DefaultMail,
662
return self._mail_client().compose_merge_request(
663
to, subject, directive, basename=basename, body=body)
664
except MailClientNotFound:
665
return Editor(self.config).compose_merge_request(
666
to, subject, directive, basename=basename, body=body)
669
mail_client_registry.register(u'default', DefaultMail,
635
670
help=DefaultMail.__doc__)
636
mail_client_registry.default_key = 'default'
671
mail_client_registry.default_key = u'default'
673
opt_mail_client = _mod_config.RegistryOption(
674
'mail_client', mail_client_registry, help='E-mail client to use.',