~bzr-pqm/bzr/bzr.dev

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