summaryrefslogtreecommitdiff
path: root/alot
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2011-10-12 22:01:11 +0100
committerPatrick Totzke <patricktotzke@gmail.com>2011-10-12 22:02:11 +0100
commit60dd4b1f2e684d43ac5fd08e93fe23f29f96d870 (patch)
treee0f3c9fa23ecabf3832c626f3d1e24f7fa7a0e64 /alot
parent2aae5ed855f34784027f33aac182fafd19468a4a (diff)
2nd attempt
Diffstat (limited to 'alot')
-rw-r--r--alot/Commands/__init__.py3
-rw-r--r--alot/Commands/globals.py45
-rw-r--r--alot/__init__.py3
-rw-r--r--alot/command.py1200
-rw-r--r--alot/commands/__init__.py188
-rw-r--r--alot/commands/envelope.py197
-rw-r--r--alot/commands/globals.py378
-rw-r--r--alot/commands/search.py135
-rw-r--r--alot/commands/taglist.py8
-rw-r--r--alot/commands/thread.py367
-rwxr-xr-xalot/init.py10
-rw-r--r--alot/ui.py4
12 files changed, 95 insertions, 2443 deletions
diff --git a/alot/Commands/__init__.py b/alot/Commands/__init__.py
new file mode 100644
index 00000000..f047554a
--- /dev/null
+++ b/alot/Commands/__init__.py
@@ -0,0 +1,3 @@
+import os
+import glob
+__all__ = list(filename[:-3] for filename in glob.glob1(os.path.dirname(__file__), '*.py'))
diff --git a/alot/Commands/globals.py b/alot/Commands/globals.py
new file mode 100644
index 00000000..e439ad88
--- /dev/null
+++ b/alot/Commands/globals.py
@@ -0,0 +1,45 @@
+from command import Command, registerCommand
+#from alot.command import Command, registerCommand
+from twisted.internet import defer
+
+registerCommand('global', 'exit', {})
+class ExitCommand(Command):
+ """shuts the MUA down cleanly"""
+ @defer.inlineCallbacks
+ def apply(self, ui):
+ if settings.config.getboolean('general', 'bug_on_exit'):
+ if (yield ui.choice('realy quit?', select='yes', cancel='no',
+ msg_position='left')) == 'no':
+ return
+ ui.exit()
+
+
+registerCommand('global', 'search', {})
+class SearchCommand(Command):
+ """open a new search buffer"""
+ def __init__(self, query, **kwargs):
+ """
+ :param query: initial querystring
+ """
+ self.query = query
+ Command.__init__(self, **kwargs)
+
+ @defer.inlineCallbacks
+ def apply(self, ui):
+ if self.query:
+ if self.query == '*' and ui.current_buffer:
+ s = 'really search for all threads? This takes a while..'
+ if (yield ui.choice(s, select='yes', cancel='no')) == 'no':
+ return
+ open_searches = ui.get_buffers_of_type(buffer.SearchBuffer)
+ to_be_focused = None
+ for sb in open_searches:
+ if sb.querystring == self.query:
+ to_be_focused = sb
+ if to_be_focused:
+ ui.buffer_focus(to_be_focused)
+ else:
+ ui.buffer_open(buffer.SearchBuffer(ui, self.query))
+ else:
+ ui.notify('empty query string')
+
diff --git a/alot/__init__.py b/alot/__init__.py
index 4a84dc9b..6a364552 100644
--- a/alot/__init__.py
+++ b/alot/__init__.py
@@ -24,3 +24,6 @@ __author_email__ = "patricktotzke@gmail.com"
__description__ = "Command-line MUA using notmuch mail"
__url__ = "https://github.com/pazz/alot"
__license__ = "Licensed under the GNU GPL v3+."
+
+import command
+from commands import *
diff --git a/alot/command.py b/alot/command.py
index 34c1928a..5b064091 100644
--- a/alot/command.py
+++ b/alot/command.py
@@ -1,50 +1,31 @@
-"""
-This file is part of alot.
-
-Alot is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License as published by the
-Free Software Foundation, either version 3 of the License, or (at your
-option) any later version.
-
-Alot is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
-for more details.
-
-You should have received a copy of the GNU General Public License
-along with notmuch. If not, see <http://www.gnu.org/licenses/>.
-
-Copyright (C) 2011 Patrick Totzke <patricktotzke@gmail.com>
-"""
import os
-import re
-import code
+#import re
+#import code
import glob
import logging
-import threading
-import subprocess
-import shlex
-import email
-import tempfile
-from email import Charset
-from email.header import Header
-from email.mime.text import MIMEText
-from email.mime.multipart import MIMEMultipart
-import urwid
-from twisted.internet import defer
-
-import buffer
+#import threading
+#import subprocess
+#import shlex
+#import email
+#import tempfile
+#from email import Charset
+#from email.header import Header
+#from email.mime.text import MIMEText
+#from email.mime.multipart import MIMEMultipart
+#import urwid
+#
+#import buffer
import settings
-import widgets
-import completion
-import helper
-from db import DatabaseROError
-from db import DatabaseLockedError
-from completion import ContactsCompleter
-from completion import AccountCompleter
-from message import decode_to_unicode
-from message import decode_header
-from message import encode_header
+#import widgets
+#import completion
+#import helper
+#from db import DatabaseROError
+#from db import DatabaseLockedError
+#from completion import ContactsCompleter
+#from completion import AccountCompleter
+#from message import decode_to_unicode
+#from message import decode_header
+#from message import encode_header
class Command(object):
@@ -59,1142 +40,33 @@ class Command(object):
pass
-class ExitCommand(Command):
- """shuts the MUA down cleanly"""
- @defer.inlineCallbacks
- def apply(self, ui):
- if settings.config.getboolean('general', 'bug_on_exit'):
- if (yield ui.choice('realy quit?', select='yes', cancel='no',
- msg_position='left')) == 'no':
- return
- ui.exit()
-
-
-class OpenThreadCommand(Command):
- """open a new thread-view buffer"""
- def __init__(self, thread=None, **kwargs):
- self.thread = thread
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.thread:
- self.thread = ui.current_buffer.get_selected_thread()
- if self.thread:
- query = ui.current_buffer.querystring
- ui.logger.info('open thread view for %s' % self.thread)
-
- sb = buffer.ThreadBuffer(ui, self.thread)
- ui.buffer_open(sb)
- sb.unfold_matching(query)
-
-
-class SearchCommand(Command):
- """open a new search buffer"""
- def __init__(self, query, **kwargs):
- """
- :param query: initial querystring
- """
- self.query = query
- Command.__init__(self, **kwargs)
-
- @defer.inlineCallbacks
- def apply(self, ui):
- if self.query:
- if self.query == '*' and ui.current_buffer:
- s = 'really search for all threads? This takes a while..'
- if (yield ui.choice(s, select='yes', cancel='no')) == 'no':
- return
- open_searches = ui.get_buffers_of_type(buffer.SearchBuffer)
- to_be_focused = None
- for sb in open_searches:
- if sb.querystring == self.query:
- to_be_focused = sb
- if to_be_focused:
- ui.buffer_focus(to_be_focused)
- else:
- ui.buffer_open(buffer.SearchBuffer(ui, self.query))
- else:
- ui.notify('empty query string')
-
-
-class PromptCommand(Command):
- """starts commandprompt"""
- def __init__(self, startstring=u'', **kwargs):
- self.startstring = startstring
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- ui.commandprompt(self.startstring)
-
-
-class RefreshCommand(Command):
- """refreshes the current buffer"""
- def apply(self, ui):
- ui.current_buffer.rebuild()
- ui.update()
-
-
-class ExternalCommand(Command):
- """calls external command"""
- def __init__(self, commandstring, path=None, spawn=False, refocus=True,
- in_thread=False, on_success=None, **kwargs):
- """
- :param commandstring: the command to call
- :type commandstring: str
- :param path: a path to a file (or None)
- :type path: str
- :param spawn: run command in a new terminal
- :type spawn: boolean
- :param in_thread: run asynchronously, don't block alot
- :type in_thread: boolean
- :param refocus: refocus calling buffer after cmd termination
- :type refocus: boolean
- :param on_success: code to execute after command successfully exited
- :type on_success: callable
- """
- self.commandstring = commandstring
- self.path = path
- self.spawn = spawn
- self.refocus = refocus
- self.in_thread = in_thread
- self.on_success = on_success
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- callerbuffer = ui.current_buffer
-
- def afterwards(data):
- if callable(self.on_success) and data == 'success':
- self.on_success()
- if self.refocus and callerbuffer in ui.buffers:
- ui.logger.info('refocussing')
- ui.buffer_focus(callerbuffer)
-
- write_fd = ui.mainloop.watch_pipe(afterwards)
-
- def thread_code(*args):
- if self.path:
- if '{}' in self.commandstring:
- cmd = self.commandstring.replace('{}',
- helper.shell_quote(self.path))
- else:
- cmd = '%s %s' % (self.commandstring,
- helper.shell_quote(self.path))
- else:
- cmd = self.commandstring
-
- if self.spawn:
- cmd = '%s %s' % (settings.config.get('general',
- 'terminal_cmd'),
- cmd)
- cmd = cmd.encode('utf-8', errors='ignore')
- ui.logger.info('calling external command: %s' % cmd)
- returncode = subprocess.call(shlex.split(cmd))
- if returncode == 0:
- os.write(write_fd, 'success')
-
- if self.in_thread:
- thread = threading.Thread(target=thread_code)
- thread.start()
- else:
- ui.mainloop.screen.stop()
- thread_code()
- ui.mainloop.screen.start()
-
-
-class EditCommand(ExternalCommand):
- def __init__(self, path, spawn=None, **kwargs):
- self.path = path
- if spawn != None:
- self.spawn = spawn
- else:
- self.spawn = settings.config.getboolean('general', 'spawn_editor')
- editor_cmd = settings.config.get('general', 'editor_cmd')
-
- ExternalCommand.__init__(self, editor_cmd, path=self.path,
- spawn=self.spawn, in_thread=self.spawn,
- **kwargs)
-
-
-class PythonShellCommand(Command):
- """opens an interactive shell for introspection"""
- def apply(self, ui):
- ui.mainloop.screen.stop()
- code.interact(local=locals())
- ui.mainloop.screen.start()
-
-
-class BufferCloseCommand(Command):
- """close a buffer"""
- def __init__(self, buffer=None, focussed=False, **kwargs):
- """
- :param buffer: the selected buffer
- :type buffer: `alot.buffer.Buffer`
- """
- self.buffer = buffer
- self.focussed = focussed
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if self.focussed:
- #if in bufferlist, this is ugly.
- self.buffer = ui.current_buffer.get_selected_buffer()
- elif not self.buffer:
- self.buffer = ui.current_buffer
- ui.buffer_close(self.buffer)
- ui.buffer_focus(ui.current_buffer)
-
-
-class BufferFocusCommand(Command):
- """focus a buffer"""
- def __init__(self, buffer=None, offset=0, **kwargs):
- """
- :param buffer: the buffer to focus
- :type buffer: `alot.buffer.Buffer`
- """
- self.buffer = buffer
- self.offset = offset
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if self.offset:
- idx = ui.buffers.index(ui.current_buffer)
- num = len(ui.buffers)
- self.buffer = ui.buffers[(idx + self.offset) % num]
- else:
- if not self.buffer:
- self.buffer = ui.current_buffer.get_selected_buffer()
- ui.buffer_focus(self.buffer)
-
-
-class OpenBufferlistCommand(Command):
- """open a bufferlist buffer"""
- def __init__(self, filtfun=None, **kwargs):
- self.filtfun = filtfun
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- blists = ui.get_buffers_of_type(buffer.BufferlistBuffer)
- if blists:
- ui.buffer_focus(blists[0])
- else:
- ui.buffer_open(buffer.BufferlistBuffer(ui, self.filtfun))
-
-
-class TagListCommand(Command):
- """open a taglisat buffer"""
- def __init__(self, filtfun=None, **kwargs):
- self.filtfun = filtfun
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- tags = ui.dbman.get_all_tags()
- buf = buffer.TagListBuffer(ui, tags, self.filtfun)
- ui.buffers.append(buf)
- buf.rebuild()
- ui.buffer_focus(buf)
-
-
-class FlushCommand(Command):
- """Flushes writes to the index. Retries until committed"""
- def apply(self, ui):
- try:
- ui.dbman.flush()
- except DatabaseLockedError:
- timeout = settings.config.getint('general', 'flush_retry_timeout')
-
- def f(*args):
- self.apply(ui)
- ui.mainloop.set_alarm_in(timeout, f)
- ui.notify('index locked, will try again in %d secs' % timeout)
- ui.update()
- return
-
-
-class ToggleThreadTagCommand(Command):
- """toggles tag in given or currently selected thread"""
- def __init__(self, tags, thread=None, **kwargs):
- assert tags
- self.thread = thread
- self.tags = set(tags)
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.thread:
- self.thread = ui.current_buffer.get_selected_thread()
- if not self.thread:
- return
- try:
- self.thread.set_tags(set(self.thread.get_tags()) ^ self.tags)
- except DatabaseROError:
- ui.notify('index in read-only mode', priority='error')
- return
-
- # flush index
- ui.apply_command(FlushCommand())
-
- # update current buffer
- # TODO: what if changes not yet flushed?
- cb = ui.current_buffer
- if isinstance(cb, buffer.SearchBuffer):
- # refresh selected threadline
- threadwidget = cb.get_selected_threadline()
- threadwidget.rebuild() # rebuild and redraw the line
- #remove line from searchlist if thread doesn't match the query
- qs = "(%s) AND thread:%s" % (cb.querystring,
- self.thread.get_thread_id())
- if ui.dbman.count_messages(qs) == 0:
- ui.logger.debug('remove: %s' % self.thread)
- cb.threadlist.remove(threadwidget)
- cb.result_count -= self.thread.get_total_messages()
- ui.update()
- elif isinstance(cb, buffer.ThreadBuffer):
- pass
-
-
-class ComposeCommand(Command):
- """compose a new email and open an envelope for it"""
- def __init__(self, mail=None, headers={}, **kwargs):
- Command.__init__(self, **kwargs)
- if not mail:
- self.mail = MIMEMultipart()
- self.mail.attach(MIMEText('', 'plain', 'UTF-8'))
- else:
- self.mail = mail
- for key, value in headers.items():
- self.mail[key] = encode_header(key, value)
-
- @defer.inlineCallbacks
- def apply(self, ui):
- # TODO: fill with default header (per account)
- # get From header
- if not 'From' in self.mail:
- accounts = ui.accountman.get_accounts()
- if len(accounts) == 0:
- ui.notify('no accounts set')
- return
- elif len(accounts) == 1:
- a = accounts[0]
- else:
- cmpl = AccountCompleter(ui.accountman)
- fromaddress = yield ui.prompt(prefix='From>', completer=cmpl,
- tab=1)
- validaddresses = [a.address for a in accounts] + [None]
- while fromaddress not in validaddresses: # TODO: not cool
- ui.notify('no account for this address. (<esc> cancels)')
- fromaddress = yield ui.prompt(prefix='From>',
- completer=cmpl)
- if not fromaddress:
- ui.notify('canceled')
- return
- a = ui.accountman.get_account_by_address(fromaddress)
- self.mail['From'] = "%s <%s>" % (a.realname, a.address)
-
- #get To header
- if 'To' not in self.mail:
- name, addr = email.Utils.parseaddr(unicode(self.mail.get('From')))
- a = ui.accountman.get_account_by_address(addr)
-
- allbooks = not settings.config.getboolean('general',
- 'complete_matching_abook_only')
- ui.logger.debug(allbooks)
- abooks = ui.accountman.get_addressbooks(order=[a],
- append_remaining=allbooks)
- ui.logger.debug(abooks)
- to = yield ui.prompt(prefix='To>',
- completer=ContactsCompleter(abooks))
- if to == None:
- ui.notify('canceled')
- return
- self.mail['To'] = encode_header('to', to)
- if settings.config.getboolean('general', 'ask_subject') and \
- not 'Subject' in self.mail:
- subject = yield ui.prompt(prefix='Subject>')
- if subject == None:
- ui.notify('canceled')
- return
- self.mail['Subject'] = encode_header('subject', subject)
-
- ui.apply_command(EnvelopeEditCommand(mail=self.mail))
-
-
-# SEARCH
-class RetagPromptCommand(Command):
- """start a commandprompt to retag selected threads' tags
- this is needed to fill the prompt with the current tags..
- """
- def apply(self, ui):
- thread = ui.current_buffer.get_selected_thread()
- if not thread:
- return
- initial_tagstring = ','.join(thread.get_tags())
- ui.commandprompt('retag ' + initial_tagstring)
-
-
-class RetagCommand(Command):
- """tag selected thread"""
- def __init__(self, tagsstring=u'', thread=None, **kwargs):
- self.tagsstring = tagsstring
- self.thread = thread
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.thread:
- self.thread = ui.current_buffer.get_selected_thread()
- if not self.thread:
- return
- tags = filter(lambda x: x, self.tagsstring.split(','))
- ui.logger.info("got %s:%s" % (self.tagsstring, tags))
- try:
- self.thread.set_tags(tags)
- except DatabaseROError:
- ui.notify('index in read-only mode', priority='error')
- return
-
- # flush index
- ui.apply_command(FlushCommand())
-
- # refresh selected threadline
- sbuffer = ui.current_buffer
- threadwidget = sbuffer.get_selected_threadline()
- threadwidget.rebuild() # rebuild and redraw the line
-
-
-class RefineCommand(Command):
- """refine the query of the currently open searchbuffer"""
- def __init__(self, query=None, **kwargs):
- self.querystring = query
- Command.__init__(self, **kwargs)
-
- @defer.inlineCallbacks
- def apply(self, ui):
- if self.querystring:
- if self.querystring == '*':
- s = 'really search for all threads? This takes a while..'
- if (yield ui.choice(s, select='yes', cancel='no')) == 'no':
- return
- sbuffer = ui.current_buffer
- oldquery = sbuffer.querystring
- if self.querystring not in [None, oldquery]:
- sbuffer.querystring = self.querystring
- sbuffer = ui.current_buffer
- sbuffer.rebuild()
- ui.update()
- else:
- ui.notify('empty query string')
-
-
-class RefinePromptCommand(Command):
- """prompt to change current search buffers query"""
- def apply(self, ui):
- sbuffer = ui.current_buffer
- oldquery = sbuffer.querystring
- ui.commandprompt('refine ' + oldquery)
-
-
-# THREAD
-class ReplyCommand(Command):
- """format reply for currently selected message and open envelope for it"""
- def __init__(self, message=None, groupreply=False, **kwargs):
- """
- :param message: the original message to reply to
- :type message: `alot.message.Message`
- :param groupreply: copy other recipients from Bcc/Cc/To to the reply
- :type groupreply: boolean
- """
- self.message = message
- self.groupreply = groupreply
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.message:
- self.message = ui.current_buffer.get_selected_message()
- mail = self.message.get_email()
- # set body text
- name, address = self.message.get_author()
- timestamp = self.message.get_date()
- qf = settings.hooks.get('reply_prefix')
- if qf:
- quotestring = qf(name, address, timestamp,
- ui=ui, dbm=ui.dbman, aman=ui.accountman,
- log=ui.logger, config=settings.config)
- else:
- quotestring = 'Quoting %s (%s)\n' % (name, timestamp)
- mailcontent = quotestring
- for line in self.message.accumulate_body().splitlines():
- mailcontent += '>' + line + '\n'
-
- Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
- bodypart = MIMEText(mailcontent.encode('utf-8'), 'plain', 'UTF-8')
- reply = MIMEMultipart()
- reply.attach(bodypart)
-
- # copy subject
- subject = mail.get('Subject', '')
- if not subject.startswith('Re:'):
- subject = 'Re: ' + subject
- reply['Subject'] = Header(subject.encode('utf-8'), 'UTF-8').encode()
-
- # set From
- my_addresses = ui.accountman.get_addresses()
- matched_address = ''
- in_to = [a for a in my_addresses if a in mail.get('To', '')]
- if in_to:
- matched_address = in_to[0]
- else:
- cc = mail.get('Cc', '') + mail.get('Bcc', '')
- in_cc = [a for a in my_addresses if a in cc]
- if in_cc:
- matched_address = in_cc[0]
- if matched_address:
- account = ui.accountman.get_account_by_address(matched_address)
- fromstring = '%s <%s>' % (account.realname, account.address)
- reply['From'] = encode_header('From', fromstring)
-
- # set To
- del(reply['To'])
- if self.groupreply:
- cleared = self.clear_my_address(my_addresses, mail.get('To', ''))
- if cleared:
- logging.info(mail['From'] + ', ' + cleared)
- to = mail['From'] + ', ' + cleared
- reply['To'] = encode_header('To', to)
- logging.info(reply['To'])
- else:
- reply['To'] = encode_header('To', mail['From'])
- # copy cc and bcc for group-replies
- if 'Cc' in mail:
- cc = self.clear_my_address(my_addresses, mail['Cc'])
- reply['Cc'] = encode_header('Cc', cc)
- if 'Bcc' in mail:
- bcc = self.clear_my_address(my_addresses, mail['Bcc'])
- reply['Bcc'] = encode_header('Bcc', bcc)
- else:
- reply['To'] = encode_header('To', mail['From'])
-
- # set In-Reply-To header
- del(reply['In-Reply-To'])
- reply['In-Reply-To'] = '<%s>' % self.message.get_message_id()
-
- # set References header
- old_references = mail.get('References', '')
- if old_references:
- old_references = old_references.split()
- references = old_references[-8:]
- if len(old_references) > 8:
- references = old_references[:1] + references
- references.append('<%s>' % self.message.get_message_id())
- reply['References'] = ' '.join(references)
- else:
- reply['References'] = '<%s>' % self.message.get_message_id()
-
- ui.apply_command(ComposeCommand(mail=reply))
-
- def clear_my_address(self, my_addresses, value):
- new_value = []
- for entry in value.split(','):
- if not [a for a in my_addresses if a in entry]:
- new_value.append(entry.strip())
- return ', '.join(new_value)
-
-
-class ForwardCommand(Command):
- def __init__(self, message=None, inline=False, **kwargs):
- """
- :param message: the original message to forward. If None, the currently
- selected one is used
- :type message: `alot.message.Message`
- :param inline: Copy originals body text instead of attaching the whole
- mail
- :type inline: boolean
- """
- self.message = message
- self.inline = inline
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.message:
- self.message = ui.current_buffer.get_selected_message()
- mail = self.message.get_email()
-
- reply = MIMEMultipart()
- Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
- if self.inline: # inline mode
- # set body text
- name, address = self.message.get_author()
- timestamp = self.message.get_date()
- qf = settings.hooks.get('forward_prefix')
- if qf:
- quote = qf(name, address, timestamp,
- ui=ui, dbm=ui.dbman, aman=ui.accountman,
- log=ui.logger, config=settings.config)
- else:
- quote = 'Forwarded message from %s (%s):\n' % (name, timestamp)
- mailcontent = quote
- for line in self.message.accumulate_body().splitlines():
- mailcontent += '>' + line + '\n'
-
- bodypart = MIMEText(mailcontent.encode('utf-8'), 'plain', 'UTF-8')
- reply.attach(bodypart)
-
- else: # attach original mode
- # create empty text msg
- bodypart = MIMEText('', 'plain', 'UTF-8')
- reply.attach(bodypart)
- # attach original msg
- reply.attach(mail)
-
- # copy subject
- subject = mail.get('Subject', '')
- subject = 'Fwd: ' + subject
- reply['Subject'] = Header(subject.encode('utf-8'), 'UTF-8').encode()
-
- # set From
- my_addresses = ui.accountman.get_addresses()
- matched_address = ''
- in_to = [a for a in my_addresses if a in mail.get('To', '')]
- if in_to:
- matched_address = in_to[0]
- else:
- cc = mail.get('Cc', '') + mail.get('Bcc', '')
- in_cc = [a for a in my_addresses if a in cc]
- if in_cc:
- matched_address = in_cc[0]
- if matched_address:
- account = ui.accountman.get_account_by_address(matched_address)
- fromstring = '%s <%s>' % (account.realname, account.address)
- reply['From'] = encode_header('From', fromstring)
- ui.apply_command(ComposeCommand(mail=reply))
-
-
-class FoldMessagesCommand(Command):
- def __init__(self, all=False, visible=None, **kwargs):
- self.all = all
- self.visible = visible
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- lines = []
- if not self.all:
- lines.append(ui.current_buffer.get_selection())
- else:
- lines = ui.current_buffer.get_message_widgets()
-
- for widget in lines:
- # in case the thread is yet unread, remove this tag
- msg = widget.get_message()
- if self.visible or (self.visible == None and widget.folded):
- if 'unread' in msg.get_tags():
- msg.remove_tags(['unread'])
- ui.apply_command(FlushCommand())
- widget.rebuild()
- widget.fold(visible=True)
- else:
- widget.fold(visible=False)
-
-
-class ToggleHeaderCommand(Command):
- def apply(self, ui):
- msgw = ui.current_buffer.get_selection()
- msgw.toggle_full_header()
-
-
-class PipeCommand(Command):
- def __init__(self, command, whole_thread=False, separately=False,
- noop_msg='no command specified', confirm_msg='',
- done_msg='done', **kwargs):
- Command.__init__(self, **kwargs)
- self.cmd = command
- self.whole_thread = whole_thread
- self.separately = separately
- self.noop_msg = noop_msg
- self.confirm_msg = confirm_msg
- self.done_msg = done_msg
-
- @defer.inlineCallbacks
- def apply(self, ui):
- # abort if command unset
- if not self.cmd:
- ui.notify(self.noop_msg, priority='error')
- return
-
- # get messages to pipe
- if self.whole_thread:
- thread = ui.current_buffer.get_selected_thread()
- if not thread:
- return
- to_print = thread.get_messages().keys()
- else:
- to_print = [ui.current_buffer.get_selected_message()]
-
- # ask for confirmation if needed
- if self.confirm_msg:
- if (yield ui.choice(self.confirm_msg, select='yes',
- cancel='no')) == 'no':
- return
-
- # prepare message sources
- mailstrings = [m.get_email().as_string() for m in to_print]
- if not self.separately:
- mailstrings = ['\n\n'.join(mailstrings)]
-
- # do teh monkey
- for mail in mailstrings:
- out, err = helper.pipe_to_command(self.cmd, mail)
- if err:
- ui.notify(err, priority='error')
- return
-
- # display 'done' message
- if self.done_msg:
- ui.notify(self.done_msg)
-
-
-class PrintCommand(PipeCommand):
- def __init__(self, whole_thread=False, separately=False, **kwargs):
- # get print command
- cmd = settings.config.get('general', 'print_cmd', fallback='')
-
- # set up notification strings
- if whole_thread:
- confirm_msg = 'print all messages in thread?'
- ok_msg = 'printed thread using %s' % cmd
- else:
- confirm_msg = 'print selected message?'
- ok_msg = 'printed message using %s' % cmd
-
- # no print cmd set
- noop_msg = 'no print command specified. Set "print_cmd" in the '\
- 'global section.'
- PipeCommand.__init__(self, cmd, whole_thread=whole_thread,
- separately=separately,
- noop_msg=noop_msg, confirm_msg=confirm_msg,
- done_msg=ok_msg, **kwargs)
-
-
-class SaveAttachmentCommand(Command):
- def __init__(self, all=False, path=None, **kwargs):
- Command.__init__(self, **kwargs)
- self.all = all
- self.path = path
-
- @defer.inlineCallbacks
- def apply(self, ui):
- pcomplete = completion.PathCompleter()
- if self.all:
- msg = ui.current_buffer.get_selected_message()
- if not self.path:
- self.path = yield ui.prompt(prefix='save attachments to:',
- text=os.path.join('~', ''),
- completer=pcomplete)
- if self.path:
- if os.path.isdir(os.path.expanduser(self.path)):
- for a in msg.get_attachments():
- dest = a.save(self.path)
- name = a.get_filename()
- if name:
- ui.notify('saved %s as: %s' % (name, dest))
- else:
- ui.notify('saved attachment as: %s' % dest)
- else:
- ui.notify('not a directory: %s' % self.path,
- priority='error')
- else:
- ui.notify('canceled')
- else: # save focussed attachment
- focus = ui.get_deep_focus()
- if isinstance(focus, widgets.AttachmentWidget):
- attachment = focus.get_attachment()
- filename = attachment.get_filename()
- if not self.path:
- msg = 'save attachment (%s) to:' % filename
- initialtext = os.path.join('~', filename)
- self.path = yield ui.prompt(prefix=msg,
- completer=pcomplete,
- text=initialtext)
- if self.path:
- try:
- dest = attachment.save(self.path)
- ui.notify('saved attachment as: %s' % dest)
- except (IOError, OSError), e:
- ui.notify(str(e), priority='error')
- else:
- ui.notify('canceled')
-
-
-class OpenAttachmentCommand(Command):
- """displays an attachment according to mailcap"""
- def __init__(self, attachment, **kwargs):
- Command.__init__(self, **kwargs)
- self.attachment = attachment
-
- def apply(self, ui):
- logging.info('open attachment')
- mimetype = self.attachment.get_content_type()
- handler = settings.get_mime_handler(mimetype)
- if handler:
- path = self.attachment.save(tempfile.gettempdir())
- handler = handler.replace('%s', '{}')
-
- def afterwards():
- os.remove(path)
- ui.apply_command(ExternalCommand(handler, path=path,
- on_success=afterwards,
- in_thread=True))
- else:
- ui.notify('unknown mime type')
-
-
-class ThreadSelectCommand(Command):
- def apply(self, ui):
- focus = ui.get_deep_focus()
- if isinstance(focus, widgets.MessageSummaryWidget):
- ui.apply_command(FoldMessagesCommand())
- elif isinstance(focus, widgets.AttachmentWidget):
- logging.info('open attachment')
- ui.apply_command(OpenAttachmentCommand(focus.get_attachment()))
- else:
- logging.info('unknown widget %s' % focus)
-
-
-### ENVELOPE
-class EnvelopeOpenCommand(Command):
- """open a new envelope buffer"""
- def __init__(self, mail=None, **kwargs):
- self.mail = mail
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- ui.buffer_open(buffer.EnvelopeBuffer(ui, mail=self.mail))
-
-
-class EnvelopeEditCommand(Command):
- """re-edits mail in from envelope buffer"""
- def __init__(self, mail=None, **kwargs):
- self.mail = mail
- self.openNew = (mail != None)
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
- if not self.mail:
- self.mail = ui.current_buffer.get_email()
-
- def openEnvelopeFromTmpfile():
- # This parses the input from the tempfile.
- # we do this ourselves here because we want to be able to
- # just type utf-8 encoded stuff into the tempfile and let alot
- # worry about encodings.
-
- # get input
- f = open(tf.name)
- enc = settings.config.get('general', 'editor_writes_encoding')
- editor_input = f.read().decode(enc)
- headertext, bodytext = editor_input.split('\n\n', 1)
-
- # call post-edit translate hook
- translate = settings.hooks.get('post_edit_translate')
- if translate:
- bodytext = translate(bodytext, ui=ui, dbm=ui.dbman,
- aman=ui.accountman, log=ui.logger,
- config=settings.config)
-
- # go through multiline, utf-8 encoded headers
- key = value = None
- for line in headertext.splitlines():
- if re.match('\w+:', line): # new k/v pair
- if key and value: # save old one from stack
- del self.mail[key] # ensure unique values in mails
- self.mail[key] = encode_header(key, value) # save
- key, value = line.strip().split(':', 1) # parse new pair
- elif key and value: # append new line without key prefix
- value += line
- if key and value: # save last one if present
- del self.mail[key]
- self.mail[key] = encode_header(key, value)
-
- if self.mail.is_multipart():
- for part in self.mail.walk():
- if part.get_content_maintype() == 'text':
- if 'Content-Transfer-Encoding' in part:
- del(part['Content-Transfer-Encoding'])
- part.set_payload(bodytext, 'utf-8')
- break
-
- f.close()
- os.unlink(tf.name)
- if self.openNew:
- ui.apply_command(EnvelopeOpenCommand(mail=self.mail))
- else:
- ui.current_buffer.set_email(self.mail)
-
- # decode header
- edit_headers = ['Subject', 'To', 'From']
- headertext = u''
- for key in edit_headers:
- value = u''
- if key in self.mail:
- value = decode_header(self.mail.get(key, ''))
- headertext += '%s: %s\n' % (key, value)
-
- if self.mail.is_multipart():
- for part in self.mail.walk():
- if part.get_content_maintype() == 'text':
- bodytext = decode_to_unicode(part)
- break
- else:
- bodytext = decode_to_unicode(self.mail)
-
- # call pre-edit translate hook
- translate = settings.hooks.get('pre_edit_translate')
- if translate:
- bodytext = translate(bodytext, ui=ui, dbm=ui.dbman,
- aman=ui.accountman, log=ui.logger,
- config=settings.config)
-
- #write stuff to tempfile
- tf = tempfile.NamedTemporaryFile(delete=False)
- content = '%s\n\n%s' % (headertext,
- bodytext)
- tf.write(content.encode('utf-8'))
- tf.flush()
- tf.close()
- cmd = EditCommand(tf.name, on_success=openEnvelopeFromTmpfile,
- refocus=False)
- ui.apply_command(cmd)
-
-
-class EnvelopeSetCommand(Command):
- """sets header fields of mail open in envelope buffer"""
-
- def __init__(self, key='', value=u'', replace=True, **kwargs):
- self.key = key
- self.value = encode_header(key, value)
- self.replace = replace
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- envelope = ui.current_buffer
- mail = envelope.get_email()
- if self.replace:
- del(mail[self.key])
- mail[self.key] = self.value
- envelope.rebuild()
-
-
-class EnvelopeRefineCommand(Command):
- """prompt to change current value of header field"""
-
- def __init__(self, key='', **kwargs):
- Command.__init__(self, **kwargs)
- self.key = key
-
- def apply(self, ui):
- mail = ui.current_buffer.get_email()
- value = decode_header(mail.get(self.key, ''))
- ui.commandprompt('set %s %s' % (self.key, value))
-
-
-class EnvelopeSendCommand(Command):
- @defer.inlineCallbacks
- def apply(self, ui):
- envelope = ui.current_buffer
- mail = envelope.get_email()
- frm = decode_header(mail.get('From'))
- sname, saddr = email.Utils.parseaddr(frm)
- account = ui.accountman.get_account_by_address(saddr)
- if account:
- # attach signature file if present
- if account.signature:
- sig = os.path.expanduser(account.signature)
- if os.path.isfile(sig):
- if account.signature_filename:
- name = account.signature_filename
- else:
- name = None
- helper.attach(sig, mail, filename=name)
- else:
- ui.notify('could not locate signature: %s' % sig,
- priority='error')
- if (yield ui.choice('send without signature',
- select='yes', cancel='no')) == 'no':
- return
-
- clearme = ui.notify('sending..', timeout=-1, block=False)
- reason = account.send_mail(mail)
- ui.clear_notify([clearme])
- if not reason: # sucessfully send mail
- cmd = BufferCloseCommand(buffer=envelope)
- ui.apply_command(cmd)
- ui.notify('mail send successful')
- else:
- ui.notify('failed to send: %s' % reason, priority='error')
- else:
- ui.notify('failed to send: no account set up for %s' % saddr,
- priority='error')
-
-
-class EnvelopeAttachCommand(Command):
- def __init__(self, path=None, mail=None, **kwargs):
- Command.__init__(self, **kwargs)
- self.mail = mail
- self.path = path
-
- def apply(self, ui):
- msg = self.mail
- if not msg:
- msg = ui.current_buffer.get_email()
-
- if self.path:
- files = filter(os.path.isfile,
- glob.glob(os.path.expanduser(self.path)))
- if not files:
- ui.notify('no matches, abort')
- return
- else:
- ui.notify('no files specified, abort')
-
- logging.info("attaching: %s" % files)
- for path in files:
- helper.attach(path, msg)
-
- if not self.mail: # set the envelope msg iff we got it from there
- ui.current_buffer.set_email(msg)
-
-
-# TAGLIST
-class TaglistSelectCommand(Command):
- def apply(self, ui):
- tagstring = ui.current_buffer.get_selected_tag()
- cmd = SearchCommand(query='tag:%s' % tagstring)
- ui.apply_command(cmd)
-
-
-class SendKeypressCommand(Command):
- def __init__(self, key, **kwargs):
- Command.__init__(self, **kwargs)
- self.key = key
-
- def apply(self, ui):
- ui.keypress(self.key)
-
-
-class HelpCommand(Command):
- def __init__(self, commandline='', **kwargs):
- Command.__init__(self, **kwargs)
- self.commandline = commandline.strip()
-
- def apply(self, ui):
- if self.commandline:
- cmd = self.commandline.split(' ', 1)[0]
- # TODO: how to I access COMMANDS from below?
- ui.notify('no help for \'%s\'' % cmd, priority='error')
- titletext = 'help for %s' % cmd
- body = urwid.Text('helpstring')
- return
- else:
- # get mappings
- modemaps = dict(settings.config.items('%s-maps' % ui.mode))
- globalmaps = dict(settings.config.items('global-maps'))
-
- # build table
- maxkeylength = len(max((modemaps).keys() + globalmaps.keys(),
- key=len))
- keycolumnwidth = maxkeylength + 2
-
- linewidgets = []
- # mode specific maps
- linewidgets.append(urwid.Text(('helptexth1',
- '\n%s-mode specific maps' % ui.mode)))
- for (k, v) in modemaps.items():
- line = urwid.Columns([('fixed', keycolumnwidth, urwid.Text(k)),
- urwid.Text(v)])
- linewidgets.append(line)
-
- # global maps
- linewidgets.append(urwid.Text(('helptexth1',
- '\nglobal maps')))
- for (k, v) in globalmaps.items():
- if k not in modemaps:
- line = urwid.Columns(
- [('fixed', keycolumnwidth, urwid.Text(k)),
- urwid.Text(v)])
- linewidgets.append(line)
-
- body = urwid.ListBox(linewidgets)
- ckey = 'cancel'
- titletext = 'Bindings Help (%s cancels)' % ckey
-
- box = widgets.DialogBox(body, titletext,
- bodyattr='helptext',
- titleattr='helptitle')
-
- # put promptwidget as overlay on main widget
- overlay = urwid.Overlay(box, ui.mainframe, 'center',
- ('relative', 70), 'middle',
- ('relative', 70))
- ui.show_as_root_until_keypress(overlay, 'cancel')
-
-
COMMANDS = {
'search': {
- 'refine': (RefineCommand, {}),
- 'refineprompt': (RefinePromptCommand, {}),
- 'openthread': (OpenThreadCommand, {}),
- 'toggletag': (ToggleThreadTagCommand, {'tags': ['inbox']}),
- 'retag': (RetagCommand, {}),
- 'retagprompt': (RetagPromptCommand, {}),
},
'envelope': {
- 'attach': (EnvelopeAttachCommand, {}),
- 'send': (EnvelopeSendCommand, {}),
- 'reedit': (EnvelopeEditCommand, {}),
- 'refine': (EnvelopeRefineCommand, {}),
- 'set': (EnvelopeSetCommand, {}),
},
'bufferlist': {
- 'closefocussed': (BufferCloseCommand, {'focussed': True}),
- 'openfocussed': (BufferFocusCommand, {}),
},
'taglist': {
- 'select': (TaglistSelectCommand, {}),
},
'thread': {
- 'reply': (ReplyCommand, {}),
- 'groupreply': (ReplyCommand, {'groupreply': True}),
- 'forward': (ForwardCommand, {}),
- 'fold': (FoldMessagesCommand, {'visible': False}),
- 'pipeto': (PipeCommand, {}),
- 'print': (PrintCommand, {}),
- 'unfold': (FoldMessagesCommand, {'visible': True}),
- 'select': (ThreadSelectCommand, {}),
- 'save': (SaveAttachmentCommand, {}),
- 'toggleheaders': (ToggleHeaderCommand, {}),
},
'global': {
- 'move': (SendKeypressCommand, {}),
- 'cancel': (SendKeypressCommand, {'key': 'cancel'}),
- 'select': (SendKeypressCommand, {'key': 'select'}),
- 'sendkey': (SendKeypressCommand, {}),
- 'bnext': (BufferFocusCommand, {'offset': 1}),
- 'bprevious': (BufferFocusCommand, {'offset': -1}),
- 'bufferlist': (OpenBufferlistCommand, {}),
- 'bclose': (BufferCloseCommand, {}),
- 'compose': (ComposeCommand, {}),
- 'edit': (EditCommand, {}),
- 'exit': (ExitCommand, {}),
- 'flush': (FlushCommand, {}),
- 'prompt': (PromptCommand, {}),
- 'pyshell': (PythonShellCommand, {}),
- 'refresh': (RefreshCommand, {}),
- 'search': (SearchCommand, {}),
- 'shellescape': (ExternalCommand, {}),
- 'taglist': (TagListCommand, {}),
- 'help': (HelpCommand, {}),
}
}
+class registerCommand(object):
+ def __init__(self, mode, name, defaultparms):
+ self.mode = mode
+ self.name = name
+ self.defaultparms = defaultparms
+
+ def __call__(self, klass):
+ COMMANDS[self.mode][self.name] = (klass, self.defaultparms)
+ return klass
+
+
def commandfactory(cmdname, mode='global', **kwargs):
if cmdname in COMMANDS[mode]:
(cmdclass, parms) = COMMANDS[mode][cmdname]
@@ -1312,3 +184,5 @@ def interpret_commandline(cmdline, mode):
return commandfactory(cmd, mode=mode)
else:
return None
+
+
diff --git a/alot/commands/__init__.py b/alot/commands/__init__.py
deleted file mode 100644
index 9598fda9..00000000
--- a/alot/commands/__init__.py
+++ /dev/null
@@ -1,188 +0,0 @@
-
-import os
-import re
-import code
-import glob
-import logging
-import threading
-import subprocess
-import shlex
-import email
-import tempfile
-from email import Charset
-from email.header import Header
-from email.mime.text import MIMEText
-from email.mime.multipart import MIMEMultipart
-import urwid
-
-import buffer
-import settings
-import widgets
-import completion
-import helper
-from db import DatabaseROError
-from db import DatabaseLockedError
-from completion import ContactsCompleter
-from completion import AccountCompleter
-from message import decode_to_unicode
-from message import decode_header
-from message import encode_header
-
-
-class Command(object):
- """base class for commands"""
- def __init__(self, prehook=None, posthook=None):
- self.prehook = prehook
- self.posthook = posthook
- self.undoable = False
- self.help = self.__doc__
-
- def apply(self, caller):
- pass
-
-
-COMMANDS = {
- 'search': {
- },
- 'envelope': {
- },
- 'bufferlist': {
- },
- 'taglist': {
- },
- 'thread': {
- },
- 'global': {
- }
-}
-
-
-class registerCommand(object):
- def __init__(self, mode, name, defaultparms):
- self.mode = mode
-
- def __call__(self, klass):
- COMMANDS[mode][name] = (klass, defaultparms)
- return klass
-
-
-def commandfactory(cmdname, mode='global', **kwargs):
- if cmdname in COMMANDS[mode]:
- (cmdclass, parms) = COMMANDS[mode][cmdname]
- elif cmdname in COMMANDS['global']:
- (cmdclass, parms) = COMMANDS['global'][cmdname]
- else:
- logging.error('there is no command %s' % cmdname)
- parms = parms.copy()
- parms.update(kwargs)
- for (key, value) in kwargs.items():
- if callable(value):
- parms[key] = value()
- else:
- parms[key] = value
-
- parms['prehook'] = settings.hooks.get('pre_' + cmdname)
- parms['posthook'] = settings.hooks.get('post_' + cmdname)
-
- logging.debug('cmd parms %s' % parms)
- return cmdclass(**parms)
-
-
-def interpret_commandline(cmdline, mode):
- # TODO: use argparser here!
- if not cmdline:
- return None
- logging.debug('mode:%s got commandline "%s"' % (mode, cmdline))
- args = cmdline.split(' ', 1)
- cmd = args[0]
- if args[1:]:
- params = args[1]
- else:
- params = ''
-
- # unfold aliases
- if settings.config.has_option('command-aliases', cmd):
- cmd = settings.config.get('command-aliases', cmd)
-
- # allow to shellescape without a space after '!'
- if cmd.startswith('!'):
- params = cmd[1:] + ' ' + params
- cmd = 'shellescape'
-
- # check if this command makes sense in current mode
- if cmd not in COMMANDS[mode] and cmd not in COMMANDS['global']:
- logging.debug('unknown command: %s' % (cmd))
- return None
-
- if cmd == 'search':
- return commandfactory(cmd, mode=mode, query=params)
- if cmd in ['move', 'sendkey']:
- return commandfactory(cmd, mode=mode, key=params)
- elif cmd == 'compose':
- h = {}
- if params:
- h = {'To': params}
- return commandfactory(cmd, mode=mode, headers=h)
- elif cmd == 'attach':
- return commandfactory(cmd, mode=mode, path=params)
- elif cmd == 'help':
- return commandfactory(cmd, mode=mode, commandline=params)
- elif cmd == 'forward':
- return commandfactory(cmd, mode=mode, inline=(params == '--inline'))
- elif cmd == 'prompt':
- return commandfactory(cmd, mode=mode, startstring=params)
- elif cmd == 'refine':
- if mode == 'search':
- return commandfactory(cmd, mode=mode, query=params)
- elif mode == 'envelope':
- return commandfactory(cmd, mode=mode, key=params)
-
- elif cmd == 'retag':
- return commandfactory(cmd, mode=mode, tagsstring=params)
- elif cmd == 'shellescape':
- return commandfactory(cmd, mode=mode, commandstring=params)
- elif cmd == 'set':
- key, value = params.split(' ', 1)
- return commandfactory(cmd, mode=mode, key=key, value=value)
- elif cmd == 'toggletag':
- return commandfactory(cmd, mode=mode, tags=params.split())
- elif cmd == 'fold':
- return commandfactory(cmd, mode=mode, all=(params == '--all'))
- elif cmd == 'unfold':
- return commandfactory(cmd, mode=mode, all=(params == '--all'))
- elif cmd == 'save':
- args = params.split(' ')
- allset = False
- pathset = None
- if args:
- if args[0] == '--all':
- allset = True
- pathset = ' '.join(args[1:])
- else:
- pathset = params
- return commandfactory(cmd, mode=mode, all=allset, path=pathset)
- elif cmd == 'edit':
- filepath = os.path.expanduser(params)
- if os.path.isfile(filepath):
- return commandfactory(cmd, mode=mode, path=filepath)
- elif cmd == 'print':
- args = [a.strip() for a in params.split()]
- return commandfactory(cmd, mode=mode,
- whole_thread=('--thread' in args),
- separately=('--separately' in args))
- elif cmd == 'pipeto':
- return commandfactory(cmd, mode=mode, command=params)
-
- elif not params and cmd in ['exit', 'flush', 'pyshell', 'taglist',
- 'bclose', 'compose', 'openfocussed',
- 'closefocussed', 'bnext', 'bprevious', 'retag',
- 'refresh', 'bufferlist', 'refineprompt',
- 'reply', 'open', 'groupreply', 'bounce',
- 'openthread', 'toggleheaders', 'send',
- 'cancel', 'reedit', 'select', 'retagprompt']:
- return commandfactory(cmd, mode=mode)
- else:
- return None
-
-
-__all__ = list(filename[:-3] for filename in glob.glob1(os.path.dirname(__file__), '*.py'))
diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py
deleted file mode 100644
index b3aed3d6..00000000
--- a/alot/commands/envelope.py
+++ /dev/null
@@ -1,197 +0,0 @@
-from commands import Command, registerCommand
-from twisted.internet import defer
-
-class EnvelopeEditCommand(Command):
- """re-edits mail in from envelope buffer"""
- def __init__(self, mail=None, **kwargs):
- self.mail = mail
- self.openNew = (mail != None)
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
- if not self.mail:
- self.mail = ui.current_buffer.get_email()
-
- def openEnvelopeFromTmpfile():
- # This parses the input from the tempfile.
- # we do this ourselves here because we want to be able to
- # just type utf-8 encoded stuff into the tempfile and let alot
- # worry about encodings.
-
- # get input
- f = open(tf.name)
- enc = settings.config.get('general', 'editor_writes_encoding')
- editor_input = f.read().decode(enc)
- headertext, bodytext = editor_input.split('\n\n', 1)
-
- # call post-edit translate hook
- translate = settings.hooks.get('post_edit_translate')
- if translate:
- bodytext = translate(bodytext, ui=ui, dbm=ui.dbman,
- aman=ui.accountman, log=ui.logger,
- config=settings.config)
-
- # go through multiline, utf-8 encoded headers
- key = value = None
- for line in headertext.splitlines():
- if re.match('\w+:', line): # new k/v pair
- if key and value: # save old one from stack
- del self.mail[key] # ensure unique values in mails
- self.mail[key] = encode_header(key, value) # save
- key, value = line.strip().split(':', 1) # parse new pair
- elif key and value: # append new line without key prefix
- value += line
- if key and value: # save last one if present
- del self.mail[key]
- self.mail[key] = encode_header(key, value)
-
- if self.mail.is_multipart():
- for part in self.mail.walk():
- if part.get_content_maintype() == 'text':
- if 'Content-Transfer-Encoding' in part:
- del(part['Content-Transfer-Encoding'])
- part.set_payload(bodytext, 'utf-8')
- break
-
- f.close()
- os.unlink(tf.name)
- if self.openNew:
- ui.apply_command(EnvelopeOpenCommand(mail=self.mail))
- else:
- ui.current_buffer.set_email(self.mail)
-
- # decode header
- edit_headers = ['Subject', 'To', 'From']
- headertext = u''
- for key in edit_headers:
- value = u''
- if key in self.mail:
- value = decode_header(self.mail.get(key, ''))
- headertext += '%s: %s\n' % (key, value)
-
- if self.mail.is_multipart():
- for part in self.mail.walk():
- if part.get_content_maintype() == 'text':
- bodytext = decode_to_unicode(part)
- break
- else:
- bodytext = decode_to_unicode(self.mail)
-
- # call pre-edit translate hook
- translate = settings.hooks.get('pre_edit_translate')
- if translate:
- bodytext = translate(bodytext, ui=ui, dbm=ui.dbman,
- aman=ui.accountman, log=ui.logger,
- config=settings.config)
-
- #write stuff to tempfile
- tf = tempfile.NamedTemporaryFile(delete=False)
- content = '%s\n\n%s' % (headertext,
- bodytext)
- tf.write(content.encode('utf-8'))
- tf.flush()
- tf.close()
- cmd = EditCommand(tf.name, on_success=openEnvelopeFromTmpfile,
- refocus=False)
- ui.apply_command(cmd)
-
-
-class EnvelopeSetCommand(Command):
- """sets header fields of mail open in envelope buffer"""
-
- def __init__(self, key='', value=u'', replace=True, **kwargs):
- self.key = key
- self.value = encode_header(key, value)
- self.replace = replace
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- envelope = ui.current_buffer
- mail = envelope.get_email()
- if self.replace:
- del(mail[self.key])
- mail[self.key] = self.value
- envelope.rebuild()
-
-
-class EnvelopeRefineCommand(Command):
- """prompt to change current value of header field"""
-
- def __init__(self, key='', **kwargs):
- Command.__init__(self, **kwargs)
- self.key = key
-
- def apply(self, ui):
- mail = ui.current_buffer.get_email()
- value = mail.get(self.key, '')
- ui.commandprompt('set %s %s' % (self.key, value))
-
-
-class EnvelopeSendCommand(Command):
- @defer.inlineCallbacks
- def apply(self, ui):
- envelope = ui.current_buffer
- mail = envelope.get_email()
- frm = decode_header(mail.get('From'))
- sname, saddr = email.Utils.parseaddr(frm)
- account = ui.accountman.get_account_by_address(saddr)
- if account:
- # attach signature file if present
- if account.signature:
- sig = os.path.expanduser(account.signature)
- if os.path.isfile(sig):
- if account.signature_filename:
- name = account.signature_filename
- else:
- name = None
- helper.attach(sig, mail, filename=name)
- else:
- ui.notify('could not locate signature: %s' % sig,
- priority='error')
- if (yield ui.choice('send without signature',
- select='yes', cancel='no')) == 'no':
- return
-
- clearme = ui.notify('sending..', timeout=-1, block=False)
- reason = account.send_mail(mail)
- ui.clear_notify([clearme])
- if not reason: # sucessfully send mail
- cmd = BufferCloseCommand(buffer=envelope)
- ui.apply_command(cmd)
- ui.notify('mail send successful')
- else:
- ui.notify('failed to send: %s' % reason, priority='error')
- else:
- ui.notify('failed to send: no account set up for %s' % saddr,
- priority='error')
-
-
-class EnvelopeAttachCommand(Command):
- def __init__(self, path=None, mail=None, **kwargs):
- Command.__init__(self, **kwargs)
- self.mail = mail
- self.path = path
-
- def apply(self, ui):
- msg = self.mail
- if not msg:
- msg = ui.current_buffer.get_email()
-
- if self.path:
- files = filter(os.path.isfile,
- glob.glob(os.path.expanduser(self.path)))
- if not files:
- ui.notify('no matches, abort')
- return
- else:
- ui.notify('no files specified, abort')
-
- logging.info("attaching: %s" % files)
- for path in files:
- helper.attach(path, msg)
-
- if not self.mail: # set the envelope msg iff we got it from there
- ui.current_buffer.set_email(msg)
-
-
diff --git a/alot/commands/globals.py b/alot/commands/globals.py
deleted file mode 100644
index 4a69bb2c..00000000
--- a/alot/commands/globals.py
+++ /dev/null
@@ -1,378 +0,0 @@
-from commands import Command, registerCommand
-from twisted.internet import defer
-
-registerCommand('global', 'exit', {})
-class ExitCommand(Command):
- """shuts the MUA down cleanly"""
- @defer.inlineCallbacks
- def apply(self, ui):
- if settings.config.getboolean('general', 'bug_on_exit'):
- if (yield ui.choice('realy quit?', select='yes', cancel='no',
- msg_position='left')) == 'no':
- return
- ui.exit()
-
-
-class SearchCommand(Command):
- """open a new search buffer"""
- def __init__(self, query, **kwargs):
- """
- :param query: initial querystring
- """
- self.query = query
- Command.__init__(self, **kwargs)
-
- @defer.inlineCallbacks
- def apply(self, ui):
- if self.query:
- if self.query == '*' and ui.current_buffer:
- s = 'really search for all threads? This takes a while..'
- if (yield ui.choice(s, select='yes', cancel='no')) == 'no':
- return
- open_searches = ui.get_buffers_of_type(buffer.SearchBuffer)
- to_be_focused = None
- for sb in open_searches:
- if sb.querystring == self.query:
- to_be_focused = sb
- if to_be_focused:
- ui.buffer_focus(to_be_focused)
- else:
- ui.buffer_open(buffer.SearchBuffer(ui, self.query))
- else:
- ui.notify('empty query string')
-
-
-class PromptCommand(Command):
- """starts commandprompt"""
- def __init__(self, startstring=u'', **kwargs):
- self.startstring = startstring
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- ui.commandprompt(self.startstring)
-
-
-class RefreshCommand(Command):
- """refreshes the current buffer"""
- def apply(self, ui):
- ui.current_buffer.rebuild()
- ui.update()
-
-
-class PythonShellCommand(Command):
- """opens an interactive shell for introspection"""
- def apply(self, ui):
- ui.mainloop.screen.stop()
- code.interact(local=locals())
- ui.mainloop.screen.start()
-
-
-class BufferCloseCommand(Command):
- """close a buffer"""
- def __init__(self, buffer=None, focussed=False, **kwargs):
- """
- :param buffer: the selected buffer
- :type buffer: `alot.buffer.Buffer`
- """
- self.buffer = buffer
- self.focussed = focussed
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if self.focussed:
- #if in bufferlist, this is ugly.
- self.buffer = ui.current_buffer.get_selected_buffer()
- elif not self.buffer:
- self.buffer = ui.current_buffer
- ui.buffer_close(self.buffer)
- ui.buffer_focus(ui.current_buffer)
-
-
-class OpenBufferlistCommand(Command):
- """open a bufferlist buffer"""
- def __init__(self, filtfun=None, **kwargs):
- self.filtfun = filtfun
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- blists = ui.get_buffers_of_type(buffer.BufferlistBuffer)
- if blists:
- ui.buffer_focus(blists[0])
- else:
- ui.buffer_open(buffer.BufferlistBuffer(ui, self.filtfun))
-
-
-class TagListCommand(Command):
- """open a taglisat buffer"""
- def __init__(self, filtfun=None, **kwargs):
- self.filtfun = filtfun
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- tags = ui.dbman.get_all_tags()
- buf = buffer.TagListBuffer(ui, tags, self.filtfun)
- ui.buffers.append(buf)
- buf.rebuild()
- ui.buffer_focus(buf)
-
-
-class FlushCommand(Command):
- """Flushes writes to the index. Retries until committed"""
- def apply(self, ui):
- try:
- ui.dbman.flush()
- except DatabaseLockedError:
- timeout = settings.config.getint('general', 'flush_retry_timeout')
-
- def f(*args):
- self.apply(ui)
- ui.mainloop.set_alarm_in(timeout, f)
- ui.notify('index locked, will try again in %d secs' % timeout)
- ui.update()
- return
-
-
-class ComposeCommand(Command):
- """compose a new email and open an envelope for it"""
- def __init__(self, mail=None, headers={}, **kwargs):
- Command.__init__(self, **kwargs)
- if not mail:
- self.mail = MIMEMultipart()
- self.mail.attach(MIMEText('', 'plain', 'UTF-8'))
- else:
- self.mail = mail
- for key, value in headers.items():
- self.mail[key] = encode_header(key, value)
-
- @defer.inlineCallbacks
- def apply(self, ui):
- # TODO: fill with default header (per account)
- # get From header
- if not 'From' in self.mail:
- accounts = ui.accountman.get_accounts()
- if len(accounts) == 0:
- ui.notify('no accounts set')
- return
- elif len(accounts) == 1:
- a = accounts[0]
- else:
- cmpl = AccountCompleter(ui.accountman)
- fromaddress = yield ui.prompt(prefix='From>', completer=cmpl,
- tab=1)
- validaddresses = [a.address for a in accounts] + [None]
- while fromaddress not in validaddresses: # TODO: not cool
- ui.notify('no account for this address. (<esc> cancels)')
- fromaddress = yield ui.prompt(prefix='From>',
- completer=cmpl)
- if not fromaddress:
- ui.notify('canceled')
- return
- a = ui.accountman.get_account_by_address(fromaddress)
- self.mail['From'] = "%s <%s>" % (a.realname, a.address)
-
- #get To header
- if 'To' not in self.mail:
- name, addr = email.Utils.parseaddr(unicode(self.mail.get('From')))
- a = ui.accountman.get_account_by_address(addr)
-
- allbooks = not settings.config.getboolean('general',
- 'complete_matching_abook_only')
- ui.logger.debug(allbooks)
- abooks = ui.accountman.get_addressbooks(order=[a],
- append_remaining=allbooks)
- ui.logger.debug(abooks)
- to = yield ui.prompt(prefix='To>',
- completer=ContactsCompleter(abooks))
- if to == None:
- ui.notify('canceled')
- return
- self.mail['To'] = encode_header('to', to)
- if settings.config.getboolean('general', 'ask_subject') and \
- not 'Subject' in self.mail:
- subject = yield ui.prompt(prefix='Subject>')
- if subject == None:
- ui.notify('canceled')
- return
- self.mail['Subject'] = encode_header('subject', subject)
-
- ui.apply_command(EnvelopeEditCommand(mail=self.mail))
-
-
-class EnvelopeOpenCommand(Command):
- """open a new envelope buffer"""
- def __init__(self, mail=None, **kwargs):
- self.mail = mail
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- ui.buffer_open(buffer.EnvelopeBuffer(ui, mail=self.mail))
-
-
-class SendKeypressCommand(Command):
- def __init__(self, key, **kwargs):
- Command.__init__(self, **kwargs)
- self.key = key
-
- def apply(self, ui):
- ui.keypress(self.key)
-
-
-class HelpCommand(Command):
- def __init__(self, commandline='', **kwargs):
- Command.__init__(self, **kwargs)
- self.commandline = commandline.strip()
-
- def apply(self, ui):
- if self.commandline:
- cmd = self.commandline.split(' ', 1)[0]
- # TODO: how to I access COMMANDS from below?
- ui.notify('no help for \'%s\'' % cmd, priority='error')
- titletext = 'help for %s' % cmd
- body = urwid.Text('helpstring')
- return
- else:
- # get mappings
- modemaps = dict(settings.config.items('%s-maps' % ui.mode))
- globalmaps = dict(settings.config.items('global-maps'))
-
- # build table
- maxkeylength = len(max((modemaps).keys() + globalmaps.keys(),
- key=len))
- keycolumnwidth = maxkeylength + 2
-
- linewidgets = []
- # mode specific maps
- linewidgets.append(urwid.Text(('helptexth1',
- '\n%s-mode specific maps' % ui.mode)))
- for (k, v) in modemaps.items():
- line = urwid.Columns([('fixed', keycolumnwidth, urwid.Text(k)),
- urwid.Text(v)])
- linewidgets.append(line)
-
- # global maps
- linewidgets.append(urwid.Text(('helptexth1',
- '\nglobal maps')))
- for (k, v) in globalmaps.items():
- if k not in modemaps:
- line = urwid.Columns(
- [('fixed', keycolumnwidth, urwid.Text(k)),
- urwid.Text(v)])
- linewidgets.append(line)
-
- body = urwid.ListBox(linewidgets)
- ckey = 'cancel'
- titletext = 'Bindings Help (%s cancels)' % ckey
-
- box = widgets.DialogBox(body, titletext,
- bodyattr='helptext',
- titleattr='helptitle')
-
- # put promptwidget as overlay on main widget
- overlay = urwid.Overlay(box, ui.mainframe, 'center',
- ('relative', 70), 'middle',
- ('relative', 70))
- ui.show_as_root_until_keypress(overlay, 'cancel')
-
-
-class ExternalCommand(Command):
- """calls external command"""
- def __init__(self, commandstring, path=None, spawn=False, refocus=True,
- in_thread=False, on_success=None, **kwargs):
- """
- :param commandstring: the command to call
- :type commandstring: str
- :param path: a path to a file (or None)
- :type path: str
- :param spawn: run command in a new terminal
- :type spawn: boolean
- :param in_thread: run asynchronously, don't block alot
- :type in_thread: boolean
- :param refocus: refocus calling buffer after cmd termination
- :type refocus: boolean
- :param on_success: code to execute after command successfully exited
- :type on_success: callable
- """
- self.commandstring = commandstring
- self.path = path
- self.spawn = spawn
- self.refocus = refocus
- self.in_thread = in_thread
- self.on_success = on_success
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- callerbuffer = ui.current_buffer
-
- def afterwards(data):
- if callable(self.on_success) and data == 'success':
- self.on_success()
- if self.refocus and callerbuffer in ui.buffers:
- ui.logger.info('refocussing')
- ui.buffer_focus(callerbuffer)
-
- write_fd = ui.mainloop.watch_pipe(afterwards)
-
- def thread_code(*args):
- if self.path:
- if '{}' in self.commandstring:
- cmd = self.commandstring.replace('{}',
- helper.shell_quote(self.path))
- else:
- cmd = '%s %s' % (self.commandstring,
- helper.shell_quote(self.path))
- else:
- cmd = self.commandstring
-
- if self.spawn:
- cmd = '%s %s' % (settings.config.get('general',
- 'terminal_cmd'),
- cmd)
- cmd = cmd.encode('ascii', errors='ignore')
- ui.logger.info('calling external command: %s' % cmd)
- returncode = subprocess.call(shlex.split(cmd))
- if returncode == 0:
- os.write(write_fd, 'success')
-
- if self.in_thread:
- thread = threading.Thread(target=thread_code)
- thread.start()
- else:
- ui.mainloop.screen.stop()
- thread_code()
- ui.mainloop.screen.start()
-
-
-class EditCommand(ExternalCommand):
- def __init__(self, path, spawn=None, **kwargs):
- self.path = path
- if spawn != None:
- self.spawn = spawn
- else:
- self.spawn = settings.config.getboolean('general', 'spawn_editor')
- editor_cmd = settings.config.get('general', 'editor_cmd')
-
- ExternalCommand.__init__(self, editor_cmd, path=self.path,
- spawn=self.spawn, in_thread=self.spawn,
- **kwargs)
-
-
-class BufferFocusCommand(Command):
- """focus a buffer"""
- def __init__(self, buffer=None, offset=0, **kwargs):
- """
- :param buffer: the buffer to focus
- :type buffer: `alot.buffer.Buffer`
- """
- self.buffer = buffer
- self.offset = offset
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if self.offset:
- idx = ui.buffers.index(ui.current_buffer)
- num = len(ui.buffers)
- self.buffer = ui.buffers[(idx + self.offset) % num]
- else:
- if not self.buffer:
- self.buffer = ui.current_buffer.get_selected_buffer()
- ui.buffer_focus(self.buffer)
diff --git a/alot/commands/search.py b/alot/commands/search.py
deleted file mode 100644
index e30eb30f..00000000
--- a/alot/commands/search.py
+++ /dev/null
@@ -1,135 +0,0 @@
-from commands import Command, registerCommand
-from twisted.internet import defer
-
-class RetagPromptCommand(Command):
- """start a commandprompt to retag selected threads' tags
- this is needed to fill the prompt with the current tags..
- """
- def apply(self, ui):
- thread = ui.current_buffer.get_selected_thread()
- if not thread:
- return
- initial_tagstring = ','.join(thread.get_tags())
- ui.commandprompt('retag ' + initial_tagstring)
-
-
-class RetagCommand(Command):
- """tag selected thread"""
- def __init__(self, tagsstring=u'', thread=None, **kwargs):
- self.tagsstring = tagsstring
- self.thread = thread
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.thread:
- self.thread = ui.current_buffer.get_selected_thread()
- if not self.thread:
- return
- tags = filter(lambda x: x, self.tagsstring.split(','))
- ui.logger.info("got %s:%s" % (self.tagsstring, tags))
- try:
- self.thread.set_tags(tags)
- except DatabaseROError:
- ui.notify('index in read-only mode', priority='error')
- return
-
- # flush index
- ui.apply_command(FlushCommand())
-
- # refresh selected threadline
- sbuffer = ui.current_buffer
- threadwidget = sbuffer.get_selected_threadline()
- threadwidget.rebuild() # rebuild and redraw the line
-
-
-class RefineCommand(Command):
- """refine the query of the currently open searchbuffer"""
- def __init__(self, query=None, **kwargs):
- self.querystring = query
- Command.__init__(self, **kwargs)
-
- @defer.inlineCallbacks
- def apply(self, ui):
- if self.querystring:
- if self.querystring == '*':
- s = 'really search for all threads? This takes a while..'
- if (yield ui.choice(s, select='yes', cancel='no')) == 'no':
- return
- sbuffer = ui.current_buffer
- oldquery = sbuffer.querystring
- if self.querystring not in [None, oldquery]:
- sbuffer.querystring = self.querystring
- sbuffer = ui.current_buffer
- sbuffer.rebuild()
- ui.update()
- else:
- ui.notify('empty query string')
-
-
-class RefinePromptCommand(Command):
- """prompt to change current search buffers query"""
- def apply(self, ui):
- sbuffer = ui.current_buffer
- oldquery = sbuffer.querystring
- ui.commandprompt('refine ' + oldquery)
-
-
-class ToggleThreadTagCommand(Command):
- """toggles tag in given or currently selected thread"""
- def __init__(self, tags, thread=None, **kwargs):
- assert tags
- self.thread = thread
- self.tags = set(tags)
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.thread:
- self.thread = ui.current_buffer.get_selected_thread()
- if not self.thread:
- return
- try:
- self.thread.set_tags(set(self.thread.get_tags()) ^ self.tags)
- except DatabaseROError:
- ui.notify('index in read-only mode', priority='error')
- return
-
- # flush index
- ui.apply_command(FlushCommand())
-
- # update current buffer
- # TODO: what if changes not yet flushed?
- cb = ui.current_buffer
- if isinstance(cb, buffer.SearchBuffer):
- # refresh selected threadline
- threadwidget = cb.get_selected_threadline()
- threadwidget.rebuild() # rebuild and redraw the line
- #remove line from searchlist if thread doesn't match the query
- qs = "(%s) AND thread:%s" % (cb.querystring,
- self.thread.get_thread_id())
- if ui.dbman.count_messages(qs) == 0:
- ui.logger.debug('remove: %s' % self.thread)
- cb.threadlist.remove(threadwidget)
- cb.result_count -= self.thread.get_total_messages()
- ui.update()
- elif isinstance(cb, buffer.ThreadBuffer):
- pass
-
-
-class OpenThreadCommand(Command):
- """open a new thread-view buffer"""
- def __init__(self, thread=None, **kwargs):
- self.thread = thread
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.thread:
- self.thread = ui.current_buffer.get_selected_thread()
- if self.thread:
- query = ui.current_buffer.querystring
- ui.logger.info('open thread view for %s' % self.thread)
-
- sb = buffer.ThreadBuffer(ui, self.thread)
- ui.buffer_open(sb)
- sb.unfold_matching(query)
-
-
diff --git a/alot/commands/taglist.py b/alot/commands/taglist.py
deleted file mode 100644
index e2dc631c..00000000
--- a/alot/commands/taglist.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from commands import Command, registerCommand
-from twisted.internet import defer
-
-class TaglistSelectCommand(Command):
- def apply(self, ui):
- tagstring = ui.current_buffer.get_selected_tag()
- cmd = SearchCommand(query='tag:%s' % tagstring)
- ui.apply_command(cmd)
diff --git a/alot/commands/thread.py b/alot/commands/thread.py
deleted file mode 100644
index be337030..00000000
--- a/alot/commands/thread.py
+++ /dev/null
@@ -1,367 +0,0 @@
-from commands import Command, registerCommand
-from twisted.internet import defer
-
-class ReplyCommand(Command):
- """format reply for currently selected message and open envelope for it"""
- def __init__(self, message=None, groupreply=False, **kwargs):
- """
- :param message: the original message to reply to
- :type message: `alot.message.Message`
- :param groupreply: copy other recipients from Bcc/Cc/To to the reply
- :type groupreply: boolean
- """
- self.message = message
- self.groupreply = groupreply
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.message:
- self.message = ui.current_buffer.get_selected_message()
- mail = self.message.get_email()
- # set body text
- name, address = self.message.get_author()
- timestamp = self.message.get_date()
- qf = settings.hooks.get('reply_prefix')
- if qf:
- quotestring = qf(name, address, timestamp,
- ui=ui, dbm=ui.dbman, aman=ui.accountman,
- log=ui.logger, config=settings.config)
- else:
- quotestring = 'Quoting %s (%s)\n' % (name, timestamp)
- mailcontent = quotestring
- for line in self.message.accumulate_body().splitlines():
- mailcontent += '>' + line + '\n'
-
- Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
- bodypart = MIMEText(mailcontent.encode('utf-8'), 'plain', 'UTF-8')
- reply = MIMEMultipart()
- reply.attach(bodypart)
-
- # copy subject
- subject = mail.get('Subject', '')
- if not subject.startswith('Re:'):
- subject = 'Re: ' + subject
- reply['Subject'] = Header(subject.encode('utf-8'), 'UTF-8').encode()
-
- # set From
- my_addresses = ui.accountman.get_addresses()
- matched_address = ''
- in_to = [a for a in my_addresses if a in mail.get('To', '')]
- if in_to:
- matched_address = in_to[0]
- else:
- cc = mail.get('Cc', '') + mail.get('Bcc', '')
- in_cc = [a for a in my_addresses if a in cc]
- if in_cc:
- matched_address = in_cc[0]
- if matched_address:
- account = ui.accountman.get_account_by_address(matched_address)
- fromstring = '%s <%s>' % (account.realname, account.address)
- reply['From'] = encode_header('From', fromstring)
-
- # set To
- del(reply['To'])
- if self.groupreply:
- cleared = self.clear_my_address(my_addresses, mail.get('To', ''))
- if cleared:
- logging.info(mail['From'] + ', ' + cleared)
- to = mail['From'] + ', ' + cleared
- reply['To'] = encode_header('To', to)
- logging.info(reply['To'])
- else:
- reply['To'] = encode_header('To', mail['From'])
- # copy cc and bcc for group-replies
- if 'Cc' in mail:
- cc = self.clear_my_address(my_addresses, mail['Cc'])
- reply['Cc'] = encode_header('Cc', cc)
- if 'Bcc' in mail:
- bcc = self.clear_my_address(my_addresses, mail['Bcc'])
- reply['Bcc'] = encode_header('Bcc', bcc)
- else:
- reply['To'] = encode_header('To', mail['From'])
-
- # set In-Reply-To header
- del(reply['In-Reply-To'])
- reply['In-Reply-To'] = '<%s>' % self.message.get_message_id()
-
- # set References header
- old_references = mail.get('References', '')
- if old_references:
- old_references = old_references.split()
- references = old_references[-8:]
- if len(old_references) > 8:
- references = old_references[:1] + references
- references.append('<%s>' % self.message.get_message_id())
- reply['References'] = ' '.join(references)
- else:
- reply['References'] = '<%s>' % self.message.get_message_id()
-
- ui.apply_command(ComposeCommand(mail=reply))
-
- def clear_my_address(self, my_addresses, value):
- new_value = []
- for entry in value.split(','):
- if not [a for a in my_addresses if a in entry]:
- new_value.append(entry.strip())
- return ', '.join(new_value)
-
-
-class ForwardCommand(Command):
- def __init__(self, message=None, inline=False, **kwargs):
- """
- :param message: the original message to forward. If None, the currently
- selected one is used
- :type message: `alot.message.Message`
- :param inline: Copy originals body text instead of attaching the whole
- mail
- :type inline: boolean
- """
- self.message = message
- self.inline = inline
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- if not self.message:
- self.message = ui.current_buffer.get_selected_message()
- mail = self.message.get_email()
-
- reply = MIMEMultipart()
- Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
- if self.inline: # inline mode
- # set body text
- name, address = self.message.get_author()
- timestamp = self.message.get_date()
- qf = settings.hooks.get('forward_prefix')
- if qf:
- quote = qf(name, address, timestamp,
- ui=ui, dbm=ui.dbman, aman=ui.accountman,
- log=ui.logger, config=settings.config)
- else:
- quote = 'Forwarded message from %s (%s):\n' % (name, timestamp)
- mailcontent = quote
- for line in self.message.accumulate_body().splitlines():
- mailcontent += '>' + line + '\n'
-
- bodypart = MIMEText(mailcontent.encode('utf-8'), 'plain', 'UTF-8')
- reply.attach(bodypart)
-
- else: # attach original mode
- # create empty text msg
- bodypart = MIMEText('', 'plain', 'UTF-8')
- reply.attach(bodypart)
- # attach original msg
- reply.attach(mail)
-
- # copy subject
- subject = mail.get('Subject', '')
- subject = 'Fwd: ' + subject
- reply['Subject'] = Header(subject.encode('utf-8'), 'UTF-8').encode()
-
- # set From
- my_addresses = ui.accountman.get_addresses()
- matched_address = ''
- in_to = [a for a in my_addresses if a in mail.get('To', '')]
- if in_to:
- matched_address = in_to[0]
- else:
- cc = mail.get('Cc', '') + mail.get('Bcc', '')
- in_cc = [a for a in my_addresses if a in cc]
- if in_cc:
- matched_address = in_cc[0]
- if matched_address:
- account = ui.accountman.get_account_by_address(matched_address)
- fromstring = '%s <%s>' % (account.realname, account.address)
- reply['From'] = encode_header('From', fromstring)
- ui.apply_command(ComposeCommand(mail=reply))
-
-
-class FoldMessagesCommand(Command):
- def __init__(self, all=False, visible=None, **kwargs):
- self.all = all
- self.visible = visible
- Command.__init__(self, **kwargs)
-
- def apply(self, ui):
- lines = []
- if not self.all:
- lines.append(ui.current_buffer.get_selection())
- else:
- lines = ui.current_buffer.get_message_widgets()
-
- for widget in lines:
- # in case the thread is yet unread, remove this tag
- msg = widget.get_message()
- if self.visible or (self.visible == None and widget.folded):
- if 'unread' in msg.get_tags():
- msg.remove_tags(['unread'])
- ui.apply_command(FlushCommand())
- widget.rebuild()
- widget.fold(visible=True)
- else:
- widget.fold(visible=False)
-
-
-class ToggleHeaderCommand(Command):
- def apply(self, ui):
- msgw = ui.current_buffer.get_selection()
- msgw.toggle_full_header()
-
-
-class PipeCommand(Command):
- def __init__(self, command, whole_thread=False, separately=False,
- noop_msg='no command specified', confirm_msg='',
- done_msg='done', **kwargs):
- Command.__init__(self, **kwargs)
- self.cmd = command
- self.whole_thread = whole_thread
- self.separately = separately
- self.noop_msg = noop_msg
- self.confirm_msg = confirm_msg
- self.done_msg = done_msg
-
- @defer.inlineCallbacks
- def apply(self, ui):
- # abort if command unset
- if not self.cmd:
- ui.notify(self.noop_msg, priority='error')
- return
-
- # get messages to pipe
- if self.whole_thread:
- thread = ui.current_buffer.get_selected_thread()
- if not thread:
- return
- to_print = thread.get_messages().keys()
- else:
- to_print = [ui.current_buffer.get_selected_message()]
-
- # ask for confirmation if needed
- if self.confirm_msg:
- if (yield ui.choice(self.confirm_msg, select='yes',
- cancel='no')) == 'no':
- return
-
- # prepare message sources
- mailstrings = [m.get_email().as_string() for m in to_print]
- if not self.separately:
- mailstrings = ['\n\n'.join(mailstrings)]
-
- # do teh monkey
- for mail in mailstrings:
- out, err = helper.pipe_to_command(self.cmd, mail)
- if err:
- ui.notify(err, priority='error')
- return
-
- # display 'done' message
- if self.done_msg:
- ui.notify(self.done_msg)
-
-
-class PrintCommand(PipeCommand):
- def __init__(self, whole_thread=False, separately=False, **kwargs):
- # get print command
- cmd = settings.config.get('general', 'print_cmd', fallback='')
-
- # set up notification strings
- if whole_thread:
- confirm_msg = 'print all messages in thread?'
- ok_msg = 'printed thread using %s' % cmd
- else:
- confirm_msg = 'print selected message?'
- ok_msg = 'printed message using %s' % cmd
-
- # no print cmd set
- noop_msg = 'no print command specified. Set "print_cmd" in the '\
- 'global section.'
- PipeCommand.__init__(self, cmd, whole_thread=whole_thread,
- separately=separately,
- noop_msg=noop_msg, confirm_msg=confirm_msg,
- done_msg=ok_msg, **kwargs)
-
-
-class SaveAttachmentCommand(Command):
- def __init__(self, all=False, path=None, **kwargs):
- Command.__init__(self, **kwargs)
- self.all = all
- self.path = path
-
- @defer.inlineCallbacks
- def apply(self, ui):
- pcomplete = completion.PathCompleter()
- if self.all:
- msg = ui.current_buffer.get_selected_message()
- if not self.path:
- self.path = yield ui.prompt(prefix='save attachments to:',
- text=os.path.join('~', ''),
- completer=pcomplete)
- if self.path:
- if os.path.isdir(os.path.expanduser(self.path)):
- for a in msg.get_attachments():
- dest = a.save(self.path)
- name = a.get_filename()
- if name:
- ui.notify('saved %s as: %s' % (name, dest))
- else:
- ui.notify('saved attachment as: %s' % dest)
- else:
- ui.notify('not a directory: %s' % self.path,
- priority='error')
- else:
- ui.notify('canceled')
- else: # save focussed attachment
- focus = ui.get_deep_focus()
- if isinstance(focus, widgets.AttachmentWidget):
- attachment = focus.get_attachment()
- filename = attachment.get_filename()
- if not self.path:
- msg = 'save attachment (%s) to:' % filename
- initialtext = os.path.join('~', filename)
- self.path = yield ui.prompt(prefix=msg,
- completer=pcomplete,
- text=initialtext)
- if self.path:
- try:
- dest = attachment.save(self.path)
- ui.notify('saved attachment as: %s' % dest)
- except (IOError, OSError), e:
- ui.notify(str(e), priority='error')
- else:
- ui.notify('canceled')
-
-
-class OpenAttachmentCommand(Command):
- """displays an attachment according to mailcap"""
- def __init__(self, attachment, **kwargs):
- Command.__init__(self, **kwargs)
- self.attachment = attachment
-
- def apply(self, ui):
- logging.info('open attachment')
- mimetype = self.attachment.get_content_type()
- handler = settings.get_mime_handler(mimetype)
- if handler:
- path = self.attachment.save(tempfile.gettempdir())
- handler = handler.replace('\'%s\'', '{}')
-
- def afterwards():
- os.remove(path)
- ui.apply_command(ExternalCommand(handler, path=path,
- on_success=afterwards,
- in_thread=True))
- else:
- ui.notify('unknown mime type')
-
-
-class ThreadSelectCommand(Command):
- def apply(self, ui):
- focus = ui.get_deep_focus()
- if isinstance(focus, widgets.MessageSummaryWidget):
- ui.apply_command(FoldMessagesCommand())
- elif isinstance(focus, widgets.AttachmentWidget):
- logging.info('open attachment')
- ui.apply_command(OpenAttachmentCommand(focus.get_attachment()))
- else:
- logging.info('unknown widget %s' % focus)
-
-
diff --git a/alot/init.py b/alot/init.py
index 86f3dc42..9ef9c3f3 100755
--- a/alot/init.py
+++ b/alot/init.py
@@ -25,8 +25,8 @@ import settings
from account import AccountManager
from db import DBManager
from ui import UI
-import commands
-from commands import *
+import command
+from Commands import *
def parse_args():
@@ -92,7 +92,7 @@ def main():
logging.basicConfig(level=numeric_loglevel, filename=logfilename)
logger = logging.getLogger()
- logger.debug(commands.COMMANDS)
+ logger.debug(command.COMMANDS)
#accountman
aman = AccountManager(settings.config)
@@ -101,12 +101,12 @@ def main():
# get initial searchstring
if args.command != '':
- cmd = commands.interpret_commandline(args.command, 'global')
+ cmd = command.interpret_commandline(args.command, 'global')
if cmd is None:
sys.exit('Invalid command: ' + args.command)
else:
default_commandline = settings.config.get('general', 'initial_command')
- cmd = commands.interpret_commandline(default_commandline, 'global')
+ cmd = command.interpret_commandline(default_commandline, 'global')
# set up and start interface
UI(dbman,
diff --git a/alot/ui.py b/alot/ui.py
index fa73898c..7856fef0 100644
--- a/alot/ui.py
+++ b/alot/ui.py
@@ -22,8 +22,8 @@ from twisted.internet import reactor, defer
from settings import config
from buffer import BufferlistBuffer
import commands
-from commands import commandfactory
-from commands import interpret_commandline
+from command import commandfactory
+from command import interpret_commandline
import widgets
from completion import CommandLineCompleter