~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mail_client.py

  • Committer: Martin Pool
  • Date: 2008-06-03 07:03:55 UTC
  • mto: This revision was merged to the branch mainline in revision 3491.
  • Revision ID: mbp@sourcefrog.net-20080603070355-65ca5vd490vj9rnk
Doc updates re PPAs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import errno
18
18
import os
19
19
import subprocess
20
20
import sys
21
21
import tempfile
22
 
import urllib
23
22
 
24
23
import bzrlib
25
24
from bzrlib import (
28
27
    msgeditor,
29
28
    osutils,
30
29
    urlutils,
31
 
    registry
32
30
    )
33
31
 
34
 
mail_client_registry = registry.Registry()
35
 
 
36
32
 
37
33
class MailClient(object):
38
34
    """A mail client that can send messages with attachements."""
41
37
        self.config = config
42
38
 
43
39
    def compose(self, prompt, to, subject, attachment, mime_subtype,
44
 
                extension, basename=None, body=None):
 
40
                extension, basename=None):
45
41
        """Compose (and possibly send) an email message
46
42
 
47
43
        Must be implemented by subclasses.
61
57
        """
62
58
        raise NotImplementedError
63
59
 
64
 
    def compose_merge_request(self, to, subject, directive, basename=None,
65
 
                              body=None):
 
60
    def compose_merge_request(self, to, subject, directive, basename=None):
66
61
        """Compose (and possibly send) a merge request
67
62
 
68
63
        :param to: The address to send the request to
75
70
        prompt = self._get_merge_prompt("Please describe these changes:", to,
76
71
                                        subject, directive)
77
72
        self.compose(prompt, to, subject, directive,
78
 
            'x-patch', '.patch', basename, body)
 
73
            'x-patch', '.patch', basename)
79
74
 
80
75
    def _get_merge_prompt(self, prompt, to, subject, attachment):
81
76
        """Generate a prompt string.  Overridden by Editor.
89
84
 
90
85
 
91
86
class Editor(MailClient):
92
 
    __doc__ = """DIY mail client that uses commit message editor"""
93
 
 
94
 
    supports_body = True
 
87
    """DIY mail client that uses commit message editor"""
95
88
 
96
89
    def _get_merge_prompt(self, prompt, to, subject, attachment):
97
90
        """See MailClient._get_merge_prompt"""
102
95
                         attachment.decode('utf-8', 'replace')))
103
96
 
104
97
    def compose(self, prompt, to, subject, attachment, mime_subtype,
105
 
                extension, basename=None, body=None):
 
98
                extension, basename=None):
106
99
        """See MailClient.compose"""
107
100
        if not to:
108
101
            raise errors.NoMailAddressSpecified()
109
 
        body = msgeditor.edit_commit_message(prompt, start_message=body)
 
102
        body = msgeditor.edit_commit_message(prompt)
110
103
        if body == '':
111
104
            raise errors.NoMessageSupplied()
112
105
        email_message.EmailMessage.send(self.config,
116
109
                                        body,
117
110
                                        attachment,
118
111
                                        attachment_mime_subtype=mime_subtype)
119
 
mail_client_registry.register('editor', Editor,
120
 
                              help=Editor.__doc__)
121
 
 
122
 
 
123
 
class BodyExternalMailClient(MailClient):
124
 
 
125
 
    supports_body = True
 
112
 
 
113
 
 
114
class ExternalMailClient(MailClient):
 
115
    """An external mail client."""
126
116
 
127
117
    def _get_client_commands(self):
128
118
        """Provide a list of commands that may invoke the mail client"""
133
123
            return self._client_commands
134
124
 
135
125
    def compose(self, prompt, to, subject, attachment, mime_subtype,
136
 
                extension, basename=None, body=None):
 
126
                extension, basename=None):
137
127
        """See MailClient.compose.
138
128
 
139
129
        Writes the attachment to a temporary file, invokes _compose.
140
130
        """
141
131
        if basename is None:
