/brz/remove-bazaar

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