34
36
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."
59
39
class MailClient(object):
60
40
"""A mail client that can send messages with attachements."""
282
268
"""See ExternalMailClient._get_compose_commandline"""
283
269
message_options = []
284
270
if subject is not None:
285
message_options.extend(
286
['-s', self._encode_safe(subject)])
271
message_options.extend(['-s', self._encode_safe(subject)])
287
272
if attach_path is not None:
288
message_options.extend(
289
['-a', self._encode_path(attach_path, 'attachment')])
273
message_options.extend(['-a',
274
self._encode_path(attach_path, 'attachment')])
290
275
if body is not None:
291
276
# Store the temp file object in self, so that it does not get
292
277
# garbage collected and delete the file before mutt can read it.
293
278
self._temp_file = tempfile.NamedTemporaryFile(
294
prefix="mutt-body-", suffix=".txt", mode="w+")
279
prefix="mutt-body-", suffix=".txt")
295
280
self._temp_file.write(body)
296
281
self._temp_file.flush()
297
282
message_options.extend(['-i', self._temp_file.name])
298
283
if to is not None:
299
284
message_options.extend(['--', self._encode_safe(to)])
300
285
return message_options
303
286
mail_client_registry.register('mutt', Mutt,
304
287
help=Mutt.__doc__)
414
391
class XDGEmail(BodyExternalMailClient):
415
__doc__ = """xdg-email attempts to invoke the preferred mail client"""
392
__doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
417
394
_client_commands = ['xdg-email']
419
396
def _get_compose_commandline(self, to, subject, attach_path, body=None):
420
397
"""See ExternalMailClient._get_compose_commandline"""
422
raise NoMailAddressSpecified()
399
raise errors.NoMailAddressSpecified()
423
400
commandline = [self._encode_safe(to)]
424
401
if subject is not None:
425
402
commandline.extend(['--subject', self._encode_safe(subject)])
426
403
if attach_path is not None:
427
404
commandline.extend(['--attach',
428
self._encode_path(attach_path, 'attachment')])
405
self._encode_path(attach_path, 'attachment')])
429
406
if body is not None:
430
407
commandline.extend(['--body', self._encode_safe(body)])
431
408
return commandline
434
409
mail_client_registry.register('xdg-email', XDGEmail,
435
410
help=XDGEmail.__doc__)
583
554
_client_commands = ['osascript']
585
556
def _get_compose_commandline(self, to, subject, attach_path, body=None,
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]
558
"""See ExternalMailClient._get_compose_commandline"""
560
fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
563
os.write(fd, 'tell application "Mail"\n')
564
os.write(fd, 'set newMessage to make new outgoing message\n')
565
os.write(fd, 'tell newMessage\n')
567
os.write(fd, 'make new to recipient with properties'
568
' {address:"%s"}\n' % to)
569
if from_ is not None:
570
# though from_ doesn't actually seem to be used
571
os.write(fd, 'set sender to "%s"\n'
572
% sender.replace('"', '\\"'))
573
if subject is not None:
574
os.write(fd, 'set subject to "%s"\n'
575
% subject.replace('"', '\\"'))
577
# FIXME: would be nice to prepend the body to the
578
# existing content (e.g., preserve signature), but
579
# can't seem to figure out the right applescript
581
os.write(fd, 'set content to "%s\\n\n"\n' %
582
body.replace('"', '\\"').replace('\n', '\\n'))
584
if attach_path is not None:
585
# FIXME: would be nice to first append a newline to
586
# ensure the attachment is on a new paragraph, but
587
# can't seem to figure out the right applescript
589
os.write(fd, 'tell content to make new attachment'
590
' with properties {file name:"%s"}'
591
' at after the last paragraph\n'
592
% self._encode_path(attach_path, 'attachment'))
593
os.write(fd, 'set visible to true\n')
594
os.write(fd, 'end tell\n')
595
os.write(fd, 'end tell\n')
597
os.close(fd) # Just close the handle but do not remove the file.
598
return [self.temp_file]
630
599
mail_client_registry.register('mail.app', MailApp,
631
600
help=MailApp.__doc__)
651
620
return self._mail_client().compose(prompt, to, subject,
652
621
attachment, mime_subtype,
653
622
extension, basename, body)
654
except MailClientNotFound:
655
return Editor(self.config).compose(
656
prompt, to, subject, attachment, mime_subtype, extension, body)
623
except errors.MailClientNotFound:
624
return Editor(self.config).compose(prompt, to, subject,
625
attachment, mime_subtype, extension, body)
658
627
def compose_merge_request(self, to, subject, directive, basename=None,
660
629
"""See MailClient.compose_merge_request"""
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,
631
return self._mail_client().compose_merge_request(to, subject,
632
directive, basename=basename, body=body)
633
except errors.MailClientNotFound:
634
return Editor(self.config).compose_merge_request(to, subject,
635
directive, basename=basename, body=body)
636
mail_client_registry.register('default', DefaultMail,
670
637
help=DefaultMail.__doc__)
671
mail_client_registry.default_key = u'default'
638
mail_client_registry.default_key = 'default'
673
opt_mail_client = _mod_config.RegistryOption(
674
'mail_client', mail_client_registry, help='E-mail client to use.',
640
opt_mail_client = _mod_config.RegistryOption('mail_client',
641
mail_client_registry, help='E-mail client to use.', invalid='error')