142
132
            basename = 'attachment'
143
 
        pathname = osutils.mkdtemp(prefix='bzr-mail-')
 
133
        pathname = tempfile.mkdtemp(prefix='bzr-mail-')
144
134
        attach_path = osutils.pathjoin(pathname, basename + extension)
145
135
        outfile = open(attach_path, 'wb')
146
136
        try:
147
137
            outfile.write(attachment)
148
138
        finally:
149
139
            outfile.close()
150
 
        if body is not None:
151
 
            kwargs = {'body': body}
152
 
        else:
153
 
            kwargs = {}
154
140
        self._compose(prompt, to, subject, attach_path, mime_subtype,
155
 
                      extension, **kwargs)
 
141
                      extension)
156
142
 
157
143
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
158
 
                 extension, body=None, from_=None):
 
144
                extension):
159
145
        """Invoke a mail client as a commandline process.
160
146
 
161
147
        Overridden by MAPIClient.
166
152
            "text", but the precise subtype can be specified here
167
153
        :param extension: A file extension (including period) associated with
168
154
            the attachment type.
169
 
        :param body: Optional body text.
170
 
        :param from_: Optional From: header.
171
155
        """
172
156
        for name in self._get_client_commands():
173
157
            cmdline = [self._encode_path(name, 'executable')]
174
 
            if body is not None:
175
 
                kwargs = {'body': body}
176
 
            else:
177
 
                kwargs = {}
178
 
            if from_ is not None:
179
 
                kwargs['from_'] = from_
180
158
            cmdline.extend(self._get_compose_commandline(to, subject,
181
 
                                                         attach_path,
182
 
                                                         **kwargs))
 
159
                                                         attach_path))
183
160
            try:
184
161
                subprocess.call(cmdline)
185
162
            except OSError, e:
190
167
        else:
191
168
            raise errors.MailClientNotFound(self._client_commands)
192
169
 
193
 
    def _get_compose_commandline(self, to, subject, attach_path, body):
 
170
    def _get_compose_commandline(self, to, subject, attach_path):
194
171
        """Determine the commandline to use for composing a message
195
172
 
196
173
        Implemented by various subclasses
209
186
        :return:    encoded string if u is unicode, u itself otherwise.
210
187
        """
211
188
        if isinstance(u, unicode):
212
 
            return u.encode(osutils.get_user_encoding(), 'replace')
 
189
            return u.encode(bzrlib.user_encoding, 'replace')
213
190
        return u
214
191
 
215
192
    def _encode_path(self, path, kind):
