summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2011-12-22 10:45:14 +0000
committerPatrick Totzke <patricktotzke@gmail.com>2011-12-22 10:45:14 +0000
commitf21c55c075ce3477cb09ccc0929116fab895c435 (patch)
tree225a479697f33b4bcf6fee3c746ae2248eb31a18
parent2d0b7ba4be907896be3a0a9132b8b775b8b7a6cd (diff)
parente2016895fb7d2850cdbdad1b628159d6136bd964 (diff)
Merge branch 'testing'
-rw-r--r--alot/account.py8
-rw-r--r--alot/buffers.py8
-rw-r--r--alot/commands/envelope.py37
-rw-r--r--alot/commands/globals.py49
-rw-r--r--alot/commands/thread.py44
-rw-r--r--alot/defaults/alot.rc3
-rw-r--r--alot/helper.py55
-rwxr-xr-xalot/init.py119
-rw-r--r--alot/message.py57
-rw-r--r--alot/ui.py30
-rwxr-xr-xbin/alot30
11 files changed, 276 insertions, 164 deletions
diff --git a/alot/account.py b/alot/account.py
index 1b7d35e2..6eb9f099 100644
--- a/alot/account.py
+++ b/alot/account.py
@@ -4,10 +4,10 @@ import time
import re
import email
import os
+import shlex
from ConfigParser import SafeConfigParser
from urlparse import urlparse
-from helper import cmd_output
import helper
@@ -140,7 +140,8 @@ class SendmailAccount(Account):
def send_mail(self, mail):
mail['Date'] = email.utils.formatdate(time.time(), True)
- out, err = helper.pipe_to_command(self.cmd, mail.as_string())
+ cmdlist = shlex.split(self.cmd.encode('utf-8', errors='ignore'))
+ out, err, retval = helper.call_cmd(cmdlist, stdin=mail.as_string())
if err:
return err + '. sendmail_cmd set to: %s' % self.cmd
self.store_sent_mail(mail)
@@ -320,7 +321,8 @@ class MatchSdtoutAddressbook(AddressBook):
return self.lookup('\'\'')
def lookup(self, prefix):
- resultstring = cmd_output('%s %s' % (self.command, prefix))
+ cmdlist = shlex.split(self.command.encode('utf-8', errors='ignore'))
+ resultstring, errmsg, retval = helper.call_cmd(cmdlist + [prefix])
if not resultstring:
return []
lines = resultstring.replace('\t', ' ' * 4).splitlines()
diff --git a/alot/buffers.py b/alot/buffers.py
index 09db8246..4fa66d62 100644
--- a/alot/buffers.py
+++ b/alot/buffers.py
@@ -86,7 +86,6 @@ class EnvelopeBuffer(Buffer):
def __init__(self, ui, envelope):
self.ui = ui
self.envelope = envelope
- self.mail = envelope.construct_mail()
self.all_headers = False
self.rebuild()
Buffer.__init__(self, ui, self.body, 'envelope')
@@ -96,15 +95,15 @@ class EnvelopeBuffer(Buffer):
return '[%s] to: %s' % (self.typename, shorten_author_string(to, 400))
def rebuild(self):
- self.mail = self.envelope.construct_mail()
displayed_widgets = []
hidden = settings.config.getstringlist('general',
'envelope_headers_blacklist')
#build lines
lines = []
- for (k, v) in self.envelope.headers.items():
+ for (k, vlist) in self.envelope.headers.items():
if (k not in hidden) or self.all_headers:
- lines.append((k, v))
+ for value in vlist:
+ lines.append((k, value))
self.header_wgt = widgets.HeadersList(lines)
displayed_widgets.append(self.header_wgt)
@@ -117,7 +116,6 @@ class EnvelopeBuffer(Buffer):
self.attachment_wgt = urwid.Pile(lines)
displayed_widgets.append(self.attachment_wgt)
- #self.body_wgt = widgets.MessageBodyWidget(self.mail)
self.body_wgt = urwid.Text(self.envelope.body)
displayed_widgets.append(self.body_wgt)
self.body = urwid.ListBox(displayed_widgets)
diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py
index 2597e9de..21b6aa26 100644
--- a/alot/commands/envelope.py
+++ b/alot/commands/envelope.py
@@ -1,12 +1,15 @@
import os
+import re
import glob
import logging
import email
import tempfile
from twisted.internet.defer import inlineCallbacks
import threading
+import datetime
from alot import buffers
+from alot import commands
from alot.commands import Command, registerCommand
from alot import settings
from alot import helper
@@ -32,7 +35,7 @@ class AttachCommand(Command):
def apply(self, ui):
envelope = ui.current_buffer.envelope
- if self.path:
+ if self.path: # TODO: not possible, otherwise argparse error before
files = filter(os.path.isfile,
glob.glob(os.path.expanduser(self.path)))
if not files:
@@ -40,6 +43,7 @@ class AttachCommand(Command):
return
else:
ui.notify('no files specified, abort')
+ return
logging.info("attaching: %s" % files)
for path in files:
@@ -72,6 +76,13 @@ class SendCommand(Command):
def apply(self, ui):
currentbuffer = ui.current_buffer # needed to close later
envelope = currentbuffer.envelope
+ if envelope.sent_time:
+ warning = 'A modified version of ' * envelope.modified_since_sent
+ warning += 'this message has been sent at %s.' % envelope.sent_time
+ warning += ' Do you want to resend?'
+ if (yield ui.choice(warning, cancel='no',
+ msg_position='left')) == 'no':
+ return
frm = envelope.get('From')
sname, saddr = email.Utils.parseaddr(frm)
omit_signature = False
@@ -107,7 +118,8 @@ class SendCommand(Command):
def afterwards(returnvalue):
ui.clear_notify([clearme])
if returnvalue == 'success': # sucessfully send mail
- ui.buffer_close(currentbuffer)
+ envelope.sent_time = datetime.datetime.now()
+ ui.apply_command(commands.globals.BufferCloseCommand())
ui.notify('mail send successful')
else:
ui.notify('failed to send: %s' % returnvalue,
@@ -181,8 +193,16 @@ class EditCommand(Command):
# decode header
headertext = u''
for key in edit_headers:
- value = self.envelope.headers.get(key, '')
- headertext += '%s: %s\n' % (key, value)
+ vlist = self.envelope.get_all(key)
+
+ # remove to be edited lines from envelope
+ del self.envelope[key]
+
+ for value in vlist:
+ # newlines (with surrounding spaces) by spaces in values
+ value = value.strip()
+ value = re.sub('[ \t\r\f\v]*\n[ \t\r\f\v]*', ' ', value)
+ headertext += '%s: %s\n' % (key, value)
bodytext = self.envelope.body
@@ -207,8 +227,7 @@ class EditCommand(Command):
@registerCommand(MODE, 'set', arguments=[
- # TODO
- #(['--append'], {'action': 'store_true', 'help':'keep previous value'}),
+ (['--append'], {'action': 'store_true', 'help':'keep previous values'}),
(['key'], {'help':'header to refine'}),
(['value'], {'nargs':'+', 'help':'value'})])
class SetCommand(Command):
@@ -222,11 +241,13 @@ class SetCommand(Command):
"""
self.key = key
self.value = ' '.join(value)
- self.append = append
+ self.reset = not append
Command.__init__(self, **kwargs)
def apply(self, ui):
- ui.current_buffer.envelope[self.key] = self.value
+ if self.reset:
+ del(ui.current_buffer.envelope[self.key])
+ ui.current_buffer.envelope.add(self.key, self.value)
ui.current_buffer.rebuild()
diff --git a/alot/commands/globals.py b/alot/commands/globals.py
index b7e55598..4ead9c58 100644
--- a/alot/commands/globals.py
+++ b/alot/commands/globals.py
@@ -250,10 +250,18 @@ class PythonShellCommand(Command):
@registerCommand(MODE, 'bclose')
class BufferCloseCommand(Command):
- """close current buffer or exit if it is the last"""
+ """close current buffer"""
def apply(self, ui):
selected = ui.current_buffer
- ui.buffer_close(selected)
+ if len(ui.buffers) == 1:
+ if settings.config.getboolean('general', 'quit_on_last_bclose'):
+ ui.logger.info('closing the last buffer, exiting')
+ ui.apply_command(ExitCommand())
+ else:
+ ui.logger.info('not closing last remaining buffer as '
+ 'global.quit_on_last_bclose is set to False')
+ else:
+ ui.buffer_close(selected)
@registerCommand(MODE, 'bprevious', forced={'offset': -1},
@@ -418,11 +426,12 @@ class HelpCommand(Command):
(['--to'], {'nargs':'+', 'help':'recipients'}),
(['--cc'], {'nargs':'+', 'help':'copy to'}),
(['--bcc'], {'nargs':'+', 'help':'blind copy to'}),
+ (['--attach'], {'nargs':'+', 'help':'attach files'}),
])
class ComposeCommand(Command):
"""compose a new email"""
def __init__(self, envelope=None, headers={}, template=None,
- sender=u'', subject=u'', to=[], cc=[], bcc=[],
+ sender=u'', subject=u'', to=[], cc=[], bcc=[], attach=None,
**kwargs):
"""
:param envelope: use existing envelope
@@ -443,8 +452,10 @@ class ComposeCommand(Command):
:type cc: str
:param bcc: Bcc-header value
:type bcc: str
+ :param attach: Path to files to be attached (globable)
+ :type attach: str
"""
-#TODO
+
Command.__init__(self, **kwargs)
self.envelope = envelope
@@ -455,6 +466,7 @@ class ComposeCommand(Command):
self.to = to
self.cc = cc
self.bcc = bcc
+ self.attach = attach
@inlineCallbacks
def apply(self, ui):
@@ -489,19 +501,19 @@ class ComposeCommand(Command):
# set forced headers
for key, value in self.headers.items():
- self.envelope.headers[key] = value
+ self.envelope.add(key, value)
# set forced headers for separate parameters
if self.sender:
- self.envelope['From'] = self.sender
+ self.envelope.add('From', self.sender)
if self.subject:
- self.envelope['Subject'] = self.subject
+ self.envelope.add('Subject', self.subject)
if self.to:
- self.envelope['To'] = ','.join(self.to)
+ self.envelope.add('To', ','.join(self.to))
if self.cc:
- self.envelope['Cc'] = ','.join(self.cc)
+ self.envelope.add('Cc', ','.join(self.cc))
if self.bcc:
- self.envelope['Bcc'] = ','.join(self.bcc)
+ self.envelope.add('Bcc', ','.join(self.bcc))
# get missing From header
if not 'From' in self.envelope.headers:
@@ -509,7 +521,7 @@ class ComposeCommand(Command):
if len(accounts) == 1:
a = accounts[0]
fromstring = "%s <%s>" % (a.realname, a.address)
- self.envelope['From'] = fromstring
+ self.envelope.add('From', fromstring)
else:
cmpl = AccountCompleter(ui.accountman)
fromaddress = yield ui.prompt(prefix='From>', completer=cmpl,
@@ -520,13 +532,13 @@ class ComposeCommand(Command):
a = ui.accountman.get_account_by_address(fromaddress)
if a is not None:
fromstring = "%s <%s>" % (a.realname, a.address)
- self.envelope['From'] = fromstring
+ self.envelope.add('From', fromstring)
else:
- self.envelope.headers['From'] = fromaddress
+ self.envelope.add('From', fromaddress)
# get missing To header
if 'To' not in self.envelope.headers:
- sender = self.envelope.headers.get('From')
+ sender = self.envelope.get('From')
name, addr = email.Utils.parseaddr(sender)
a = ui.accountman.get_account_by_address(addr)
@@ -541,7 +553,7 @@ class ComposeCommand(Command):
if to == None:
ui.notify('canceled')
return
- self.envelope.headers['To'] = to
+ self.envelope.add('To', to)
if settings.config.getboolean('general', 'ask_subject') and \
not 'Subject' in self.envelope.headers:
@@ -550,7 +562,12 @@ class ComposeCommand(Command):
if subject == None:
ui.notify('canceled')
return
- self.envelope['Subject'] = subject
+ self.envelope.add('Subject', subject)
+
+ if self.attach:
+ for a in self.attach:
+ self.envelope.attach(a)
+
cmd = commands.envelope.EditCommand(envelope=self.envelope)
ui.apply_command(cmd)
diff --git a/alot/commands/thread.py b/alot/commands/thread.py
index 464fb91a..b7159cc1 100644
--- a/alot/commands/thread.py
+++ b/alot/commands/thread.py
@@ -3,6 +3,7 @@ import logging
import tempfile
from twisted.internet.defer import inlineCallbacks
import mimetypes
+import shlex
from alot.commands import Command, registerCommand
from alot.commands.globals import ExternalCommand
@@ -59,7 +60,7 @@ class ReplyCommand(Command):
subject = decode_header(mail.get('Subject', ''))
if not subject.startswith('Re:'):
subject = 'Re: ' + subject
- envelope['Subject'] = subject
+ envelope.add('Subject', subject)
# set From
my_addresses = ui.accountman.get_addresses()
@@ -75,7 +76,7 @@ class ReplyCommand(Command):
if matched_address:
account = ui.accountman.get_account_by_address(matched_address)
fromstring = '%s <%s>' % (account.realname, account.address)
- envelope['From'] = fromstring
+ envelope.add('From', fromstring)
# set To
if self.groupreply:
@@ -83,21 +84,22 @@ class ReplyCommand(Command):
if cleared:
logging.info(mail['From'] + ', ' + cleared)
to = mail['From'] + ', ' + cleared
- envelope['To'] = to
+ envelope.add('To', decode_header(to))
+
else:
- envelope['To'] = mail['From']
+ envelope.add('To', decode_header(mail['From']))
# copy cc and bcc for group-replies
if 'Cc' in mail:
cc = self.clear_my_address(my_addresses, mail['Cc'])
- envelope['Cc'] = cc
+ envelope.add('Cc', decode_header(cc))
if 'Bcc' in mail:
bcc = self.clear_my_address(my_addresses, mail['Bcc'])
- envelope['Bcc'] = bcc
+ envelope.add('Bcc', decode_header(bcc))
else:
- envelope['To'] = mail['From']
+ envelope.add('To', decode_header(mail['From']))
# set In-Reply-To header
- envelope['In-Reply-To'] = '<%s>' % self.message.get_message_id()
+ envelope.add('In-Reply-To', '<%s>' % self.message.get_message_id())
# set References header
old_references = mail.get('References', '')
@@ -107,9 +109,9 @@ class ReplyCommand(Command):
if len(old_references) > 8:
references = old_references[:1] + references
references.append('<%s>' % self.message.get_message_id())
- envelope['References'] = ' '.join(references)
+ envelope.add('References', ' '.join(references))
else:
- envelope['References'] = '<%s>' % self.message.get_message_id()
+ envelope.add('References', '<%s>' % self.message.get_message_id())
ui.apply_command(ComposeCommand(envelope=envelope))
@@ -168,9 +170,11 @@ class ForwardCommand(Command):
# copy subject
subject = decode_header(mail.get('Subject', ''))
subject = 'Fwd: ' + subject
- envelope['Subject'] = subject
+ envelope.add('Subject', subject)
# set From
+ # we look for own addresses in the To,Cc,Ccc headers in that order
+ # and use the first match as new From header if there is one.
my_addresses = ui.accountman.get_addresses()
matched_address = ''
in_to = [a for a in my_addresses if a in mail.get('To', '')]
@@ -184,7 +188,7 @@ class ForwardCommand(Command):
if matched_address:
account = ui.accountman.get_account_by_address(matched_address)
fromstring = '%s <%s>' % (account.realname, account.address)
- envelope['From'] = fromstring
+ envelope.add('From', fromstring)
ui.apply_command(ComposeCommand(envelope=envelope))
@@ -239,7 +243,7 @@ class ToggleHeaderCommand(Command):
@registerCommand(MODE, 'pipeto', arguments=[
- (['cmd'], {'help':'shellcommand to pipe to'}),
+ (['cmd'], {'nargs':'?', 'help':'shellcommand to pipe to'}),
(['--all'], {'action': 'store_true', 'help':'pass all messages'}),
(['--decode'], {'action': 'store_true',
'help':'use only decoded body lines'}),
@@ -250,14 +254,13 @@ class ToggleHeaderCommand(Command):
)
class PipeCommand(Command):
"""pipe message(s) to stdin of a shellcommand"""
- #TODO: make cmd a list
#TODO: use raw arg from print command here
def __init__(self, cmd, all=False, ids=False, separately=False,
decode=True, noop_msg='no command specified', confirm_msg='',
done_msg='done', **kwargs):
"""
:param cmd: shellcommand to open
- :type cmd: str
+ :type cmd: list of str
:param all: pipe all, not only selected message
:type all: bool
:param ids: only write message ids, not the message source
@@ -273,7 +276,7 @@ class PipeCommand(Command):
:type done_msg: str
"""
Command.__init__(self, **kwargs)
- self.cmd = cmd
+ self.cmdlist = cmd
self.whole_thread = all
self.separately = separately
self.ids = ids
@@ -285,7 +288,7 @@ class PipeCommand(Command):
@inlineCallbacks
def apply(self, ui):
# abort if command unset
- if not self.cmd:
+ if not self.cmdlist:
ui.notify(self.noop_msg, priority='error')
return
@@ -324,7 +327,7 @@ class PipeCommand(Command):
# do teh monkey
for mail in mailstrings:
ui.logger.debug("%s" % mail)
- out, err = helper.pipe_to_command(self.cmd, mail)
+ out, err, retval = helper.call_cmd(self.cmdlist, stdin=mail)
if err:
ui.notify(err, priority='error')
return
@@ -353,6 +356,7 @@ class PrintCommand(PipeCommand):
"""
# get print command
cmd = settings.config.get('general', 'print_cmd', fallback='')
+ cmdlist = shlex.split(cmd.encode('utf-8', errors='ignore'))
# set up notification strings
if all:
@@ -365,7 +369,7 @@ class PrintCommand(PipeCommand):
# no print cmd set
noop_msg = 'no print command specified. Set "print_cmd" in the '\
'global section.'
- PipeCommand.__init__(self, cmd, all=all,
+ PipeCommand.__init__(self, cmdlist, all=all,
separately=separately,
decode=not raw,
noop_msg=noop_msg, confirm_msg=confirm_msg,
@@ -374,7 +378,7 @@ class PrintCommand(PipeCommand):
@registerCommand(MODE, 'save', arguments=[
(['--all'], {'action': 'store_true', 'help':'save all attachments'}),
- (['path'], {'nargs':'?', 'help':'path to save to'})])
+ (['path'], {'help':'path to save to'})])
class SaveAttachmentCommand(Command):
"""save attachment(s)"""
def __init__(self, all=False, path=None, **kwargs):
diff --git a/alot/defaults/alot.rc b/alot/defaults/alot.rc
index f92d1454..087d1a12 100644
--- a/alot/defaults/alot.rc
+++ b/alot/defaults/alot.rc
@@ -93,6 +93,9 @@ initial_command = search tag:inbox AND NOT tag:killed
# look in the abook of the account matching the sender address
complete_matching_abook_only = False
+# shut down when the last buffer gets closed
+quit_on_last_bclose = False
+
[global-maps]
j = move down
k = move up
diff --git a/alot/helper.py b/alot/helper.py
index c4e6d7fa..4d0c01f4 100644
--- a/alot/helper.py
+++ b/alot/helper.py
@@ -2,7 +2,6 @@ from datetime import date
from datetime import timedelta
from collections import deque
from string import strip
-import shlex
import subprocess
import email
import mimetypes
@@ -213,39 +212,37 @@ def pretty_datetime(d):
return string
-def cmd_output(command_line):
- args = shlex.split(command_line.encode('utf-8', errors='ignore'))
+def call_cmd(cmdlist, stdin=None):
+ """
+ get a shell commands output, error message and return value
+
+ :param cmdlist: shellcommand to call, already splitted into a list accepted
+ by :meth:`subprocess.Popen`
+ :type cmdlist: list of str
+ :param stdin: string to pipe to the process
+ :type stdin: str
+ :return: triple of stdout, error msg, return value of the shell command
+ :rtype: str, str, int
+ """
+
+ out, err, ret = '', '', 0
try:
- output = subprocess.check_output(args)
- output = string_decode(output, urwid.util.detected_encoding)
- except subprocess.CalledProcessError:
- return None
- except OSError:
- return None
- return output
-
-
-def pipe_to_command(cmd, stdin):
- # remove quotes which have been put around the whole command
- cmd = cmd.strip()
- stdin = stdin + '\n'
- if cmd[0] == '"' and cmd[-1] == '"':
- cmd = cmd[1:-1]
- args = shlex.split(cmd.encode('utf-8', errors='ignore'))
- try:
- proc = subprocess.Popen(args, stdin=subprocess.PIPE,
+ if stdin:
+ proc = subprocess.Popen(cmdlist, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = proc.communicate(stdin)
- except OSError, e:
- return '', str(e)
- if proc.poll(): # returncode is not 0
- e = 'return value != 0'
- if err.strip():
- e = e + ': %s' % err
- return '', e
+ ret = proc.poll()
else:
- return out, err
+ out = subprocess.check_output(cmdlist)
+ # todo: get error msg. rval
+ except (subprocess.CalledProcessError, OSError), e:
+ err = str(e)
+ ret = -1
+
+ out = string_decode(out, urwid.util.detected_encoding)
+ err = string_decode(err, urwid.util.detected_encoding)
+ return out, err, ret
def mimewrap(path, filename=None, ctype=None):
diff --git a/alot/init.py b/alot/init.py
index b0a84777..d88e7346 100755
--- a/alot/init.py
+++ b/alot/init.py
@@ -1,6 +1,5 @@
#!/usr/bin/env python
import sys
-import argparse
import logging
import os
@@ -14,42 +13,74 @@ from commands import *
from alot.commands import CommandParseError
import alot
-
-def parse_args():
- parser = argparse.ArgumentParser()
- parser.add_argument('-c', dest='configfile',
- default=None,
- help='alot\'s config file')
- parser.add_argument('-n', dest='notmuchconfigfile',
- default='~/.notmuch-config',
- help='notmuch\'s config file')
- parser.add_argument('-C', dest='colours',
- type=int,
- choices=[1, 16, 256],
- help='colour mode')
- parser.add_argument('-r', dest='read_only',
- action='store_true',
- help='open db in read only mode')
- parser.add_argument('-p', dest='db_path',
- help='path to notmuch index')
- parser.add_argument('-d', dest='debug_level',
- default='info',
- choices=['debug', 'info', 'warning', 'error'],
- help='debug level')
- parser.add_argument('-l', dest='logfile',
- default='/dev/null',
- help='logfile')
- parser.add_argument('command', nargs='?',
- default='',
- help='initial command')
- parser.add_argument('-v', '--version', action='version',
- version='%(prog)s ' + alot.__version__)
- return parser.parse_args()
+from twisted.python import usage
+
+
+class SubcommandOptions(usage.Options):
+ def parseArgs(self, *args):
+ self.args = args
+
+ def as_argparse_opts(self):
+ optstr = ''
+ for k, v in self.items():
+ if v is not None:
+ optstr += '--%s \'%s\' ' % (k, v)
+ return optstr
+
+ def opt_version(self):
+ print alot.__version__
+ sys.exit(0)
+
+
+class ComposeOptions(SubcommandOptions):
+ optParameters = [
+ ['sender', '', None, 'From line'],
+ ['subject', '', None, 'subject line'],
+ ['cc', '', None, 'copy to'],
+ ['bcc', '', None, 'blind copy to'],
+ ['template', '', None, 'path to template file'],
+ ['attach', '', None, 'files to attach'],
+ ]
+
+ def parseArgs(self, *args):
+ SubcommandOptions.parseArgs(self, *args)
+ self['to'] = ' '.join(args)
+
+
+class Options(usage.Options):
+ optFlags = [["read-only", "r", 'open db in read only mode'], ]
+
+ def colourint(val):
+ val = int(val)
+ if val not in [1, 16, 256]:
+ raise ValueError("Not in range")
+ return val
+ colourint.coerceDoc = "Must be 1, 16 or 256"
+ optParameters = [
+ ['config', 'c', '~/.config/alot/config', 'config file'],
+ ['notmuch-config', 'n', '~/.notmuch-config', 'notmuch config'],
+ ['colour-mode', 'C', 256, 'terminal colour mode', colourint],
+ ['mailindex-path', 'p', None, 'path to notmuch index'],
+ ['debug-level', 'd', 'info', 'debug level used with -l'],
+ ['logfile', 'l', '/dev/null', 'logfile'],
+ ]
+ subCommands = [['search', None, SubcommandOptions, "search for threads"],
+ ['compose', None, ComposeOptions, "compose a message"]]
+
+ def opt_version(self):
+ print alot.__version__
+ sys.exit(0)
def main():
# interpret cml arguments
- args = parse_args()
+ args = Options()
+ try:
+ args.parseOptions() # When given no argument, parses sys.argv[1:]
+ except usage.UsageError, errortext:
+ print '%s: %s' % (sys.argv[0], errortext)
+ print '%s: Try --help for usage details.' % (sys.argv[0])
+ sys.exit(1)
# locate alot config files
configfiles = [
@@ -58,14 +89,14 @@ def main():
'alot', 'config'),
os.path.expanduser('~/.alot.rc'),
]
- if args.configfile:
- expanded_path = os.path.expanduser(args.configfile)
+ if args['config']:
+ expanded_path = os.path.expanduser(args['config'])
if not os.path.exists(expanded_path):
sys.exit('File %s does not exist' % expanded_path)
configfiles.insert(0, expanded_path)
# locate notmuch config
- notmuchfile = os.path.expanduser(args.notmuchconfigfile)
+ notmuchfile = os.path.expanduser(args['notmuch-config'])
try:
# read the first alot config file we find
@@ -81,8 +112,8 @@ def main():
sys.exit(e)
# setup logging
- numeric_loglevel = getattr(logging, args.debug_level.upper(), None)
- logfilename = os.path.expanduser(args.logfile)
+ numeric_loglevel = getattr(logging, args['debug-level'].upper(), None)
+ logfilename = os.path.expanduser(args['logfile'])
logging.basicConfig(level=numeric_loglevel, filename=logfilename)
logger = logging.getLogger()
@@ -91,12 +122,16 @@ def main():
aman = AccountManager(settings.config)
# get ourselves a database manager
- dbman = DBManager(path=args.db_path, ro=args.read_only)
+ dbman = DBManager(path=args['mailindex-path'], ro=args['read-only'])
# get initial searchstring
try:
- if args.command != '':
- cmd = commands.commandfactory(args.command, 'global')
+ if args.subCommand == 'search':
+ query = ' '.join(args.subOptions.args)
+ cmd = commands.commandfactory('search ' + query, 'global')
+ elif args.subCommand == 'compose':
+ cmdstring = 'compose %s' % args.subOptions.as_argparse_opts()
+ cmd = commands.commandfactory(cmdstring, 'global')
else:
default_commandline = settings.config.get('general',
'initial_command')
@@ -109,7 +144,7 @@ def main():
logger,
aman,
cmd,
- args.colours,
+ args['colour-mode'],
)
if __name__ == "__main__":
diff --git a/alot/message.py b/alot/message.py
index d674b730..0b8131c1 100644
--- a/alot/message.py
+++ b/alot/message.py
@@ -3,6 +3,7 @@ import email
import tempfile
import re
import mimetypes
+import shlex
from datetime import datetime
from email.header import Header
import email.charset as charset
@@ -276,7 +277,8 @@ def extract_body(mail, types=None):
tmpfile.close()
#create and call external command
cmd = handler % tmpfile.name
- rendered_payload = helper.cmd_output(cmd)
+ cmdlist = shlex.split(cmd.encode('utf-8', errors='ignore'))
+ rendered_payload, errmsg, retval = helper.call_cmd(cmdlist)
#remove tempfile
os.unlink(tmpfile.name)
if rendered_payload: # handler had output
@@ -434,6 +436,8 @@ class Envelope(object):
self.attachments = list(attachments)
self.sign = sign
self.encrypt = encrypt
+ self.sent_time = None
+ self.modified_since_sent = False
def __str__(self):
return "Envelope (%s)\n%s" % (self.headers, self.body)
@@ -445,6 +449,9 @@ class Envelope(object):
"""
self.headers[name] = val
+ if self.sent_time:
+ self.modified_since_sent = True
+
def __getitem__(self, name):
"""getter for header values.
:raises: KeyError if undefined
@@ -454,15 +461,36 @@ class Envelope(object):
def __delitem__(self, name):
del(self.headers[name])
+ if self.sent_time:
+ self.modified_since_sent = True
+
def get(self, key, fallback=None):
"""secure getter for header values that allows specifying a `fallback`
- return string (defaults to None). This doesn't raise KeyErrors"""
+ return string (defaults to None). This returns the first matching value
+ and doesn't raise KeyErrors"""
+ if key in self.headers:
+ value = self.headers[key][0]
+ else:
+ value = fallback
+ return value
+
+ def get_all(self, key, fallback=[]):
+ """returns all header values for given key"""
if key in self.headers:
value = self.headers[key]
else:
value = fallback
return value
+ def add(self, key, value):
+ """add header value"""
+ if key not in self.headers:
+ self.headers[key] = []
+ self.headers[key].append(value)
+
+ if self.sent_time:
+ self.modified_since_sent = True
+
def attach(self, path, filename=None, ctype=None):
"""
attach a file
@@ -475,9 +503,13 @@ class Envelope(object):
:type ctype: str
"""
+ path = os.path.expanduser(path)
part = helper.mimewrap(path, filename, ctype)
self.attachments.append(part)
+ if self.sent_time:
+ self.modified_since_sent = True
+
def construct_mail(self):
"""
compiles the information contained in this envelope into a
@@ -489,18 +521,20 @@ class Envelope(object):
msg.attach(textpart)
else:
msg = textpart
- for k, v in self.headers.items():
- msg[k] = encode_header(k, v)
+ for k, vlist in self.headers.items():
+ for v in vlist:
+ msg[k] = encode_header(k, v)
for a in self.attachments:
msg.attach(a)
- logging.debug(msg)
return msg
- def parse_template(self, tmp):
+ def parse_template(self, tmp, reset=False):
"""parses a template or user edited string to fills this envelope.
:param tmp: the string to parse.
:type tmp: str
+ :param reset: remove previous envelope content
+ :type reset: bool
"""
logging.debug('GoT: """\n%s\n"""' % tmp)
m = re.match('(?P<h>([a-zA-Z0-9_-]+:.+\n)*)(?P<b>(\s*.*)*)', tmp)
@@ -510,6 +544,10 @@ class Envelope(object):
headertext = d['h']
self.body = d['b']
+ # remove existing content
+ if reset:
+ self.headers = {}
+
# go through multiline, utf-8 encoded headers
# we decode the edited text ourselves here as
# email.message_from_file can't deal with raw utf8 header values
@@ -517,9 +555,12 @@ class Envelope(object):
for line in headertext.splitlines():
if re.match('[a-zA-Z0-9_-]+:', line): # new k/v pair
if key and value: # save old one from stack
- self.headers[key] = value # save
+ self.add(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
- self.headers[key] = value
+ self.add(key, value)
+
+ if self.sent_time:
+ self.modified_since_sent = True
diff --git a/alot/ui.py b/alot/ui.py
index a152dfc1..d3ada992 100644
--- a/alot/ui.py
+++ b/alot/ui.py
@@ -201,31 +201,25 @@ class UI(object):
"""
closes given :class:`~alot.buffers.Buffer`.
- This shuts down alot in case its the last
- active buffer. Otherwise it removes it from the bufferlist
- and calls its cleanup() method.
+ This it removes it from the bufferlist and calls its cleanup() method.
"""
buffers = self.buffers
if buf not in buffers:
string = 'tried to close unknown buffer: %s. \n\ni have:%s'
self.logger.error(string % (buf, self.buffers))
- elif len(buffers) == 1:
- self.logger.info('closing the last buffer, exiting')
- cmd = commandfactory('exit')
- self.apply_command(cmd)
+ elif self.current_buffer == buf:
+ self.logger.debug('UI: closing current buffer %s' % buf)
+ index = buffers.index(buf)
+ buffers.remove(buf)
+ offset = config.getint('general', 'bufferclose_focus_offset')
+ nextbuffer = buffers[(index + offset) % len(buffers)]
+ self.buffer_focus(nextbuffer)
+ buf.cleanup()
else:
- if self.current_buffer == buf:
- self.logger.debug('UI: closing current buffer %s' % buf)
- index = buffers.index(buf)
- buffers.remove(buf)
- offset = config.getint('general', 'bufferclose_focus_offset')
- nextbuffer = buffers[(index + offset) % len(buffers)]
- self.buffer_focus(nextbuffer)
- else:
- string = 'closing buffer %d:%s'
- self.logger.debug(string % (buffers.index(buf), buf))
- buffers.remove(buf)
+ string = 'closing buffer %d:%s'
+ self.logger.debug(string % (buffers.index(buf), buf))
+ buffers.remove(buf)
buf.cleanup()
def buffer_focus(self, buf):
diff --git a/bin/alot b/bin/alot
index c5db0e5a..dfa68ead 100755
--- a/bin/alot
+++ b/bin/alot
@@ -1,20 +1,20 @@
#!/usr/bin/env python
-"""
-This file is part of alot, a terminal UI to notmuch mail (notmuchmail.org).
-Copyright (C) 2011 Patrick Totzke <patricktotzke@gmail.com>
-This program 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.
+# This file is part of alot, a terminal UI to notmuch mail (notmuchmail.org).
+# Copyright (C) 2011 Patrick Totzke <patricktotzke@gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-"""
from alot.init import main
main()