"""
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 .
Copyright (C) 2011 Patrick Totzke
"""
import os
import code
import logging
import threading
import subprocess
import email
import tempfile
from email.parser import Parser
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email import Charset
from email.header import Header
import buffer
import settings
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:
"""base class for commands"""
def __init__(self, prehook=None, posthook=None, **ignored):
self.prehook = prehook
self.posthook = posthook
self.undoable = False
self.help = self.__doc__
def apply(self, caller):
pass
class ExitCommand(Command):
"""shuts the MUA down cleanly"""
def apply(self, ui):
ui.shutdown()
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()
ui.logger.info('open thread view for %s' % self.thread)
# in case the thread is yet unread, remove this tag
if 'unread' in self.thread.get_tags():
self.thread.remove_tags(['unread'], sync_maildir_flags=True)
ui.apply_command(FlushCommand())
self.thread.refresh()
sb = buffer.ThreadBuffer(ui, self.thread)
ui.buffer_open(sb)
class SearchCommand(Command):
"""open a new search buffer"""
def __init__(self, query, force_new=False, **kwargs):
"""
@param query initial querystring
@param force_new True forces a new buffer
"""
self.query = query
self.force_new = force_new
Command.__init__(self, **kwargs)
def apply(self, ui):
if not self.force_new:
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.buffer_open(buffer.SearchBuffer(ui, self.query))
class PromptCommand(Command):
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, spawn=False, refocus=True,
in_thread=False, on_success=None, **kwargs):
"""
:param commandstring: the command to call
:type commandstring: str
:param spawn: run command in a new terminal
:type spawn: 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.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):
cmd = self.commandstring
if self.spawn:
cmd = '%s %s' % (settings.config.get('general',
'terminal_cmd'),
cmd)
ui.logger.info('calling external command: %s' % cmd)
returncode = subprocess.call(cmd, shell=True)
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')
cmd = editor_cmd + ' ' + self.path
ExternalCommand.__init__(self, cmd, 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
@param buffer the selected buffer
"""
def __init__(self, buffer=None, focussed=False, **kwargs):
self.buffer = buffer
self.focussed = focussed
Command.__init__(self, **kwargs)
def apply(self, ui):
if self.focussed:
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
@param buffer the selected buffer
"""
def __init__(self, buffer=None, offset=0, **kwargs):
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
"""
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 taglist
"""
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 CommandPromptCommand(Command):
"""
"""
def apply(self, ui):
ui.commandprompt()
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):
"""
"""
def __init__(self, tag, thread=None, **kwargs):
assert tag
self.thread = thread
self.tag = tag
Command.__init__(self, **kwargs)
def apply(self, ui):
if not self.thread:
self.thread = ui.current_buffer.get_selected_thread()
try:
if self.tag in self.thread.get_tags():
self.thread.remove_tags([self.tag])
else:
self.thread.add_tags([self.tag])
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())
msg_count = ui.dbman.count_messages(qs)
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):
def __init__(self, mail=None, **kwargs):
if not mail:
self.mail = MIMEMultipart()
self.mail.attach(MIMEText('', 'plain', 'UTF-8'))
else:
self.mail = mail
Command.__init__(self, **kwargs)
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 = ui.prompt(prefix='From>', completer=cmpl, tab=1)
validaddresses = [a.address for a in accounts] + [None]
while fromaddress not in validaddresses:
ui.notify('no account for this address. ( cancels)')
fromaddress = 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:
to = ui.prompt(prefix='To>', completer=ContactsCompleter())
self.mail['To'] = encode_header('to', to)
if settings.config.getboolean('general', 'ask_subject') and \
not 'Subject' in self.mail:
subject = ui.prompt(prefix='Subject>')
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"""
def apply(self, ui):
thread = ui.current_buffer.get_selected_thread()
initial_tagstring = ','.join(thread.get_tags())
ui.commandprompt('retag ' + initial_tagstring)
class RetagCommand(Command):
"""tag selected thread"""
def __init__(self, tagsstring=u'', **kwargs):
self.tagsstring = tagsstring
Command.__init__(self, **kwargs)
def apply(self, ui):
thread = ui.current_buffer.get_selected_thread()
initial_tagstring = ','.join(thread.get_tags())
tags = filter(lambda x: x, self.tagsstring.split(','))
ui.logger.info("got %s:%s" % (self.tagsstring, tags))
try:
thread.set_tags(tags)
except DatabaseROError, e:
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)
def apply(self, ui):
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()
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):
def __init__(self, groupreply=False, **kwargs):
self.groupreply = groupreply
Command.__init__(self, **kwargs)
def apply(self, ui):
msg = ui.current_buffer.get_selected_message()
mail = msg.get_email()
# set body text
mailcontent = '\nOn %s, %s wrote:\n' % (msg.get_datestring(),
msg.get_author()[0])
for line in msg.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['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_account_addresses()
matched_address = ''
in_to = [a for a in my_addresses if a in mail['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
#reply['To'] = Header(mail['From'].encode('utf-8'), 'UTF-8').encode()
del(reply['To'])
if self.groupreply:
cleared = self.clear_my_address(my_addresses, mail['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>' % msg.get_message_id()
# set References header
old_references = mail['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>' % msg.get_message_id())
reply['References'] = ' '.join(references)
else:
reply['References'] = '<%s>' % msg.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, inline=False, **kwargs):
self.inline = inline
Command.__init__(self, **kwargs)
def apply(self, ui):
msg = ui.current_buffer.get_selected_message()
mail = msg.get_email()
reply = MIMEMultipart()
Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
if self.inline: # inline mode
# set body text
author = msg.get_author()[0]
mailcontent = '\nForwarded message from %s:\n' % author
for line in msg.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['Subject']
subject = 'Fwd: ' + subject
reply['Subject'] = Header(subject.encode('utf-8'), 'UTF-8').encode()
# set From
my_addresses = ui.accountman.get_account_addresses()
matched_address = ''
in_to = [a for a in my_addresses if a in mail['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 BounceMailCommand(Command):
def apply(self, ui):
msg = ui.current_buffer.get_selected_message()
mail = msg.get_email()
del(mail['To'])
ui.apply_command(ComposeCommand(mail=mail))
### ENVELOPE
class EnvelopeOpenCommand(Command):
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():
f = open(tf.name)
editor_input = f.read().decode('utf-8')
#split editor out
headertext, bodytext = editor_input.split('\n\n', 1)
for line in headertext.splitlines():
key, value = line.strip().split(':', 1)
value = value.strip()
del self.mail[key] # ensure there is only one
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[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)
#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 EnvelopeSendCommand(Command):
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:
success, reason = account.sender.send_mail(mail)
if success:
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')
# 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)
#Factory
COMMANDS = {
'bnext': (BufferFocusCommand, {'offset': 1}),
'bprevious': (BufferFocusCommand, {'offset': -1}),
'bufferlist': (OpenBufferlistCommand, {}),
'close': (BufferCloseCommand, {}),
'closefocussed': (BufferCloseCommand, {'focussed': True}),
'openfocussed': (BufferFocusCommand, {}),
'commandprompt': (CommandPromptCommand, {}),
'compose': (ComposeCommand, {}),
'edit': (EditCommand, {}),
'exit': (ExitCommand, {}),
'flush': (FlushCommand, {}),
'openthread': (OpenThreadCommand, {}),
'prompt': (PromptCommand, {}),
'pyshell': (PythonShellCommand, {}),
'refine': (RefineCommand, {}),
'refineprompt': (RefinePromptCommand, {}),
'refresh': (RefreshCommand, {}),
'search': (SearchCommand, {}),
'shellescape': (ExternalCommand, {}),
'taglist': (TagListCommand, {}),
'toggletag': (ToggleThreadTagCommand, {'tag': 'inbox'}),
# envelope
'send': (EnvelopeSendCommand, {}),
'reedit': (EnvelopeEditCommand, {}),
'subject': (EnvelopeSetCommand, {'key': 'Subject'}),
'to': (EnvelopeSetCommand, {'key': 'To'}),
'retag': (RetagCommand, {}),
'retagprompt': (RetagPromptCommand, {}),
# thread
'reply': (ReplyCommand, {}),
'groupreply': (ReplyCommand, {'groupreply': True}),
'forward': (ForwardCommand, {}),
'bounce': (BounceMailCommand, {}),
# taglist
'select': (TaglistSelectCommand, {}),
}
def commandfactory(cmdname, **kwargs):
if cmdname in COMMANDS:
(cmdclass, parms) = COMMANDS[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)
else:
logging.error('there is no command %s' % cmdname)
aliases = {'clo': 'close',
'bn': 'bnext',
'bp': 'bprevious',
'bcf': 'buffer close focussed',
'ls': 'bufferlist',
'quit': 'exit',
}
globalcommands = [
'bnext',
'bprevious',
'bufferlist',
'close',
'compose',
'prompt',
'edit',
'exit',
'flush',
'pyshell',
'refresh',
'search',
'shellescape',
'taglist',
]
ALLOWED_COMMANDS = {
'search': ['refine', 'refineprompt', 'toggletag', 'openthread', 'retag',
'retagprompt'] + globalcommands,
'envelope': ['send', 'reedit', 'to', 'subject'] + globalcommands,
'bufferlist': ['openfocussed', 'closefocussed'] + globalcommands,
'taglist': ['select'] + globalcommands,
'thread': globalcommands + ['toggletag', 'reply', 'groupreply', 'bounce',
'forward'],
}
def interpret_commandline(cmdline, mode):
if not cmdline:
return None
logging.debug('mode:%s got commandline "%s"' % (mode, cmdline))
args = cmdline.strip().split(' ', 1)
cmd = args[0]
if args[1:]:
params = args[1]
else:
params = ''
# unfold aliases
if cmd in aliases:
cmd = 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 ALLOWED_COMMANDS[mode]:
logging.debug('not allowed in mode %s: %s' % (mode, cmd))
return None
if not params: # commands that work without parameter
if cmd in ['exit', 'flush', 'pyshell', 'taglist', 'close', 'compose',
'openfocussed', 'closefocussed', 'bnext', 'bprevious',
'retag', 'refresh', 'bufferlist', 'refineprompt', 'reply',
'forward',
'groupreply', 'bounce', 'openthread', 'send', 'reedit',
'select', 'retagprompt']:
return commandfactory(cmd)
else:
return None
else:
if cmd == 'search':
return commandfactory(cmd, query=params)
elif cmd == 'compose':
return commandfactory(cmd, headers={'To': params})
elif cmd == 'prompt':
return commandfactory(cmd, startstring=params)
elif cmd == 'refine':
return commandfactory(cmd, query=params)
elif cmd == 'retag':
return commandfactory(cmd, tagsstring=params)
elif cmd == 'subject':
return commandfactory(cmd, key='Subject', value=params)
elif cmd == 'shellescape':
return commandfactory(cmd, commandstring=params)
elif cmd == 'to':
return commandfactory(cmd, key='To', value=params)
elif cmd == 'toggletag':
return commandfactory(cmd, tag=params)
elif cmd == 'edit':
filepath = params[0]
if os.path.isfile(filepath):
return commandfactory(cmd, path=filepath)
else:
return None