223
200
        """
224
201
        if isinstance(path, unicode):
225
202
            try:
226
 
                return path.encode(osutils.get_user_encoding())
 
203
                return path.encode(bzrlib.user_encoding)
227
204
            except UnicodeEncodeError:
228
205
                raise errors.UnableEncodePath(path, kind)
229
206
        return path
230
207
 
231
208
 
232
 
class ExternalMailClient(BodyExternalMailClient):
233
 
    __doc__ = """An external mail client."""
234
 
 
235
 
    supports_body = False
236
 
 
237
 
 
238
 
class Evolution(BodyExternalMailClient):
239
 
    __doc__ = """Evolution mail client."""
 
209
class Evolution(ExternalMailClient):
 
210
    """Evolution mail client."""
240
211
 
241
212
    _client_commands = ['evolution']
242
213
 
243
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
 
214
    def _get_compose_commandline(self, to, subject, attach_path):
244
215
        """See ExternalMailClient._get_compose_commandline"""
245
216
        message_options = {}
246
217
        if subject is not None:
247
218
            message_options['subject'] = subject
248
219
        if attach_path is not None:
249
220
            message_options['attach'] = attach_path
250
 
        if body is not None:
251
 
            message_options['body'] = body
252
221
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
253
222
                        sorted(message_options.iteritems())]
254
223
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
255
224
            '&'.join(options_list))]
256
 
mail_client_registry.register('evolution', Evolution,
257
 
                              help=Evolution.__doc__)
258
 
 
259
 
 
260
 
class Mutt(BodyExternalMailClient):
261
 
    __doc__ = """Mutt mail client."""
 
225
 
 
226
 
 
227
class Mutt(ExternalMailClient):
 
228
    """Mutt mail client."""
262
229
 
263
230
    _client_commands = ['mutt']
264
231
 
265
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
 
232
    def _get_compose_commandline(self, to, subject, attach_path):
266
233
        """See ExternalMailClient._get_compose_commandline"""
267
234
        message_options = []
268
235
        if subject is not None:
270
237
        if attach_path is not None:
271
238
            message_options.extend(['-a',
272
239
                self._encode_path(attach_path, 'attachment')])
273
 
        if body is not None:
274
 
            # Store the temp file object in self, so that it does not get
275
 
            # garbage collected and delete the file before mutt can read it.
276
 
            self._temp_file = tempfile.NamedTemporaryFile(
277
 
                prefix="mutt-body-", suffix=".txt")
278
 
            self._temp_file.write(body)
279
 
            self._temp_file.flush()
280
 
            message_options.extend(['-i', self._temp_file.name])
281
240
        if to is not None:
282
 
            message_options.extend(['--', self._encode_safe(to)])
 
241
            message_options.append(self._encode_safe(to))
283
242
        return message_options
284
 
mail_client_registry.register('mutt', Mutt,
285
 
                              help=Mutt.__doc__)
286
 
 
287
 
 
288
 
class Thunderbird(BodyExternalMailClient):
289
 
    __doc__ = """Mozilla Thunderbird (or Icedove)
 
243
 
 
244
 
 
245
class Thunderbird(ExternalMailClient):
 
246
    """Mozilla Thunderbird (or Icedove)
290
247
 
291
248
    Note that Thunderbird 1.5 is buggy and does not support setting
292
249
    "to" simultaneously with including a attachment.
296
253
    """
297
254
 
298
255
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove',
299
 
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
300
 
        '/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
 
256
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin']
301
257
 
302
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
 
258
    def _get_compose_commandline(self, to, subject, attach_path):
303
259
        """See ExternalMailClient._get_compose_commandline"""
304
260
        message_options = {}
305
261
        if to is not None:
309
265
        if attach_path is not None:
310
266
            message_options['attachment'] = urlutils.local_path_to_url(
311
267
                attach_path)
312
 
        if body is not None:
313
 
            options_list = ['body=%s' % urllib.quote(self._encode_safe(body))]
314
 
        else:
315
 
            options_list = []
316
 
        options_list.extend(["%s='%s'" % (k, v) for k, v in
317
 
                        sorted(message_options.iteritems())])
 
268
        options_list = ["%s='%s'" % (k, v) for k, v in
 
269
                        sorted(message_options.iteritems())]
318
270
        return ['-compose', ','.join(options_list)]
319
 
mail_client_registry.register('thunderbird', Thunderbird,
320
 
                              help=Thunderbird.__doc__)
321
271
 
322
272
 
323
273
class KMail(ExternalMailClient):
324
 
    __doc__ = """KDE mail client."""
 
274
    """KDE mail client."""
325
275
 
326
276
    _client_commands = ['kmail']
327
277
 
336
286
        if to is not None:
337
287
            message_options.extend([self._encode_safe(to)])
338
288
        return message_options
339
 
mail_client_registry.register('kmail', KMail,
340
 
                              help=KMail.__doc__)
341
 
 
342
 
 
343
 
class Claws(ExternalMailClient):
344
 
    __doc__ = """Claws mail client."""
345
 
 
346
 
    supports_body = True
347
 
 
348
 
    _client_commands = ['claws-mail']
349
 
 
350
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None,
351
 
                                 from_=None):
352
 
        """See ExternalMailClient._get_compose_commandline"""
353
 
        compose_url = []
354
 
        if from_ is not None:
355
 
            compose_url.append('from=' + urllib.quote(from_))
