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