253
259
help=Evolution.__doc__)
256
class Mutt(ExternalMailClient):
257
"""Mutt mail client."""
262
class Mutt(BodyExternalMailClient):
263
__doc__ = """Mutt mail client."""
259
265
_client_commands = ['mutt']
261
def _get_compose_commandline(self, to, subject, attach_path):
267
def _get_compose_commandline(self, to, subject, attach_path, body=None):
262
268
"""See ExternalMailClient._get_compose_commandline"""
263
269
message_options = []
264
270
if subject is not None:
266
272
if attach_path is not None:
267
273
message_options.extend(['-a',
268
274
self._encode_path(attach_path, 'attachment')])
276
# Store the temp file object in self, so that it does not get
277
# garbage collected and delete the file before mutt can read it.
278
self._temp_file = tempfile.NamedTemporaryFile(
279
prefix="mutt-body-", suffix=".txt")
280
self._temp_file.write(body)
281
self._temp_file.flush()
282
message_options.extend(['-i', self._temp_file.name])
269
283
if to is not None:
270
284
message_options.extend(['--', self._encode_safe(to)])
271
285
return message_options
331
345
class Claws(ExternalMailClient):
332
"""Claws mail client."""
346
__doc__ = """Claws mail client."""
334
350
_client_commands = ['claws-mail']
336
def _get_compose_commandline(self, to, subject, attach_path):
352
def _get_compose_commandline(self, to, subject, attach_path, body=None,
337
354
"""See ExternalMailClient._get_compose_commandline"""
338
compose_url = ['mailto:']
340
compose_url.append(self._encode_safe(to))
341
compose_url.append('?')
356
if from_ is not None:
357
compose_url.append('from=' + urlutils.quote(from_))
342
358
if subject is not None:
343
# Don't use urllib.quote_plus because Claws doesn't seem
359
# Don't use urlutils.quote_plus because Claws doesn't seem
344
360
# to recognise spaces encoded as "+".
345
361
compose_url.append(
346
'subject=%s' % urllib.quote(self._encode_safe(subject)))
362
'subject=' + urlutils.quote(self._encode_safe(subject)))
365
'body=' + urlutils.quote(self._encode_safe(body)))
366
# to must be supplied for the claws-mail --compose syntax to work.
368
raise errors.NoMailAddressSpecified()
369
compose_url = 'mailto:%s?%s' % (
370
self._encode_safe(to), '&'.join(compose_url))
347
371
# Collect command-line options.
348
message_options = ['--compose', ''.join(compose_url)]
372
message_options = ['--compose', compose_url]
349
373
if attach_path is not None:
350
374
message_options.extend(
351
375
['--attach', self._encode_path(attach_path, 'attachment')])
352
376
return message_options
378
def _compose(self, prompt, to, subject, attach_path, mime_subtype,
379
extension, body=None, from_=None):
380
"""See ExternalMailClient._compose"""
382
from_ = self.config.get('email')
383
super(Claws, self)._compose(prompt, to, subject, attach_path,
384
mime_subtype, extension, body, from_)
353
387
mail_client_registry.register('claws', Claws,
354
388
help=Claws.__doc__)
357
391
class XDGEmail(BodyExternalMailClient):
358
"""xdg-email attempts to invoke the user's preferred mail client"""
392
__doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
360
394
_client_commands = ['xdg-email']
484
523
class MAPIClient(BodyExternalMailClient):
485
"""Default Windows mail client launched using MAPI."""
524
__doc__ = """Default Windows mail client launched using MAPI."""
487
526
def _compose(self, prompt, to, subject, attach_path, mime_subtype,
527
extension, body=None):
489
528
"""See ExternalMailClient._compose.
491
530
This implementation uses MAPI via the simplemapi ctypes wrapper
502
541
help=MAPIClient.__doc__)
544
class MailApp(BodyExternalMailClient):
545
__doc__ = """Use MacOS X's Mail.app for sending email messages.
547
Although it would be nice to use appscript, it's not installed
548
with the shipped Python installations. We instead build an
549
AppleScript and invoke the script using osascript(1). We don't
550
use the _encode_safe() routines as it's not clear what encoding
551
osascript expects the script to be in.
554
_client_commands = ['osascript']
556
def _get_compose_commandline(self, to, subject, attach_path, body=None,
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]
599
mail_client_registry.register('mail.app', MailApp,
600
help=MailApp.__doc__)
505
603
class DefaultMail(MailClient):
506
"""Default mail handling. Tries XDGEmail (or MAPIClient on Windows),
604
__doc__ = """Default mail handling. Tries XDGEmail (or MAPIClient on Windows),
507
605
falls back to Editor"""
509
609
def _mail_client(self):
510
610
"""Determine the preferred mail client for this platform"""
511
611
if osutils.supports_mapi():
518
618
"""See MailClient.compose"""
520
620
return self._mail_client().compose(prompt, to, subject,
521
attachment, mimie_subtype,
621
attachment, mime_subtype,
522
622
extension, basename, body)
523
623
except errors.MailClientNotFound:
524
624
return Editor(self.config).compose(prompt, to, subject,
525
attachment, mimie_subtype, extension, body)
625
attachment, mime_subtype, extension, body)
527
627
def compose_merge_request(self, to, subject, directive, basename=None,