356
 
        if subject is not None:
357
 
            # Don't use urllib.quote_plus because Claws doesn't seem
358
 
            # to recognise spaces encoded as "+".
359
 
            compose_url.append(
360
 
                'subject=' + urllib.quote(self._encode_safe(subject)))
361
 
        if body is not None:
362
 
            compose_url.append(
363
 
                'body=' + urllib.quote(self._encode_safe(body)))
364
 
        # to must be supplied for the claws-mail --compose syntax to work.
365
 
        if to is None:
366
 
            raise errors.NoMailAddressSpecified()
367
 
        compose_url = 'mailto:%s?%s' % (
368
 
            self._encode_safe(to), '&'.join(compose_url))
369
 
        # Collect command-line options.
370
 
        message_options = ['--compose', compose_url]
371
 
        if attach_path is not None:
372
 
            message_options.extend(
373
 
                ['--attach', self._encode_path(attach_path, 'attachment')])
374
 
        return message_options
375
 
 
376
 
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
377
 
                 extension, body=None, from_=None):
378
 
        """See ExternalMailClient._compose"""
379
 
        if from_ is None:
380
 
            from_ = self.config.get_user_option('email')
381
 
        super(Claws, self)._compose(prompt, to, subject, attach_path,
382
 
                                    mime_subtype, extension, body, from_)
383
 
 
384
 
 
385
 
mail_client_registry.register('claws', Claws,
386
 
                              help=Claws.__doc__)
387
 
 
388
 
 
389
 
class XDGEmail(BodyExternalMailClient):
390
 
    __doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
 
289
 
 
290
 
 
291
class XDGEmail(ExternalMailClient):
 
292
    """xdg-email attempts to invoke the user's preferred mail client"""
391
293
 
392
294
    _client_commands = ['xdg-email']
393
295
 
394
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
 
296
    def _get_compose_commandline(self, to, subject, attach_path):
395
297
        """See ExternalMailClient._get_compose_commandline"""
396
298
        if not to:
397
299
            raise errors.NoMailAddressSpecified()
401
303
        if attach_path is not None:
402
304
            commandline.extend(['--attach',
403
305
                self._encode_path(attach_path, 'attachment')])
404
 
        if body is not None:
405
 
            commandline.extend(['--body', self._encode_safe(body)])
406
306
        return commandline
407
 
mail_client_registry.register('xdg-email', XDGEmail,
408
 
                              help=XDGEmail.__doc__)
409
307
 
410
308
 
411
309
class EmacsMail(ExternalMailClient):
412
 
    __doc__ = """Call emacsclient to have a mail buffer.
 
310
    """Call emacsclient to have a mail buffer.
413
311
 
414
312
    This only work for emacs >= 22.1 due to recent -e/--eval support.
415
313
 
424
322
 
425
323
    _client_commands = ['emacsclient']
426
324
 
427
 
    def __init__(self, config):
428
 
        super(EmacsMail, self).__init__(config)
429
 
        self.elisp_tmp_file = None
430
 
 
431
325
    def _prepare_send_function(self):
432
326
        """Write our wrapper function into a temporary file.
433
327
 
434
328
        This temporary file will be loaded at runtime in
435
329
        _get_compose_commandline function.
436
330
 
437
 
        This function does not remove the file.  That's a wanted
 
331
        FIXME: this function does not remove the file. That's a wanted
438
332
        behaviour since _get_compose_commandline won't run the send
439
333
        mail function directly but return the eligible command line.
440
334
        Removing our temporary file here would prevent our sendmail
441
 
        function to work.  (The file is deleted by some elisp code
442
 
        after being read by Emacs.)
 
335
        function to work.
 
336
 
 
337
        A possible workaround could be to load the function here with
 
338
        emacsclient --eval '(load temp)' but this is not robust since
 
339
        emacs could have been stopped between here and the call to
 
340
        mail client.
443
341
        """
444
342
 
