/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5177.1.1 by Vincent Ladeuil
Manually assign docstrings to command objects, so that they work with python -OO
1
# Copyright (C) 2007-2010 Canonical Ltd
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
16
6379.6.3 by Jelmer Vernooij
Use absolute_import.
17
from __future__ import absolute_import
18
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
19
import errno
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
20
import os
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
21
import subprocess
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
22
import sys
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
23
import tempfile
24
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
25
import breezy
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
26
from . import (
6449.5.1 by Jelmer Vernooij
Migrate mail_client to config stacks.
27
    config as _mod_config,
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
28
    email_message,
29
    errors,
30
    msgeditor,
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
31
    osutils,
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
32
    urlutils,
3638.2.1 by Neil Martinsen-Burrell
Use a registry for mail clients.
33
    registry
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
34
    )
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
35
3638.2.1 by Neil Martinsen-Burrell
Use a registry for mail clients.
36
mail_client_registry = registry.Registry()
37
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
38
6734.1.1 by Jelmer Vernooij
Fix more imports.
39
class MailClientNotFound(errors.BzrError):
40
41
    _fmt = "Unable to find mail client with the following names:"\
42
        " %(mail_command_list_string)s"
43
44
    def __init__(self, mail_command_list):
45
        mail_command_list_string = ', '.join(mail_command_list)
46
        errors.BzrError.__init__(
47
            self, mail_command_list=mail_command_list,
48
            mail_command_list_string=mail_command_list_string)
49
50
51
class NoMessageSupplied(errors.BzrError):
52
53
    _fmt = "No message supplied."
54
55
56
class NoMailAddressSpecified(errors.BzrError):
57
58
    _fmt = "No mail-to address (--mail-to) or output (-o) specified."
59
60
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
61
class MailClient(object):
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
62
    """A mail client that can send messages with attachements."""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
63
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
64
    def __init__(self, config):
65
        self.config = config
66
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
67
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
68
                extension, basename=None, body=None):
2681.1.36 by Aaron Bentley
Update docs
69
        """Compose (and possibly send) an email message
70
71
        Must be implemented by subclasses.
72
73
        :param prompt: A message to tell the user what to do.  Supported by
74
            the Editor client, but ignored by others
75
        :param to: The address to send the message to
76
        :param subject: The contents of the subject line
77
        :param attachment: An email attachment, as a bytestring
78
        :param mime_subtype: The attachment is assumed to be a subtype of
79
            Text.  This allows the precise subtype to be specified, e.g.
80
            "plain", "x-patch", etc.
81
        :param extension: The file extension associated with the attachment
82
            type, e.g. ".patch"
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
83
        :param basename: The name to use for the attachment, e.g.
84
            "send-nick-3252"
2681.1.36 by Aaron Bentley
Update docs
85
        """
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
86
        raise NotImplementedError
87
4098.5.2 by Aaron Bentley
Push body support through bzr send.
88
    def compose_merge_request(self, to, subject, directive, basename=None,
89
                              body=None):
2681.1.36 by Aaron Bentley
Update docs
90
        """Compose (and possibly send) a merge request
91
92
        :param to: The address to send the request to
93
        :param subject: The subject line to use for the request
94
        :param directive: A merge directive representing the merge request, as
95
            a bytestring.
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
96
        :param basename: The name to use for the attachment, e.g.
97
            "send-nick-3252"
2681.1.36 by Aaron Bentley
Update docs
98
        """
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
99
        prompt = self._get_merge_prompt("Please describe these changes:", to,
100
                                        subject, directive)
101
        self.compose(prompt, to, subject, directive,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
102
            'x-patch', '.patch', basename, body)
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
103
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
104
    def _get_merge_prompt(self, prompt, to, subject, attachment):
2681.1.36 by Aaron Bentley
Update docs
105
        """Generate a prompt string.  Overridden by Editor.
106
107
        :param prompt: A string suggesting what user should do
108
        :param to: The address the mail will be sent to
109
        :param subject: The subject line of the mail
110
        :param attachment: The attachment that will be used
111
        """
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
112
        return ''
