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