445
343
        _defun = r"""(defun bzr-add-mime-att (file)
446
 
  "Attach FILE to a mail buffer as a MIME attachment."
 
344
  "Attach FILe to a mail buffer as a MIME attachment."
447
345
  (let ((agent mail-user-agent))
 
346
    (mail-text)
 
347
    (newline)
448
348
    (if (and file (file-exists-p file))
449
349
        (cond
450
350
         ((eq agent 'sendmail-user-agent)
451
 
          (progn
452
 
            (mail-text)
453
 
            (newline)
454
 
            (if (functionp 'etach-attach)
455
 
              (etach-attach file)
456
 
              (mail-attach-file file))))
457
 
         ((or (eq agent 'message-user-agent)
458
 
              (eq agent 'gnus-user-agent)
459
 
              (eq agent 'mh-e-user-agent))
460
 
          (progn
461
 
            (mml-attach-file file "text/x-patch" "BZR merge" "inline")))
462
 
         ((eq agent 'mew-user-agent)
463
 
          (progn
464
 
            (mew-draft-prepare-attachments)
465
 
            (mew-attach-link file (file-name-nondirectory file))
466
 
            (let* ((nums (mew-syntax-nums))
467
 
                   (syntax (mew-syntax-get-entry mew-encode-syntax nums)))
468
 
              (mew-syntax-set-cd syntax "BZR merge")
469
 
              (mew-encode-syntax-print mew-encode-syntax))
470
 
            (mew-header-goto-body)))
 
351
          (etach-attach file))
 
352
         ((or (eq agent 'message-user-agent)(eq agent 'gnus-user-agent))
 
353
          (mml-attach-file file "text/x-patch" "BZR merge" "attachment"))
471
354
         (t
472
 
          (message "Unhandled MUA, report it on bazaar@lists.canonical.com")))
 
355
          (message "Unhandled MUA")))
473
356
      (error "File %s does not exist." file))))
474
357
"""
475
358
 
488
371
        _subject = "nil"
489
372
 
490
373
        if to is not None:
491
 
            _to = ("\"%s\"" % self._encode_safe(to).replace('"', '\\"'))
 
374
            _to = ("\"%s\"" % self._encode_safe(to))
492
375
        if subject is not None:
493
 
            _subject = ("\"%s\"" %
494
 
                        self._encode_safe(subject).replace('"', '\\"'))
 
376
            _subject = ("\"%s\"" % self._encode_safe(subject))
495
377
 
496
378
        # Funcall the default mail composition function
497
379
        # This will work with any mail mode including default mail-mode
503
385
        # Try to attach a MIME attachment using our wrapper function
504
386
        if attach_path is not None:
505
387
            # Do not create a file if there is no attachment
506
 
            elisp = self._prepare_send_function()
507
 
            self.elisp_tmp_file = elisp
508
 
            lmmform = '(load "%s")' % elisp
 
388
            lmmform = '(load "%s")' % self._prepare_send_function()
509
389
            mmform  = '(bzr-add-mime-att "%s")' % \
510
390
                self._encode_path(attach_path, 'attachment')
511
 
            rmform = '(delete-file "%s")' % elisp
512
391
            commandline.append(lmmform)
513
392
            commandline.append(mmform)
514
 
            commandline.append(rmform)
515
393
 
516
394
        return commandline
517
 
mail_client_registry.register('emacsclient', EmacsMail,
518
 
                              help=EmacsMail.__doc__)
519
 
 
520
 
 
521
 
class MAPIClient(BodyExternalMailClient):
522
 
    __doc__ = """Default Windows mail client launched using MAPI."""
 
395
 
 
396
 
 
397
class MAPIClient(ExternalMailClient):
 
398
    """Default Windows mail client launched using MAPI."""
523
399
 
524
400
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
525
 
                 extension, body=None):
 
401
                 extension):
526
402
        """See ExternalMailClient._compose.
527
403
 
528
404
        This implementation uses MAPI via the simplemapi ctypes wrapper
529
405
        """