113
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
114
115
class Editor(MailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
116
    __doc__ = """DIY mail client that uses commit message editor"""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
117
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
118
    supports_body = True
119
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
120
    def _get_merge_prompt(self, prompt, to, subject, attachment):
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
121
        """See MailClient._get_merge_prompt"""
122
        return (u"%s\n\n"
123
                u"To: %s\n"
124
                u"Subject: %s\n\n"
125
                u"%s" % (prompt, to, subject,
126
                         attachment.decode('utf-8', 'replace')))
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
127
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
128
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
129
                extension, basename=None, body=None):
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
130
        """See MailClient.compose"""
3042.1.1 by Lukáš Lalinský
Make mail-to address in ``bzr send`` optional for interactive mail clients.
131
        if not to:
6734.1.1 by Jelmer Vernooij
Fix more imports.
132
            raise NoMailAddressSpecified()
4098.5.2 by Aaron Bentley
Push body support through bzr send.
133
        body = msgeditor.edit_commit_message(prompt, start_message=body)
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
134
        if body == '':
6734.1.1 by Jelmer Vernooij
Fix more imports.
135
            raise NoMessageSupplied()
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
136
        email_message.EmailMessage.send(self.config,
6449.5.1 by Jelmer Vernooij
Migrate mail_client to config stacks.
137
                                        self.config.get('email'),
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
138
                                        to,
139
                                        subject,
140
                                        body,
141
                                        attachment,
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
142
                                        attachment_mime_subtype=mime_subtype)
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
143
mail_client_registry.register('editor', Editor,
144
                              help=Editor.__doc__)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
145
146
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
147
class BodyExternalMailClient(MailClient):
148
149
    supports_body = True
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
150
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
151
    def _get_client_commands(self):
2681.1.36 by Aaron Bentley
Update docs
152
        """Provide a list of commands that may invoke the mail client"""
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
153
        if sys.platform == 'win32':
2681.1.29 by Aaron Bentley
Make conditional import explicit
154
            import win32utils
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
155
            return [win32utils.get_app_path(i) for i in self._client_commands]
156
        else:
157
            return self._client_commands
158
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
159
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
160
                extension, basename=None, body=None):
2681.1.36 by Aaron Bentley
Update docs
161
        """See MailClient.compose.
162
163
        Writes the attachment to a temporary file, invokes _compose.
164
        """
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
165
        if basename is None:
166
            basename = 'attachment'
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
167
        pathname = osutils.mkdtemp(prefix='bzr-mail-')
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
168
        attach_path = osutils.pathjoin(pathname, basename + extension)
169
        outfile = open(attach_path, 'wb')
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
170
        try:
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
171
            outfile.write(attachment)
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
172
        finally:
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
173
            outfile.close()
4282.2.4 by Neil Martinsen-Burrell
a better fix that doesn't break backwards compatibility
174
        if body is not None:
175
            kwargs = {'body': body}
176
        else:
177
            kwargs = {}
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
178
        self._compose(prompt, to, subject, attach_path, mime_subtype,
4282.2.4 by Neil Martinsen-Burrell
a better fix that doesn't break backwards compatibility
179
                      extension, **kwargs)
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
180
181
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
4401.2.2 by Barry Warsaw
Much simpler approach to support From: in Claws, after discussion with
182
                 extension, body=None, from_=None):
2681.1.36 by Aaron Bentley
Update docs
183
        """Invoke a mail client as a commandline process.
184
185
        Overridden by MAPIClient.
186
        :param to: The address to send the mail to
187
        :param subject: The subject line for the mail
188
        :param pathname: The path to the attachment
189
        :param mime_subtype: The attachment is assumed to have a major type of
190
            "text", but the precise subtype can be specified here
191
        :param extension: A file extension (including period) associated with
192
            the attachment type.
4401.2.2 by Barry Warsaw
Much simpler approach to support From: in Claws, after discussion with
193
        :param body: Optional body text.
194
        :param from_: Optional From: header.
2681.1.36 by Aaron Bentley
Update docs
195
        """
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
196
        for name in self._get_client_commands():
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
197
            cmdline = [self._encode_path(name, 'executable')]
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
198
            if body is not None:
199
                kwargs = {'body': body}
200
            else:
201
                kwargs = {}
4401.2.2 by Barry Warsaw
Much simpler approach to support From: in Claws, after discussion with
202
            if from_ is not None:
203
                kwargs['from_'] = from_
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
204
            cmdline.extend(self._get_compose_commandline(to, subject,
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
205
                                                         attach_path,
206
                                                         **kwargs))
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
207
            try:
208
                subprocess.call(cmdline)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
