summaryrefslogtreecommitdiff
path: root/alot
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2012-02-23 22:11:35 +0000
committerPatrick Totzke <patricktotzke@gmail.com>2012-02-23 22:11:35 +0000
commit5d9448b5d262302225d775c0b3c38e6369ea19dc (patch)
treeaa5a94bdee27f3eaf2dcec3379668fb8c4bcc560 /alot
parent45cfa1ae28c783ff7ad23f74978c2d9d062c1fef (diff)
parent6667f882da353654749d6073dc28af9aad84116a (diff)
Merge branch 'rewrite-configs' into testing
Diffstat (limited to 'alot')
-rw-r--r--alot/account.py123
-rw-r--r--alot/buffers.py17
-rw-r--r--alot/commands/__init__.py7
-rw-r--r--alot/commands/envelope.py36
-rw-r--r--alot/commands/globals.py50
-rw-r--r--alot/commands/thread.py23
-rw-r--r--alot/completion.py32
-rw-r--r--alot/db.py4
-rw-r--r--alot/defaults/alot.rc437
-rw-r--r--alot/defaults/alot.rc.new10
-rw-r--r--alot/defaults/alot.rc.spec162
-rw-r--r--alot/defaults/bindings65
-rw-r--r--alot/defaults/default.theme317
-rw-r--r--alot/defaults/notmuch.rc.spec3
-rw-r--r--alot/defaults/theme.spec317
-rw-r--r--alot/helper.py7
-rwxr-xr-xalot/init.py66
-rw-r--r--alot/message.py17
-rw-r--r--alot/settings.py588
-rw-r--r--alot/ui.py40
-rw-r--r--alot/widgets.py100
21 files changed, 1393 insertions, 1028 deletions
diff --git a/alot/account.py b/alot/account.py
index 106acc46..d15e7f2a 100644
--- a/alot/account.py
+++ b/alot/account.py
@@ -49,12 +49,11 @@ class Account(object):
gpg_key=None, signature=None, signature_filename=None,
signature_as_attachment=False, sent_box=None,
sent_tags=['sent'], draft_box=None, draft_tags=['draft'],
- abook=None):
+ abook=None, **rest):
self.address = address
self.abook = abook
self.aliases = []
- if aliases:
- self.aliases = aliases.split(';')
+ self.aliases = aliases
self.realname = realname
self.gpg_key = gpg_key
self.signature = signature
@@ -195,124 +194,6 @@ class SendmailAccount(Account):
return d
-class AccountManager(object):
- """
- creates and organizes multiple :class:`Accounts <Account>` that were
- defined in the "account" sections of a given
- :class:`~alot.settings.AlotConfigParser`.
- """
- allowed = ['realname',
- 'address',
- 'aliases',
- 'gpg_key',
- 'signature',
- 'signature_filename',
- 'signature_as_attachment',
- 'type',
- 'sendmail_command',
- 'abook_command',
- 'abook_regexp',
- 'sent_box',
- 'sent_tags',
- 'draft_box',
- 'draft_tags']
- manditory = ['realname', 'address']
- parse_lists = ['sent_tags', 'draft_tags']
- accountmap = {}
- accounts = []
- ordered_addresses = []
-
- def __init__(self, config):
- """
- :param config: the config object to read account information from
- :type config: :class:`~alot.settings.AlotConfigParser`.
- """
- sections = config.sections()
- accountsections = filter(lambda s: s.startswith('account '), sections)
- for s in accountsections:
- options = filter(lambda x: x in self.allowed, config.options(s))
-
- args = {}
- if 'abook_command' in options:
- cmd = config.get(s, 'abook_command').encode('ascii',
- errors='ignore')
- options.remove('abook_command')
- if 'abook_regexp' in options:
- regexp = config.get(s, 'abook_regexp')
- options.remove('abook_regexp')
- else:
- regexp = None # will use default in constructor
- args['abook'] = MatchSdtoutAddressbook(cmd, match=regexp)
-
- if 'signature_as_attachment' in options:
- value = config.getboolean(s, 'signature_as_attachment')
- args['signature_as_attachment'] = value
- options.remove('signature_as_attachment')
-
- to_set = self.manditory
- for o in options:
- if o not in self.parse_lists:
- args[o] = config.get(s, o)
- else:
- args[o] = config.getstringlist(s, o)
- if o in to_set:
- to_set.remove(o) # removes obligation
- if not to_set: # all manditory fields were present
- sender_type = args.pop('type', 'sendmail')
- if sender_type == 'sendmail':
- cmd = args.pop('sendmail_command', 'sendmail')
- newacc = (SendmailAccount(cmd, **args))
- self.accountmap[newacc.address] = newacc
- self.accounts.append(newacc)
- for alias in newacc.aliases:
- self.accountmap[alias] = newacc
- else:
- logging.info('account section %s lacks %s' % (s, to_set))
-
- def get_accounts(self):
- """
- returns known accounts
-
- :rtype: list of :class:`Account`
- """
- return self.accounts
-
- def get_account_by_address(self, address):
- """
- returns :class:`Account` for a given email address (str)
-
- :param address: address to look up
- :type address: string
- :rtype: :class:`Account` or None
- """
-
- for myad in self.get_addresses():
- if myad in address:
- return self.accountmap[myad]
- return None
-
- def get_main_addresses(self):
- """returns addresses of known accounts without its aliases"""
- return [a.address for a in self.accounts]
-
- def get_addresses(self):
- """returns addresses of known accounts including all their aliases"""
- return self.accountmap.keys()
-
- def get_addressbooks(self, order=[], append_remaining=True):
- """returns list of all defined :class:`AddressBook` objects"""
- abooks = []
- for a in order:
- if a:
- if a.abook:
- abooks.append(a.abook)
- if append_remaining:
- for a in self.accounts:
- if a.abook and a.abook not in abooks:
- abooks.append(a.abook)
- return abooks
-
-
class AddressBook(object):
"""can look up email addresses and realnames for contacts.
diff --git a/alot/buffers.py b/alot/buffers.py
index 407dc47e..f8d70de3 100644
--- a/alot/buffers.py
+++ b/alot/buffers.py
@@ -2,7 +2,7 @@ import urwid
from notmuch import NotmuchError
import widgets
-import settings
+from settings import settings
import commands
from walker import PipeWalker
from helper import shorten_author_string
@@ -70,10 +70,13 @@ class BufferlistBuffer(Buffer):
for (num, b) in enumerate(displayedbuffers):
line = widgets.BufferlineWidget(b)
if (num % 2) == 0:
- attr = 'bufferlist_results_even'
+ attr = settings.get_theming_attribute('bufferlist',
+ 'results_even')
else:
- attr = 'bufferlist_results_odd'
- buf = urwid.AttrMap(line, attr, 'bufferlist_focus')
+ attr = settings.get_theming_attribute('bufferlist',
+ 'results_odd')
+ focus_att = settings.get_theming_attribute('bufferlist', 'focus')
+ buf = urwid.AttrMap(line, attr, focus_att)
num = urwid.Text('%3d:' % self.index_of(b))
lines.append(urwid.Columns([('fixed', 4, num), buf]))
self.bufferlist = urwid.ListBox(urwid.SimpleListWalker(lines))
@@ -105,8 +108,7 @@ class EnvelopeBuffer(Buffer):
def rebuild(self):
displayed_widgets = []
- hidden = settings.config.getstringlist('general',
- 'envelope_headers_blacklist')
+ hidden = settings.get('envelope_headers_blacklist')
#build lines
lines = []
for (k, vlist) in self.envelope.headers.items():
@@ -145,8 +147,7 @@ class SearchBuffer(Buffer):
self.dbman = ui.dbman
self.ui = ui
self.querystring = initialquery
- default_order = settings.config.get('general',
- 'search_threads_sort_order')
+ default_order = settings.get('search_threads_sort_order')
self.sort_order = sort_order or default_order
self.result_count = 0
self.isinitialized = False
diff --git a/alot/commands/__init__.py b/alot/commands/__init__.py
index 76bbd63a..ff9b7951 100644
--- a/alot/commands/__init__.py
+++ b/alot/commands/__init__.py
@@ -5,7 +5,7 @@ import shlex
import logging
import argparse
-import alot.settings
+from alot.settings import settings
import alot.helper
@@ -177,8 +177,7 @@ def commandfactory(cmdline, mode='global'):
args = args[1:]
# unfold aliases
- if alot.settings.config.has_option('command-aliases', cmdname):
- cmdname = alot.settings.config.get('command-aliases', cmdname)
+ # TODO: read from settingsmanager
# get class, argparser and forced parameter
(cmdclass, parser, forcedparms) = lookup_command(cmdname, mode)
@@ -192,7 +191,7 @@ def commandfactory(cmdline, mode='global'):
logging.debug('PARMS: %s' % parms)
# set pre and post command hooks
- get_hook = alot.settings.config.get_hook
+ get_hook = settings.get_hook
parms['prehook'] = get_hook('pre_%s_%s' % (mode, cmdname)) or \
get_hook('pre_global_%s' % cmdname)
parms['posthook'] = get_hook('post_%s_%s' % (mode, cmdname)) or \
diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py
index 81544629..cc67a5a1 100644
--- a/alot/commands/envelope.py
+++ b/alot/commands/envelope.py
@@ -5,16 +5,15 @@ import logging
import email
import tempfile
from twisted.internet.defer import inlineCallbacks
-from twisted.internet import threads
import datetime
from alot.account import SendingMailFailed
from alot import buffers
from alot import commands
from alot.commands import Command, registerCommand
-from alot import settings
from alot.commands import globals
from alot.helper import string_decode
+from alot.settings import settings
MODE = 'envelope'
@@ -77,13 +76,13 @@ class SaveCommand(Command):
# determine account to use
sname, saddr = email.Utils.parseaddr(envelope.get('From'))
- account = ui.accountman.get_account_by_address(saddr)
+ account = settings.get_account_by_address(saddr)
if account == None:
- if not ui.accountman.get_accounts():
+ if not settings.get_accounts():
ui.notify('no accounts set.', priority='error')
return
else:
- account = ui.accountman.get_accounts()[0]
+ account = settings.get_accounts()[0]
if account.draft_box == None:
ui.notify('abort: account <%s> has no draft_box set.' % saddr,
@@ -121,16 +120,15 @@ class SendCommand(Command):
return
frm = envelope.get('From')
sname, saddr = email.Utils.parseaddr(frm)
- omit_signature = False
# determine account to use for sending
- account = ui.accountman.get_account_by_address(saddr)
+ account = settings.get_account_by_address(saddr)
if account == None:
- if not ui.accountman.get_accounts():
+ if not settings.get_accounts():
ui.notify('no accounts set', priority='error')
return
else:
- account = ui.accountman.get_accounts()[0]
+ account = settings.get_accounts()[0]
# send
clearme = ui.notify('sending..', timeout=-1)
@@ -193,16 +191,14 @@ class EditCommand(Command):
self.envelope = ui.current_buffer.envelope
#determine editable headers
- edit_headers = set(settings.config.getstringlist('general',
- 'edit_headers_whitelist'))
+ edit_headers = set(settings.get('edit_headers_whitelist'))
if '*' in edit_headers:
edit_headers = set(self.envelope.headers.keys())
- blacklist = set(settings.config.getstringlist('general',
- 'edit_headers_blacklist'))
+ blacklist = set(settings.get('edit_headers_blacklist'))
if '*' in blacklist:
blacklist = set(self.envelope.headers.keys())
self.edit_headers = edit_headers - blacklist
- logging.info('editable headers: %s' % blacklist)
+ logging.info('editable headers: %s' % self.edit_headers)
def openEnvelopeFromTmpfile():
# This parses the input from the tempfile.
@@ -213,15 +209,14 @@ class EditCommand(Command):
# get input
f = open(tf.name)
os.unlink(tf.name)
- enc = settings.config.get('general', 'editor_writes_encoding')
+ enc = settings.get('editor_writes_encoding')
template = string_decode(f.read(), enc)
f.close()
# call post-edit translate hook
- translate = settings.config.get_hook('post_edit_translate')
+ translate = settings.get_hook('post_edit_translate')
if translate:
- template = translate(template, ui=ui, dbm=ui.dbman,
- aman=ui.accountman, config=settings.config)
+ template = translate(template, ui=ui, dbm=ui.dbman)
self.envelope.parse_template(template)
if self.openNew:
ui.buffer_open(buffers.EnvelopeBuffer(ui, self.envelope))
@@ -246,10 +241,9 @@ class EditCommand(Command):
bodytext = self.envelope.body
# call pre-edit translate hook
- translate = settings.config.get_hook('pre_edit_translate')
+ translate = settings.get_hook('pre_edit_translate')
if translate:
- bodytext = translate(bodytext, ui=ui, dbm=ui.dbman,
- aman=ui.accountman, config=settings.config)
+ bodytext = translate(bodytext, ui=ui, dbm=ui.dbman)
#write stuff to tempfile
tf = tempfile.NamedTemporaryFile(delete=False)
diff --git a/alot/commands/globals.py b/alot/commands/globals.py
index 81385456..db944a00 100644
--- a/alot/commands/globals.py
+++ b/alot/commands/globals.py
@@ -15,7 +15,6 @@ from alot.completion import CommandLineCompleter
from alot.commands import CommandParseError
from alot.commands import commandfactory
from alot import buffers
-from alot import settings
from alot import widgets
from alot import helper
from alot.db import DatabaseLockedError
@@ -23,6 +22,7 @@ from alot.completion import ContactsCompleter
from alot.completion import AccountCompleter
from alot.message import Envelope
from alot import commands
+from alot.settings import settings
MODE = 'global'
@@ -32,7 +32,7 @@ class ExitCommand(Command):
"""shut down cleanly"""
@inlineCallbacks
def apply(self, ui):
- if settings.config.getboolean('general', 'bug_on_exit'):
+ if settings.get('bug_on_exit'):
if (yield ui.choice('realy quit?', select='yes', cancel='no',
msg_position='left')) == 'no':
return
@@ -91,7 +91,6 @@ class PromptCommand(Command):
cmdline = yield ui.prompt(prefix=':',
text=self.startwith,
completer=CommandLineCompleter(ui.dbman,
- ui.accountman,
mode,
ui.current_buffer,
),
@@ -178,9 +177,7 @@ class ExternalCommand(Command):
cmd = self.commandstring
if self.spawn:
- cmd = '%s %s' % (settings.config.get('general',
- 'terminal_cmd'),
- cmd)
+ cmd = '%s %s' % (settings.get('terminal_cmd'), cmd)
cmd = cmd.encode('utf-8', errors='ignore')
logging.info('calling external command: %s' % cmd)
try:
@@ -219,19 +216,17 @@ class EditCommand(ExternalCommand):
if spawn != None:
self.spawn = spawn
else:
- self.spawn = settings.config.getboolean('general', 'editor_spawn')
+ self.spawn = settings.get('editor_spawn')
if thread != None:
self.thread = thread
else:
- self.thread = settings.config.getboolean('general',
- 'editor_in_thread')
+ self.thread = settings.get('editor_in_thread')
self.editor_cmd = None
if os.path.isfile('/usr/bin/editor'):
self.editor_cmd = '/usr/bin/editor'
self.editor_cmd = os.environ.get('EDITOR', self.editor_cmd)
- self.editor_cmd = settings.config.get('general', 'editor_cmd',
- fallback=self.editor_cmd)
+ self.editor_cmd = settings.get('editor_cmd') or self.editor_cmd
logging.debug('using editor_cmd: %s' % self.editor_cmd)
ExternalCommand.__init__(self, self.editor_cmd, path=self.path,
@@ -269,7 +264,7 @@ class BufferCloseCommand(Command):
if self.buffer == None:
self.buffer = ui.current_buffer
if len(ui.buffers) == 1:
- if settings.config.getboolean('general', 'quit_on_last_bclose'):
+ if settings.get('quit_on_last_bclose'):
logging.info('closing the last buffer, exiting')
ui.apply_command(ExitCommand())
else:
@@ -353,7 +348,7 @@ class FlushCommand(Command):
try:
ui.dbman.flush()
except DatabaseLockedError:
- timeout = settings.config.getint('general', 'flush_retry_timeout')
+ timeout = settings.get('flush_retry_timeout')
def f(*args):
self.apply(ui)
@@ -383,8 +378,8 @@ class HelpCommand(Command):
logging.debug('HELP')
if self.commandname == 'bindings':
# get mappings
- modemaps = dict(settings.config.items('%s-maps' % ui.mode))
- globalmaps = dict(settings.config.items('global-maps'))
+ modemaps = dict(settings._bindings[ui.mode].items())
+ globalmaps = dict(settings._bindings['global'].items())
# build table
maxkeylength = len(max((modemaps).keys() + globalmaps.keys(),
@@ -414,9 +409,11 @@ class HelpCommand(Command):
ckey = 'cancel'
titletext = 'Bindings Help (%s cancels)' % ckey
+ text_att = settings.get_theming_attribute('help', 'text')
+ title_att = settings.get_theming_attribute('help', 'title')
box = widgets.DialogBox(body, titletext,
- bodyattr='help_text',
- titleattr='help_title')
+ bodyattr=text_att,
+ titleattr=title_att)
# put promptwidget as overlay on main widget
overlay = urwid.Overlay(box, ui.mainframe, 'center',
@@ -499,7 +496,7 @@ class ComposeCommand(Command):
self.envelope = Envelope()
if self.template is not None:
#get location of tempsdir, containing msg templates
- tempdir = settings.config.get('general', 'template_dir')
+ tempdir = settings.get('template_dir')
tempdir = os.path.expanduser(tempdir)
if not tempdir:
xdgdir = os.environ.get('XDG_CONFIG_HOME',
@@ -542,19 +539,19 @@ class ComposeCommand(Command):
# get missing From header
if not 'From' in self.envelope.headers:
- accounts = ui.accountman.get_accounts()
+ accounts = settings.get_accounts()
if len(accounts) == 1:
a = accounts[0]
fromstring = "%s <%s>" % (a.realname, a.address)
self.envelope.add('From', fromstring)
else:
- cmpl = AccountCompleter(ui.accountman)
+ cmpl = AccountCompleter()
fromaddress = yield ui.prompt(prefix='From>', completer=cmpl,
tab=1)
if fromaddress is None:
ui.notify('canceled')
return
- a = ui.accountman.get_account_by_address(fromaddress)
+ a = settings.get_account_by_address(fromaddress)
if a is not None:
fromstring = "%s <%s>" % (a.realname, a.address)
self.envelope.add('From', fromstring)
@@ -564,7 +561,7 @@ class ComposeCommand(Command):
# add signature
if not self.omit_signature:
name, addr = email.Utils.parseaddr(self.envelope['From'])
- account = ui.accountman.get_account_by_address(addr)
+ account = settings.get_account_by_address(addr)
if account is not None:
if account.signature:
logging.debug('has signature')
@@ -594,13 +591,12 @@ class ComposeCommand(Command):
if 'To' not in self.envelope.headers:
sender = self.envelope.get('From')
name, addr = email.Utils.parseaddr(sender)
- account = ui.accountman.get_account_by_address(addr)
+ account = settings.get_account_by_address(addr)
- allbooks = not settings.config.getboolean('general',
- 'complete_matching_abook_only')
+ allbooks = not settings.get('complete_matching_abook_only')
logging.debug(allbooks)
if account is not None:
- abooks = ui.accountman.get_addressbooks(order=[account],
+ abooks = settings.get_addressbooks(order=[account],
append_remaining=allbooks)
logging.debug(abooks)
completer = ContactsCompleter(abooks)
@@ -613,7 +609,7 @@ class ComposeCommand(Command):
return
self.envelope.add('To', to)
- if settings.config.getboolean('general', 'ask_subject') and \
+ if settings.get('ask_subject') and \
not 'Subject' in self.envelope.headers:
subject = yield ui.prompt(prefix='Subject>')
logging.debug('SUBJECT: "%s"' % subject)
diff --git a/alot/commands/thread.py b/alot/commands/thread.py
index 31b4b19f..c3a5da8d 100644
--- a/alot/commands/thread.py
+++ b/alot/commands/thread.py
@@ -12,7 +12,6 @@ from alot.commands.globals import ExternalCommand
from alot.commands.globals import FlushCommand
from alot.commands.globals import ComposeCommand
from alot.commands.globals import RefreshCommand
-from alot import settings
from alot import widgets
from alot import completion
from alot.message import decode_header
@@ -21,7 +20,7 @@ from alot.message import extract_headers
from alot.message import extract_body
from alot.message import Envelope
from alot.db import DatabaseROError
-from alot.db import DatabaseError
+from alot.settings import settings
MODE = 'thread'
@@ -82,7 +81,7 @@ class ReplyCommand(Command):
def apply(self, ui):
# look if this makes sense: do we have any accounts set up?
- my_accounts = ui.accountman.get_accounts()
+ my_accounts = settings.get_accounts()
if not my_accounts:
ui.notify('no accounts set', priority='error')
return
@@ -95,11 +94,9 @@ class ReplyCommand(Command):
# set body text
name, address = self.message.get_author()
timestamp = self.message.get_date()
- qf = settings.config.get_hook('reply_prefix')
+ qf = settings.get_hook('reply_prefix')
if qf:
- quotestring = qf(name, address, timestamp,
- ui=ui, dbm=ui.dbman, aman=ui.accountman,
- config=settings.config)
+ quotestring = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
else:
quotestring = 'Quoting %s (%s)\n' % (name, timestamp)
mailcontent = quotestring
@@ -121,7 +118,7 @@ class ReplyCommand(Command):
# set To
sender = mail['Reply-To'] or mail['From']
recipients = [sender]
- my_addresses = ui.accountman.get_addresses()
+ my_addresses = settings.get_addresses()
if self.groupreply:
if sender != mail['From']:
recipients.append(mail['From'])
@@ -180,7 +177,7 @@ class ForwardCommand(Command):
def apply(self, ui):
# look if this makes sense: do we have any accounts set up?
- my_accounts = ui.accountman.get_accounts()
+ my_accounts = settings.get_accounts()
if not my_accounts:
ui.notify('no accounts set', priority='error')
return
@@ -196,11 +193,9 @@ class ForwardCommand(Command):
# set body text
name, address = self.message.get_author()
timestamp = self.message.get_date()
- qf = settings.config.get_hook('forward_prefix')
+ qf = settings.get_hook('forward_prefix')
if qf:
- quote = qf(name, address, timestamp,
- ui=ui, dbm=ui.dbman, aman=ui.accountman,
- config=settings.config)
+ quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
else:
quote = 'Forwarded message from %s (%s):\n' % (name, timestamp)
mailcontent = quote
@@ -548,7 +543,7 @@ class PrintCommand(PipeCommand):
:type add_tags: bool
"""
# get print command
- cmd = settings.config.get('general', 'print_cmd', fallback='')
+ cmd = settings.get('print_cmd', fallback='')
# set up notification strings
if all:
diff --git a/alot/completion.py b/alot/completion.py
index 94ab33c0..e9e44226 100644
--- a/alot/completion.py
+++ b/alot/completion.py
@@ -6,6 +6,7 @@ import argparse
import alot.commands as commands
from alot.buffers import EnvelopeBuffer
+from alot.settings import settings
class Completer(object):
@@ -42,7 +43,7 @@ class StringlistCompleter(Completer):
def __init__(self, resultlist):
"""
:param resultlist: strings used for completion
- :type accountman: list of str
+ :type resultlist: list of str
"""
self.resultlist = resultlist
@@ -97,16 +98,13 @@ class MultipleSelectionCompleter(Completer):
class QueryCompleter(Completer):
"""completion for a notmuch query string"""
- def __init__(self, dbman, accountman):
+ def __init__(self, dbman):
"""
:param dbman: used to look up avaliable tagstrings
:type dbman: :class:`~alot.db.DBManager`
- :param accountman: used to look up known addresses to complete 'from'
- and 'to' queries
- :type accountman: :class:`~alot.account.AccountManager`
"""
self.dbman = dbman
- abooks = accountman.get_addressbooks()
+ abooks = settings.get_addressbooks()
self._abookscompleter = AbooksCompleter(abooks, addressesonly=True)
self._tagcompleter = TagCompleter(dbman)
self.keywords = ['tag', 'from', 'to', 'subject', 'attachment',
@@ -210,6 +208,7 @@ class AbooksCompleter(Completer):
returnlist.append((newtext, len(newtext)))
return returnlist
+
class ArgparseOptionCompleter(Completer):
"""completes option parameters for a given argparse.Parser"""
def __init__(self, parser):
@@ -226,7 +225,7 @@ class ArgparseOptionCompleter(Completer):
res = []
for act in self.actions:
if '=' in pref:
- optionstring = pref[:pref.rfind('=')+1]
+ optionstring = pref[:pref.rfind('=') + 1]
# get choices
if 'choices' in act.__dict__:
choices = act.choices or []
@@ -241,15 +240,12 @@ class ArgparseOptionCompleter(Completer):
return [(a, len(a)) for a in res]
+
class AccountCompleter(StringlistCompleter):
"""completes users' own mailaddresses"""
- def __init__(self, accountman):
- """
- :param accountman: used to look up the list of addresses
- :type accountman: :class:`~alot.account.AccountManager`
- """
- resultlist = accountman.get_main_addresses()
+ def __init__(self):
+ resultlist = settings.get_main_addresses()
StringlistCompleter.__init__(self, resultlist)
@@ -276,13 +272,10 @@ class CommandCompleter(Completer):
class CommandLineCompleter(Completer):
"""completion for commandline"""
- def __init__(self, dbman, accountman, mode, currentbuffer=None):
+ def __init__(self, dbman, mode, currentbuffer=None):
"""
:param dbman: used to look up avaliable tagstrings
:type dbman: :class:`~alot.db.DBManager`
- :param accountman: used to look up known addresses to complete 'from'
- and 'to' queries
- :type accountman: :class:`~alot.account.AccountManager`
:param mode: mode identifier
:type mode: str
:param currentbuffer: currently active buffer. If defined, this will be
@@ -291,13 +284,12 @@ class CommandLineCompleter(Completer):
:type currentbuffer: :class:`~alot.buffers.Buffer`
"""
self.dbman = dbman
- self.accountman = accountman
self.mode = mode
self.currentbuffer = currentbuffer
self._commandcompleter = CommandCompleter(mode)
- self._querycompleter = QueryCompleter(dbman, accountman)
+ self._querycompleter = QueryCompleter(dbman)
self._tagcompleter = TagCompleter(dbman)
- abooks = accountman.get_addressbooks()
+ abooks = settings.get_addressbooks()
self._contactscompleter = ContactsCompleter(abooks)
self._pathcompleter = PathCompleter()
diff --git a/alot/db.py b/alot/db.py
index a4dfe998..44ca608d 100644
--- a/alot/db.py
+++ b/alot/db.py
@@ -7,7 +7,7 @@ from datetime import datetime
from collections import deque
from message import Message
-from settings import notmuchconfig as config
+from settings import settings
DB_ENC = 'utf-8'
@@ -94,7 +94,7 @@ class DBManager(object):
raise DatabaseLockedError()
# read notmuch's config regarding imap flag synchronization
- sync = config.getboolean('maildir', 'synchronize_flags')
+ sync = settings.get_notmuch_setting('maildir', 'synchronize_flags')
# go through writequeue entries
while self.writequeue:
diff --git a/alot/defaults/alot.rc b/alot/defaults/alot.rc
deleted file mode 100644
index d6dfa0c3..00000000
--- a/alot/defaults/alot.rc
+++ /dev/null
@@ -1,437 +0,0 @@
-[general]
-
-# ask for subject when compose
-ask_subject = True
-
-# confirm exit
-bug_on_exit = False
-
-# offset of next focussed buffer if the current one gets closed
-bufferclose_focus_offset=-1
-
-# number of colours your terminal supports
-colourmode = 256
-
-# number of spaces used to replace tab characters
-tabwidth = 8
-
-# templates directory that contains your message templates.
-# It will be used if you give `compose --template` a filename without a path prefix.
-# This defaults to `$XDG_CONFIG_HOME/alot/templates` if unset.
-template_dir =
-
-# fill threadline with message content
-display_content_in_threadline = False
-
-# headers that get displayed by default
-displayed_headers = From,To,Cc,Bcc,Subject
-
-# headers that are hidden in envelope buffers by default
-envelope_headers_blacklist = In-Reply-To,References
-
-# set terminal command used for spawning shell commands
-terminal_cmd = /usr/bin/x-terminal-emulator -e
-
-####################
-# EDITOR settings #
-####################
-# editor command
-# if unset, alot will first try the EDITOR env variable, then /usr/bin/editor
-#editor_cmd = /usr/bin/vim -f -c 'set filetype=mail' +
-
-# file encoding used by your editor
-editor_writes_encoding = UTF-8
-
-# use terminal_command to spawn a new terminal for the editor?
-editor_spawn = False
-
-# call editor in separate thread.
-# in case your editor doesn't run in the same window as alot, setting true here
-# will make alot non-blocking during edits
-editor_in_thread = False
-
-
-# Which header fields should be editable in your editor
-# used are those that match the whitelist and don't macht the blacklist.
-# in both cases '*' may be used to indicate all fields.
-edit_headers_whitelist = *
-edit_headers_blacklist = Content-Type,MIME-Version,References,In-Reply-To
-
-
-# timeout in secs after a failed attempt to flush is repeated
-flush_retry_timeout = 5
-
-# where to look up hooks
-hooksfile = ~/.config/alot/hooks.py
-
-# time in secs to display status messages
-notify_timeout = 2
-
-# display statusline?
-show_statusbar = True
-
-# strftime format for timestamps. Note: you must escape % twice here:
-# use '%%%%' instead of '%' (and use '%%%%%%%%' to get a literal '%').
-# for the strftime format, see
-# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
-# timestamp_format = ''
-
-# max length of authors line in thread widgets
-authors_maxlength = 30
-
-# how to print messages:
-# this specifies a shellcommand used pro printing.
-# threads/messages are piped to this as plaintext.
-# muttprint/a2ps works nicely
-print_cmd = ''
-
-# initial command when none is given as argument:
-initial_command = search tag:inbox AND NOT tag:killed
-
-# default sort order of results in a search
-# must be one of one of 'oldest_first', 'newest_first', 'message_id' or 'unsorted'
-search_threads_sort_order = newest_first
-
-# in case more than one account has an address book:
-# Set this to True to make tabcompletion for recipients during compose only
-# 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
-
-# value of the User-Agent header used for outgoing mails.
-# setting this to the empty string will cause alot to omit the header all together.
-# The string '%(version)s' will be replaced by the version string of the running instance.
-# Beware ne necessary 3-fold escaping for the symbol '%' though.
-user_agent = 'alot/%%%%%%%%(version)s'
-
-
-[global-maps]
-j = move down
-k = move up
-' ' = move page down
-esc = cancel
-enter = select
-
-@ = refresh
-? = help bindings
-I = search tag:inbox AND NOT tag:killed
-L = taglist
-shift tab = bprevious
-U = search tag:unread
-tab = bnext
-\ = prompt 'search '
-d = bclose
-$ = flush
-m = compose
-o = prompt 'search '
-q = exit
-';' = bufferlist
-colon = prompt
-
-[bufferlist-maps]
-x = close
-select = openfocussed
-
-[search-maps]
-a = toggletags inbox
-& = toggletags killed
-! = toggletags flagged
-s = toggletags unread
-l = retagprompt
-O = refineprompt
-| = refineprompt
-
-[envelope-maps]
-a = prompt 'attach ~/'
-y = send
-P = save
-s = 'refine Subject'
-t = 'refine To'
-b = 'refine Bcc'
-c = 'refine Cc'
-select = edit
-H = toggleheaders
-
-[taglist-maps]
-
-[thread-maps]
-C = fold --all
-E = unfold --all
-c = fold
-e = unfold
-< = fold
-> = unfold
-H = toggleheaders
-h = togglesource
-P = print --all --separately --add_tags
-S = save --all
-g = reply --all
-f = forward
-p = print --add_tags
-n = editnew
-s = save
-r = reply
-| = prompt 'pipeto '
-
-[command-aliases]
-quit = exit
-bn = bnext
-clo = close
-bp = bprevious
-ls = bufferlist
-
-[highlighting]
-# Thread lines in the search buffer can be highlighted if they match a query
-# by theming their components.
-
-# dictionary of highlighting rules. The keys are queries you want highlighting
-# for; values are chosen designators that identify themeing options in the
-# colour scheme:
-# search_thread_<component>_<id>_[focus_][fg|bg]
-# Note that the sequence of the list defines the search order. The first
-# specified query that matches selects the themeing.
-rules = { "tag:unread AND tag:flagged":"isunread+flagged",
- "tag:unread":"isunread",
- "tag:flagged":"isflagged" }
-
-# comma separated list of the components of a thread line you want highlighted
-# if a query matches.
-# Possible components are [date|mailcount|tags|authors|subject|content].
-components = subject
-
-[256c-theme]
-
-# default formating for tagstrings
-# add 'tag_X_[focus_]fg' and 'tag_X_[focus_]bg' to your config to specify
-# formating for tagstring 'X'
-tag_bg = default
-tag_fg = brown
-tag_focus_bg = #68a
-tag_focus_fg = #ffa
-
-#draft tag in red
-tag_draft_bg = #d66
-tag_draft_fg = white
-
-# formating of the `help bindings` overlay
-help_text_bg = g35
-help_text_fg = default
-help_section_bg = g35
-help_section_fg = bold,underline
-help_title_bg = g35
-help_title_fg = white,bold,underline
-
-# attributes used in all modi
-global_footer_bg = #006
-global_footer_fg = white
-global_notify_error_bg = dark red
-global_notify_error_fg = white
-global_notify_normal_bg = #68a
-global_notify_normal_fg = light gray
-global_prompt_bg = g10
-global_prompt_fg = light gray
-
-# mode specific attributes
-bufferlist_focus_bg = #68a
-bufferlist_focus_fg = #ffa
-bufferlist_results_even_bg = g3
-bufferlist_results_even_fg = default
-bufferlist_results_odd_bg = default
-bufferlist_results_odd_fg = default
-search_thread_authors_bg = default
-search_thread_authors_fg = #6d6
-search_thread_authors_isunread_fg = #6d6,bold
-search_thread_authors_isunread+flagged_fg = #6d6,bold
-search_thread_authors_focus_bg = #68a
-search_thread_authors_focus_fg = #8f6
-search_thread_authors_isunread_focus_fg = #8f6,bold
-search_thread_authors_isunread+flagged_focus_fg = #8f6,bold
-search_thread_bg = default
-search_thread_content_bg = default
-search_thread_content_fg = #866
-search_thread_content_isunread_fg = #866,bold
-search_thread_content_isunread+flagged_fg = #866,bold
-search_thread_content_focus_bg = #68a
-search_thread_content_focus_fg = #866
-search_thread_content_isunread_focus_fg = #866,bold
-search_thread_content_isunread+flagged_focus_fg = #866,bold
-search_thread_date_bg = default
-search_thread_date_fg = g58
-search_thread_date_isunread_fg = g58,bold
-search_thread_date_isflagged_fg = light red
-search_thread_date_isunread+flagged_fg = light red,bold
-search_thread_date_focus_bg = #68a
-search_thread_date_focus_fg = g89
-search_thread_date_isunread_focus_fg = g89,bold
-search_thread_date_isflagged_focus_fg = light red
-search_thread_date_isunread+flagged_focus_fg = light red,bold
-search_thread_fg = default
-search_thread_focus_bg = #68a
-search_thread_focus_fg = white
-search_thread_mailcount_bg = default
-search_thread_mailcount_fg = light gray
-search_thread_mailcount_isunread_fg = light gray,bold
-search_thread_mailcount_isflagged_fg = light red
-search_thread_mailcount_isunread+flagged_fg = light red,bold
-search_thread_mailcount_focus_bg = #68a
-search_thread_mailcount_focus_fg = g89
-search_thread_mailcount_isunread_focus_fg = g89,bold
-search_thread_mailcount_isflagged_focus_fg = light red
-search_thread_mailcount_isunread+flagged_focus_fg = light red,bold
-search_thread_subject_bg = default
-search_thread_subject_fg = g58
-search_thread_subject_isunread_fg = g58,bold
-search_thread_subject_isflagged_fg = light red
-search_thread_subject_isunread+flagged_fg = light red,bold
-search_thread_subject_focus_bg = #68a
-search_thread_subject_focus_fg = g89
-search_thread_subject_isunread_focus_fg = g89,bold
-search_thread_subject_isflagged_focus_fg = light red
-search_thread_subject_isunread+flagged_focus_fg = light red,bold
-search_thread_tags_bg = default
-search_thread_tags_fg = #a86
-search_thread_tags_focus_bg = #68a
-search_thread_tags_focus_fg = #ff8
-thread_attachment_bg = dark gray
-thread_attachment_fg = light gray
-thread_attachment_focus_bg = light green
-thread_attachment_focus_fg = light gray
-thread_body_bg = default
-thread_body_fg = light gray
-thread_header_bg = dark gray
-thread_header_fg = white
-thread_header_key_bg = dark gray
-thread_header_key_fg = white
-thread_header_value_bg = dark gray
-thread_header_value_fg = light gray
-thread_summary_even_bg = #068
-thread_summary_even_fg = white
-thread_summary_focus_bg = g58
-thread_summary_focus_fg = #ff8
-thread_summary_odd_bg = #006
-thread_summary_odd_fg = white
-
-
-[16c-theme]
-global_footer_bg = dark blue
-global_footer_fg = light green
-global_notify_error_bg = dark red
-global_notify_error_fg = white
-global_notify_normal_bg = dark gray
-global_notify_normal_fg = light gray
-global_prompt_bg = black
-global_prompt_fg = light gray
-help_text_bg = dark gray
-help_text_fg = default
-help_section_bg = dark gray
-help_section_fg = bold,underline
-help_title_bg = dark blue
-help_title_fg = white
-tag_bg = black
-tag_fg = brown
-tag_focus_bg = dark gray
-tag_focus_fg = white
-tag_draft_bg = light red
-tag_draft_fg = white
-bufferlist_focus_bg = dark gray
-bufferlist_focus_fg = white
-bufferlist_results_even_bg = black
-bufferlist_results_even_fg = light gray
-bufferlist_results_odd_bg = black
-bufferlist_results_odd_fg = light gray
-thread_attachment_bg = dark gray
-thread_attachment_fg = light gray
-thread_attachment_focus_bg = light green
-thread_attachment_focus_fg = light gray
-thread_body_bg = default
-thread_body_fg = light gray
-thread_header_bg = dark gray
-thread_header_fg = white
-thread_header_key_bg = dark gray
-thread_header_key_fg = white
-thread_header_value_bg = dark gray
-thread_header_value_fg = light gray
-thread_summary_even_bg = light blue
-thread_summary_even_fg = white
-thread_summary_focus_bg = dark cyan
-thread_summary_focus_fg = white
-thread_summary_odd_bg = dark blue
-thread_summary_odd_fg = white
-search_thread_authors_bg = default
-search_thread_authors_fg = dark green
-search_thread_authors_isunread_fg = dark green,bold
-search_thread_authors_focus_bg = dark gray
-search_thread_authors_focus_fg = dark green,bold
-search_thread_bg = default
-search_thread_content_bg = default
-search_thread_content_fg = dark gray
-search_thread_content_focus_bg = dark gray
-search_thread_content_focus_fg = black
-search_thread_date_bg = default
-search_thread_date_fg = light gray
-search_thread_date_focus_bg = dark gray
-search_thread_date_focus_fg = light gray
-search_thread_fg = default
-search_thread_focus_bg = dark gray
-search_thread_focus_fg = light gray
-search_thread_mailcount_bg = default
-search_thread_mailcount_fg = light gray
-search_thread_mailcount_focus_bg = dark gray
-search_thread_mailcount_focus_fg = light gray
-search_thread_subject_bg = default
-search_thread_subject_fg = light gray
-search_thread_subject_isunread_fg = light gray,bold
-search_thread_subject_isflagged_fg = light red
-search_thread_subject_focus_bg = dark gray
-search_thread_subject_focus_fg = light gray
-search_thread_subject_isunread_focus_fg = light gray,bold
-search_thread_subject_isflagged_focus_fg = light red,bold
-search_thread_tags_bg = default
-search_thread_tags_fg = brown
-search_thread_tags_focus_bg = dark gray
-search_thread_tags_focus_fg = yellow,bold
-
-
-[1c-theme]
-global_footer = standout
-global_notify_error = standout
-global_notify_normal = default
-global_prompt =
-help_text = default
-help_section = underline
-help_title = standout
-tag = default
-tag_focus = standout, bold
-tag_draft = standout
-bufferlist_focus = standout
-bufferlist_results_even = default
-bufferlist_results_odd = default
-search_thread = default
-search_thread_authors = default,underline
-search_thread_authors_focus = standout
-search_thread_content = default
-search_thread_content_focus = standout
-search_thread_date = default
-search_thread_date_focus = standout
-search_thread_focus = standout
-search_thread_mailcount = default
-search_thread_mailcount_focus = standout
-search_thread_subject = default
-search_thread_subject_isunread = bold
-search_thread_subject_isflagged = underline
-search_thread_subject_focus = standout
-search_thread_subject_isunread_focus = standout,bold
-search_thread_subject_isflagged_focus = standout,underline
-search_thread_tags = bold
-search_thread_tags_focus = standout
-thread_attachment = default
-thread_attachment_focus = underline
-thread_body = default
-thread_header = default
-thread_header_key = default
-thread_header_value = default
-thread_summary_even =
-thread_summary_focus = standout
-thread_summary_odd =
diff --git a/alot/defaults/alot.rc.new b/alot/defaults/alot.rc.new
new file mode 100644
index 00000000..099d730f
--- /dev/null
+++ b/alot/defaults/alot.rc.new
@@ -0,0 +1,10 @@
+[tags]
+[[flagged]]
+ fg = light red
+ translated = ⚑
+[[unread]]
+ translated = ✉
+[[replied]]
+ translated = ⏎
+[[inbox]]
+ translated = ⊡
diff --git a/alot/defaults/alot.rc.spec b/alot/defaults/alot.rc.spec
new file mode 100644
index 00000000..296d58f0
--- /dev/null
+++ b/alot/defaults/alot.rc.spec
@@ -0,0 +1,162 @@
+ask_subject = boolean(default=True) # ask for subject when compose
+
+# confirm exit
+bug_on_exit = boolean(default=False)
+
+# offset of next focussed buffer if the current one gets closed
+bufferclose_focus_offset = integer(default=-1)
+
+# number of colours your terminal supports
+colourmode = option(1, 16, 256, default=256)
+
+# number of spaces used to replace tab characters
+tabwidth = integer(default=8)
+
+# templates directory that contains your message templates.
+# It will be used if you give `compose --template` a filename without a path prefix.
+template_dir = string(default='$XDG_CONFIG_HOME/alot/templates')
+
+# directory containing theme files
+themes_dir = string(default=None)
+
+# name of the theme to use
+theme = string(default=None)
+
+# fill threadline with message content
+display_content_in_threadline = boolean(default=False)
+
+# headers that get displayed by default
+displayed_headers = string_list(default=list(From,To,Cc,Bcc,Subject))
+
+# headers that are hidden in envelope buffers by default
+envelope_headers_blacklist = string_list(default=list(In-Reply-To,References))
+
+# set terminal command used for spawning shell commands
+terminal_cmd = string(default='x-terminal-emulator -e')
+
+# editor command
+# if unset, alot will first try the EDITOR env variable, then /usr/bin/editor
+editor_cmd = string(default=None)
+
+# file encoding used by your editor
+editor_writes_encoding = string(default='UTF-8')
+
+# use terminal_command to spawn a new terminal for the editor?
+editor_spawn = boolean(default=False)
+
+# call editor in separate thread.
+# In case your editor doesn't run in the same window as alot, setting true here
+# will make alot non-blocking during edits
+editor_in_thread = boolean(default=False)
+
+# Which header fields should be editable in your editor
+# used are those that match the whitelist and don't macht the blacklist.
+# in both cases '*' may be used to indicate all fields.
+edit_headers_whitelist = string_list(default=list(*,))
+edit_headers_blacklist = string_list(default=list(Content-Type,MIME-Version,References,In-Reply-To))
+
+# timeout in secs after a failed attempt to flush is repeated
+flush_retry_timeout = integer(default=5)
+
+# where to look up hooks
+hooksfile = string(default='~/.config/alot/hooks.py')
+
+# time in secs to display status messages
+notify_timeout = integer(default=2)
+
+# display statusline?
+show_statusbar = boolean(default=True)
+
+# timestamp format in strftime format syntax:
+# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
+timestamp_format = string(default=None)
+
+# max length of authors line in thread widgets
+authors_maxlength = integer(default=30)
+
+# how to print messages:
+# this specifies a shellcommand used pro printing.
+# threads/messages are piped to this as plaintext.
+# muttprint/a2ps works nicely
+print_cmd = string(default=None)
+
+# initial command when none is given as argument:
+initial_command = string(default='search tag:inbox AND NOT tag:killed')
+
+# default sort order of results in a search
+search_threads_sort_order = option('oldest_first', 'newest_first', 'message_id', 'unsorted', default='newest_first')
+
+# in case more than one account has an address book:
+# Set this to True to make tabcompletion for recipients during compose only
+# look in the abook of the account matching the sender address
+complete_matching_abook_only = boolean(default=False)
+
+# shut down when the last buffer gets closed
+quit_on_last_bclose = boolean(default=False)
+
+# value of the User-Agent header used for outgoing mails.
+# setting this to the empty string will cause alot to omit the header all together.
+# The string '$VERSION' will be replaced by the version string of the running instance.
+user_agent = string(default='alot/$VERSION')
+
+# Keybindings
+[bindings]
+ __many__ = string(default=None)
+ [[___many___]]
+ __many__ = string(default=None)
+
+[tags]
+ # for each tag
+ [[__many__]]
+ # foreground
+ fg = string(default=None)
+ # background
+ bg = string(default=None)
+ # foreground if focussed
+ focus_fg = string(default=None)
+ # background if focussed
+ focus_bg = string(default=None)
+ # alternative string representation
+ translated = string(default=None)
+
+[accounts]
+[[__many__]]
+ # your email address
+ address = string
+
+ # used to format the (proposed) From-header in outgoing mails
+ realname = string
+
+ # used to clear your addresses/ match account when formating replies
+ aliases = string_list(default=list())
+
+ # how to send mails
+ sendmail_command = string(default='sendmail')
+
+ # specifies the mailbox where you want outgoing mails to be stored after successfully sending them, e.g.
+ # where to store outgoing mail, e.g. `maildir:///home/you/mail//Sent`
+ # You can use mbox, maildir, mh, babyl and mmdf in the protocol part of the url.
+ sent_box = string(default=None)
+
+ # how to tag sent mails.
+ sent_tags = string_list(default=list('sent'))
+
+ # path to signature file that gets attached to all outgoing mails from this account, optionally
+ # renamed to `signature_filename`.
+ signature = string(default=None)
+
+ # attach signature file if set to True, append its content (mimetype text)
+ # to the body text if set to False. Defaults to False.
+ signature_as_attachment = boolean(default=False)
+
+ # signature file's name as it appears in outgoing mails if
+ # signature_as_attachment is set to True
+ signature_filename = string(default=None)
+
+ # command to lookup contacts.
+ # If you specified `abook_command`, it will be used for tab completion in queries (to/from) and in message
+ # composition. The command will be called with your prefix as only argument and its output is searched for name-email pairs.
+ abook_command = string(default=None)
+
+ # The regular expression used to match name/address pairs in the output of `abook_command`
+ abook_regexp = string(default=None)
diff --git a/alot/defaults/bindings b/alot/defaults/bindings
new file mode 100644
index 00000000..50ac0d32
--- /dev/null
+++ b/alot/defaults/bindings
@@ -0,0 +1,65 @@
+j = move down
+k = move up
+' ' = move page down
+esc = cancel
+enter = select
+@ = refresh
+? = help bindings
+I = search tag:inbox AND NOT tag:killed
+L = taglist
+shift tab = bprevious
+U = search tag:unread
+tab = bnext
+\ = prompt 'search '
+d = bclose
+$ = flush
+m = compose
+o = prompt 'search '
+q = exit
+';' = bufferlist
+':' = prompt
+
+[bufferlist]
+ x = close
+ select = openfocussed
+
+[search]
+ a = toggletags inbox
+ & = toggletags killed
+ ! = toggletags flagged
+ s = toggletags unread
+ l = retagprompt
+ O = refineprompt
+ | = refineprompt
+
+[envelope]
+ a = prompt "attach ~/"
+ y = send
+ P = save
+ s = 'refine Subject'
+ t = 'refine To'
+ b = 'refine Bcc'
+ c = 'refine Cc'
+ select = edit
+ H = toggleheaders
+
+[taglist]
+
+[thread]
+ C = fold --all
+ E = unfold --all
+ c = fold
+ e = unfold
+ < = fold
+ > = unfold
+ H = toggleheaders
+ h = togglesource
+ P = print --all --separately --add_tags
+ S = save --all
+ g = reply --all
+ f = forward
+ p = print --add_tags
+ n = editnew
+ s = save
+ r = reply
+ | = prompt 'pipeto '
diff --git a/alot/defaults/default.theme b/alot/defaults/default.theme
new file mode 100644
index 00000000..269bf856
--- /dev/null
+++ b/alot/defaults/default.theme
@@ -0,0 +1,317 @@
+[1]
+[[global]]
+ [[[footer]]]
+ fg = 'standout'
+ [[[notify_error]]]
+ fg = 'standout'
+ [[[notify_normal]]]
+ fg = 'default'
+ [[[prompt]]]
+ fg = 'default'
+ [[[tag]]]
+ fg = 'default'
+ [[[tag_focus]]]
+ fg = 'standout, bold'
+ [[[tag_draft]]]
+ fg = 'standout'
+[[help]]
+ [[[text]]]
+ fg = 'default'
+ [[[section]]]
+ fg = 'underline'
+ [[[title]]]
+ fg = 'standout'
+[[bufferlist]]
+ [[[focus]]]
+ fg = 'standout'
+ [[[results_even]]]
+ fg = 'default'
+ [[[results_odd]]]
+ fg = 'default'
+[[search]]
+ [[[thread]]]
+ fg = 'default'
+ [[[thread_authors]]]
+ fg = 'default,underline'
+ [[[thread_authors_focus]]]
+ fg = 'standout'
+ [[[thread_content]]]
+ fg = 'default'
+ [[[thread_content_focus]]]
+ fg = 'standout'
+ [[[thread_date]]]
+ fg = 'default'
+ [[[thread_date_focus]]]
+ fg = 'standout'
+ [[[thread_focus]]]
+ fg = 'standout'
+ [[[thread_mailcount]]]
+ fg = 'default'
+ [[[thread_mailcount_focus]]]
+ fg = 'standout'
+ [[[thread_subject]]]
+ fg = 'default'
+ [[[thread_subject_isunread]]]
+ fg = 'bold'
+ [[[thread_subject_isflagged]]]
+ fg = 'underline'
+ [[[thread_subject_focus]]]
+ fg = 'standout'
+ [[[thread_subject_isunread_focus]]]
+ fg = 'standout,bold'
+ [[[thread_subject_isflagged_focus]]]
+ fg = 'standout,underline'
+ [[[thread_tags]]]
+ fg = 'bold'
+ [[[thread_tags_focus]]]
+ fg = 'standout'
+[[thread]]
+ [[[attachment]]]
+ fg = 'default'
+ [[[attachment_focus]]]
+ fg = 'underline'
+ [[[body]]]
+ fg = 'default'
+ [[[header]]]
+ fg = 'default'
+ [[[header_key]]]
+ fg = 'default'
+ [[[header_value]]]
+ fg = 'default'
+ [[[summary_even]]]
+ fg = 'default'
+ [[[summary_focus]]]
+ fg = 'standout'
+ [[[summary_odd]]]
+ fg = 'default'
+
+[16]
+[[global]]
+ [[[footer]]]
+ bg = 'dark blue'
+ fg = 'light green'
+ [[[notify_error]]]
+ bg = 'dark red'
+ fg = 'white'
+ [[[notify_normal]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[prompt]]]
+ bg = 'black'
+ fg = 'light gray'
+ [[[tag]]]
+ bg = 'black'
+ fg = 'brown'
+ [[[tag_focus]]]
+ bg = 'dark gray'
+ fg = 'white'
+ [[[tag_draft]]]
+ bg = 'light red'
+ fg = 'white'
+[[help]]
+ [[[text]]]
+ bg = 'dark gray'
+ fg = 'default'
+ [[[section]]]
+ bg = 'dark gray'
+ fg = 'bold,underline'
+ [[[title]]]
+ bg = 'dark blue'
+ fg = 'white'
+[[bufferlist]]
+ [[[focus]]]
+ bg = 'dark gray'
+ fg = 'white'
+ [[[results_even]]]
+ bg = 'black'
+ fg = 'light gray'
+ [[[results_odd]]]
+ bg = 'black'
+ fg = 'light gray'
+[[thread]]
+ [[[attachment]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[attachment_focus]]]
+ bg = 'light green'
+ fg = 'light gray'
+ [[[body]]]
+ bg = 'default'
+ fg = 'light gray'
+ [[[header]]]
+ bg = 'dark gray'
+ fg = 'white'
+ [[[header_key]]]
+ bg = 'dark gray'
+ fg = 'white'
+ [[[header_value]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[summary_even]]]
+ bg = 'light blue'
+ fg = 'white'
+ [[[summary_focus]]]
+ bg = 'dark cyan'
+ fg = 'white'
+ [[[summary_odd]]]
+ bg = 'dark blue'
+ fg = 'white'
+[[search]]
+ [[[thread]]]
+ bg = 'default'
+ fg = 'default'
+ [[[thread_authors]]]
+ bg = 'default'
+ fg = 'dark green'
+ [[[thread_authors_focus]]]
+ bg = 'dark gray'
+ fg = 'dark green,bold'
+ [[[thread_content]]]
+ bg = 'default'
+ fg = 'dark gray'
+ [[[thread_content_focus]]]
+ bg = 'dark gray'
+ fg = 'black'
+ [[[thread_date]]]
+ bg = 'default'
+ fg = 'light gray'
+ [[[thread_date_focus]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[thread_focus]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[thread_mailcount]]]
+ bg = 'default'
+ fg = 'light gray'
+ [[[thread_mailcount_focus]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[thread_subject]]]
+ bg = 'default'
+ fg = 'light gray'
+ [[[thread_subject_focus]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[thread_tags]]]
+ bg = 'default'
+ fg = 'brown'
+ [[[thread_tags_focus]]]
+ bg = 'dark gray'
+ fg = 'yellow,bold'
+
+[256]
+[[global]]
+ # attributes used in all modi
+ [[[footer]]]
+ bg = '#006'
+ fg = 'white'
+ [[[notify_error]]]
+ bg = 'dark red'
+ fg = 'white'
+ [[[notify_normal]]]
+ bg = '#68a'
+ fg = 'light gray'
+ [[[prompt]]]
+ bg = 'g10'
+ fg = 'light gray'
+ [[[tag]]]
+ bg = 'default'
+ fg = 'brown'
+ [[[tag_focus]]]
+ bg = '#68a'
+ fg = '#ffa'
+[[help]]
+ # formating of the `help bindings` overlay
+ [[[text]]]
+ bg = 'g35'
+ fg = 'default'
+ [[[section]]]
+ bg = 'g35'
+ fg = 'bold,underline'
+ [[[title]]]
+ bg = 'g35'
+ fg = 'white,bold,underline'
+# mode specific attributes
+[[bufferlist]]
+ [[[focus]]]
+ bg = '#68a'
+ fg = '#ffa'
+ [[[results_even]]]
+ bg = 'g3'
+ fg = 'default'
+ [[[results_odd]]]
+ bg = 'default'
+ fg = 'default'
+[[search]]
+ [[[thread]]]
+ bg = 'default'
+ fg = '#6d6'
+
+ [[[thread_authors]]]
+ bg = 'default'
+ fg = '#6d6'
+ [[[thread_authors_focus]]]
+ bg = '#68a'
+ fg = '#8f6'
+ [[[thread_content]]]
+ bg = 'default'
+ fg = '#866'
+ [[[thread_content_focus]]]
+ bg = '#68a'
+ fg = '#866'
+ [[[thread_date]]]
+ bg = 'default'
+ fg = 'g58'
+ [[[thread_date_focus]]]
+ bg = '#68a'
+ fg = 'g89'
+ [[[thread_focus]]]
+ bg = '#68a'
+ fg = 'white'
+ [[[thread_mailcount]]]
+ bg = 'default'
+ fg = 'light gray'
+ [[[thread_mailcount_focus]]]
+ bg = '#68a'
+ fg = 'g89'
+ [[[thread_subject]]]
+ bg = 'default'
+ fg = 'g58'
+ [[[thread_subject_focus]]]
+ bg = '#68a'
+ fg = 'g89'
+ [[[thread_tags]]]
+ bg = 'default'
+ fg = '#a86'
+ [[[thread_tags_focus]]]
+ bg = '#68a'
+ fg = '#ff8'
+[[thread]]
+ [[[attachment]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[attachment_focus]]]
+ bg = 'light green'
+ fg = 'light gray'
+ [[[body]]]
+ bg = 'default'
+ fg = 'light gray'
+ [[[header]]]
+ bg = 'dark gray'
+ fg = 'white'
+ [[[header_key]]]
+ bg = 'dark gray'
+ fg = 'white'
+ [[[header_value]]]
+ bg = 'dark gray'
+ fg = 'light gray'
+ [[[summary_even]]]
+ bg = '#068'
+ fg = 'white'
+ [[[summary_focus]]]
+ bg = 'g58'
+ fg = '#ff8'
+ [[[summary_odd]]]
+ bg = '#006'
+ fg = 'white'
diff --git a/alot/defaults/notmuch.rc.spec b/alot/defaults/notmuch.rc.spec
new file mode 100644
index 00000000..fb94ffd0
--- /dev/null
+++ b/alot/defaults/notmuch.rc.spec
@@ -0,0 +1,3 @@
+[maildir]
+synchronize_flags = boolean(default=False)
+
diff --git a/alot/defaults/theme.spec b/alot/defaults/theme.spec
new file mode 100644
index 00000000..d1ea47ab
--- /dev/null
+++ b/alot/defaults/theme.spec
@@ -0,0 +1,317 @@
+[1]
+[[global]]
+ [[[footer]]]
+ fg = string(default='default')
+ [[[notify_error]]]
+ fg = string(default='default')
+ [[[notify_normal]]]
+ fg = string(default='default')
+ [[[prompt]]]
+ fg = string(default='default')
+ [[[tag]]]
+ fg = string(default='default')
+ [[[tag_focus]]]
+ fg = string(default='default')
+ [[[tag_draft]]]
+ fg = string(default='default')
+[[help]]
+ [[[text]]]
+ fg = string(default='default')
+ [[[section]]]
+ fg = string(default='default')
+ [[[title]]]
+ fg = string(default='default')
+[[bufferlist]]
+ [[[focus]]]
+ fg = string(default='default')
+ [[[results_even]]]
+ fg = string(default='default')
+ [[[results_odd]]]
+ fg = string(default='default')
+[[search]]
+ [[[thread]]]
+ fg = string(default='default')
+ [[[thread_authors]]]
+ fg = string(default='default')
+ [[[thread_authors_focus]]]
+ fg = string(default='default')
+ [[[thread_content]]]
+ fg = string(default='default')
+ [[[thread_content_focus]]]
+ fg = string(default='default')
+ [[[thread_date]]]
+ fg = string(default='default')
+ [[[thread_date_focus]]]
+ fg = string(default='default')
+ [[[thread_focus]]]
+ fg = string(default='default')
+ [[[thread_mailcount]]]
+ fg = string(default='default')
+ [[[thread_mailcount_focus]]]
+ fg = string(default='default')
+ [[[thread_subject]]]
+ fg = string(default='default')
+ [[[thread_subject_isunread]]]
+ fg = string(default='default')
+ [[[thread_subject_isflagged]]]
+ fg = string(default='default')
+ [[[thread_subject_focus]]]
+ fg = string(default='default')
+ [[[thread_subject_isunread_focus]]]
+ fg = string(default='default')
+ [[[thread_subject_isflagged_focus]]]
+ fg = string(default='default')
+ [[[thread_tags]]]
+ fg = string(default='default')
+ [[[thread_tags_focus]]]
+ fg = string(default='default')
+[[thread]]
+ [[[attachment]]]
+ fg = string(default='default')
+ [[[attachment_focus]]]
+ fg = string(default='default')
+ [[[body]]]
+ fg = string(default='default')
+ [[[header]]]
+ fg = string(default='default')
+ [[[header_key]]]
+ fg = string(default='default')
+ [[[header_value]]]
+ fg = string(default='default')
+ [[[summary_even]]]
+ fg = string(default='default')
+ [[[summary_focus]]]
+ fg = string(default='default')
+ [[[summary_odd]]]
+ fg = string(default='default')
+
+[16]
+[[global]]
+ [[[footer]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[notify_error]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[notify_normal]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[prompt]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[tag]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[tag_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[tag_draft]]]
+ bg = string(default='default')
+ fg = string(default='default')
+[[help]]
+ [[[text]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[section]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[title]]]
+ bg = string(default='default')
+ fg = string(default='default')
+[[bufferlist]]
+ [[[focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[results_even]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[results_odd]]]
+ bg = string(default='default')
+ fg = string(default='default')
+[[thread]]
+ [[[attachment]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[attachment_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[body]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[header]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[header_key]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[header_value]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[summary_even]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[summary_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[summary_odd]]]
+ bg = string(default='default')
+ fg = string(default='default')
+[[search]]
+ [[[thread]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_authors]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_authors_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_content]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_content_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_date]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_date_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_mailcount]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_mailcount_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_subject]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_subject_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_tags]]]
+ bg = string(default='default')
+ fg = string(default='default')
+ [[[thread_tags_focus]]]
+ bg = string(default='default')
+ fg = string(default='default')
+
+[256]
+[[global]]
+ # attributes used in all modi
+ [[[footer]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[notify_error]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[notify_normal]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[prompt]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[tag]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[tag_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+[[help]]
+ # formating of the `help bindings` overlay
+ [[[text]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[section]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[title]]]
+ bg = string(default=None)
+ fg = string(default=None)
+# mode specific attributes
+[[bufferlist]]
+ [[[focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[results_even]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[results_odd]]]
+ bg = string(default=None)
+ fg = string(default=None)
+[[search]]
+ [[[thread]]]
+ fg = string(default=None)
+ [[[thread_authors]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_authors_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_content]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_content_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_date]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_date_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_mailcount]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_mailcount_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_subject]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_subject_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_tags]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[thread_tags_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+[[thread]]
+ [[[attachment]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[attachment_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[body]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[header]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[header_key]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[header_value]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[summary_even]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[summary_focus]]]
+ bg = string(default=None)
+ fg = string(default=None)
+ [[[summary_odd]]]
+ bg = string(default=None)
+ fg = string(default=None)
+
+
diff --git a/alot/helper.py b/alot/helper.py
index cd258c7a..91aae324 100644
--- a/alot/helper.py
+++ b/alot/helper.py
@@ -1,3 +1,4 @@
+# coding=utf-8
from datetime import date
from datetime import timedelta
from collections import deque
@@ -17,8 +18,6 @@ from twisted.internet.defer import Deferred
import StringIO
import logging
-from settings import config
-
def safely_get(clb, E, on_error=''):
"""
@@ -38,7 +37,7 @@ def safely_get(clb, E, on_error=''):
return on_error
-def string_sanitize(string, tab_width=None):
+def string_sanitize(string, tab_width=8):
r"""
strips, and replaces non-printable characters
@@ -53,8 +52,6 @@ def string_sanitize(string, tab_width=None):
>>> string_sanitize('foo\t\tbar', 8)
'foo bar'
"""
- if tab_width == None:
- tab_width = config.getint('general', 'tabwidth')
string = string.strip()
string = string.replace('\r', '')
diff --git a/alot/init.py b/alot/init.py
index e602595b..b3bba246 100755
--- a/alot/init.py
+++ b/alot/init.py
@@ -3,9 +3,8 @@ import sys
import logging
import os
-import settings
+from settings import settings, ConfigError
import ConfigParser
-from account import AccountManager
from db import DBManager
from ui import UI
import alot.commands as commands
@@ -114,6 +113,17 @@ def main():
print '%s: Try --help for usage details.' % (sys.argv[0])
sys.exit(1)
+ # logging
+ root_logger = logging.getLogger()
+ for log_handler in root_logger.handlers:
+ root_logger.removeHandler(log_handler)
+ root_logger = None
+ numeric_loglevel = getattr(logging, args['debug-level'].upper(), None)
+ logfilename = os.path.expanduser(args['logfile'])
+ logformat = '%(levelname)s:%(module)s:%(message)s'
+ logging.basicConfig(level=numeric_loglevel, filename=logfilename,
+ format=logformat)
+
# locate alot config files
configfiles = [
os.path.join(os.environ.get('XDG_CONFIG_HOME',
@@ -128,42 +138,29 @@ def main():
configfiles.insert(0, expanded_path)
# locate notmuch config
- notmuchfile = os.path.expanduser(args['notmuch-config'])
-
- try:
- # read the first alot config file we find
- for configfilename in configfiles:
- if os.path.exists(configfilename):
- settings.config.read(configfilename)
- break # use only the first
+ notmuchconfig = os.path.expanduser(args['notmuch-config'])
- # read notmuch config
- settings.notmuchconfig.read(notmuchfile)
+ alotconfig = None
+ # read the first alot config file we find
+ for configfilename in configfiles:
+ if os.path.exists(configfilename):
+ alotconfig = configfilename
+ break # use only the first
- except ConfigParser.Error, e: # exit on parse errors
+ try:
+ settings.read_config(alotconfig)
+ settings.read_notmuch_config(notmuchconfig)
+ except ConfigError, e: # exit on parse errors
sys.exit(e)
- # logging
- ## reset
- root_logger = logging.getLogger()
- for log_handler in root_logger.handlers:
- root_logger.removeHandler(log_handler)
- root_logger = None
- ## setup
- numeric_loglevel = getattr(logging, args['debug-level'].upper(), None)
- logfilename = os.path.expanduser(args['logfile'])
- logformat = '%(levelname)s:%(module)s:%(message)s'
- logging.basicConfig(level=numeric_loglevel, filename=logfilename,
- format=logformat)
+ # store options given by config swiches to the settingsManager:
+ if args['colour-mode']:
+ settings.set('colourmode', args['colour-mode'])
- #logging.debug(commands.COMMANDS)
# get ourselves a database manager
dbman = DBManager(path=args['mailindex-path'], ro=args['read-only'])
- #accountman
- aman = AccountManager(settings.config)
-
- # get initial searchstring
+ # determine what to do
try:
if args.subCommand == 'search':
query = ' '.join(args.subOptions.args)
@@ -174,18 +171,13 @@ def main():
cmdstring = 'compose %s' % args.subOptions.as_argparse_opts()
cmd = commands.commandfactory(cmdstring, 'global')
else:
- default_commandline = settings.config.get('general',
- 'initial_command')
+ default_commandline = settings.get('initial_command')
cmd = commands.commandfactory(default_commandline, 'global')
except CommandParseError, e:
sys.exit(e)
# set up and start interface
- UI(dbman,
- aman,
- cmd,
- args['colour-mode'],
- )
+ UI(dbman, cmd)
if __name__ == "__main__":
main()
diff --git a/alot/message.py b/alot/message.py
index 61a49804..4feb9a62 100644
--- a/alot/message.py
+++ b/alot/message.py
@@ -15,8 +15,7 @@ from notmuch import NullPointerError
from alot import __version__
import logging
import helper
-from settings import get_mime_handler
-from settings import config
+from settings import settings
from helper import string_sanitize
from helper import string_decode
@@ -135,11 +134,11 @@ class Message(object):
"""
if self._datetime == None:
return None
- if config.has_option('general', 'timestamp_format'):
- formatstring = config.get('general', 'timestamp_format')
- res = self._datetime.strftime(formatstring)
- else:
+ formatstring = settings.get('timestamp_format')
+ if formatstring == None:
res = helper.pretty_datetime(self._datetime)
+ else:
+ res = self._datetime.strftime(formatstring)
return res
def get_author(self):
@@ -313,8 +312,8 @@ def extract_body(mail, types=None):
body_parts.append(string_sanitize(raw_payload))
else:
#get mime handler
- handler = get_mime_handler(ctype, key='view',
- interactive=False)
+ handler = settings.get_mime_handler(ctype, key='view',
+ interactive=False)
if handler:
#open tempfile. Not all handlers accept stuff from stdin
tmpfile = tempfile.NamedTemporaryFile(delete=False,
@@ -597,7 +596,7 @@ class Envelope(object):
if 'User-Agent' in headers:
uastring_format = headers['User-Agent'][0]
else:
- uastring_format = config.get('general', 'user_agent').strip()
+ uastring_format = settings.get('user_agent').strip()
uastring = uastring_format % {'version': __version__}
if uastring:
headers['User-Agent'] = [uastring]
diff --git a/alot/settings.py b/alot/settings.py
index d88b5be0..b95595d7 100644
--- a/alot/settings.py
+++ b/alot/settings.py
@@ -6,244 +6,316 @@ import json
import mailcap
import codecs
import logging
+import urwid
+from urwid import AttrSpec, AttrSpecError
+from configobj import ConfigObj, ConfigObjError, flatten_errors, Section
+from validate import Validator
+
+from account import SendmailAccount, MatchSdtoutAddressbook
from collections import OrderedDict
from ConfigParser import SafeConfigParser, ParsingError, NoOptionError
+DEFAULTSPATH = os.path.join(os.path.dirname(__file__), 'defaults')
-class FallbackConfigParser(SafeConfigParser):
- """:class:`~ConfigParser.SafeConfigParser` that allows fallback values"""
- def __init__(self):
- SafeConfigParser.__init__(self)
- self.optionxform = lambda x: x
- def get(self, section, option, fallback=None, *args, **kwargs):
- """get a config option
+class ConfigError(Exception):
+ pass
- :param section: section name
- :type section: str
- :param option: option key
- :type option: str
- :param fallback: the value to fall back if option undefined
- :type fallback: str
- """
- if SafeConfigParser.has_option(self, section, option):
- return SafeConfigParser.get(self, section, option, *args, **kwargs)
- elif fallback != None:
- return fallback
- else:
- raise NoOptionError(option, section)
- def getstringlist(self, section, option, **kwargs):
- """directly parses a config value into a list of strings"""
- stringlist = list()
- if self.has_option(section, option):
- value = self.get(section, option, **kwargs)
- stringlist = [s.strip() for s in value.split(',') if s.strip()]
- return stringlist
+def read_config(configpath=None, specpath=None):
+ """
+ get a (validated) config object for given config file path.
+ :param configpath: path to config-file
+ :type configpath: str
+ :param specpath: path to spec-file
+ :type specpath: str
+ :rtype: `configobj.ConfigObj`
+ """
+ try:
+ config = ConfigObj(infile=configpath, configspec=specpath,
+ file_error=True, encoding='UTF8')
+ except (ConfigObjError, IOError), e:
+ raise ConfigError('Could not read "%s": %s' % (configpath, e))
+
+ if specpath:
+ validator = Validator()
+ results = config.validate(validator)
+
+ if results != True:
+ error_msg = 'Validation errors occurred:\n'
+ for (section_list, key, _) in flatten_errors(config, results):
+ if key is not None:
+ msg = 'key "%s" in section "%s" failed validation'
+ msg = msg % (key, ', '.join(section_list))
+ else:
+ msg = 'section "%s" is malformed' % ', '.join(section_list)
+ error_msg += msg + '\n'
+ raise ConfigError(error_msg)
+ return config
-class AlotConfigParser(FallbackConfigParser):
- """:class:`FallbackConfigParser` for alots config."""
- def __init__(self):
- FallbackConfigParser.__init__(self)
- self.hooks = None
- def get_hook(self, key):
- """return hook (`callable`) identified by `key`"""
- if self.hooks:
- if key in self.hooks.__dict__:
- return self.hooks.__dict__[key]
- return None
+class Theme(object):
+ """Colour theme"""
+ def __init__(self, path):
+ """
+ :param path: path to theme file
+ :type path: str
+ """
+ self._spec = os.path.join(DEFAULTSPATH, 'theme.spec')
+ self._config = read_config(path, self._spec)
+ self.attributes = self._parse_attributes(self._config)
- def read(self, file):
- if not os.path.isfile(file):
- return
-
- SafeConfigParser.readfp(self, codecs.open(file, "r", "utf8"))
- if self.has_option('general', 'hooksfile'):
- hf = os.path.expanduser(self.get('general', 'hooksfile'))
- if hf is not None:
- try:
- self.hooks = imp.load_source('hooks', hf)
- except:
- logging.debug('unable to load hooks file:%s' % hf)
-
- # fix quoted keys / values
- for section in self.sections():
- for key, value in self.items(section):
- if value and value[0] in "\"'":
- value = ast.literal_eval(value)
-
- transformed_key = False
- if key[0] in "\"'":
- transformed_key = ast.literal_eval(key)
- elif key == 'colon':
- transformed_key = ':'
-
- if transformed_key:
- self.remove_option(section, key)
- self.set(section, transformed_key, value)
- else:
- self.set(section, key, value)
+ def _parse_attributes(self, c):
+ """
+ parse a (previously validated) valid theme file
+ into urwid AttrSpec attributes for internal use.
- # parse highlighting rules
- self._highlighting_rules = self._parse_highlighting_rules()
+ :param c: config object for theme file
+ :type c: `configobj.ConfigObj`
+ :raises: `ConfigError`
+ """
- def get_palette(self):
- """parse the sections '1c-theme', '16c-theme' and '256c-theme'
- into an urwid compatible colour palette.
+ attributes = {}
+ for sec in c.sections:
+ try:
+ colours = int(sec)
+ except ValueError:
+ err_msg = 'section name %s is not a valid colour mode'
+ raise ConfigError(err_msg % sec)
+ attributes[colours] = {}
+ for mode in c[sec].sections:
+ attributes[colours][mode] = {}
+ for themable in c[sec][mode].sections:
+ block = c[sec][mode][themable]
+ fg = block['fg']
+ if colours == 1:
+ bg = 'default'
+ else:
+ bg = block['bg']
+ if colours == 256:
+ fg = fg or c['16'][mode][themable][fg]
+ bg = bg or c['16'][mode][themable][bg]
+ try:
+ att = AttrSpec(fg, bg, colours)
+ except AttrSpecError, e:
+ raise ConfigError(e)
+ attributes[colours][mode][themable] = att
+ return attributes
+
+ def get_attribute(self, mode, name, colourmode):
+ """
+ returns requested attribute
- :returns: a palette
- :rtype: list
+ :param mode: ui-mode (e.g. `search`,`thread`...)
+ :type mode: str
+ :param name: identifier of the atttribute
+ :type name: str
+ :param colourmode: colour mode; in [1, 16, 256]
+ :type colourmode: int
"""
- mode = self.getint('general', 'colourmode')
- ms = "%dc-theme" % mode
- names = self.options(ms)
- if mode != 1: # truncate '_fg'/'_bg' suffixes for multi-colour themes
- names = set([s[:-3] for s in names])
- p = list()
- for attr in names:
- nf = self._get_theming_option('16c-theme', attr + '_fg')
- nb = self._get_theming_option('16c-theme', attr + '_bg')
- m = self._get_theming_option('1c-theme', attr)
- hf = self._get_theming_option('256c-theme', attr + '_fg')
- hb = self._get_theming_option('256c-theme', attr + '_bg')
- p.append((attr, nf, nb, m, hf, hb))
- if attr.startswith('tag_') and attr + '_focus' not in names:
- nb = self.get('16c-theme', 'tag_focus_bg',
- fallback='default')
- hb = self.get('256c-theme', 'tag_focus_bg',
- fallback='default')
- p.append((attr + '_focus', nf, nb, m, hf, hb))
- return p
-
- def _get_theming_option(self, section, option, default='default'):
+ return self.attributes[colourmode][mode][name]
+
+
+class SettingsManager(object):
+ """Organizes user settings"""
+ def __init__(self, alot_rc=None, notmuch_rc=None, theme=None):
"""
- Retrieve the value of the given option from the given section of the
- config file.
-
- If the option does not exist, try its parent options before falling
- back to the specified default. The parent of an option is the name of
- the option itself minus the last section enclosed in underscores;
- so the parent of the option `aaa_bbb_ccc_fg` is of the form
- `aaa_bbb_fg`.
-
- :param section: the section of the config file to search for the given
- option
- :type section: string
- :param option: the option to lookup
- :type option: string
- :param default: the value that is to be returned if neither the
- requested option nor a parent exists
- :type default: string
- :return: the value of the given option, or the specified default
- :rtype: string
+ :param alot_rc: path to alot's config file
+ :type alot_rc: str
+ :param notmuch_rc: path to notmuch's config file
+ :type notmuch_rc: str
+ :theme: path to initially used theme file
+ :type theme: str
"""
- result = ''
- if 'focus' in option:
- parent_option_re = '(.+)_[^_]+_(focus_(?:fg|bg))'
- else:
- parent_option_re = '(.+)_[^_]+_(fg|bg)'
-
- if self.has_option(section, option):
- result = self.get(section, option)
+ self.hooks = None
+ self._mailcaps = mailcap.getcaps()
+
+ theme_path = theme or os.path.join(DEFAULTSPATH, 'default.theme')
+ self._theme = Theme(theme_path)
+ self._bindings = read_config(os.path.join(DEFAULTSPATH, 'bindings'))
+
+ self._config = ConfigObj()
+ self._accounts = None
+ self._accountmap = None
+ self.read_config(alot_rc)
+ self.read_notmuch_config(notmuch_rc)
+
+ def read_notmuch_config(self, path):
+ """parse notmuch's config file from path"""
+ spec = os.path.join(DEFAULTSPATH, 'notmuch.rc.spec')
+ self._notmuchconfig = read_config(path, spec)
+
+ def read_config(self, path):
+ """parse alot's config file from path"""
+ spec = os.path.join(DEFAULTSPATH, 'alot.rc.spec')
+ newconfig = read_config(path, spec)
+ self._config.merge(newconfig)
+
+ hooks_path = os.path.expanduser(self._config.get('hooksfile'))
+ try:
+ self.hooks = imp.load_source('hooks', hooks_path)
+ except:
+ logging.debug('unable to load hooks file:%s' % hooks_path)
+ if 'bindings' in newconfig:
+ newbindings = newconfig['bindings']
+ if isinstance(newbindings, Section):
+ self._bindings.merge(newbindings)
+ # themes
+ themestring = newconfig['theme']
+ themes_dir = self._config.get('themes_dir')
+ if themes_dir:
+ themes_dir = os.path.expanduser(themes_dir)
else:
- has_parent_option = re.search(parent_option_re, option)
- if has_parent_option:
- parent_option = '{0}_{1}'.format(has_parent_option.group(1),
- has_parent_option.group(2))
- result = self._get_theming_option(section, parent_option)
+ themes_dir = os.path.join(os.environ.get('XDG_CONFIG_HOME',
+ os.path.expanduser('~/.config')), 'alot', 'themes')
+ logging.debug(themes_dir)
+
+ if themestring:
+ if not os.path.isdir(themes_dir):
+ err_msg = 'cannot find theme %s: themes_dir %s is missing'
+ raise ConfigError(err_msg % (themestring, themes_dir))
else:
- result = default
- return result
+ theme_path = os.path.join(themes_dir, themestring)
+ self._theme = Theme(theme_path)
+
+ self._accounts = self._parse_accounts(self._config)
+ self._accountmap = self._account_table(self._accounts)
- def _parse_highlighting_rules(self):
+ def _parse_accounts(self, config):
"""
- Parse the highlighting rules from the config file.
+ read accounts information from config
- :returns: The highlighting rules
- :rtype: :py:class:`collections.OrderedDict`
+ :param config: valit alot config
+ :type config: `configobj.ConfigObj`
+ :returns: list of accounts
"""
- rules = OrderedDict()
- config_string = self.get('highlighting', 'rules')
- try:
- rules = json.loads(config_string, object_pairs_hook=OrderedDict)
- except ValueError as err:
- raise ParsingError("Could not parse config option" \
- " 'rules' in section 'highlighting':" \
- " {reason}".format(reason=err))
- return rules
-
- def get_highlight_rules(self):
+ accounts = []
+ if 'accounts' in config:
+ for acc in config['accounts'].sections:
+ accsec = config['accounts'][acc]
+ args = dict(config['accounts'][acc])
+
+ if 'abook_command' in accsec:
+ cmd = accsec['abook_command']
+ regexp = accsec['abook_regexp']
+ args['abook'] = MatchSdtoutAddressbook(cmd, match=regexp)
+ del(args['abook_command'])
+ del(args['abook_regexp'])
+
+ cmd = args['sendmail_command']
+ del(args['sendmail_command'])
+ newacc = SendmailAccount(cmd, **args)
+ accounts.append(newacc)
+ return accounts
+
+ def _account_table(self, accounts):
+ """
+ creates a lookup table (emailaddress -> account) for a given list of
+ accounts
+
+ :param accounts: list of accounts
+ :type accounts: list of `alot.account.Account`
+ :returns: hashtable
+ :rvalue: dict (str -> `alot.account.Account`)
+ """
+ accountmap = {}
+ for acc in accounts:
+ accountmap[acc.address] = acc
+ for alias in acc.aliases:
+ accountmap[alias] = acc
+ return accountmap
+
+ def get(self, key):
+ """
+ look up global config values from alot's config
+
+ :param key: key to look up
+ :type key: str
+ :returns: config value with type as specified in the spec-file
+ """
+ value = None
+ if key in self._config:
+ value = self._config[key]
+ if isinstance(value, Section):
+ value = None
+ return value
+
+ def set(self, key, value):
"""
- Getter for the highlighting rules.
+ setter for global config values
- :returns: The highlighting rules
- :rtype: :py:class:`collections.OrderedDict`
+ :param key: config option identifise
+ :type key: str
+ :param value: option to set
+ :type value: depends on the specfile :file:`alot.rc.spec`
"""
- return self._highlighting_rules
+ self._config[key] = value
- def get_tag_theme(self, tag, focus=False, highlight=''):
+ def get_notmuch_setting(self, section, key):
"""
- look up attribute string to use for a given tagstring
-
- :param tag: tagstring to look up
- :type tag: str
- :param focus: return the 'focussed' attribute
- :type focus: bool
- :param highlight: suffix of highlight theme
- :type highlight: str
+ look up config values from notmuch's config
+
+ :param section: key is in
+ :type section: str
+ :param key: key to look up
+ :type key: str
+ :returns: config value with type as specified in the spec-file
"""
- themes = self._select_tag_themes(tag, focus, highlight)
- selected_theme = themes[-1]
- for theme in themes:
- if self.has_theming(theme):
- selected_theme = theme
- break
- return selected_theme
-
- def _select_tag_themes(self, tag, focus=False, highlight=''):
+ value = None
+ if key in self._notmuchconfig:
+ value = self._notmuchconfig[key]
+ return value
+
+ def get_theming_attribute(self, mode, name):
"""
- Select tag themes based on tag name, focus and highlighting.
-
- :param tag: the name of the tag to theme
- :type tag: str
- :param focus: True if this tag is focussed, False otherwise
- :type focus: bool
- :param highlight: the suffix of the highlighting theme
- :type highlight: str
- :return: the selected themes, highest priority first
- :rtype: list
+ looks up theming attribute
+
+ :param mode: ui-mode (e.g. `search`,`thread`...)
+ :type mode: str
+ :param name: identifier of the atttribute
+ :type name: str
"""
- base = 'tag'
- themes = [base, base + '_{}'.format(tag)]
- if (highlight and
- 'tags' in self.getstringlist('highlighting', 'components')):
- themes.insert(1, base + '_{}'.format(highlight))
- themes.insert(3, base + '_{}_{}'.format(tag, highlight))
- if focus:
- themes = [theme + '_focus' for theme in themes]
- themes.reverse()
- return themes
-
- def has_theming(self, theming):
+ colours = int(self._config.get('colourmode'))
+ return self._theme.get_attribute(mode, name, colours)
+
+ def get_tagstring_representation(self, tag):
"""
- Return true if the given theming option exists in the current colour
- theme.
+ looks up user's preferred way to represent a given tagstring
- :param theming: The theming option to check for
- :type theming: string
- :return: True if theming exist, False otherwise
- :rtype: bool
+ This returns a dictionary mapping
+ 'normal' and 'focussed' to `urwid.AttrSpec` sttributes,
+ and 'translated' to an alternative string representation
"""
- mode = self.getint('general', 'colourmode')
- theme = '{colours}c-theme'.format(colours=mode)
- has_fg = self.has_option(theme, theming + '_fg')
- has_bg = self.has_option(theme, theming + '_bg')
- return (has_fg or has_bg)
+ colours = int(self._config.get('colourmode'))
+ # default attributes: normal and focussed
+ default = self._theme.get_attribute('global', 'tag', colours)
+ default_f = self._theme.get_attribute('global', 'tag_focus', colours)
+ if tag in self._config['tags']:
+ fg = self._config['tags'][tag]['fg'] or default.foreground
+ bg = self._config['tags'][tag]['bg'] or default.background
+ normal = urwid.AttrSpec(fg, bg, colours)
+ ffg = self._config['tags'][tag]['focus_fg'] or default_f.foreground
+ fbg = self._config['tags'][tag]['focus_bg'] or default_f.background
+ focussed = urwid.AttrSpec(ffg, fbg, colours)
+ translated = self._config['tags'][tag]['translated'] or tag
+ else:
+ normal = default
+ focussed = default_f
+ translated = tag
+
+ return {'normal': normal, 'focussed': focussed,
+ 'translated': translated}
+
+ def get_hook(self, key):
+ """return hook (`callable`) identified by `key`"""
+ if self.hooks:
+ if key in self.hooks.__dict__:
+ return self.hooks.__dict__[key]
+ return None
- def get_mapping(self, mode, key):
+ def get_keybinding(self, mode, key):
"""look up keybinding from `MODE-maps` sections
:param mode: mode identifier
@@ -254,47 +326,83 @@ class AlotConfigParser(FallbackConfigParser):
:rtype: str
"""
cmdline = None
- if self.has_option(mode + '-maps', key):
- cmdline = self.get(mode + '-maps', key)
- elif self.has_option('global-maps', key):
- cmdline = self.get('global-maps', key)
+ bindings = self._bindings
+ if key in bindings.scalars:
+ cmdline = bindings[key]
+ if mode in bindings.sections:
+ if key in bindings[mode].scalars:
+ cmdline = bindings[mode][key]
return cmdline
+ def get_accounts(self):
+ """
+ returns known accounts
-config = AlotConfigParser()
-config.read(os.path.join(os.path.dirname(__file__), 'defaults', 'alot.rc'))
-notmuchconfig = FallbackConfigParser()
-notmuchconfig.read(os.path.join(os.path.dirname(__file__),
- 'defaults',
- 'notmuch.rc'))
-mailcaps = mailcap.getcaps()
+ :rtype: list of :class:`Account`
+ """
+ return self._accounts
+ def get_account_by_address(self, address):
+ """
+ returns :class:`Account` for a given email address (str)
-def get_mime_handler(mime_type, key='view', interactive=True):
- """
- get shellcomand defined in the users `mailcap` as handler for files of
- given `mime_type`.
-
- :param mime_type: file type
- :type mime_type: str
- :param key: identifies one of possibly many commands for this type by
- naming the intended usage, e.g. 'edit' or 'view'. Defaults
- to 'view'.
- :type key: str
- :param interactive: choose the "interactive session" handler rather than
- the "print to stdout and immediately return" handler
- :type interactive: bool
- """
- if interactive:
- mc_tuple = mailcap.findmatch(mailcaps,
- mime_type,
- key=key)
- else:
- mc_tuple = mailcap.findmatch(mailcaps,
- mime_type,
- key='copiousoutput')
- if mc_tuple:
- if mc_tuple[1]:
- return mc_tuple[1][key]
- else:
+ :param address: address to look up
+ :type address: string
+ :rtype: :class:`Account` or None
+ """
+
+ for myad in self.get_addresses():
+ if myad in address:
+ return self._accountmap[myad]
return None
+
+ def get_main_addresses(self):
+ """returns addresses of known accounts without its aliases"""
+ return [a.address for a in self._accounts]
+
+ def get_addresses(self):
+ """returns addresses of known accounts including all their aliases"""
+ return self._accountmap.keys()
+
+ def get_addressbooks(self, order=[], append_remaining=True):
+ """returns list of all defined :class:`AddressBook` objects"""
+ abooks = []
+ for a in order:
+ if a:
+ if a.abook:
+ abooks.append(a.abook)
+ if append_remaining:
+ for a in self._accounts:
+ if a.abook and a.abook not in abooks:
+ abooks.append(a.abook)
+ return abooks
+
+ def get_mime_handler(self, mime_type, key='view', interactive=True):
+ """
+ get shellcomand defined in the users `mailcap` as handler for files of
+ given `mime_type`.
+
+ :param mime_type: file type
+ :type mime_type: str
+ :param key: identifies one of possibly many commands for this type by
+ naming the intended usage, e.g. 'edit' or 'view'. Defaults
+ to 'view'.
+ :type key: str
+ :param interactive: choose the "interactive session" handler rather
+ than the "print to stdout and immediately return"
+ handler
+ :type interactive: bool
+ """
+ if interactive:
+ mc_tuple = mailcap.findmatch(self._mailcaps, mime_type, key=key)
+ else:
+ mc_tuple = mailcap.findmatch(self._mailcaps, mime_type,
+ key='copiousoutput')
+ if mc_tuple:
+ if mc_tuple[1]:
+ return mc_tuple[1][key]
+ else:
+ return None
+
+
+settings = SettingsManager()
diff --git a/alot/ui.py b/alot/ui.py
index 4a6b37fe..e218ace4 100644
--- a/alot/ui.py
+++ b/alot/ui.py
@@ -1,9 +1,8 @@
import urwid
import logging
from twisted.internet import reactor, defer
-from twisted.python.failure import Failure
-from settings import config
+from settings import settings
from buffers import BufferlistBuffer
import commands
from commands import commandfactory
@@ -46,7 +45,7 @@ class InputWrap(urwid.WidgetWrap):
mode = self.ui.mode
if self.select_cancel_only:
mode = 'global'
- cmdline = config.get_mapping(mode, key)
+ cmdline = settings.get_keybinding(mode, key)
if cmdline:
try:
cmd = commandfactory(cmdline, mode)
@@ -71,34 +70,28 @@ class UI(object):
"""points to currently active :class:`~alot.buffers.Buffer`"""
dbman = None
"""Database manager (:class:`~alot.db.DBManager`)"""
- accountman = None
- """account manager (:class:`~alot.account.AccountManager`)"""
- def __init__(self, dbman, accountman, initialcmd, colourmode):
+ def __init__(self, dbman, initialcmd):
"""
:param dbman: :class:`~alot.db.DBManager`
- :param accountman: :class:`~alot.account.AccountManager`
:param initialcmd: commandline applied after setting up interface
:type initialcmd: str
:param colourmode: determines which theme to chose
:type colourmode: int in [1,16,256]
"""
self.dbman = dbman
- self.accountman = accountman
- if not colourmode:
- colourmode = config.getint('general', 'colourmode')
+ colourmode = int(settings.get('colourmode'))
logging.info('setup gui in %d colours' % colourmode)
self.mainframe = urwid.Frame(urwid.SolidFill())
self.inputwrap = InputWrap(self, self.mainframe)
self.mainloop = urwid.MainLoop(self.inputwrap,
- config.get_palette(),
handle_mouse=False,
event_loop=urwid.TwistedEventLoop(),
unhandled_input=self.unhandeled_input)
self.mainloop.screen.set_terminal_properties(colors=colourmode)
- self.show_statusbar = config.getboolean('general', 'show_statusbar')
+ self.show_statusbar = settings.get('show_statusbar')
self.notificationbar = None
self.mode = 'global'
self.commandprompthistory = []
@@ -169,7 +162,8 @@ class UI(object):
('fixed', len(prefix), leftpart),
('weight', 1, editpart),
])
- both = urwid.AttrMap(both, 'global_prompt')
+ att = settings.get_theming_attribute('global', 'prompt')
+ both = urwid.AttrMap(both, att)
# put promptwidget as overlay on main widget
overlay = urwid.Overlay(both, oldroot,
@@ -208,7 +202,7 @@ class UI(object):
logging.debug('UI: closing current buffer %s' % buf)
index = buffers.index(buf)
buffers.remove(buf)
- offset = config.getint('general', 'bufferclose_focus_offset')
+ offset = settings.get('bufferclose_focus_offset')
nextbuffer = buffers[(index + offset) % len(buffers)]
self.buffer_focus(nextbuffer)
buf.cleanup()
@@ -313,7 +307,8 @@ class UI(object):
], dividechars=1)
else: # above
both = urwid.Pile([msgpart, choicespart])
- both = urwid.AttrMap(both, 'prompt', 'prompt')
+ att = settings.get_theming_attribute('global', 'prompt')
+ both = urwid.AttrMap(both, att, att)
# put promptwidget as overlay on main widget
overlay = urwid.Overlay(both, oldroot,
@@ -347,7 +342,8 @@ class UI(object):
"""
def build_line(msg, prio):
cols = urwid.Columns([urwid.Text(msg)])
- return urwid.AttrMap(cols, 'global_notify_' + prio)
+ att = settings.get_theming_attribute('global', 'notify_' + prio)
+ return urwid.AttrMap(cols, att)
msgs = [build_line(message, priority)]
if not self.notificationbar:
@@ -374,7 +370,7 @@ class UI(object):
else:
if timeout >= 0:
if timeout == 0:
- timeout = config.getint('general', 'notify_timeout')
+ timeout = settings.get('notify_timeout')
self.mainloop.set_alarm_in(timeout, clear)
return msgs[0]
@@ -420,7 +416,8 @@ class UI(object):
columns = urwid.Columns([
footerleft,
('fixed', len(righttxt), footerright)])
- return urwid.AttrMap(columns, 'global_footer')
+ footer_att = settings.get_theming_attribute('global', 'footer')
+ return urwid.AttrMap(columns, footer_att)
def apply_command(self, cmd):
"""
@@ -437,9 +434,7 @@ class UI(object):
if cmd.prehook:
logging.debug('calling pre-hook')
try:
- cmd.prehook(ui=self, dbm=self.dbman, aman=self.accountman,
- config=config)
-
+ cmd.prehook(ui=self, dbm=self.dbman)
except:
logging.exception('prehook failed')
@@ -448,8 +443,7 @@ class UI(object):
if cmd.posthook:
logging.debug('calling post-hook')
try:
- cmd.posthook(ui=self, dbm=self.dbman,
- aman=self.accountman, config=config)
+ cmd.posthook(ui=self, dbm=self.dbman)
except:
logging.exception('posthook failed')
diff --git a/alot/widgets.py b/alot/widgets.py
index a85e31e8..01e7363e 100644
--- a/alot/widgets.py
+++ b/alot/widgets.py
@@ -1,7 +1,7 @@
import urwid
import logging
-from settings import config
+from settings import settings
from helper import shorten_author_string
from helper import pretty_datetime
from helper import tag_cmp
@@ -75,14 +75,11 @@ class ThreadlineWidget(urwid.AttrMap):
self.thread = dbman.get_thread(tid)
#logging.debug('tid: %s' % self.thread)
self.tag_widgets = []
- self.display_content = config.getboolean('general',
- 'display_content_in_threadline')
- self.highlight_components = config.getstringlist('highlighting',
- 'components')
- self.highlight_rules = config.get_highlight_rules()
+ self.display_content = settings.get('display_content_in_threadline')
self.rebuild()
- urwid.AttrMap.__init__(self, self.columns,
- 'search_thread', 'search_thread_focus')
+ normal = settings.get_theming_attribute('search', 'thread')
+ focussed = settings.get_theming_attribute('search', 'thread_focus')
+ urwid.AttrMap.__init__(self, self.columns, normal, focussed)
def rebuild(self):
cols = []
@@ -93,12 +90,11 @@ class ThreadlineWidget(urwid.AttrMap):
if newest == None:
datestring = u' ' * 10
else:
- if config.has_option('general', 'timestamp_format'):
- formatstring = config.get('general', 'timestamp_format')
- datestring = newest.strftime(formatstring)
- else:
+ formatstring = settings.get('timestamp_format')
+ if formatstring is None:
datestring = pretty_datetime(newest).rjust(10)
- self.highlight_theme_suffix = self._get_highlight_theme_suffix()
+ else:
+ datestring = newest.strftime(formatstring)
self.date_w = urwid.AttrMap(urwid.Text(datestring),
self._get_theme('date'))
cols.append(('fixed', len(datestring), self.date_w))
@@ -112,7 +108,7 @@ class ThreadlineWidget(urwid.AttrMap):
cols.append(('fixed', len(mailcountstring), self.mailcount_w))
if self.thread:
- self.tag_widgets = [TagWidget(t, self.highlight_theme_suffix)
+ self.tag_widgets = [TagWidget(t)
for t in self.thread.get_tags()]
else:
self.tag_widgets = []
@@ -125,7 +121,7 @@ class ThreadlineWidget(urwid.AttrMap):
authors = self.thread.get_authors() or '(None)'
else:
authors = '(None)'
- maxlength = config.getint('general', 'authors_maxlength')
+ maxlength = settings.get('authors_maxlength')
authorsstring = shorten_author_string(authors, maxlength)
self.authors_w = urwid.AttrMap(urwid.Text(authorsstring),
self._get_theme('authors'))
@@ -158,7 +154,6 @@ class ThreadlineWidget(urwid.AttrMap):
self.original_widget = self.columns
def render(self, size, focus=False):
- self.highlight_theme_suffix = self._get_highlight_theme_suffix()
if focus:
self.date_w.set_attr_map({None: self._get_theme('date', focus)})
self.mailcount_w.set_attr_map({None:
@@ -192,28 +187,11 @@ class ThreadlineWidget(urwid.AttrMap):
def get_thread(self):
return self.thread
- def _get_highlight_theme_suffix(self):
- suffix = None
- for query in self.highlight_rules.keys():
- if self.thread.matches(query):
- suffix = self.highlight_rules[query]
- break
- return suffix
-
def _get_theme(self, component, focus=False):
- theme = 'search_thread_{0}'.format(component)
- if (self.highlight_theme_suffix and
- component in self.highlight_components):
- highlight_theme = (theme +
- '_{id}'.format(id=self.highlight_theme_suffix))
- if focus:
- theme += '_focus'
- highlight_theme += '_focus'
- if config.has_theming(highlight_theme):
- theme = highlight_theme
- elif focus:
- theme = theme + '_focus'
- return theme
+ attr_key = 'thread_{0}'.format(component)
+ if focus:
+ attr_key += '_focus'
+ return settings.get_theming_attribute('search', attr_key)
class BufferlineWidget(urwid.Text):
@@ -244,14 +222,14 @@ class TagWidget(urwid.AttrMap):
It looks up the string it displays in the `tag-translate` section
of the config as well as custom theme settings for its tag.
"""
- def __init__(self, tag, theme=''):
+ def __init__(self, tag):
self.tag = tag
- self.highlight = theme
- self.translated = config.get('tag-translate', tag, fallback=tag)
- self.txt = urwid.Text(self.translated.encode('utf-8'), wrap='clip')
- normal = config.get_tag_theme(tag, highlight=theme)
- focus = config.get_tag_theme(tag, focus=True, highlight=theme)
- urwid.AttrMap.__init__(self, self.txt, normal, focus)
+ representation = settings.get_tagstring_representation(tag)
+ self.translated = representation['translated']
+ self.txt = urwid.Text(self.translated, wrap='clip')
+ self.normal_att = representation['normal']
+ self.focus_att = representation['focussed']
+ urwid.AttrMap.__init__(self, self.txt, self.normal_att, self.focus_att)
def width(self):
# evil voodoo hotfix for double width chars that may
@@ -268,14 +246,10 @@ class TagWidget(urwid.AttrMap):
return self.tag
def set_focussed(self):
- self.set_attr_map({None: config.get_tag_theme(
- self.tag, focus=True,
- highlight=self.highlight)})
+ self.set_attr_map({None: self.focus_att})
def set_unfocussed(self):
- self.set_attr_map({None: config.get_tag_theme(
- self.tag,
- highlight=self.highlight)})
+ self.set_attr_map({None: self.normal_att})
class ChoiceWidget(urwid.Text):
@@ -412,7 +386,7 @@ class MessageWidget(urwid.WidgetWrap):
# set available and to be displayed headers
self._all_headers = self.mail.keys()
- displayed = config.getstringlist('general', 'displayed_headers')
+ displayed = settings.get('displayed_headers')
self._filtered_headers = [k for k in displayed if k in self.mail]
self._displayed_headers = None
@@ -590,9 +564,9 @@ class MessageSummaryWidget(urwid.WidgetWrap):
self.message = message
self.even = even
if even:
- attr = 'thread_summary_even'
+ attr = settings.get_theming_attribute('thread', 'summary_even')
else:
- attr = 'thread_summary_odd'
+ attr = settings.get_theming_attribute('thread', 'summary_odd')
cols = []
sumstr = self.__str__()
@@ -605,8 +579,9 @@ class MessageSummaryWidget(urwid.WidgetWrap):
tag_widgets.sort(tag_cmp, lambda tag_widget: tag_widget.translated)
for tag_widget in tag_widgets:
cols.append(('fixed', tag_widget.width(), tag_widget))
+ focus_att = settings.get_theming_attribute('thread', 'summary_focus')
line = urwid.AttrMap(urwid.Columns(cols, dividechars=1), attr,
- 'thread_summary_focus')
+ focus_att)
urwid.WidgetWrap.__init__(self, line)
def __str__(self):
@@ -636,7 +611,8 @@ class HeadersList(urwid.WidgetWrap):
def __init__(self, headerslist):
self.headers = headerslist
pile = urwid.Pile(self._build_lines(headerslist))
- pile = urwid.AttrMap(pile, 'thread_header')
+ att = settings.get_theming_attribute('thread', 'header')
+ pile = urwid.AttrMap(pile, att)
urwid.WidgetWrap.__init__(self, pile)
def __str__(self):
@@ -645,6 +621,8 @@ class HeadersList(urwid.WidgetWrap):
def _build_lines(self, lines):
max_key_len = 1
headerlines = []
+ key_att = settings.get_theming_attribute('thread', 'header_key')
+ value_att = settings.get_theming_attribute('thread', 'header_value')
#calc max length of key-string
for key, value in lines:
if len(key) > max_key_len:
@@ -652,8 +630,8 @@ class HeadersList(urwid.WidgetWrap):
for key, value in lines:
##todo : even/odd
keyw = ('fixed', max_key_len + 1,
- urwid.Text(('thread_header_key', key)))
- valuew = urwid.Text(('thread_header_value', value))
+ urwid.Text((key_att, key)))
+ valuew = urwid.Text((value_att, value))
line = urwid.Columns([keyw, valuew])
headerlines.append(line)
return headerlines
@@ -669,7 +647,8 @@ class MessageBodyWidget(urwid.AttrMap):
def __init__(self, msg):
bodytxt = message.extract_body(msg)
- urwid.AttrMap.__init__(self, urwid.Text(bodytxt), 'thread_body')
+ att = settings.get_theming_attribute('thread', 'body')
+ urwid.AttrMap.__init__(self, urwid.Text(bodytxt), att)
class AttachmentWidget(urwid.WidgetWrap):
@@ -685,9 +664,10 @@ class AttachmentWidget(urwid.WidgetWrap):
self.attachment = attachment
if not isinstance(attachment, message.Attachment):
self.attachment = message.Attachment(self.attachment)
+ att = settings.get_theming_attribute('thread', 'attachment')
+ focus_att = settings.get_theming_attribute('thread', 'attachment_focus')
widget = urwid.AttrMap(urwid.Text(self.attachment.__str__()),
- 'thread_attachment',
- 'thread_attachment_focus')
+ att, focus_att)
urwid.WidgetWrap.__init__(self, widget)
def get_attachment(self):