530
406
        from bzrlib.util import simplemapi
531
407
        try:
532
 
            simplemapi.SendMail(to or '', subject or '', body or '',
533
 
                                attach_path)
 
408
            simplemapi.SendMail(to or '', subject or '', '', attach_path)
534
409
        except simplemapi.MAPIError, e:
535
410
            if e.code != simplemapi.MAPI_USER_ABORT:
536
411
                raise errors.MailClientNotFound(['MAPI supported mail client'
537
412
                                                 ' (error %d)' % (e.code,)])
538
 
mail_client_registry.register('mapi', MAPIClient,
539
 
                              help=MAPIClient.__doc__)
540
 
 
541
 
 
542
 
class MailApp(BodyExternalMailClient):
543
 
    __doc__ = """Use MacOS X's Mail.app for sending email messages.
544
 
 
545
 
    Although it would be nice to use appscript, it's not installed
546
 
    with the shipped Python installations.  We instead build an
547
 
    AppleScript and invoke the script using osascript(1).  We don't
548
 
    use the _encode_safe() routines as it's not clear what encoding
549
 
    osascript expects the script to be in.
550
 
    """
551
 
 
552
 
    _client_commands = ['osascript']
553
 
 
554
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None,
555
 
                                from_=None):
556
 
       """See ExternalMailClient._get_compose_commandline"""
557
 
 
558
 
       fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
559
 
                                         suffix=".scpt")
560
 
       try:
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')
564
 
           if to is not None:
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('"', '\\"'))
574
 
           if body is not None:
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
578
 
               # incantation.
579
 
               os.write(fd, 'set content to "%s\\n\n"\n' %
580
 
                   body.replace('"', '\\"').replace('\n', '\\n'))
581
 
 
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
586
 
               # incantation.
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')
594
 
       finally:
595
 
           os.close(fd) # Just close the handle but do not remove the file.
596
 
       return [self.temp_file]
597
 
mail_client_registry.register('mail.app', MailApp,
598
 
                              help=MailApp.__doc__)
599
413
 
600
414
 
601
415
class DefaultMail(MailClient):
602
 
    __doc__ = """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
 
416
    """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
603
417
    falls back to Editor"""
604
418
 
605
 
    supports_body = True
606
 
 
607
419
    def _mail_client(self):
608
420
        """Determine the preferred mail client for this platform"""
609
421
        if osutils.supports_mapi():
612
424
            return XDGEmail(self.config)
613
425
 
614
426
    def compose(self, prompt, to, subject, attachment, mime_subtype,
615
 
                extension, basename=None, body=None):
 
427
                extension, basename=None):
616
428
        """See MailClient.compose"""
617
429
        try:
618
430
            return self._mail_client().compose(prompt, to, subject,
619
431
                                               attachment, mimie_subtype,
620
 
                                               extension, basename, body)
 
432
                                               extension, basename)
621
433
        except errors.MailClientNotFound:
622
434
            return Editor(self.config).compose(prompt, to, subject,
623
 
                          attachment, mimie_subtype, extension, body)
 
435
                          attachment, mimie_subtype, extension)
624
436
 
625
 
    def compose_merge_request(self, to, subject, directive, basename=None,
626
 
                              body=None):
 
437
    def compose_merge_request(self, to, subject, directive, basename=None):
627
438
        """See MailClient.compose_merge_request"""
628
439
        try:
629
440
            return self._mail_client().compose_merge_request(to, subject,
630
 
                    directive, basename=basename, body=body)
 
441
                    directive, basename=basename)
631
442
        except errors.MailClientNotFound:
632
443
            return Editor(self.config).compose_merge_request(to, subject,
633
 
                          directive, basename=basename, body=body)
634
 
mail_client_registry.register('default', DefaultMail,
635
 
                              help=DefaultMail.__doc__)
636
 
mail_client_registry.default_key = 'default'
637
 
 
638
 
 
 
444
                          directive, basename=basename)