209
            except OSError as e:
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
210
                if e.errno != errno.ENOENT:
211
                    raise
212
            else:
213
                break
214
        else:
6734.1.1 by Jelmer Vernooij
Fix more imports.
215
            raise MailClientNotFound(self._client_commands)
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
216
4098.5.2 by Aaron Bentley
Push body support through bzr send.
217
    def _get_compose_commandline(self, to, subject, attach_path, body):
2681.1.36 by Aaron Bentley
Update docs
218
        """Determine the commandline to use for composing a message
219
220
        Implemented by various subclasses
221
        :param to: The address to send the mail to
222
        :param subject: The subject line for the mail
223
        :param attach_path: The path to the attachment
224
        """
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
225
        raise NotImplementedError
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
226
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
227
    def _encode_safe(self, u):
228
        """Encode possible unicode string argument to 8-bit string
229
        in user_encoding. Unencodable characters will be replaced
230
        with '?'.
231
232
        :param  u:  possible unicode string.
233
        :return:    encoded string if u is unicode, u itself otherwise.
234
        """
235
        if isinstance(u, unicode):
3224.5.8 by Andrew Bennetts
Fix failing tests.
236
            return u.encode(osutils.get_user_encoding(), 'replace')
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
237
        return u
238
239
    def _encode_path(self, path, kind):
240
        """Encode unicode path in user encoding.
241
242
        :param  path:   possible unicode path.
243
        :param  kind:   path kind ('executable' or 'attachment').
244
        :return:        encoded path if path is unicode,
245
                        path itself otherwise.
246
        :raise:         UnableEncodePath.
247
        """
248
        if isinstance(path, unicode):
249
            try:
3224.5.8 by Andrew Bennetts
Fix failing tests.
250
                return path.encode(osutils.get_user_encoding())
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
251
            except UnicodeEncodeError:
252
                raise errors.UnableEncodePath(path, kind)
253
        return path
3234.2.3 by Alexander Belchenko
mail_client.py: provide new private method ExternalMailClient._get_compose_8bit_commandline to make bug #139318 testable (as Aaron requested).
254
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
255
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
256
class ExternalMailClient(BodyExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
257
    __doc__ = """An external mail client."""
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
258
259
    supports_body = False
260
261
262
class Evolution(BodyExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
263
    __doc__ = """Evolution mail client."""
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
264
265
    _client_commands = ['evolution']
266
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
267
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
2681.1.36 by Aaron Bentley
Update docs
268
        """See ExternalMailClient._get_compose_commandline"""
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
269
        message_options = {}
270
        if subject is not None:
271
            message_options['subject'] = subject
272
        if attach_path is not None:
273
            message_options['attach'] = attach_path
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
274
        if body is not None:
275
            message_options['body'] = body
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
276
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
6656.1.1 by Martin
Apply 2to3 dict fixer and clean up resulting mess using view helpers
277
                        sorted(message_options.items())]
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
278
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
279
            '&'.join(options_list))]
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
280
mail_client_registry.register('evolution', Evolution,
281
                              help=Evolution.__doc__)
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
282
283
4416.1.1 by Edwin Grubbs
Added ability to pass the body into mutt.
284
class Mutt(BodyExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
285
    __doc__ = """Mutt mail client."""
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
286
287
    _client_commands = ['mutt']
288
4416.1.1 by Edwin Grubbs
Added ability to pass the body into mutt.
289
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
290
        """See ExternalMailClient._get_compose_commandline"""
291
        message_options = []
292
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
293
            message_options.extend(['-s', self._encode_safe(subject)])
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
294
        if attach_path is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
295
            message_options.extend(['-a',
296
                self._encode_path(attach_path, 'attachment')])
4416.1.1 by Edwin Grubbs
Added ability to pass the body into mutt.
297
        if body is not None:
4416.1.3 by Edwin Grubbs
Stored temp file in self to allow using NamedTemporaryFile.
298
            # Store the temp file object in self, so that it does not get
299
            # garbage collected and delete the file before mutt can read it.
300
            self._temp_file = tempfile.NamedTemporaryFile(
301
                prefix="mutt-body-", suffix=".txt")
302
            self._temp_file.write(body)
303
            self._temp_file.flush()
304
            message_options.extend(['-i', self._temp_file.name])
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
305
        if to is not None:
4292.1.1 by Jelmer Vernooij
Mutt requires -- before the recipient address if -a is being used.
306
            message_options.extend(['--', self._encode_safe(to)])
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
307
        return message_options
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
308
mail_client_registry.register('mutt', Mutt,
309
                              help=Mutt.__doc__)
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
310
311
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
312
class Thunderbird(BodyExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
313
    __doc__ = """Mozilla Thunderbird (or Icedove)
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
314
315
    Note that Thunderbird 1.5 is buggy and does not support setting
316
    "to" simultaneously with including a attachment.
317
318
    There is a workaround if no attachment is present, but we always need to
319
    send attachments.
320
    """
321
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
322
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove',
3638.2.1 by Neil Martinsen-Burrell
Use a registry for mail clients.
323
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
324
        '/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
325
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
326
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
2681.1.36 by Aaron Bentley
Update docs
327
        """See ExternalMailClient._get_compose_commandline"""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
328
        message_options = {}
329
        if to is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
330
            message_options['to'] = self._encode_safe(to)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
331
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
332
            message_options['subject'] = self._encode_safe(subject)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
333
        if attach_path is not None:
3234.2.2 by Alexander Belchenko
[merge] URL is always ascii.
334
            message_options['attachment'] = urlutils.local_path_to_url(
335
                attach_path)
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
336
        if body is not None:
6379.4.2 by Jelmer Vernooij
Add urlutils.quote / urlutils.unquote.
337
            options_list = ['body=%s' % urlutils.quote(self._encode_safe(body))]
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
338
        else:
339
            options_list = []
340
        options_list.extend(["%s='%s'" % (k, v) for k, v in
6656.1.1 by Martin
Apply 2to3 dict fixer and clean up resulting mess using view helpers
341
                        sorted(message_options.items())])
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
342
        return ['-compose', ','.join(options_list)]
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
343
mail_client_registry.register('thunderbird', Thunderbird,
344
                              help=Thunderbird.__doc__)
2681.1.23 by Aaron Bentley
Add support for xdg-email
345
346
2681.5.3 by ghigo
Add KMail mail client
347
class KMail(ExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
348
    __doc__ = """KDE mail client."""
2681.5.1 by ghigo
Add KMail support to bzr send
349
350
    _client_commands = ['kmail']
351
352
    def _get_compose_commandline(self, to, subject, attach_path):
2681.1.36 by Aaron Bentley
Update docs
353
        """See ExternalMailClient._get_compose_commandline"""
2681.5.1 by ghigo
Add KMail support to bzr send
354
        message_options = []
355
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
356
            message_options.extend(['-s', self._encode_safe(subject)])
2681.5.1 by ghigo
Add KMail support to bzr send
357
        if attach_path is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
358
            message_options.extend(['--attach',
359
                self._encode_path(attach_path, 'attachment')])
2681.5.1 by ghigo
Add KMail support to bzr send
360
        if to is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
361
            message_options.extend([self._encode_safe(to)])
2681.5.1 by ghigo
Add KMail support to bzr send
362
        return message_options
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
363
mail_client_registry.register('kmail', KMail,
364
                              help=KMail.__doc__)
2681.5.1 by ghigo
Add KMail support to bzr send
365
366
3921.2.1 by Gavin Panella
Support Claws.
367
class Claws(ExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
368
    __doc__ = """Claws mail client."""
3921.2.1 by Gavin Panella
Support Claws.
369
4401.2.1 by Barry Warsaw
Make Claws mail client work, with bodies and setting the From field.
370
    supports_body = True
371
3921.2.1 by Gavin Panella
Support Claws.
372
    _client_commands = ['claws-mail']
373
4401.2.2 by Barry Warsaw
Much simpler approach to support From: in Claws, after discussion with
374
    def _get_compose_commandline(self, to, subject, attach_path, body=None,
375
                                 from_=None):
3921.2.1 by Gavin Panella
Support Claws.
376
        """See ExternalMailClient._get_compose_commandline"""
4401.2.1 by Barry Warsaw
Make Claws mail client work, with bodies and setting the From field.
377
        compose_url = []
4401.2.2 by Barry Warsaw
Much simpler approach to support From: in Claws, after discussion with
378
        if from_ is not None:
6379.4.2 by Jelmer Vernooij
Add urlutils.quote / urlutils.unquote.
379
            compose_url.append('from=' + urlutils.quote(from_))
3921.2.1 by Gavin Panella
Support Claws.
380
        if subject is not None:
6379.4.2 by Jelmer Vernooij
Add urlutils.quote / urlutils.unquote.
381
            # Don't use urlutils.quote_plus because Claws doesn't seem
3921.2.4 by Gavin Panella
Use the --attach option, and don't specify a From: header.
382
            # to recognise spaces encoded as "+".
383
            compose_url.append(
6379.4.2 by Jelmer Vernooij
Add urlutils.quote / urlutils.unquote.
384
                'subject=' + urlutils.quote(self._encode_safe(subject)))
4401.2.1 by Barry Warsaw
Make Claws mail client work, with bodies and setting the From field.
385
        if body is not None:
386
            compose_url.append(
6379.4.2 by Jelmer Vernooij
Add urlutils.quote / urlutils.unquote.
387
                'body=' + urlutils.quote(self._encode_safe(body)))
4401.2.1 by Barry Warsaw
Make Claws mail client work, with bodies and setting the From field.
388
        # to must be supplied for the claws-mail --compose syntax to work.
389
        if to is None:
6734.1.1 by Jelmer Vernooij
Fix more imports.
390
            raise NoMailAddressSpecified()
4401.2.1 by Barry Warsaw
Make Claws mail client work, with bodies and setting the From field.
391
        compose_url = 'mailto:%s?%s' % (
392
            self._encode_safe(to), '&'.join(compose_url))
3921.2.4 by Gavin Panella
Use the --attach option, and don't specify a From: header.
393
        # Collect command-line options.
4401.2.1 by Barry Warsaw
Make Claws mail client work, with bodies and setting the From field.
394
        message_options = ['--compose', compose_url]
3921.2.1 by Gavin Panella
Support Claws.
395
        if attach_path is not None:
3921.2.4 by Gavin Panella
Use the --attach option, and don't specify a From: header.
396
            message_options.extend(
397
                ['--attach', self._encode_path(attach_path, 'attachment')])
398
        return message_options
4401.2.2 by Barry Warsaw
Much simpler approach to support From: in Claws, after discussion with
399
400
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
401
                 extension, body=None, from_=None):
402
        """See ExternalMailClient._compose"""
403
        if from_ is None:
6449.5.1 by Jelmer Vernooij
Migrate mail_client to config stacks.
404
            from_ = self.config.get('email')
4401.2.2 by Barry Warsaw
Much simpler approach to support From: in Claws, after discussion with
405
        super(Claws, self)._compose(prompt, to, subject, attach_path,
406
                                    mime_subtype, extension, body, from_)
407
408
3921.2.1 by Gavin Panella
Support Claws.
409
mail_client_registry.register('claws', Claws,
410
                              help=Claws.__doc__)
411
412
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
413
class XDGEmail(BodyExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
414
    __doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
2681.1.23 by Aaron Bentley
Add support for xdg-email
415
416
    _client_commands = ['xdg-email']
417
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
418
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
2681.1.36 by Aaron Bentley
Update docs
419
        """See ExternalMailClient._get_compose_commandline"""
3042.1.1 by Lukáš Lalinský
Make mail-to address in ``bzr send`` optional for interactive mail clients.
420
        if not to:
6734.1.1 by Jelmer Vernooij
Fix more imports.
421
            raise NoMailAddressSpecified()
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
422
        commandline = [self._encode_safe(to)]
2681.1.23 by Aaron Bentley
Add support for xdg-email
423
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
424
            commandline.extend(['--subject', self._encode_safe(subject)])
2681.1.23 by Aaron Bentley
Add support for xdg-email
425
        if attach_path is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
426
            commandline.extend(['--attach',
427
                self._encode_path(attach_path, 'attachment')])
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
428
        if body is not None:
429
            commandline.extend(['--body', self._encode_safe(body)])
2681.1.23 by Aaron Bentley
Add support for xdg-email
430
        return commandline
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
431
mail_client_registry.register('xdg-email', XDGEmail,
432
                              help=XDGEmail.__doc__)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
433
434
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
435
class EmacsMail(ExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
436
    __doc__ = """Call emacsclient to have a mail buffer.
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
437
438
    This only work for emacs >= 22.1 due to recent -e/--eval support.
439
440
    The good news is that this implementation will work with all mail
441
    agents registered against ``mail-user-agent``. So there is no need
442
    to instantiate ExternalMailClient for each and every GNU Emacs
443
    MUA.
444
445
    Users just have to ensure that ``mail-user-agent`` is set according
446
    to their tastes.
3322.1.1 by Ian Clatworthy
Add mail-mode GNU Emacs mail package as a mail client option (Xavier Maillard)
447
    """
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
448
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
449
    _client_commands = ['emacsclient']
450
4659.2.1 by Vincent Ladeuil
Cleanup emacs-bzr-send-XXXXXX.el leaks in /tmp during selftest.
451
    def __init__(self, config):
452
        super(EmacsMail, self).__init__(config)
453
        self.elisp_tmp_file = None
454
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
455
    def _prepare_send_function(self):
456
        """Write our wrapper function into a temporary file.
457
458
        This temporary file will be loaded at runtime in
459
        _get_compose_commandline function.
460
3506.1.6 by Christophe Troestler
EmacsMail: _prepare_send_function: corrected doc and proper handling of
461
        This function does not remove the file.  That's a wanted
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
462
        behaviour since _get_compose_commandline won't run the send
463
        mail function directly but return the eligible command line.
464
        Removing our temporary file here would prevent our sendmail
3506.1.6 by Christophe Troestler
EmacsMail: _prepare_send_function: corrected doc and proper handling of
465
        function to work.  (The file is deleted by some elisp code
466
        after being read by Emacs.)
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
467
        """
468
469
        _defun = r"""(defun bzr-add-mime-att (file)
3506.1.1 by Christophe Troestler
Handled the MUA "mew" in the class EmacsMail.
470
  "Attach FILE to a mail buffer as a MIME attachment."
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
471
  (let ((agent mail-user-agent))
472
    (if (and file (file-exists-p file))
473
        (cond
474
         ((eq agent 'sendmail-user-agent)
3506.1.1 by Christophe Troestler
Handled the MUA "mew" in the class EmacsMail.
475
          (progn
3506.1.10 by Christophe Troestler
Removed TABS in mail_client.py and added a NEWS entry.
476
            (mail-text)
477
            (newline)
478
            (if (functionp 'etach-attach)
479
              (etach-attach file)
480
              (mail-attach-file file))))
4056.3.1 by epg at pretzelnet
Support MH-E in EmacsMail, using mml.
481
         ((or (eq agent 'message-user-agent)
482
              (eq agent 'gnus-user-agent)
483
              (eq agent 'mh-e-user-agent))
3506.1.1 by Christophe Troestler
Handled the MUA "mew" in the class EmacsMail.
484
          (progn
3506.1.10 by Christophe Troestler
Removed TABS in mail_client.py and added a NEWS entry.
485
            (mml-attach-file file "text/x-patch" "BZR merge" "inline")))
486
         ((eq agent 'mew-user-agent)
487
          (progn
488
            (mew-draft-prepare-attachments)
489
            (mew-attach-link file (file-name-nondirectory file))
490
            (let* ((nums (mew-syntax-nums))
491
                   (syntax (mew-syntax-get-entry mew-encode-syntax nums)))
492
              (mew-syntax-set-cd syntax "BZR merge")
493
              (mew-encode-syntax-print mew-encode-syntax))
494
            (mew-header-goto-body)))
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
495
         (t
3506.1.8 by Christophe Troestler
When the Emacs MUA is not supported, the error message encourage to report it.
496
          (message "Unhandled MUA, report it on bazaar@lists.canonical.com")))
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
497
      (error "File %s does not exist." file))))
498
"""
499
500
        fd, temp_file = tempfile.mkstemp(prefix="emacs-bzr-send-",
501
                                         suffix=".el")
502
        try:
503
            os.write(fd, _defun)
504
        finally:
505
            os.close(fd) # Just close the handle but do not remove the file.
506
        return temp_file
507
3322.1.1 by Ian Clatworthy
Add mail-mode GNU Emacs mail package as a mail client option (Xavier Maillard)
508
    def _get_compose_commandline(self, to, subject, attach_path):
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
509
        commandline = ["--eval"]
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
510
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
511
        _to = "nil"
512
        _subject = "nil"
513
514
        if to is not None:
3506.1.3 by Christophe Troestler
Better escaping of To and Subject in the class EmacsMail.
515
            _to = ("\"%s\"" % self._encode_safe(to).replace('"', '\\"'))
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
516
        if subject is not None:
3506.1.3 by Christophe Troestler
Better escaping of To and Subject in the class EmacsMail.
517
            _subject = ("\"%s\"" %
518
                        self._encode_safe(subject).replace('"', '\\"'))
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
519
520
        # Funcall the default mail composition function
521
        # This will work with any mail mode including default mail-mode
522
        # User must tweak mail-user-agent variable to tell what function
523
        # will be called inside compose-mail.
524
        mail_cmd = "(compose-mail %s %s)" % (_to, _subject)
525
        commandline.append(mail_cmd)
526
527
        # Try to attach a MIME attachment using our wrapper function
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
528
        if attach_path is not None:
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
529
            # Do not create a file if there is no attachment
3506.1.4 by Christophe Troestler
Remove the temporary elisp file created for attachments by EmacsMail.
530
            elisp = self._prepare_send_function()
4659.2.1 by Vincent Ladeuil
Cleanup emacs-bzr-send-XXXXXX.el leaks in /tmp during selftest.
531
            self.elisp_tmp_file = elisp
3506.1.4 by Christophe Troestler
Remove the temporary elisp file created for attachments by EmacsMail.
532
            lmmform = '(load "%s")' % elisp
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
533
            mmform  = '(bzr-add-mime-att "%s")' % \
534
                self._encode_path(attach_path, 'attachment')
3506.1.4 by Christophe Troestler
Remove the temporary elisp file created for attachments by EmacsMail.
535
            rmform = '(delete-file "%s")' % elisp
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
536
            commandline.append(lmmform)
537
            commandline.append(mmform)
3506.1.4 by Christophe Troestler
Remove the temporary elisp file created for attachments by EmacsMail.
538
            commandline.append(rmform)
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
539
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
540
        return commandline
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
541
mail_client_registry.register('emacsclient', EmacsMail,
542
                              help=EmacsMail.__doc__)
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
543
544
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
545
class MAPIClient(BodyExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
546
    __doc__ = """Default Windows mail client launched using MAPI."""
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
547
548
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
4282.2.4 by Neil Martinsen-Burrell
a better fix that doesn't break backwards compatibility
549
                 extension, body=None):
2681.1.36 by Aaron Bentley
Update docs
550
        """See ExternalMailClient._compose.
551
552
        This implementation uses MAPI via the simplemapi ctypes wrapper
553
        """
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
554
        from .util import simplemapi
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
555
        try:
4098.5.3 by Aaron Bentley
Add Windows MAPI support for send --body
556
            simplemapi.SendMail(to or '', subject or '', body or '',
557
                                attach_path)
6619.3.2 by Jelmer Vernooij
Apply 2to3 except fix.
558
        except simplemapi.MAPIError as e:
2681.3.6 by Lukáš Lalinsky
New version of simplemapi.py with MIT license.
559
            if e.code != simplemapi.MAPI_USER_ABORT:
6734.1.1 by Jelmer Vernooij
Fix more imports.
560
                raise MailClientNotFound(['MAPI supported mail client'
2681.3.6 by Lukáš Lalinsky
New version of simplemapi.py with MIT license.
561
                                                 ' (error %d)' % (e.code,)])
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
562
mail_client_registry.register('mapi', MAPIClient,
563
                              help=MAPIClient.__doc__)
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
564
565
4715.3.1 by Brian de Alwis
Introduce new mailer to support MacOS X's Mail.app
566
class MailApp(BodyExternalMailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
567
    __doc__ = """Use MacOS X's Mail.app for sending email messages.
4715.3.1 by Brian de Alwis
Introduce new mailer to support MacOS X's Mail.app
568
569
    Although it would be nice to use appscript, it's not installed
570
    with the shipped Python installations.  We instead build an
571
    AppleScript and invoke the script using osascript(1).  We don't
572
    use the _encode_safe() routines as it's not clear what encoding
573
    osascript expects the script to be in.
574
    """
575
576
    _client_commands = ['osascript']
577
578
    def _get_compose_commandline(self, to, subject, attach_path, body=None,
579
                                from_=None):
580
       """See ExternalMailClient._get_compose_commandline"""
581
582
       fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
583
                                         suffix=".scpt")
584
       try:
585
           os.write(fd, 'tell application "Mail"\n')
586
           os.write(fd, 'set newMessage to make new outgoing message\n')
587
           os.write(fd, 'tell newMessage\n')
588
           if to is not None:
589
               os.write(fd, 'make new to recipient with properties'
590
                   ' {address:"%s"}\n' % to)
591
           if from_ is not None:
592
               # though from_ doesn't actually seem to be used
593
               os.write(fd, 'set sender to "%s"\n'
594
                   % sender.replace('"', '\\"'))
595
           if subject is not None:
596
               os.write(fd, 'set subject to "%s"\n'
597
                   % subject.replace('"', '\\"'))
598
           if body is not None:
599
               # FIXME: would be nice to prepend the body to the
600
               # existing content (e.g., preserve signature), but
601
               # can't seem to figure out the right applescript
602
               # incantation.
603
               os.write(fd, 'set content to "%s\\n\n"\n' %
604
                   body.replace('"', '\\"').replace('\n', '\\n'))
605
606
           if attach_path is not None:
607
               # FIXME: would be nice to first append a newline to
608
               # ensure the attachment is on a new paragraph, but
609
               # can't seem to figure out the right applescript
610
               # incantation.
611
               os.write(fd, 'tell content to make new attachment'
612
                   ' with properties {file name:"%s"}'
613
                   ' at after the last paragraph\n'
614
                   % self._encode_path(attach_path, 'attachment'))
615
           os.write(fd, 'set visible to true\n')
616
           os.write(fd, 'end tell\n')
617
           os.write(fd, 'end tell\n')
618
       finally:
619
           os.close(fd) # Just close the handle but do not remove the file.
620
       return [self.temp_file]
621
mail_client_registry.register('mail.app', MailApp,
622
                              help=MailApp.__doc__)
623
624
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
625
class DefaultMail(MailClient):
5131.2.1 by Martin
Permit bzrlib to run under python -OO by explictly assigning to __doc__ for user-visible docstrings
626
    __doc__ = """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
627
    falls back to Editor"""
628
4273.2.2 by Aaron Bentley
Indicate that DefaultMail supports message bodies.
629
    supports_body = True
630
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
631
    def _mail_client(self):
2681.1.36 by Aaron Bentley
Update docs
632
        """Determine the preferred mail client for this platform"""
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
633
        if osutils.supports_mapi():
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
634
            return MAPIClient(self.config)
635
        else:
636
            return XDGEmail(self.config)
2681.1.25 by Aaron Bentley
Cleanup
637
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
638
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
639
                extension, basename=None, body=None):
2681.1.36 by Aaron Bentley
Update docs
640
        """See MailClient.compose"""
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
641
        try:
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
642
            return self._mail_client().compose(prompt, to, subject,
6449.5.1 by Jelmer Vernooij
Migrate mail_client to config stacks.
643
                                               attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
644
                                               extension, basename, body)
6734.1.1 by Jelmer Vernooij
Fix more imports.
645
        except MailClientNotFound:
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
646
            return Editor(self.config).compose(prompt, to, subject,
6449.5.1 by Jelmer Vernooij
Migrate mail_client to config stacks.
647
                          attachment, mime_subtype, extension, body)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
648
4098.5.2 by Aaron Bentley
Push body support through bzr send.
649
    def compose_merge_request(self, to, subject, directive, basename=None,
650
                              body=None):
2681.1.36 by Aaron Bentley
Update docs
651
        """See MailClient.compose_merge_request"""
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
652
        try:
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
653
            return self._mail_client().compose_merge_request(to, subject,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
654
                    directive, basename=basename, body=body)
6734.1.1 by Jelmer Vernooij
Fix more imports.
655
        except MailClientNotFound:
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
656
            return Editor(self.config).compose_merge_request(to, subject,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
657
                          directive, basename=basename, body=body)
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
658
mail_client_registry.register('default', DefaultMail,
659
                              help=DefaultMail.__doc__)
3638.2.2 by Neil Martinsen-Burrell
put registry information with each class
660
mail_client_registry.default_key = 'default'
4715.3.1 by Brian de Alwis
Introduce new mailer to support MacOS X's Mail.app
661
6449.5.1 by Jelmer Vernooij
Migrate mail_client to config stacks.
662
opt_mail_client = _mod_config.RegistryOption('mail_client',
6449.5.5 by Jelmer Vernooij
Consider invalid mail clients an error rather than just a warning.
663
        mail_client_registry, help='E-mail client to use.', invalid='error')